react-native-readium 1.0.0-alpha.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.
Files changed (225) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +135 -0
  3. package/android/build.gradle +198 -0
  4. package/android/gradle.properties +3 -0
  5. package/android/src/main/AndroidManifest.xml +4 -0
  6. package/android/src/main/java/com/reactnativereadium/ReadiumPackage.kt +17 -0
  7. package/android/src/main/java/com/reactnativereadium/ReadiumView.kt +84 -0
  8. package/android/src/main/java/com/reactnativereadium/ReadiumViewManager.kt +80 -0
  9. package/android/src/main/java/com/reactnativereadium/epub/UserSettings.kt +236 -0
  10. package/android/src/main/java/com/reactnativereadium/reader/BaseReaderFragment.kt +81 -0
  11. package/android/src/main/java/com/reactnativereadium/reader/EpubReaderFragment.kt +375 -0
  12. package/android/src/main/java/com/reactnativereadium/reader/ImageReaderFragment.kt +68 -0
  13. package/android/src/main/java/com/reactnativereadium/reader/PdfReaderFragment.kt +82 -0
  14. package/android/src/main/java/com/reactnativereadium/reader/ReaderService.kt +116 -0
  15. package/android/src/main/java/com/reactnativereadium/reader/ReaderViewModel.kt +120 -0
  16. package/android/src/main/java/com/reactnativereadium/reader/VisualReaderFragment.kt +78 -0
  17. package/android/src/main/java/com/reactnativereadium/search/SearchFragment.kt +100 -0
  18. package/android/src/main/java/com/reactnativereadium/search/SearchPagingSource.kt +44 -0
  19. package/android/src/main/java/com/reactnativereadium/search/SearchResultAdapter.kt +68 -0
  20. package/android/src/main/java/com/reactnativereadium/utils/ContentResolverUtil.kt +156 -0
  21. package/android/src/main/java/com/reactnativereadium/utils/Dimensions.kt +6 -0
  22. package/android/src/main/java/com/reactnativereadium/utils/EventChannel.kt +61 -0
  23. package/android/src/main/java/com/reactnativereadium/utils/FragmentFactory.kt +46 -0
  24. package/android/src/main/java/com/reactnativereadium/utils/R2DispatcherActivity.kt +45 -0
  25. package/android/src/main/java/com/reactnativereadium/utils/SectionDecoration.kt +98 -0
  26. package/android/src/main/java/com/reactnativereadium/utils/SingleClickListener.kt +32 -0
  27. package/android/src/main/java/com/reactnativereadium/utils/SystemUiManagement.kt +63 -0
  28. package/android/src/main/java/com/reactnativereadium/utils/extensions/Bitmap.kt +23 -0
  29. package/android/src/main/java/com/reactnativereadium/utils/extensions/Context.kt +16 -0
  30. package/android/src/main/java/com/reactnativereadium/utils/extensions/File.kt +22 -0
  31. package/android/src/main/java/com/reactnativereadium/utils/extensions/InputStream.kt +23 -0
  32. package/android/src/main/java/com/reactnativereadium/utils/extensions/Link.kt +6 -0
  33. package/android/src/main/java/com/reactnativereadium/utils/extensions/Metadata.kt +6 -0
  34. package/android/src/main/java/com/reactnativereadium/utils/extensions/URL.kt +29 -0
  35. package/android/src/main/java/com/reactnativereadium/utils/extensions/Uri.kt +17 -0
  36. package/android/src/main/res/drawable/background_action_mode.xml +6 -0
  37. package/android/src/main/res/drawable/cnl.png +0 -0
  38. package/android/src/main/res/drawable/cover.png +0 -0
  39. package/android/src/main/res/drawable/ic_add_white_24dp.xml +9 -0
  40. package/android/src/main/res/drawable/ic_baseline_arrow_forward_24.xml +5 -0
  41. package/android/src/main/res/drawable/ic_baseline_bookmark_24.xml +10 -0
  42. package/android/src/main/res/drawable/ic_baseline_delete_24.xml +10 -0
  43. package/android/src/main/res/drawable/ic_baseline_edit_24.xml +10 -0
  44. package/android/src/main/res/drawable/ic_baseline_enhanced_encryption_24.xml +10 -0
  45. package/android/src/main/res/drawable/ic_baseline_fast_forward_24.xml +10 -0
  46. package/android/src/main/res/drawable/ic_baseline_fast_rewind_24.xml +10 -0
  47. package/android/src/main/res/drawable/ic_baseline_headphones_24.xml +10 -0
  48. package/android/src/main/res/drawable/ic_baseline_pause_24.xml +10 -0
  49. package/android/src/main/res/drawable/ic_baseline_play_arrow_24.xml +10 -0
  50. package/android/src/main/res/drawable/ic_baseline_search_24.xml +10 -0
  51. package/android/src/main/res/drawable/ic_baseline_skip_next_24.xml +10 -0
  52. package/android/src/main/res/drawable/ic_baseline_skip_previous_24.xml +10 -0
  53. package/android/src/main/res/drawable/ic_dashboard_black_24dp.xml +9 -0
  54. package/android/src/main/res/drawable/ic_fastforward_30.xml +7 -0
  55. package/android/src/main/res/drawable/ic_info_black_24dp.xml +9 -0
  56. package/android/src/main/res/drawable/ic_local_library_black_24dp.xml +9 -0
  57. package/android/src/main/res/drawable/ic_notch.xml +4 -0
  58. package/android/src/main/res/drawable/ic_outline_add_24.xml +10 -0
  59. package/android/src/main/res/drawable/ic_outline_format_align_justify_24.xml +10 -0
  60. package/android/src/main/res/drawable/ic_outline_format_align_left_24.xml +11 -0
  61. package/android/src/main/res/drawable/ic_outline_format_size_24.xml +10 -0
  62. package/android/src/main/res/drawable/ic_outline_light_mode_24.xml +10 -0
  63. package/android/src/main/res/drawable/ic_outline_menu_24.xml +10 -0
  64. package/android/src/main/res/drawable/ic_outline_remove_24.xml +10 -0
  65. package/android/src/main/res/drawable/ic_outline_wb_sunny_24.xml +10 -0
  66. package/android/src/main/res/drawable/ic_rewind_30.xml +7 -0
  67. package/android/src/main/res/drawable/icon_font_decrease.png +0 -0
  68. package/android/src/main/res/drawable/icon_font_increase.png +0 -0
  69. package/android/src/main/res/drawable/icon_overflow.png +0 -0
  70. package/android/src/main/res/drawable/rbtn_selector.xml +25 -0
  71. package/android/src/main/res/drawable/rbtn_textcolor_selector.xml +16 -0
  72. package/android/src/main/res/drawable/repfr.png +0 -0
  73. package/android/src/main/res/drawable/selector_blue.xml +42 -0
  74. package/android/src/main/res/drawable/selector_green.xml +42 -0
  75. package/android/src/main/res/drawable/selector_purple.xml +42 -0
  76. package/android/src/main/res/drawable/selector_red.xml +42 -0
  77. package/android/src/main/res/drawable/selector_yellow.xml +41 -0
  78. package/android/src/main/res/layout/activity_epub.xml +23 -0
  79. package/android/src/main/res/layout/activity_main.xml +32 -0
  80. package/android/src/main/res/layout/activity_reader.xml +6 -0
  81. package/android/src/main/res/layout/add_catalog_dialog.xml +25 -0
  82. package/android/src/main/res/layout/filter_row.xml +34 -0
  83. package/android/src/main/res/layout/filter_window.xml +24 -0
  84. package/android/src/main/res/layout/fragment_about.xml +150 -0
  85. package/android/src/main/res/layout/fragment_audiobook.xml +151 -0
  86. package/android/src/main/res/layout/fragment_bookshelf.xml +35 -0
  87. package/android/src/main/res/layout/fragment_catalog.xml +56 -0
  88. package/android/src/main/res/layout/fragment_catalog_feed_list.xml +27 -0
  89. package/android/src/main/res/layout/fragment_drm_management.xml +284 -0
  90. package/android/src/main/res/layout/fragment_listview.xml +24 -0
  91. package/android/src/main/res/layout/fragment_outline.xml +31 -0
  92. package/android/src/main/res/layout/fragment_publication_detail.xml +55 -0
  93. package/android/src/main/res/layout/fragment_reader.xml +6 -0
  94. package/android/src/main/res/layout/fragment_screen_reader.xml +143 -0
  95. package/android/src/main/res/layout/fragment_search.xml +39 -0
  96. package/android/src/main/res/layout/item_group_view.xml +22 -0
  97. package/android/src/main/res/layout/item_recycle_book.xml +55 -0
  98. package/android/src/main/res/layout/item_recycle_bookmark.xml +72 -0
  99. package/android/src/main/res/layout/item_recycle_button.xml +23 -0
  100. package/android/src/main/res/layout/item_recycle_catalog.xml +56 -0
  101. package/android/src/main/res/layout/item_recycle_highlight.xml +85 -0
  102. package/android/src/main/res/layout/item_recycle_horizontal.xml +45 -0
  103. package/android/src/main/res/layout/item_recycle_navigation.xml +34 -0
  104. package/android/src/main/res/layout/item_recycle_search.xml +14 -0
  105. package/android/src/main/res/layout/item_spinner_days.xml +19 -0
  106. package/android/src/main/res/layout/my_fragment.xml +13 -0
  107. package/android/src/main/res/layout/popup_delete.xml +29 -0
  108. package/android/src/main/res/layout/popup_note.xml +105 -0
  109. package/android/src/main/res/layout/popup_passphrase.xml +126 -0
  110. package/android/src/main/res/layout/popup_window_user_settings.xml +576 -0
  111. package/android/src/main/res/layout/section_header.xml +25 -0
  112. package/android/src/main/res/layout/view_action_mode.xml +100 -0
  113. package/android/src/main/res/layout/view_action_mode_reverse.xml +99 -0
  114. package/android/src/main/res/menu/bottom_nav_menu.xml +19 -0
  115. package/android/src/main/res/menu/menu_action_mode.xml +26 -0
  116. package/android/src/main/res/menu/menu_bookmark.xml +18 -0
  117. package/android/src/main/res/menu/menu_epub.xml +41 -0
  118. package/android/src/main/res/menu/menu_filter.xml +21 -0
  119. package/android/src/main/res/menu/menu_reader.xml +33 -0
  120. package/android/src/main/res/navigation/navigation.xml +46 -0
  121. package/android/src/main/res/values/arrays.xml +18 -0
  122. package/android/src/main/res/values/colors.xml +22 -0
  123. package/android/src/main/res/values/refs.xml +5 -0
  124. package/android/src/main/res/values/strings.xml +200 -0
  125. package/android/src/main/res/values/styles.xml +46 -0
  126. package/android/src/main/res/xml/network_security_config.xml +38 -0
  127. package/ios/App/AppModule.swift +62 -0
  128. package/ios/Common/Paths.swift +52 -0
  129. package/ios/Common/Publication.swift +15 -0
  130. package/ios/Common/Toolkit/Extensions/AnyPublisher.swift +14 -0
  131. package/ios/Common/Toolkit/Extensions/Future.swift +16 -0
  132. package/ios/Common/Toolkit/Extensions/HTTPClient.swift +65 -0
  133. package/ios/Common/Toolkit/Extensions/Locator.swift +14 -0
  134. package/ios/Common/Toolkit/Extensions/String.swift +16 -0
  135. package/ios/Common/Toolkit/Extensions/UIImage.swift +12 -0
  136. package/ios/Common/Toolkit/Extensions/UIViewController.swift +19 -0
  137. package/ios/Common/Toolkit/ScreenOrientation.swift +13 -0
  138. package/ios/Data/Bookmark.swift +23 -0
  139. package/ios/Reader/Common/ReaderViewController.swift +309 -0
  140. package/ios/Reader/EPUB/AssociatedColors.swift +27 -0
  141. package/ios/Reader/EPUB/EPUBModule.swift +38 -0
  142. package/ios/Reader/EPUB/EPUBViewController.swift +79 -0
  143. package/ios/Reader/ReaderError.swift +25 -0
  144. package/ios/Reader/ReaderFormatModule.swift +27 -0
  145. package/ios/Reader/ReaderModule.swift +84 -0
  146. package/ios/Reader/ReaderService.swift +126 -0
  147. package/ios/Readium-Bridging-Header.h +2 -0
  148. package/ios/Readium.xcodeproj/project.pbxproj +293 -0
  149. package/ios/ReadiumView.swift +156 -0
  150. package/ios/ReadiumViewManager.m +10 -0
  151. package/ios/ReadiumViewManager.swift +9 -0
  152. package/lib/commonjs/enums/Appearance.js +16 -0
  153. package/lib/commonjs/enums/Appearance.js.map +1 -0
  154. package/lib/commonjs/enums/ColumnCount.js +16 -0
  155. package/lib/commonjs/enums/ColumnCount.js.map +1 -0
  156. package/lib/commonjs/enums/FontFamily.js +21 -0
  157. package/lib/commonjs/enums/FontFamily.js.map +1 -0
  158. package/lib/commonjs/enums/TextAlignment.js +15 -0
  159. package/lib/commonjs/enums/TextAlignment.js.map +1 -0
  160. package/lib/commonjs/enums/index.js +58 -0
  161. package/lib/commonjs/enums/index.js.map +1 -0
  162. package/lib/commonjs/index.js +105 -0
  163. package/lib/commonjs/index.js.map +1 -0
  164. package/lib/commonjs/interfaces/Dimensions.js +2 -0
  165. package/lib/commonjs/interfaces/Dimensions.js.map +1 -0
  166. package/lib/commonjs/interfaces/File.js +6 -0
  167. package/lib/commonjs/interfaces/File.js.map +1 -0
  168. package/lib/commonjs/interfaces/Locator.js +2 -0
  169. package/lib/commonjs/interfaces/Locator.js.map +1 -0
  170. package/lib/commonjs/interfaces/Settings.js +71 -0
  171. package/lib/commonjs/interfaces/Settings.js.map +1 -0
  172. package/lib/commonjs/interfaces/index.js +58 -0
  173. package/lib/commonjs/interfaces/index.js.map +1 -0
  174. package/lib/commonjs/utils/index.js +30 -0
  175. package/lib/commonjs/utils/index.js.map +1 -0
  176. package/lib/module/enums/Appearance.js +9 -0
  177. package/lib/module/enums/Appearance.js.map +1 -0
  178. package/lib/module/enums/ColumnCount.js +9 -0
  179. package/lib/module/enums/ColumnCount.js.map +1 -0
  180. package/lib/module/enums/FontFamily.js +14 -0
  181. package/lib/module/enums/FontFamily.js.map +1 -0
  182. package/lib/module/enums/TextAlignment.js +8 -0
  183. package/lib/module/enums/TextAlignment.js.map +1 -0
  184. package/lib/module/enums/index.js +5 -0
  185. package/lib/module/enums/index.js.map +1 -0
  186. package/lib/module/index.js +61 -0
  187. package/lib/module/index.js.map +1 -0
  188. package/lib/module/interfaces/Dimensions.js +2 -0
  189. package/lib/module/interfaces/Dimensions.js.map +1 -0
  190. package/lib/module/interfaces/File.js +2 -0
  191. package/lib/module/interfaces/File.js.map +1 -0
  192. package/lib/module/interfaces/Locator.js +2 -0
  193. package/lib/module/interfaces/Locator.js.map +1 -0
  194. package/lib/module/interfaces/Settings.js +61 -0
  195. package/lib/module/interfaces/Settings.js.map +1 -0
  196. package/lib/module/interfaces/index.js +5 -0
  197. package/lib/module/interfaces/index.js.map +1 -0
  198. package/lib/module/utils/index.js +17 -0
  199. package/lib/module/utils/index.js.map +1 -0
  200. package/lib/typescript/enums/Appearance.d.ts +11 -0
  201. package/lib/typescript/enums/ColumnCount.d.ts +5 -0
  202. package/lib/typescript/enums/FontFamily.d.ts +10 -0
  203. package/lib/typescript/enums/TextAlignment.d.ts +4 -0
  204. package/lib/typescript/enums/index.d.ts +4 -0
  205. package/lib/typescript/index.d.ts +17 -0
  206. package/lib/typescript/interfaces/Dimensions.d.ts +4 -0
  207. package/lib/typescript/interfaces/File.d.ts +11 -0
  208. package/lib/typescript/interfaces/Locator.d.ts +14 -0
  209. package/lib/typescript/interfaces/Settings.d.ts +40 -0
  210. package/lib/typescript/interfaces/index.d.ts +4 -0
  211. package/lib/typescript/utils/index.d.ts +10 -0
  212. package/package.json +160 -0
  213. package/react-native-readium.podspec +25 -0
  214. package/src/enums/Appearance.ts +14 -0
  215. package/src/enums/ColumnCount.ts +6 -0
  216. package/src/enums/FontFamily.ts +11 -0
  217. package/src/enums/TextAlignment.ts +5 -0
  218. package/src/enums/index.ts +4 -0
  219. package/src/index.tsx +78 -0
  220. package/src/interfaces/Dimensions.ts +4 -0
  221. package/src/interfaces/File.ts +14 -0
  222. package/src/interfaces/Locator.ts +14 -0
  223. package/src/interfaces/Settings.ts +85 -0
  224. package/src/interfaces/index.ts +4 -0
  225. package/src/utils/index.ts +18 -0
