diff --git a/android/src/main/java/com/reactnativereadium/ReadiumView.kt b/android/src/main/java/com/reactnativereadium/ReadiumView.kt index c62bc1e..a200ea9 100644 --- a/android/src/main/java/com/reactnativereadium/ReadiumView.kt +++ b/android/src/main/java/com/reactnativereadium/ReadiumView.kt @@ -78,6 +78,15 @@ class ReadiumView( payload ) } + is ReaderViewModel.Event.MetadataLoaded -> { + val json = event.metadata.toJSON() + val payload = Arguments.makeNativeMap(json.toMap()) + module.receiveEvent( + this.id.toInt(), + ReadiumViewManager.ON_METADATA, + payload + ) + } else -> { // do nothing } diff --git a/android/src/main/java/com/reactnativereadium/ReadiumViewManager.kt b/android/src/main/java/com/reactnativereadium/ReadiumViewManager.kt index e5d8710..e4ed0e8 100644 --- a/android/src/main/java/com/reactnativereadium/ReadiumViewManager.kt +++ b/android/src/main/java/com/reactnativereadium/ReadiumViewManager.kt @@ -41,6 +41,13 @@ class ReadiumViewManager( MapBuilder.of("bubbled", ON_TABLE_OF_CONTENTS) ) ) + .put( + ON_METADATA, + MapBuilder.of( + "phasedRegistrationNames", + MapBuilder.of("bubbled", ON_METADATA) + ) + ) .build() } @@ -141,6 +148,7 @@ class ReadiumViewManager( companion object { var ON_LOCATION_CHANGE = "onLocationChange" var ON_TABLE_OF_CONTENTS = "onTableOfContents" + var ON_METADATA = "onMetadata" var COMMAND_CREATE = 1 } } diff --git a/android/src/main/java/com/reactnativereadium/reader/BaseReaderFragment.kt b/android/src/main/java/com/reactnativereadium/reader/BaseReaderFragment.kt index ac995ab..b31b90a 100644 --- a/android/src/main/java/com/reactnativereadium/reader/BaseReaderFragment.kt +++ b/android/src/main/java/com/reactnativereadium/reader/BaseReaderFragment.kt @@ -9,6 +9,7 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import org.readium.r2.navigator.* import org.readium.r2.shared.publication.Locator +import org.readium.r2.shared.publication.Metadata import com.reactnativereadium.utils.EventChannel import kotlinx.coroutines.channels.Channel @@ -38,6 +39,7 @@ abstract class BaseReaderFragment : Fragment() { val viewScope = viewLifecycleOwner.lifecycleScope channel.send(ReaderViewModel.Event.TableOfContentsLoaded(model.publication.tableOfContents)) + channel.send(ReaderViewModel.Event.MetadataLoaded(model.publication.metadata)) navigator.currentLocator .onEach { channel.send(ReaderViewModel.Event.LocatorUpdate(it)) } .launchIn(viewScope) diff --git a/android/src/main/java/com/reactnativereadium/reader/ReaderViewModel.kt b/android/src/main/java/com/reactnativereadium/reader/ReaderViewModel.kt index 944f85b..537de00 100644 --- a/android/src/main/java/com/reactnativereadium/reader/ReaderViewModel.kt +++ b/android/src/main/java/com/reactnativereadium/reader/ReaderViewModel.kt @@ -22,6 +22,7 @@ import org.readium.r2.shared.Search import org.readium.r2.shared.UserException import org.readium.r2.shared.publication.Link import org.readium.r2.shared.util.Try +import org.readium.r2.shared.publication.Metadata @OptIn(Search::class, ExperimentalDecorator::class) class ReaderViewModel( @@ -113,6 +114,7 @@ class ReaderViewModel( class Failure(val error: UserException) : Event() class LocatorUpdate(val locator: Locator) : Event() class TableOfContentsLoaded(val toc: List) : Event() + class MetadataLoaded(val metadata: Metadata) : Event() } sealed class FeedbackEvent { diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index c916992..095cbee 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -964,7 +964,7 @@ PODS: - React-Mapbuffer (0.74.3): - glog - React-debug - - react-native-readium (1.2.1): + - react-native-readium (2.0.2): - R2Navigator - R2Shared - R2Streamer @@ -1503,7 +1503,7 @@ SPEC CHECKSUMS: React-jsitracing: 6b3c8c98313642140530f93c46f5a6ca4530b446 React-logger: fa92ba4d3a5d39ac450f59be2a3cec7b099f0304 React-Mapbuffer: 9f68550e7c6839d01411ac8896aea5c868eff63a - react-native-readium: 7f71dab8b9c3eff510a2b7527e7a249691637215 + react-native-readium: 3de612400580e915706306b4f8879d7ebd029ed7 react-native-safe-area-context: b7daa1a8df36095a032dff095a1ea8963cb48371 react-native-slider: ce295d2bf830a7990af05b0bd70ab28c133e230c React-nativeconfig: fa5de9d8f4dbd5917358f8ad3ad1e08762f01dcb @@ -1535,7 +1535,7 @@ SPEC CHECKSUMS: RNVectorIcons: fcc2f6cb32f5735b586e66d14103a74ce6ad61f8 SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d SwiftSoup: 33e8d374021a6c332788f78fa29a93227d8a7699 - Yoga: 88480008ccacea6301ff7bf58726e27a72931c8d + Yoga: 04f1db30bb810187397fa4c37dd1868a27af229c PODFILE CHECKSUM: 686539c4adb72be4e293af103a245974f4ffa466 diff --git a/example/src/components/Reader.tsx b/example/src/components/Reader.tsx index 0140f04..4823364 100644 --- a/example/src/components/Reader.tsx +++ b/example/src/components/Reader.tsx @@ -1,10 +1,7 @@ import React, { useEffect, useState, useRef } from 'react'; import { StyleSheet, View, Text, Platform, DimensionValue } from 'react-native'; -import { - ReadiumView, - Settings, -} from 'react-native-readium'; -import type { Link, Locator, File } from 'react-native-readium'; +import { ReadiumView, Settings } from 'react-native-readium'; +import type { Link, Locator, File, Metadata } from 'react-native-readium'; import RNFS from '../utils/RNFS'; import { @@ -22,11 +19,13 @@ export const Reader: React.FC = () => { const [file, setFile] = useState(); const [location, setLocation] = useState(); const [settings, setSettings] = useState>(DEFAULT_SETTINGS); + const [metadata, setMetadata] = useState(null); const ref = useRef(); + console.log('Metadata:', JSON.stringify(metadata, null, 2)); + useEffect(() => { async function run() { - if (Platform.OS === 'web') { setFile({ url: EPUB_URL, @@ -56,7 +55,7 @@ export const Reader: React.FC = () => { } } - run() + run(); }, []); if (file) { @@ -66,11 +65,13 @@ export const Reader: React.FC = () => { setLocation({ - href: loc.href, - type: 'application/xhtml+xml', - title: loc.title || '', - })} + onPress={(loc) => + setLocation({ + href: loc.href, + type: 'application/xhtml+xml', + title: loc.title || '', + }) + } /> @@ -97,7 +98,10 @@ export const Reader: React.FC = () => { settings={settings} onLocationChange={(locator: Locator) => setLocation(locator)} onTableOfContents={(toc: Link[] | null) => { - if (toc) setToc(toc) + if (toc) setToc(toc); + }} + onMetadata={(metadata: Metadata) => { + if (metadata) setMetadata(metadata); }} /> @@ -118,7 +122,7 @@ export const Reader: React.FC = () => { downloading file ); -} +}; const styles = StyleSheet.create({ container: { diff --git a/ios/ReadiumView.swift b/ios/ReadiumView.swift index dd798b1..3a1a0f5 100644 --- a/ios/ReadiumView.swift +++ b/ios/ReadiumView.swift @@ -35,6 +35,7 @@ class ReadiumView : UIView, Loggable { } @objc var onLocationChange: RCTDirectEventBlock? @objc var onTableOfContents: RCTDirectEventBlock? + @objc var onMetadata: RCTDirectEventBlock? func loadBook( url: String, @@ -163,5 +164,9 @@ class ReadiumView : UIView, Loggable { return link.json }) ]) + self.onMetadata?([ + "metadata": vc.publication.metadata.json + ]) + } } diff --git a/ios/ReadiumViewManager.m b/ios/ReadiumViewManager.m index acedabe..01503d9 100644 --- a/ios/ReadiumViewManager.m +++ b/ios/ReadiumViewManager.m @@ -7,5 +7,6 @@ @interface RCT_EXTERN_MODULE(ReadiumViewManager, RCTViewManager) RCT_EXPORT_VIEW_PROPERTY(settings, NSDictionary *) RCT_EXPORT_VIEW_PROPERTY(onLocationChange, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onTableOfContents, RCTDirectEventBlock) +RCT_EXPORT_VIEW_PROPERTY(onMetadata, RCTDirectEventBlock) @end diff --git a/src/components/ReadiumView.tsx b/src/components/ReadiumView.tsx index 49d6ae7..55cbbd1 100644 --- a/src/components/ReadiumView.tsx +++ b/src/components/ReadiumView.tsx @@ -1,4 +1,12 @@ -import React, { useCallback, useState, useEffect, forwardRef, ForwardedRef, useRef, createRef } from 'react'; +import React, { + useCallback, + useState, + useEffect, + forwardRef, + ForwardedRef, + useRef, + createRef, +} from 'react'; import { View, Platform, findNodeHandle, StyleSheet } from 'react-native'; import type { BaseReadiumViewProps, Dimensions } from '../interfaces'; @@ -8,74 +16,102 @@ import { BaseReadiumView } from './BaseReadiumView'; export type ReadiumProps = BaseReadiumViewProps; -export const ReadiumView: React.FC = forwardRef(({ - onLocationChange: wrappedOnLocationChange, - onTableOfContents: wrappedOnTableOfContents, - settings: unmappedSettings, - ...props -}, forwardedRef) => { - const defaultRef = useRef(null); - const [{ height, width }, setDimensions] = useState({ - width: 0, - height: 0, - }); - - // set the view dimensions on layout - const onLayout = useCallback(({ nativeEvent: { layout: { width, height } }}: any) => { - setDimensions({ - width: dimension(width), - height: dimension(height), +export const ReadiumView: React.FC = forwardRef( + ( + { + onLocationChange: wrappedOnLocationChange, + onTableOfContents: wrappedOnTableOfContents, + onMetadata: wrappedOnMetadata, + settings: unmappedSettings, + ...props + }, + forwardedRef + ) => { + const defaultRef = useRef(null); + const [{ height, width }, setDimensions] = useState({ + width: 0, + height: 0, }); - }, []); - // wrap the native onLocationChange and extract the raw event value - const onLocationChange = useCallback((event: any) => { - if (wrappedOnLocationChange) { - wrappedOnLocationChange(event.nativeEvent); - } - }, [wrappedOnLocationChange]); + // set the view dimensions on layout + const onLayout = useCallback( + ({ + nativeEvent: { + layout: { width, height }, + }, + }: any) => { + setDimensions({ + width: dimension(width), + height: dimension(height), + }); + }, + [] + ); - const onTableOfContents = useCallback((event: any) => { - if (wrappedOnTableOfContents) { - const toc = event.nativeEvent.toc || null; - wrappedOnTableOfContents(toc); - } - }, [wrappedOnTableOfContents]); + // wrap the native onLocationChange and extract the raw event value + const onLocationChange = useCallback( + (event: any) => { + if (wrappedOnLocationChange) { + wrappedOnLocationChange(event.nativeEvent); + } + }, + [wrappedOnLocationChange] + ); - // create the view fragment on android - useEffect(() => { - if (Platform.OS === 'android' && defaultRef.current) { - const viewId = findNodeHandle(defaultRef.current); - createFragment(viewId); - } - }, []); + const onTableOfContents = useCallback( + (event: any) => { + if (wrappedOnTableOfContents) { + const toc = event.nativeEvent.toc || null; + wrappedOnTableOfContents(toc); + } + }, + [wrappedOnTableOfContents] + ); - // assign the forwarded ref - useEffect(() => { - if (forwardedRef && 'current' in forwardedRef) { - forwardedRef.current = defaultRef.current; - } else if (forwardedRef) { - forwardedRef(defaultRef); - } - }, [defaultRef.current !== null]) + const onMetadata = useCallback( + (event: any) => { + if (wrappedOnMetadata) { + wrappedOnMetadata(event.nativeEvent); + } + }, + [wrappedOnMetadata] + ); - return ( - - - - ); -}); + // create the view fragment on android + useEffect(() => { + if (Platform.OS === 'android' && defaultRef.current) { + const viewId = findNodeHandle(defaultRef.current); + createFragment(viewId); + } + }, []); + + // assign the forwarded ref + useEffect(() => { + if (forwardedRef && 'current' in forwardedRef) { + forwardedRef.current = defaultRef.current; + } else if (forwardedRef) { + forwardedRef(defaultRef); + } + }, [defaultRef.current !== null]); + + return ( + + + + ); + } +); const styles = StyleSheet.create({ container: { width: '100%', height: '100%' }, diff --git a/src/interfaces/BaseReadiumViewProps.ts b/src/interfaces/BaseReadiumViewProps.ts index b645b2e..b348719 100644 --- a/src/interfaces/BaseReadiumViewProps.ts +++ b/src/interfaces/BaseReadiumViewProps.ts @@ -4,6 +4,7 @@ import type { Settings } from './Settings'; import type { Link } from './Link'; import type { Locator } from './Locator'; import type { File } from './File'; +import type { Metadata } from './Metadata'; export type BaseReadiumViewProps = { file: File; @@ -12,6 +13,7 @@ export type BaseReadiumViewProps = { style?: ViewStyle; onLocationChange?: (locator: Locator) => void; onTableOfContents?: (toc: Link[] | null) => void; + onMetadata?: (metadata: Metadata) => void; ref?: any; height?: number; width?: number; diff --git a/src/interfaces/Metadata.ts b/src/interfaces/Metadata.ts new file mode 100644 index 0000000..aa98ef7 --- /dev/null +++ b/src/interfaces/Metadata.ts @@ -0,0 +1,39 @@ +/** + * An interface representing the Readium Metadata object. + */ +export interface Metadata { + title: { + und: string; + }; + identifier: string; + language: string[]; + author: { + name: { + und: string; + }; + sortAs?: { + und: string; + }; + }[]; + description: string; + published?: string; + publisher?: { + name: { + und: string; + }; + }[]; + subject?: { + name: { + und: string; + }; + }[]; + presentation?: { + layout: string; + spread: string; + overflow: string; + orientation: string; + continuous: boolean; + }; + readingProgression: string; + conformsTo?: string[]; +} diff --git a/src/interfaces/index.ts b/src/interfaces/index.ts index b480442..83d2a97 100644 --- a/src/interfaces/index.ts +++ b/src/interfaces/index.ts @@ -4,3 +4,4 @@ export * from './File'; export * from './Link'; export * from './Locator'; export * from './Settings'; +export * from './Metadata';