react-native-readium 4.0.2 → 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.
Files changed (118) hide show
  1. package/README.md +93 -9
  2. package/android/build.gradle +32 -29
  3. package/android/gradle.properties +3 -3
  4. package/android/src/main/java/com/reactnativereadium/ReadiumView.kt +88 -28
  5. package/android/src/main/java/com/reactnativereadium/ReadiumViewManager.kt +20 -19
  6. package/android/src/main/java/com/reactnativereadium/reader/BaseReaderFragment.kt +27 -6
  7. package/android/src/main/java/com/reactnativereadium/reader/EpubReaderFragment.kt +53 -69
  8. package/android/src/main/java/com/reactnativereadium/reader/PositionLabelManager.kt +132 -0
  9. package/android/src/main/java/com/reactnativereadium/reader/ReaderService.kt +64 -65
  10. package/android/src/main/java/com/reactnativereadium/reader/ReaderViewModel.kt +6 -85
  11. package/android/src/main/java/com/reactnativereadium/reader/VisualReaderFragment.kt +32 -2
  12. package/android/src/main/java/com/reactnativereadium/utils/FragmentFactory.kt +2 -3
  13. package/android/src/main/java/com/reactnativereadium/utils/JsonExtensions.kt +61 -0
  14. package/android/src/main/java/com/reactnativereadium/utils/MetadataNormalizer.kt +183 -0
  15. package/android/src/main/java/com/reactnativereadium/utils/NormalizedMetadata.kt +179 -0
  16. package/android/src/main/java/com/reactnativereadium/utils/extensions/InputStream.kt +0 -9
  17. package/ios/App/AppModule.swift +3 -9
  18. package/ios/Common/Toolkit/Extensions/Locator.swift +1 -1
  19. package/ios/Data/Bookmark.swift +1 -1
  20. package/ios/Reader/Common/ReaderViewController.swift +118 -21
  21. package/ios/Reader/EPUB/AssociatedColors.swift +1 -1
  22. package/ios/Reader/EPUB/EPUBHTTPServer.swift +13 -0
  23. package/ios/Reader/EPUB/EPUBModule.swift +1 -1
  24. package/ios/Reader/EPUB/EPUBViewController.swift +3 -4
  25. package/ios/Reader/ReaderFormatModule.swift +1 -1
  26. package/ios/Reader/ReaderModule.swift +1 -1
  27. package/ios/Reader/ReaderService.swift +70 -35
  28. package/ios/ReadiumView.swift +62 -25
  29. package/ios/ReadiumViewManager.m +1 -1
  30. package/lib/src/ReadiumViewNativeComponent.d.ts +19 -0
  31. package/lib/src/ReadiumViewNativeComponent.js +10 -0
  32. package/lib/src/components/BaseReadiumView.d.ts +1 -2
  33. package/lib/src/components/BaseReadiumView.js +3 -7
  34. package/lib/src/components/ReadiumView.js +15 -15
  35. package/lib/src/components/ReadiumView.web.js +100 -21
  36. package/lib/src/interfaces/BaseReadiumViewProps.d.ts +2 -1
  37. package/lib/src/interfaces/Preferences.d.ts +3 -2
  38. package/lib/src/interfaces/PublicationMetadata.d.ts +114 -0
  39. package/lib/src/interfaces/PublicationMetadata.js +1 -0
  40. package/lib/src/interfaces/PublicationReady.d.ts +15 -0
  41. package/lib/src/interfaces/PublicationReady.js +1 -0
  42. package/lib/src/interfaces/index.d.ts +2 -0
  43. package/lib/src/interfaces/index.js +2 -0
  44. package/lib/src/utils/index.d.ts +0 -1
  45. package/lib/src/utils/index.js +0 -1
  46. package/lib/web/hooks/index.d.ts +3 -2
  47. package/lib/web/hooks/index.js +3 -2
  48. package/lib/web/hooks/useLocationObserver.d.ts +2 -1
  49. package/lib/web/hooks/useLocationObserver.js +18 -11
  50. package/lib/web/hooks/useNavigator.d.ts +12 -0
  51. package/lib/web/hooks/useNavigator.js +87 -0
  52. package/lib/web/hooks/usePositionLabel.d.ts +9 -0
  53. package/lib/web/hooks/usePositionLabel.js +33 -0
  54. package/lib/web/hooks/usePreferencesObserver.d.ts +2 -0
  55. package/lib/web/hooks/usePreferencesObserver.js +54 -0
  56. package/lib/web/utils/manifestFetcher.d.ts +8 -0
  57. package/lib/web/utils/manifestFetcher.js +28 -0
  58. package/lib/web/utils/manifestNormalizer.d.ts +8 -0
  59. package/lib/web/utils/manifestNormalizer.js +70 -0
  60. package/lib/web/utils/metadataNormalizer.d.ts +53 -0
  61. package/lib/web/utils/metadataNormalizer.js +220 -0
  62. package/lib/web/utils/navigatorListeners.d.ts +6 -0
  63. package/lib/web/utils/navigatorListeners.js +50 -0
  64. package/lib/web/utils/publicationUtils.d.ts +15 -0
  65. package/lib/web/utils/publicationUtils.js +39 -0
  66. package/package.json +24 -14
  67. package/react-native-readium.podspec +7 -5
  68. package/src/ReadiumViewNativeComponent.ts +35 -0
  69. package/src/components/BaseReadiumView.tsx +3 -10
  70. package/src/components/ReadiumView.tsx +15 -15
  71. package/src/components/ReadiumView.web.tsx +120 -27
  72. package/src/interfaces/BaseReadiumViewProps.ts +2 -1
  73. package/src/interfaces/Preferences.ts +3 -2
  74. package/src/interfaces/PublicationMetadata.ts +141 -0
  75. package/src/interfaces/PublicationReady.ts +18 -0
  76. package/src/interfaces/index.ts +2 -0
  77. package/src/utils/index.ts +0 -1
  78. package/web/hooks/index.ts +3 -2
  79. package/web/hooks/useLocationObserver.ts +24 -11
  80. package/web/hooks/useNavigator.ts +146 -0
  81. package/web/hooks/usePositionLabel.ts +51 -0
  82. package/web/hooks/usePreferencesObserver.ts +69 -0
  83. package/web/utils/manifestFetcher.ts +38 -0
  84. package/web/utils/manifestNormalizer.ts +74 -0
  85. package/web/utils/metadataNormalizer.ts +238 -0
  86. package/web/utils/navigatorListeners.ts +60 -0
  87. package/web/utils/publicationUtils.ts +47 -0
  88. package/android/src/main/java/com/reactnativereadium/search/SearchFragment.kt +0 -100
  89. package/android/src/main/java/com/reactnativereadium/search/SearchPagingSource.kt +0 -44
  90. package/android/src/main/java/com/reactnativereadium/search/SearchResultAdapter.kt +0 -68
  91. package/android/src/main/java/com/reactnativereadium/utils/R2DispatcherActivity.kt +0 -45
  92. package/android/src/main/java/com/reactnativereadium/utils/SectionDecoration.kt +0 -98
  93. package/android/src/main/java/com/reactnativereadium/utils/SingleClickListener.kt +0 -32
  94. package/android/src/main/java/com/reactnativereadium/utils/extensions/Bitmap.kt +0 -23
  95. package/android/src/main/java/com/reactnativereadium/utils/extensions/Context.kt +0 -16
  96. package/android/src/main/java/com/reactnativereadium/utils/extensions/File.kt +0 -22
  97. package/android/src/main/java/com/reactnativereadium/utils/extensions/Link.kt +0 -6
  98. package/android/src/main/java/com/reactnativereadium/utils/extensions/Metadata.kt +0 -6
  99. package/android/src/main/java/com/reactnativereadium/utils/extensions/URL.kt +0 -29
  100. package/android/src/main/java/com/reactnativereadium/utils/extensions/Uri.kt +0 -17
  101. package/android/src/main/res/layout/fragment_search.xml +0 -39
  102. package/android/src/main/res/layout/item_recycle_search.xml +0 -14
  103. package/ios/Common/EPUBPreferences.swift +0 -8
  104. package/ios/Common/Paths.swift +0 -52
  105. package/ios/Common/Publication.swift +0 -15
  106. package/ios/Common/Toolkit/Extensions/HTTPClient.swift +0 -65
  107. package/ios/Common/Toolkit/Extensions/UIImage.swift +0 -12
  108. package/ios/Common/Toolkit/Extensions/UIViewController.swift +0 -19
  109. package/ios/Common/Toolkit/ScreenOrientation.swift +0 -13
  110. package/lib/src/utils/createFragment.d.ts +0 -1
  111. package/lib/src/utils/createFragment.js +0 -10
  112. package/lib/web/hooks/useReaderRef.d.ts +0 -3
  113. package/lib/web/hooks/useReaderRef.js +0 -85
  114. package/lib/web/hooks/useSettingsObserver.d.ts +0 -2
  115. package/lib/web/hooks/useSettingsObserver.js +0 -44
  116. package/src/utils/createFragment.ts +0 -15
  117. package/web/hooks/useReaderRef.ts +0 -109
  118. package/web/hooks/useSettingsObserver.ts +0 -61