@@ -0,0 +1,156 @@
1
+ package com.reactnativereadium.utils
2
+
3
+ import android.content.ContentUris
4
+ import android.content.Context
5
+ import android.net.Uri
6
+ import android.provider.DocumentsContract
7
+ import android.provider.MediaStore
8
+ import android.text.TextUtils
9
+ import kotlinx.coroutines.Dispatchers
10
+ import kotlinx.coroutines.withContext
11
+ import com.reactnativereadium.utils.extensions.toFile
12
+ import java.io.File
13
+ import java.io.FileNotFoundException
14
+ import java.io.InputStream
15
+ import java.net.URL
16
+
17
+ object ContentResolverUtil {
18
+
19
+ suspend fun getContentInputStream(context: Context, uri: Uri, publicationPath: String) {
20
+ withContext(Dispatchers.IO) {
21
+ try {
22
+ val path = getRealPath(context, uri)
23
+ if (path != null) {
24
+ File(path).copyTo(File(publicationPath))
25
+ } else {
26
+ val input = URL(uri.toString()).openStream()
27
+ input.toFile(publicationPath)
28
+ }
29
+ } catch (e: Exception) {
30
+ val input = getInputStream(context, uri)
31
+ input?.let {
32
+ input.toFile(publicationPath)
33
+ }
34
+ }
35
+ }
36
+ }
37
+
38
+ private fun getInputStream(context: Context, uri: Uri): InputStream? {
39
+ return try {
40
+ context.contentResolver.openInputStream(uri)
41
+ } catch (e: FileNotFoundException) {
42
+ e.printStackTrace()
43
+ null
44
+ }
45
+ }
46
+
47
+ private fun getRealPath(context: Context, uri: Uri): String? {
48
+ // DocumentProvider
49
+ if (DocumentsContract.isDocumentUri(context, uri)) {
50
+ // ExternalStorageProvider
51
+ if (isExternalStorageDocument(uri)) {
52
+ val docId = DocumentsContract.getDocumentId(uri)
53
+ val split = docId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
54
+ val type = split[0]
55
+ if ("primary".equals(type, ignoreCase = true)) {
56
+ return context.getExternalFilesDir(null).toString() + "/" + split[1]
57
+ }
58
+ // TODO handle non-primary volumes
59
+
60
+ } else if (isDownloadsDocument(uri)) {
61
+ val id = DocumentsContract.getDocumentId(uri)
62
+ if (!TextUtils.isEmpty(id)) {
63
+ if (id.startsWith("raw:")) {
64
+ return id.replaceFirst("raw:".toRegex(), "")
65
+ }
66
+ return try {
67
+ val contentUri = ContentUris.withAppendedId(
68
+ Uri.parse("content://downloads/public_downloads"), java.lang.Long.valueOf(id))
69
+ getDataColumn(context, contentUri, null, null)
70
+ } catch (e: NumberFormatException) {
71
+ null
72
+ }
73
+
74
+ }
75
+ } else if (isMediaDocument(uri)) {
76
+ val docId = DocumentsContract.getDocumentId(uri)
77
+ val split = docId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
78
+ val type = split[0]
79
+
80
+ var contentUri: Uri? = null
81
+ when (type) {
82
+ "image" -> contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
83
+ "video" -> contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
84
+ "audio" -> contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
85
+ }
86
+
87
+ val selection = "_id=?"
88
+ val selectionArgs = arrayOf(split[1])
89
+
90
+ return getDataColumn(context, contentUri, selection, selectionArgs)
91
+ }
92
+ } else if ("content".equals(uri.scheme!!, ignoreCase = true)) {
93
+
94
+ // Return the remote address
95
+ return getDataColumn(context, uri, null, null)
96
+
97
+ } else if ("file".equals(uri.scheme!!, ignoreCase = true)) {
98
+ return uri.path
99
+ }
100
+
101
+ return null
102
+ }
103
+
104
+ /**
105
+ * Get the value of the data column for this Uri. This is useful for
106
+ * MediaStore Uris, and other file-based ContentProviders.
107
+ *
108
+ * @param context The context.
109
+ * @param uri The Uri to query.
110
+ * @param selection (Optional) Filter used in the query.
111
+ * @param selectionArgs (Optional) Selection arguments used in the query.
112
+ * @return The value of the _data column, which is typically a file path.
113
+ */
114
+ private fun getDataColumn(context: Context, uri: Uri?, selection: String?,
115
+ selectionArgs: Array<String>?): String? {
116
+
117
+ val column = "_data"
118
+ val projection = arrayOf(column)
119
+ context.contentResolver.query(uri!!, projection, selection, selectionArgs, null).use { cursor ->
120
+ cursor?.let {
121
+ if (cursor.moveToFirst()) {
122
+ val index = cursor.getColumnIndexOrThrow(column)
123
+ return cursor.getString(index)
124
+ }
125
+ }
126
+ }
127
+ return null
128
+ }
129
+
130
+
131
+ /**
132
+ * @param uri The Uri to check.
133
+ * @return Whether the Uri authority is ExternalStorageProvider.
134
+ */
135
+ private fun isExternalStorageDocument(uri: Uri): Boolean {
136
+ return "com.android.externalstorage.documents" == uri.authority
137
+ }
138
+
139
+ /**
140
+ * @param uri The Uri to check.
141
+ * @return Whether the Uri authority is DownloadsProvider.
142
+ */
143
+ private fun isDownloadsDocument(uri: Uri): Boolean {
144
+ return "com.android.providers.downloads.documents" == uri.authority
145
+ }
146
+
147
+ /**
148
+ * @param uri The Uri to check.
149
+ * @return Whether the Uri authority is MediaProvider.
150
+ */
151
+ private fun isMediaDocument(uri: Uri): Boolean {
152
+ return "com.android.providers.media.documents" == uri.authority
153
+ }
154
+
155
+
156
+ }
@@ -0,0 +1,6 @@
1
+ package com.reactnativereadium.utils
2
+
3
+ class Dimensions (
4
+ var width: Int = 0,
5
+ var height: Int = 0
6
+ ) {}
@@ -0,0 +1,61 @@
1
+ package com.reactnativereadium.utils
2
+
3
+ import androidx.lifecycle.Lifecycle
4
+ import androidx.lifecycle.LifecycleObserver
5
+ import androidx.lifecycle.LifecycleOwner
6
+ import androidx.lifecycle.OnLifecycleEvent
7
+ import androidx.lifecycle.lifecycleScope
8
+ import kotlinx.coroutines.CoroutineScope
9
+ import kotlinx.coroutines.Job
10
+ import kotlinx.coroutines.channels.Channel
11
+ import kotlinx.coroutines.flow.Flow
12
+ import kotlinx.coroutines.flow.collect
13
+ import kotlinx.coroutines.flow.receiveAsFlow
14
+ import kotlinx.coroutines.launch
15
+
16
+ class EventChannel<T>(private val channel: Channel<T>, private val sendScope: CoroutineScope) {
17
+
18
+ fun send(event: T) {
19
+ sendScope.launch {
20
+ channel.send(event)
21
+ }
22
+ }
23
+
24
+ fun receive(lifecycleOwner: LifecycleOwner, callback: suspend (T) -> Unit) {
25
+ val observer = FlowObserver(lifecycleOwner, channel.receiveAsFlow(), callback)
26
+ lifecycleOwner.lifecycle.addObserver(observer)
27
+ }
28
+ }
29
+
30
+ class FlowObserver<T> (
31
+ private val lifecycleOwner: LifecycleOwner,
32
+ private val flow: Flow<T>,
33
+ private val collector: suspend (T) -> Unit
34
+ ) : LifecycleObserver {
35
+
36
+ private var job: Job? = null
37
+
38
+ @OnLifecycleEvent(Lifecycle.Event.ON_START)
39
+ fun onStart() {
40
+ if (job == null) {
41
+ job = lifecycleOwner.lifecycleScope.launch {
42
+ flow.collect { collector(it) }
43
+ }
44
+ }
45
+ }
46
+
47
+ @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
48
+ fun onStop() {
49
+ job?.cancel()
50
+ job = null
51
+ }
52
+ }
53
+
54
+
55
+ inline fun <reified T> Flow<T>.observeWhenStarted(
56
+ lifecycleOwner: LifecycleOwner,
57
+ noinline collector: suspend (T) -> Unit
58
+ ) {
59
+ val observer = FlowObserver(lifecycleOwner, this, collector)
60
+ lifecycleOwner.lifecycle.addObserver(observer)
61
+ }
@@ -0,0 +1,46 @@
1
+ package com.reactnativereadium.utils
2
+
3
+ import androidx.fragment.app.Fragment
4
+ import androidx.fragment.app.FragmentFactory
5
+ import org.readium.r2.shared.extensions.tryOrNull
6
+
7
+ /**
8
+ * Creates a [FragmentFactory] for a single type of [Fragment] using the result of the given
9
+ * [factory] closure.
10
+ */
11
+ inline fun <reified T : Fragment> createFragmentFactory(crossinline factory: () -> T): FragmentFactory = object : FragmentFactory() {
12
+
13
+ override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
14
+ return when (className) {
15
+ T::class.java.name -> factory()
16
+ else -> super.instantiate(classLoader, className)
17
+ }
18
+ }
19
+
20
+ }
21
+
22
+ /**
23
+ * A [FragmentFactory] which will iterate over a provided list of [factories] until finding one
24
+ * instantiating successfully the requested [Fragment].
25
+ *
26
+ * ```
27
+ * supportFragmentManager.fragmentFactory = CompositeFragmentFactory(
28
+ * EpubNavigatorFragment.createFactory(publication, baseUrl, initialLocator, this),
29
+ * PdfNavigatorFragment.createFactory(publication, initialLocator, this)
30
+ * )
31
+ * ```
32
+ */
33
+ class CompositeFragmentFactory(private val factories: List<FragmentFactory>) : FragmentFactory() {
34
+
35
+ constructor(vararg factories: FragmentFactory) : this(factories.toList())
36
+
37
+ override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
38
+ for (factory in factories) {
39
+ tryOrNull { factory.instantiate(classLoader, className) }
40
+ ?.let { return it }
41
+ }
42
+
43
+ return super.instantiate(classLoader, className)
44
+ }
45
+
46
+ }
@@ -0,0 +1,45 @@
1
+ package com.reactnativereadium.utils
2
+
3
+ import android.app.Activity
4
+ import android.content.Intent
5
+ import android.net.Uri
6
+ import android.os.Bundle
7
+ //import com.reactnativereadium.MainActivity
8
+ import timber.log.Timber
9
+
10
+ class R2DispatcherActivity : Activity() {
11
+
12
+ override fun onCreate(savedInstanceState: Bundle?) {
13
+ super.onCreate(savedInstanceState)
14
+ dispatchIntent(intent)
15
+ finish()
16
+ }
17
+
18
+ private fun dispatchIntent(intent: Intent) {
19
+ val uri = uriFromIntent(intent)
20
+ ?: run {
21
+ Timber.d("Got an empty intent.")
22
+ return
23
+ }
24
+ // FIXME: MainActivity
25
+ // val newIntent = Intent(this, MainActivity::class.java).apply {
26
+ // addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
27
+ // data = uri
28
+ // }
29
+ // startActivity(newIntent)
30
+ }
31
+
32
+ private fun uriFromIntent(intent: Intent): Uri? =
33
+ when (intent.action) {
34
+ Intent.ACTION_SEND -> {
35
+ if ("text/plain" == intent.type) {
36
+ intent.getStringExtra(Intent.EXTRA_TEXT).let { Uri.parse(it) }
37
+ } else {
38
+ intent.getParcelableExtra(Intent.EXTRA_STREAM)
39
+ }
40
+ }
41
+ else -> {
42
+ intent.data
43
+ }
44
+ }
45
+ }
@@ -0,0 +1,98 @@
1
+ package com.reactnativereadium.utils
2
+
3
+ import android.content.Context
4
+ import android.graphics.Canvas
5
+ import android.graphics.Rect
6
+ import android.view.LayoutInflater
7
+ import android.view.View
8
+ import android.view.ViewGroup
9
+ import android.widget.TextView
10
+ import androidx.core.view.children
11
+ import androidx.recyclerview.widget.RecyclerView
12
+ import androidx.recyclerview.widget.RecyclerView.NO_POSITION
13
+ /* import com.reactnativereadium.databinding.SectionHeaderBinding */
14
+
15
+ class SectionDecoration(
16
+ private val context: Context,
17
+ private val listener: Listener
18
+ ) : RecyclerView.ItemDecoration() {
19
+
20
+ interface Listener {
21
+ fun isStartOfSection(itemPos: Int): Boolean
22
+ fun sectionTitle(itemPos: Int): String
23
+ }
24
+
25
+ private lateinit var headerView: View
26
+ private lateinit var sectionTitleView: TextView
27
+
28
+ override fun getItemOffsets(
29
+ outRect: Rect,
30
+ view: View,
31
+ parent: RecyclerView,
32
+ state: RecyclerView.State
33
+ ) {
34
+ super.getItemOffsets(outRect, view, parent, state)
35
+ val pos = parent.getChildAdapterPosition(view)
36
+ initHeaderViewIfNeeded(parent)
37
+ if (listener.sectionTitle(pos) != "" && listener.isStartOfSection(pos)) {
38
+ sectionTitleView.text = listener.sectionTitle(pos)
39
+ fixLayoutSize(headerView, parent)
40
+ outRect.top = headerView.height
41
+ }
42
+ }
43
+
44
+ override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
45
+ super.onDrawOver(c, parent, state)
46
+ initHeaderViewIfNeeded(parent)
47
+
48
+ val children = parent.children.toList()
49
+ children.forEach { child ->
50
+ val pos = parent.getChildAdapterPosition(child)
51
+ if (pos != NO_POSITION && listener.sectionTitle(pos) != "" &&
52
+ (listener.isStartOfSection(pos) || isTopChild(child, children))) {
53
+ sectionTitleView.text = listener.sectionTitle(pos)
54
+ fixLayoutSize(headerView, parent)
55
+ drawHeader(c, child, headerView)
56
+ }
57
+ }
58
+ }
59
+
60
+ private fun initHeaderViewIfNeeded(parent: RecyclerView) {
61
+ if (::headerView.isInitialized) return
62
+ /* FIXME: databinding.SectionHeaderBinding */
63
+ /* SectionHeaderBinding.inflate(
64
+ LayoutInflater.from(context),
65
+ parent,
66
+ false
67
+ ).apply {
68
+ headerView = root
69
+ sectionTitleView = header
70
+ } */
71
+ }
72
+
73
+ private fun fixLayoutSize(v: View, parent: ViewGroup) {
74
+ val widthSpec = View.MeasureSpec.makeMeasureSpec(parent.width, View.MeasureSpec.EXACTLY)
75
+ val heightSpec = View.MeasureSpec.makeMeasureSpec(parent.height, View.MeasureSpec.UNSPECIFIED)
76
+ val childWidth = ViewGroup.getChildMeasureSpec(widthSpec, parent.paddingStart + parent.paddingEnd, v.layoutParams.width)
77
+ val childHeight = ViewGroup.getChildMeasureSpec(heightSpec, parent.paddingTop + parent.paddingBottom, v.layoutParams.height)
78
+ v.measure(childWidth, childHeight)
79
+ v.layout(0, 0, v.measuredWidth, v.measuredHeight)
80
+ }
81
+
82
+ private fun drawHeader(c: Canvas, child: View, headerView: View) {
83
+ c.run {
84
+ save()
85
+ translate(0F, maxOf(0, child.top - headerView.height).toFloat())
86
+ headerView.draw(this)
87
+ restore()
88
+ }
89
+ }
90
+
91
+ private fun isTopChild(child: View, children: List<View>): Boolean {
92
+ var tmp = child.top
93
+ children.forEach { c ->
94
+ tmp = minOf(c.top, tmp)
95
+ }
96
+ return child.top == tmp
97
+ }
98
+ }
@@ -0,0 +1,32 @@
1
+ package com.reactnativereadium.utils
2
+
3
+ import android.view.View
4
+
5
+
6
+ /**
7
+ * Prevents from double clicks on a view, which could otherwise lead to unpredictable states. Useful
8
+ * while transitioning to another activity for instance.
9
+ */
10
+ class SingleClickListener(private val click: (v: View) -> Unit) : View.OnClickListener {
11
+
12
+ companion object {
13
+ private const val DOUBLE_CLICK_TIMEOUT = 2500
14
+ }
15
+
16
+ private var lastClick: Long = 0
17
+
18
+ override fun onClick(v: View) {
19
+ if (getLastClickTimeout() > DOUBLE_CLICK_TIMEOUT) {
20
+ lastClick = System.currentTimeMillis()
21
+ click(v)
22
+ }
23
+ }
24
+
25
+ private fun getLastClickTimeout(): Long {
26
+ return System.currentTimeMillis() - lastClick
27
+ }
28
+ }
29
+
30
+ fun View.singleClick(l: (View) -> Unit) {
31
+ setOnClickListener(SingleClickListener(l))
32
+ }
@@ -0,0 +1,63 @@
1
+ package com.reactnativereadium.utils
2
+
3
+ import android.app.Activity
4
+ import android.view.View
5
+ import android.view.WindowInsets
6
+ import androidx.core.view.WindowInsetsCompat
7
+
8
+ // Using ViewCompat and WindowInsetsCompat does not work properly in all versions of Android
9
+ @Suppress("DEPRECATION")
10
+ /** Returns `true` if fullscreen or immersive mode is not set. */
11
+ private fun Activity.isSystemUiVisible(): Boolean {
12
+ return this.window.decorView.systemUiVisibility and View.SYSTEM_UI_FLAG_FULLSCREEN == 0
13
+ }
14
+
15
+ // Using ViewCompat and WindowInsetsCompat does not work properly in all versions of Android
16
+ @Suppress("DEPRECATION")
17
+ /** Enable fullscreen or immersive mode. */
18
+ fun Activity.hideSystemUi() {
19
+ this.window.decorView.systemUiVisibility = (
20
+ View.SYSTEM_UI_FLAG_IMMERSIVE
21
+ or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
22
+ or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
23
+ or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
24
+ or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
25
+ or View.SYSTEM_UI_FLAG_FULLSCREEN
26
+ )
27
+ }
28
+
29
+ // Using ViewCompat and WindowInsetsCompat does not work properly in all versions of Android
30
+ @Suppress("DEPRECATION")
31
+ /** Disable fullscreen or immersive mode. */
32
+ fun Activity.showSystemUi() {
33
+ this.window.decorView.systemUiVisibility = (
34
+ View.SYSTEM_UI_FLAG_LAYOUT_STABLE
35
+ or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
36
+ or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
37
+ )
38
+ }
39
+
40
+ /** Toggle fullscreen or immersive mode. */
41
+ fun Activity.toggleSystemUi() {
42
+ if (this.isSystemUiVisible()) {
43
+ this.hideSystemUi()
44
+ } else {
45
+ this.showSystemUi()
46
+ }
47
+ }
48
+
49
+ /** Set padding around view so that content doesn't overlap system UI */
50
+ fun View.padSystemUi(insets: WindowInsets, activity: Activity) =
51
+ WindowInsetsCompat.toWindowInsetsCompat(insets, this)
52
+ .getInsets(WindowInsetsCompat.Type.systemBars()).apply {
53
+ setPadding(
54
+ left,
55
+ top,
56
+ right,
57
+ bottom
58
+ )
59
+ }
60
+
61
+ /** Clear padding around view */
62
+ fun View.clearPadding() =
63
+ setPadding(0, 0, 0, 0)
@@ -0,0 +1,23 @@
1
+ package com.reactnativereadium.utils.extensions
2
+
3
+ import android.graphics.Bitmap
4
+ import android.util.Base64
5
+ import timber.log.Timber
6
+ import java.io.ByteArrayOutputStream
7
+
8
+ /**
9
+ * Converts the receiver bitmap into a data URL ready to be used in HTML or CSS.
10
+ *
11
+ * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs
12
+ */
13
+ fun Bitmap.toDataUrl(): String? =
14
+ try {
15
+ val stream = ByteArrayOutputStream()
16
+ compress(Bitmap.CompressFormat.PNG, 100, stream)
17
+ .also { success -> if (!success) throw Exception("Can't compress image to PNG") }
18
+ val b64 = Base64.encodeToString(stream.toByteArray(), Base64.DEFAULT)
19
+ "data:image/png;base64,$b64"
20
+ } catch (e: Exception) {
21
+ Timber.e(e)
22
+ null
23
+ }
@@ -0,0 +1,16 @@
1
+ package com.reactnativereadium.utils.extensions
2
+
3
+ import android.content.Context
4
+ import androidx.annotation.ColorInt
5
+ import androidx.annotation.ColorRes
6
+ import androidx.core.content.ContextCompat
7
+
8
+
9
+ /**
10
+ * Extensions
11
+ */
12
+
13
+ @ColorInt
14
+ fun Context.color(@ColorRes id: Int): Int {
15
+ return ContextCompat.getColor(this, id)
16
+ }
@@ -0,0 +1,22 @@
1
+ package com.reactnativereadium.utils.extensions
2
+
3
+ import kotlinx.coroutines.Dispatchers
4
+ import kotlinx.coroutines.withContext
5
+ import java.io.File
6
+ import java.io.FileFilter
7
+ import java.io.IOException
8
+
9
+ suspend fun File.moveTo(target: File) = withContext(Dispatchers.IO) {
10
+ if (!this@moveTo.renameTo(target))
11
+ throw IOException()
12
+ }
13
+
14
+
15
+ /**
16
+ * As there are cases where [File.listFiles] returns null even though it is a directory, we return
17
+ * an empty list instead.
18
+ */
19
+ fun File.listFilesSafely(filter: FileFilter? = null): List<File> {
20
+ val array: Array<File>? = if (filter == null) listFiles() else listFiles(filter)
21
+ return array?.toList() ?: emptyList()
22
+ }
@@ -0,0 +1,23 @@
1
+ package com.reactnativereadium.utils.extensions
2
+
3
+ import kotlinx.coroutines.Dispatchers
4
+ import kotlinx.coroutines.withContext
5
+ import org.readium.r2.shared.extensions.tryOrNull
6
+ import java.io.File
7
+ import java.io.InputStream
8
+ import java.util.*
9
+
10
+
11
+ suspend fun InputStream.toFile(path: String) {
12
+ withContext(Dispatchers.IO) {
13
+ use { input ->
14
+ File(path).outputStream().use { input.copyTo(it) }
15
+ }
16
+ }
17
+ }
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
+ }
@@ -0,0 +1,6 @@
1
+ package com.reactnativereadium.utils.extensions
2
+
3
+ import org.readium.r2.shared.publication.Link
4
+
5
+ val Link.outlineTitle: String
6
+ get() = title ?: href
@@ -0,0 +1,6 @@
1
+ package com.reactnativereadium.utils.extensions
2
+
3
+ import org.readium.r2.shared.publication.Metadata
4
+
5
+ val Metadata.authorName: String get() =
6
+ authors.firstOrNull()?.name ?: ""
@@ -0,0 +1,29 @@
1
+ package com.reactnativereadium.utils.extensions
2
+
3
+ import kotlinx.coroutines.Dispatchers
4
+ import kotlinx.coroutines.withContext
5
+ import org.readium.r2.shared.extensions.extension
6
+ import org.readium.r2.shared.extensions.tryOr
7
+ import org.readium.r2.shared.extensions.tryOrNull
8
+ import java.io.File
9
+ import java.io.FileOutputStream
10
+ import java.net.URL
11
+ import java.util.*
12
+
13
+ suspend fun URL.download(path: String): File? = tryOr(null) {
14
+ val file = File(path)
15
+ withContext(Dispatchers.IO) {
16
+ openStream().use { input ->
17
+ FileOutputStream(file).use { output ->
18
+ input.copyTo(output)
19
+ }
20
+ }
21
+ }
22
+ file
23
+ }
24
+
25
+ suspend fun URL.copyToTempFile(dir: String): File? = tryOrNull {
26
+ val filename = UUID.randomUUID().toString()
27
+ val path = "$dir$filename.$extension"
28
+ download(path)
29
+ }
@@ -0,0 +1,17 @@
1
+ package com.reactnativereadium.utils.extensions
2
+
3
+ import android.content.Context
4
+ import android.net.Uri
5
+ import org.readium.r2.shared.extensions.tryOrNull
6
+ import org.readium.r2.shared.util.mediatype.MediaType
7
+ import com.reactnativereadium.utils.ContentResolverUtil
8
+ import java.io.File
9
+ import java.util.*
10
+
11
+ suspend fun Uri.copyToTempFile(context: Context, dir: String): File? = tryOrNull {
12
+ val filename = UUID.randomUUID().toString()
13
+ val mediaType = MediaType.ofUri(this, context.contentResolver)
14
+ val path = "$dir$filename.${mediaType?.fileExtension ?: "tmp"}"
15
+ ContentResolverUtil.getContentInputStream(context, this, path)
16
+ return@tryOrNull File(path)
17
+ }
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <shape xmlns:android="http://schemas.android.com/apk/res/android"
3
+ android:shape="rectangle" android:padding = "10dp">
4
+ <solid android:color="#000" />
5
+ <corners android:radius="8dp" />
6
+ </shape>