package/README.md CHANGED
@@ -24,7 +24,7 @@ allows you to do things like:
24
24
 
25
25
  - Render an ebook view.
26
26
  - Register for location changes (as the user pages through the book).
27
- - Register for the Table of Contents (so that you can display things like chapters in your app)
27
+ - Access publication metadata including table of contents, positions, and more via the `onPublicationReady` callback
28
28
  - Control settings of the Reader. Things like:
29
29
  - Dark Mode, Light Mode, Sepia Mode
30
30
  - Font Size
@@ -71,36 +71,59 @@ yarn add react-native-readium
71
71
 
72
72
  #### iOS
73
73
 
74
+ Requirements:
75
+ - Minimum iOS deployment target: iOS 13.4
76
+ - Swift compiler: Swift 6.0
77
+ - Xcode: Xcode 16.2 (or newer)
78
+
74
79
  Due to the current state of the `Readium` swift libraries you need to manually
75
80
  update your `Podfile` ([see more on that here](https://github.com/readium/swift-toolkit/issues/38)).
76
81
 
82
+ ##### Breaking change when upgrading from v4 to v5!
83
+
84
+ If you are migrating from v4 to v5, please note that you must update your iOS Podfile to use the new Readium Pods (see iOS documentation below). Please make a note of both the new Pod names and the addition of the `source`'s in the Podfile.
85
+
77
86
  ```rb
78
87
  # ./ios/Podfile
88
+ source 'https://github.com/readium/podspecs'
89
+ source 'https://cdn.cocoapods.org/'
90
+
79
91
  ...
80
- platform :ios, '13.0'
92
+
93
+ platform :ios, '13.4'
81
94
 
82
95
  ...
83
96
 
84
97
  target 'ExampleApp' do
85
98
  config = use_native_modules!
86
99
  ...
87
- pod 'GCDWebServer', podspec: 'https://raw.githubusercontent.com/readium/GCDWebServer/3.7.5/GCDWebServer.podspec', modular_headers: true
88
- pod 'ReadiumAdapterGCDWebServer', podspec: 'https://raw.githubusercontent.com/readium/swift-toolkit/2.6.0/Support/CocoaPods/ReadiumAdapterGCDWebServer.podspec', modular_headers: true
89
- pod 'R2Navigator', podspec: 'https://raw.githubusercontent.com/readium/swift-toolkit/2.6.0/Support/CocoaPods/ReadiumNavigator.podspec'
90
- pod 'R2Shared', podspec: 'https://raw.githubusercontent.com/readium/swift-toolkit/2.6.0/Support/CocoaPods/ReadiumShared.podspec'
91
- pod 'R2Streamer', podspec: 'https://raw.githubusercontent.com/readium/swift-toolkit/2.6.0/Support/CocoaPods/ReadiumStreamer.podspec'
92
- pod 'ReadiumInternal', podspec: 'https://raw.githubusercontent.com/readium/swift-toolkit/2.6.0/Support/CocoaPods/ReadiumInternal.podspec'
100
+ pod 'ReadiumGCDWebServer', :modular_headers => true
101
+ pod 'ReadiumAdapterGCDWebServer', '~> 3.5.0'
102
+ pod 'ReadiumInternal', '~> 3.5.0'
103
+ pod 'ReadiumShared', '~> 3.5.0'
104
+ pod 'ReadiumStreamer', '~> 3.5.0'
105
+ pod 'ReadiumNavigator', '~> 3.5.0'
93
106
  pod 'Minizip', modular_headers: true
94
107
  ...
95
108
  end
96
109
  ```
97
110
 
111
+
98
112
  Finally, install the pods:
99
113
 
100
114
  `pod install`
101
115
 
102
116
  #### Android
103
117
 
118
+ ##### Breaking change when upgrading from v4 to v5!
119
+
120
+ This release upgrades the Android native implementation to a newer Readium Kotlin Toolkit.
121
+ Most apps won’t need code changes, but your **Android build configuration** might.
122
+
123
+ Requirements:
124
+ - **JDK 17** is required to build the Android app (the library targets Java/Kotlin 17).
125
+ - **compileSdkVersion** must be >= `31`.
126
+
104
127
  If you're not using `compileSdkVersion` >= 31 you'll need to update that:
105
128
 
106
129
  ```groovy
@@ -114,8 +137,35 @@ buildscript {
114
137
  ...
115
138
  ```
116
139
 
140
+ ##### Core library desugaring (may be required)
141
+
142
+ If you see build errors related to missing Java 8+ APIs (commonly `java.time.*`), enable
143
+ core library desugaring in your app:
144
+
145
+ ```groovy
146
+ // android/app/build.gradle
147
+ android {
148
+ ...
149
+ compileOptions {
150
+ coreLibraryDesugaringEnabled true
151
+ }
152
+ }
153
+
154
+ dependencies {
155
+ coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:2.1.2"
156
+ }
157
+ ```
158
+
159
+ ##### Expo managed workflow
160
+
161
+ If your app uses Expo managed workflow (native `android/` is generated via `prebuild` / EAS),
162
+ apply the desugaring settings through an Expo config plugin (or `expo-build-properties`) so
163
+ they persist across builds.
164
+
117
165
  ## Usage
118
166
 
167
+ ### Basic Example
168
+
119
169
  ```tsx
120
170
  import React, { useState } from 'react';
121
171
  import { ReadiumView } from 'react-native-readium';
@@ -134,6 +184,40 @@ const MyComponent: React.FC = () => {
134
184
  }
135
185
  ```
136
186
 
187
+ ### Using Publication Metadata
188
+
189
+ Access the table of contents, positions, and metadata when the publication is ready:
190
+
191
+ ```tsx
192
+ import React, { useState } from 'react';
193
+ import { ReadiumView } from 'react-native-readium';
194
+ import type { File, PublicationReadyEvent } from 'react-native-readium';
195
+
196
+ const MyComponent: React.FC = () => {
197
+ const [file] = useState<File>({
198
+ url: SOME_LOCAL_FILE_URL,
199
+ });
200
+
201
+ const [toc, setToc] = useState([]);
202
+
203
+ const handlePublicationReady = (event: PublicationReadyEvent) => {
204
+ console.log('Title:', event.metadata.title);
205
+ console.log('Author:', event.metadata.author);
206
+ console.log('Table of Contents:', event.tableOfContents);
207
+ console.log('Positions:', event.positions);
208
+
209
+ setToc(event.tableOfContents);
210
+ };
211
+
212
+ return (
213
+ <ReadiumView
214
+ file={file}
215
+ onPublicationReady={handlePublicationReady}
216
+ />
217
+ );
218
+ }
219
+ ```
220
+
137
221
  [Take a look at the Example App](https://github.com/5-stones/react-native-readium/blob/main/example/src/App.tsx) for a more complex usage example.
138
222
 
139
223
  ## Supported Formats & DRM
@@ -164,7 +248,7 @@ DRM is not supported at this time. However, there is a clear path to [support it
164
248
  | `preferences` | [`Partial<Preferences>`](https://github.com/readium/swift-toolkit/blob/main/docs/Guides/Navigator%20Preferences.md#appendix-preference-constraints) | :white_check_mark: | An object that allows you to control various aspects of the reader's UI (epub only) |
165
249
  | `style` | `ViewStyle` | :white_check_mark: | A traditional style object. |
166
250
  | `onLocationChange` | `(locator: Locator) => void` | :white_check_mark: | A callback that fires whenever the location is changed (e.g. the user transitions to a new page)|
167
- | `onTableOfContents` | `(toc: Link[] \| null) => void` | :white_check_mark: | A callback that fires once the file is parsed and emits the table of contents embedded in the file. Returns `null` or an empty `[]` if no TOC exists. See the [`Link`](https://github.com/5-stones/react-native-readium/blob/main/src/interfaces/Link.ts) interface for more info. |
251
+ | `onPublicationReady` | `(event: PublicationReadyEvent) => void` | :white_check_mark: | A callback that fires once the publication is loaded and provides access to the table of contents, positions, and metadata. See the [`PublicationReadyEvent`](https://github.com/5-stones/react-native-readium/blob/main/src/interfaces/PublicationReady.ts) interface for details. |
168
252
 
169
253
  #### :warning: Web vs Native File URLs
170
254
 
@@ -1,7 +1,7 @@
1
1
  buildscript {
2
2
  // Buildscript is evaluated before everything else so we can't use getExtOrDefault
3
3
  def kotlin_version = rootProject.ext.has('kotlinVersion') ? rootProject.ext.get('kotlinVersion') : project.properties['Readium_kotlinVersion']
4
- ext.readium_version = '2.4.1'
4
+ ext.readium_version = '3.1.0'
5
5
 
6
6
  repositories {
7
7
  google()
@@ -9,7 +9,7 @@ buildscript {
9
9
  }
10
10
 
11
11
  dependencies {
12
- classpath 'com.android.tools.build:gradle:4.2.2'
12
+ classpath 'com.android.tools.build:gradle:8.5.0'
13
13
  // noinspection DifferentKotlinGradleVersion
14
14
  classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
15
15
  }
@@ -28,6 +28,7 @@ def getExtOrIntegerDefault(name) {
28
28
 
29
29
  android {
30
30
  compileSdkVersion getExtOrIntegerDefault('compileSdkVersion')
31
+ namespace "com.reactnativereadium"
31
32
  defaultConfig {
32
33
  minSdkVersion 21
33
34
  targetSdkVersion getExtOrIntegerDefault('targetSdkVersion')
@@ -40,12 +41,15 @@ android {
40
41
  minifyEnabled false
41
42
  }
42
43
  }
43
- lintOptions {
44
+ lint {
44
45
  disable 'GradleCompatible'
45
46
  }
46
47
  compileOptions {
47
- sourceCompatibility JavaVersion.VERSION_1_8
48
- targetCompatibility JavaVersion.VERSION_1_8
48
+ sourceCompatibility JavaVersion.VERSION_17
49
+ targetCompatibility JavaVersion.VERSION_17
50
+ }
51
+ kotlinOptions {
52
+ jvmTarget = "17"
49
53
  }
50
54
 
51
55
  buildFeatures {
@@ -140,46 +144,45 @@ dependencies {
140
144
  }
141
145
 
142
146
  // other deps
143
- implementation "androidx.core:core-ktx:1.7.0"
144
- implementation "androidx.activity:activity-ktx:1.4.0"
145
- implementation "androidx.appcompat:appcompat:1.4.1"
146
- implementation "androidx.browser:browser:1.4.0"
147
+ implementation "androidx.core:core-ktx:1.13.1"
148
+ implementation "androidx.activity:activity-ktx:1.9.0"
149
+ implementation "androidx.appcompat:appcompat:1.6.1"
150
+ implementation "androidx.browser:browser:1.7.0"
147
151
  implementation "androidx.cardview:cardview:1.0.0"
148
- implementation "androidx.constraintlayout:constraintlayout:2.1.3"
149
- implementation "androidx.fragment:fragment-ktx:1.4.0"
150
- implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.0"
151
- implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.0"
152
- implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0"
153
- implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
154
- implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'
155
- implementation "androidx.paging:paging-runtime-ktx:3.1.0"
156
- implementation "androidx.recyclerview:recyclerview:1.2.1"
157
- implementation "androidx.viewpager2:viewpager2:1.0.0"
158
- implementation "androidx.webkit:webkit:1.4.0"
159
- implementation "com.google.android.material:material:1.5.0"
152
+ implementation "androidx.constraintlayout:constraintlayout:2.1.4"
153
+ implementation "androidx.fragment:fragment-ktx:1.7.1"
154
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.8.4"
155
+ implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.8.4"
156
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.4"
157
+ implementation 'androidx.navigation:navigation-fragment-ktx:2.7.7'
158
+ implementation 'androidx.navigation:navigation-ui-ktx:2.7.7'
159
+ implementation "androidx.paging:paging-runtime-ktx:3.2.1"
160
+ implementation "androidx.recyclerview:recyclerview:1.3.2"
161
+ implementation "androidx.viewpager2:viewpager2:1.1.0"
162
+ implementation "androidx.webkit:webkit:1.8.0"
163
+ implementation "com.google.android.material:material:1.11.0"
160
164
  implementation "com.jakewharton.timber:timber:5.0.1"
161
165
  // AM NOTE: needs to stay this version for now (June 24,2020)
162
166
  //noinspection GradleDependency
163
167
  implementation "com.squareup.picasso:picasso:2.71828"
164
168
  implementation "joda-time:joda-time:2.10.13"
165
- implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0"
166
- // AM NOTE: needs to stay this version for now (June 24,2020)
167
- //noinspection GradleDependency
168
- implementation "org.jsoup:jsoup:1.14.3"
169
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0"
170
+
171
+ implementation "org.jsoup:jsoup:1.18.3"
169
172
 
170
173
  // Room database
171
- final roomVersion = "2.4.1"
174
+ final roomVersion = "2.6.1"
172
175
  implementation "androidx.room:room-runtime:$roomVersion"
173
176
  implementation "androidx.room:room-ktx:$roomVersion"
174
177
  annotationProcessor "androidx.room:room-compiler:$roomVersion"
175
178
 
176
179
  implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
177
180
  //noinspection LifecycleAnnotationProcessorWithJava8
178
- annotationProcessor "androidx.lifecycle:lifecycle-compiler:2.4.0"
181
+ annotationProcessor "androidx.lifecycle:lifecycle-compiler:2.8.4"
179
182
 
180
183
  testImplementation "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
181
184
  testImplementation "junit:junit:4.13.2"
182
185
 
183
- androidTestImplementation "androidx.test.espresso:espresso-core:3.4.0"
184
- androidTestImplementation "androidx.test:runner:1.4.0"
186
+ androidTestImplementation "androidx.test.espresso:espresso-core:3.5.1"
187
+ androidTestImplementation "androidx.test:runner:1.5.2"
185
188
  }
@@ -1,3 +1,3 @@
1
- Readium_kotlinVersion=1.5.31
2
- Readium_compileSdkVersion=31
3
- Readium_targetSdkVersion=29
1
+ Readium_kotlinVersion=1.9.25
2
+ Readium_compileSdkVersion=35
3
+ Readium_targetSdkVersion=35
@@ -1,11 +1,14 @@
1
1
  package com.reactnativereadium
2
2
 
3
+ import android.util.Log
3
4
  import android.view.Choreographer
4
5
  import android.widget.FrameLayout
5
6
  import androidx.fragment.app.FragmentActivity
6
7
  import com.facebook.react.bridge.Arguments
8
+ import com.facebook.react.bridge.WritableMap
7
9
  import com.facebook.react.uimanager.ThemedReactContext
8
- import com.facebook.react.uimanager.events.RCTEventEmitter
10
+ import com.facebook.react.uimanager.UIManagerHelper
11
+ import com.facebook.react.uimanager.events.Event
9
12
  import com.reactnativereadium.reader.BaseReaderFragment
10
13
  import com.reactnativereadium.reader.EpubReaderFragment
11
14
  import com.reactnativereadium.reader.ReaderViewModel
@@ -13,18 +16,26 @@ import com.reactnativereadium.reader.VisualReaderFragment
13
16
  import com.reactnativereadium.utils.Dimensions
14
17
  import com.reactnativereadium.utils.File
15
18
  import com.reactnativereadium.utils.LinkOrLocator
19
+ import com.reactnativereadium.utils.MetadataNormalizer
20
+ import com.reactnativereadium.utils.toWritableArray
21
+ import com.reactnativereadium.utils.toWritableMap
16
22
  import org.readium.r2.navigator.epub.EpubNavigatorFragment
17
23
  import org.readium.r2.navigator.epub.EpubPreferences
18
- import org.readium.r2.shared.extensions.toMap
19
24
 
20
25
  class ReadiumView(
21
26
  val reactContext: ThemedReactContext
22
27
  ) : FrameLayout(reactContext) {
28
+ companion object {
29
+ private const val TAG = "ReadiumView"
30
+ }
31
+
23
32
  var dimensions: Dimensions = Dimensions(0,0)
24
33
  var file: File? = null
25
34
  var fragment: BaseReaderFragment? = null
26
35
  var isViewInitialized: Boolean = false
36
+ var isFragmentAdded: Boolean = false
27
37
  var lateInitSerializedUserPreferences: String? = null
38
+ private var frameCallback: Choreographer.FrameCallback? = null
28
39
 
29
40
  fun updateLocation(location: LinkOrLocator) : Boolean {
30
41
  if (fragment == null) {
@@ -46,52 +57,93 @@ class ReadiumView(
46
57
  }
47
58
 
48
59
  fun addFragment(frag: BaseReaderFragment) {
60
+ if (isFragmentAdded) {
61
+ return
62
+ }
63
+
49
64
  fragment = frag
65
+ isFragmentAdded = true
50
66
  setupLayout()
51
67
  lateInitSerializedUserPreferences?.let { updatePreferencesFromJsonString(it)}
52
68
  val activity: FragmentActivity? = reactContext.currentActivity as FragmentActivity?
69
+
53
70
  activity!!.supportFragmentManager
54
71
  .beginTransaction()
55
72
  .replace(this.id, frag, this.id.toString())
56
- .commit()
73
+ .commitNow()
74
+
75
+ // Ensure the fragment's view fills the container
76
+ frag.view?.layoutParams = FrameLayout.LayoutParams(
77
+ FrameLayout.LayoutParams.MATCH_PARENT,
78
+ FrameLayout.LayoutParams.MATCH_PARENT
79
+ )
80
+
81
+ val eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, this.id)
82
+
83
+ val dispatch: (String, WritableMap?) -> Unit = { eventName, payload ->
84
+ if (eventDispatcher != null) {
85
+ eventDispatcher.dispatchEvent(ReadiumEvent(this.id, eventName, payload))
86
+ } else {
87
+ Log.w(TAG, "EventDispatcher is null for view id ${this.id}")
88
+ }
89
+ }
57
90
 
58
- val module = reactContext.getJSModule(RCTEventEmitter::class.java)
59
91
  // subscribe to reader events
60
92
  frag.channel.receive(frag) { event ->
61
93
  when (event) {
62
94
  is ReaderViewModel.Event.LocatorUpdate -> {
63
- val json = event.locator.toJSON()
64
- val payload = Arguments.makeNativeMap(json.toMap())
65
- module.receiveEvent(
66
- this.id.toInt(),
67
- ReadiumViewManager.ON_LOCATION_CHANGE,
68
- payload
69
- )
95
+ val payload = event.locator.toWritableMap()
96
+ dispatch(ReadiumViewManager.ON_LOCATION_CHANGE, payload)
70
97
  }
71
- is ReaderViewModel.Event.TableOfContentsLoaded -> {
72
- val map = event.toc.map { it.toJSON().toMap() }
73
- val payload = Arguments.makeNativeMap(mapOf("toc" to map))
74
- module.receiveEvent(
75
- this.id.toInt(),
76
- ReadiumViewManager.ON_TABLE_OF_CONTENTS,
77
- payload
78
- )
79
- }
80
- else -> {
81
- // do nothing
98
+ is ReaderViewModel.Event.PublicationReady -> {
99
+ val payload = Arguments.createMap().apply {
100
+ putArray("tableOfContents", event.tableOfContents.toWritableArray())
101
+ putArray("positions", event.positions.map { it.toWritableMap() }.let { list ->
102
+ Arguments.createArray().apply {
103
+ list.forEach { pushMap(it) }
104
+ }
105
+ })
106
+ // Use spec-based normalizer to ensure consistent structure
107
+ putMap("metadata", MetadataNormalizer.normalize(event.metadata))
108
+ }
109
+ dispatch(ReadiumViewManager.ON_PUBLICATION_READY, payload)
82
110
  }
83
111
  }
84
112
  }
85
113
  }
86
114
 
115
+ // Custom event class for new architecture
116
+ private class ReadiumEvent(
117
+ viewTag: Int,
118
+ private val _eventName: String,
119
+ private val _eventData: WritableMap?
120
+ ) : Event<ReadiumEvent>(viewTag) {
121
+ override fun getEventName(): String = _eventName
122
+ override fun getEventData(): WritableMap? = _eventData
123
+ }
124
+
87
125
  private fun setupLayout() {
88
- Choreographer.getInstance().postFrameCallback(object : Choreographer.FrameCallback {
126
+ frameCallback = object : Choreographer.FrameCallback {
89
127
  override fun doFrame(frameTimeNanos: Long) {
90
128
  manuallyLayoutChildren()
91
129
  this@ReadiumView.viewTreeObserver.dispatchOnGlobalLayout()
92
130
  Choreographer.getInstance().postFrameCallback(this)
93
131
  }
94
- })
132
+ }
133
+ frameCallback?.let { Choreographer.getInstance().postFrameCallback(it) }
134
+ }
135
+
136
+ override fun onDetachedFromWindow() {
137
+ super.onDetachedFromWindow()
138
+ // remove frame callback to avoid leaks/continuous callbacks after view is destroyed
139
+ frameCallback?.let {
140
+ try {
141
+ Choreographer.getInstance().removeFrameCallback(it)
142
+ } catch (e: Exception) {
143
+ Log.w(TAG, "Failed to remove frame callback: ${e.message}")
144
+ }
145
+ }
146
+ frameCallback = null
95
147
  }
96
148
 
97
149
  /**
@@ -101,9 +153,17 @@ class ReadiumView(
101
153
  // propWidth and propHeight coming from react-native props
102
154
  val width = dimensions.width
103
155
  val height = dimensions.height
104
- this.measure(
105
- MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
106
- MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY))
107
- this.layout(0, 0, width, height)
156
+
157
+ // Measure and layout each child within this container
158
+ for (i in 0 until childCount) {
159
+ val child = getChildAt(i)
160
+ child.measure(
161
+ MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
162
+ MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
163
+ )
164
+ // Position child at (0, 0) within this container, filling the container
165
+ child.layout(0, 0, width, height)
166
+ }
108
167
  }
109
168
  }
169
+
@@ -1,5 +1,6 @@
1
1
  package com.reactnativereadium
2
2
 
3
+ import android.util.Log
3
4
  import com.facebook.react.bridge.*
4
5
  import com.facebook.react.common.MapBuilder
5
6
  import com.facebook.react.uimanager.annotations.ReactProp
@@ -35,33 +36,27 @@ class ReadiumViewManager(
35
36
  )
36
37
  )
37
38
  .put(
38
- ON_TABLE_OF_CONTENTS,
39
+ ON_PUBLICATION_READY,
39
40
  MapBuilder.of(
40
41
  "phasedRegistrationNames",
41
- MapBuilder.of("bubbled", ON_TABLE_OF_CONTENTS)
42
+ MapBuilder.of("bubbled", ON_PUBLICATION_READY)
42
43
  )
43
44
  )
44
45
  .build()
45
46
  }
46
47
 
47
- override fun getCommandsMap(): MutableMap<String, Int> {
48
- return MapBuilder.of("create", COMMAND_CREATE)
49
- }
50
-
51
48
  override fun receiveCommand(view: ReadiumView, commandId: String?, args: ReadableArray?) {
52
49
  super.receiveCommand(view, commandId, args)
53
- val reactNativeViewId = args!!.getInt(0)
54
- val commandIdInt = commandId!!.toInt()
55
50
 
56
- when (commandIdInt) {
57
- COMMAND_CREATE -> {
51
+ when (commandId) {
52
+ "create" -> {
58
53
  view.isViewInitialized = true
59
-
60
54
  if (view.file != null) {
61
55
  buildForViewIfReady(view)
62
56
  }
63
57
  }
64
58
  else -> {
59
+ Log.w(TAG, "Unknown command received: $commandId")
65
60
  }
66
61
  }
67
62
  }
@@ -122,17 +117,23 @@ class ReadiumViewManager(
122
117
 
123
118
  @ReactPropGroup(names = ["width", "height"], customType = "Style")
124
119
  fun setStyle(view: ReadiumView?, index: Int, value: Int) {
125
- if (index == 0) {
126
- view?.dimensions?.width = value
127
- }
128
- if (index == 1) {
129
- view?.dimensions?.height = value
120
+ if (view != null) {
121
+ if (index == 0) {
122
+ view.dimensions.width = value
123
+ }
124
+ if (index == 1) {
125
+ view.dimensions.height = value
126
+ }
127
+ buildForViewIfReady(view)
130
128
  }
131
129
  }
132
130
 
133
131
  private fun buildForViewIfReady(view: ReadiumView) {
134
132
  var file = view.file
135
- if (file != null && view.isViewInitialized) {
133
+ val width = view.dimensions.width
134
+ val height = view.dimensions.height
135
+
136
+ if (file != null && view.isViewInitialized && width > 0 && height > 0) {
136
137
  runBlocking {
137
138
  svc.openPublication(file.path, file.initialLocation) { fragment ->
138
139
  view.addFragment(fragment)
@@ -142,8 +143,8 @@ class ReadiumViewManager(
142
143
  }
143
144
 
144
145
  companion object {
146
+ private const val TAG = "ReadiumViewManager"
145
147
  var ON_LOCATION_CHANGE = "onLocationChange"
146
- var ON_TABLE_OF_CONTENTS = "onTableOfContents"
147
- var COMMAND_CREATE = 1
148
+ var ON_PUBLICATION_READY = "onPublicationReady"
148
149
  }
149
150
  }
@@ -4,20 +4,21 @@ import android.os.Bundle
4
4
  import android.view.*
5
5
  import androidx.fragment.app.Fragment
6
6
  import androidx.lifecycle.lifecycleScope
7
+ import com.reactnativereadium.utils.EventChannel
7
8
  import com.reactnativereadium.utils.LinkOrLocator
9
+ import kotlinx.coroutines.channels.Channel
8
10
  import kotlinx.coroutines.flow.launchIn
9
11
  import kotlinx.coroutines.flow.onEach
10
- import org.readium.r2.navigator.*
12
+ import kotlinx.coroutines.launch
13
+ import org.readium.r2.navigator.Navigator
11
14
  import org.readium.r2.shared.publication.Locator
12
- import com.reactnativereadium.utils.EventChannel
13
- import kotlinx.coroutines.channels.Channel
15
+ import org.readium.r2.shared.publication.services.positions
14
16
 
15
17
  /*
16
18
  * Base reader fragment class
17
19
  *
18
20
  * Provides common menu items and saves last location on stop.
19
21
  */
20
- @OptIn(ExperimentalDecorator::class)
21
22
  abstract class BaseReaderFragment : Fragment() {
22
23
  val channel = EventChannel(
23
24
  Channel<ReaderViewModel.Event>(Channel.BUFFERED),
@@ -37,7 +38,27 @@ abstract class BaseReaderFragment : Fragment() {
37
38
 
38
39
  val viewScope = viewLifecycleOwner.lifecycleScope
39
40
 
40
- channel.send(ReaderViewModel.Event.TableOfContentsLoaded(model.publication.tableOfContents))
41
+ // Emit PublicationReady event with all metadata
42
+ viewScope.launch {
43
+ // positions() is a suspending function that returns List<Locator>
44
+ val positions = try {
45
+ model.publication.positions()
46
+ } catch (e: Exception) {
47
+ emptyList<Locator>()
48
+ }
49
+
50
+ // Normalize metadata to ensure consistent structure across platforms
51
+ // This uses spec-based normalization to handle LocalizedStrings and other
52
+ // platform-specific serialization differences
53
+ channel.send(
54
+ ReaderViewModel.Event.PublicationReady(
55
+ tableOfContents = model.publication.tableOfContents,
56
+ positions = positions,
57
+ metadata = model.publication.metadata
58
+ )
59
+ )
60
+ }
61
+
41
62
  navigator.currentLocator
42
63
  .onEach { channel.send(ReaderViewModel.Event.LocatorUpdate(it)) }
43
64
  .launchIn(viewScope)
@@ -53,7 +74,7 @@ abstract class BaseReaderFragment : Fragment() {
53
74
  var locator: Locator? = null
54
75
  when (location) {
55
76
  is LinkOrLocator.Link -> {
56
- locator = navigator.publication.locatorFromLink(location.link)
77
+ locator = model.publication.locatorFromLink(location.link)
57
78
  }
58
79
  is LinkOrLocator.Locator -> {
59
80
  locator = location.locator