expo-media-library 18.3.0-canary-20250919-7a31b96 → 18.3.0-canary-20250930-9dc59d3

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 (72) hide show
  1. package/CHANGELOG.md +3 -0
  2. package/android/build.gradle +6 -6
  3. package/android/src/main/java/expo/modules/medialibrary/next/MediaLibraryNextModule.kt +27 -18
  4. package/android/src/main/java/expo/modules/medialibrary/next/extensions/resolver/CursorExtensions.kt +15 -5
  5. package/android/src/main/java/expo/modules/medialibrary/next/objects/album/Album.kt +16 -17
  6. package/android/src/main/java/expo/modules/medialibrary/next/objects/album/AlbumQuery.kt +3 -2
  7. package/android/src/main/java/expo/modules/medialibrary/next/objects/album/factories/AlbumFactory.kt +1 -0
  8. package/android/src/main/java/expo/modules/medialibrary/next/objects/album/factories/AlbumLegacyFactory.kt +13 -4
  9. package/android/src/main/java/expo/modules/medialibrary/next/objects/album/factories/AlbumModernFactory.kt +13 -4
  10. package/android/src/main/java/expo/modules/medialibrary/next/objects/asset/Asset.kt +2 -18
  11. package/android/src/main/java/expo/modules/medialibrary/next/objects/asset/delegates/AssetLegacyDelegate.kt +9 -9
  12. package/android/src/main/java/expo/modules/medialibrary/next/objects/asset/delegates/AssetModernDelegate.kt +10 -4
  13. package/android/src/main/java/expo/modules/medialibrary/next/objects/asset/deleters/AssetDeleter.kt +8 -0
  14. package/android/src/main/java/expo/modules/medialibrary/next/objects/asset/deleters/AssetLegacyDeleter.kt +46 -0
  15. package/android/src/main/java/expo/modules/medialibrary/next/objects/asset/deleters/AssetModernDeleter.kt +21 -0
  16. package/android/src/main/java/expo/modules/medialibrary/next/objects/asset/factories/AssetFactory.kt +2 -1
  17. package/android/src/main/java/expo/modules/medialibrary/next/objects/asset/factories/AssetLegacyFactory.kt +25 -10
  18. package/android/src/main/java/expo/modules/medialibrary/next/objects/asset/factories/AssetModernFactory.kt +22 -3
  19. package/android/src/main/java/expo/modules/medialibrary/next/objects/query/Query.kt +6 -2
  20. package/android/src/main/java/expo/modules/medialibrary/next/permissions/MediaStorePermissionsDelegate.kt +12 -6
  21. package/build/next/types/Asset.d.ts +4 -3
  22. package/build/next/types/Asset.d.ts.map +1 -1
  23. package/build/next/types/Asset.js.map +1 -1
  24. package/expo-module.config.json +1 -1
  25. package/ios/next/MediaLibraryNextModule.swift +7 -7
  26. package/ios/next/objects/Query/Query.swift +3 -3
  27. package/ios/next/objects/album/Album.swift +2 -2
  28. package/ios/next/objects/asset/Asset.swift +12 -5
  29. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/{18.3.0-canary-20250919-7a31b96/expo.modules.medialibrary-18.3.0-canary-20250919-7a31b96-sources.jar → 18.3.0-canary-20250930-9dc59d3/expo.modules.medialibrary-18.3.0-canary-20250930-9dc59d3-sources.jar} +0 -0
  30. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250930-9dc59d3/expo.modules.medialibrary-18.3.0-canary-20250930-9dc59d3-sources.jar.md5 +1 -0
  31. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250930-9dc59d3/expo.modules.medialibrary-18.3.0-canary-20250930-9dc59d3-sources.jar.sha1 +1 -0
  32. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250930-9dc59d3/expo.modules.medialibrary-18.3.0-canary-20250930-9dc59d3-sources.jar.sha256 +1 -0
  33. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250930-9dc59d3/expo.modules.medialibrary-18.3.0-canary-20250930-9dc59d3-sources.jar.sha512 +1 -0
  34. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250930-9dc59d3/expo.modules.medialibrary-18.3.0-canary-20250930-9dc59d3.aar +0 -0
  35. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250930-9dc59d3/expo.modules.medialibrary-18.3.0-canary-20250930-9dc59d3.aar.md5 +1 -0
  36. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250930-9dc59d3/expo.modules.medialibrary-18.3.0-canary-20250930-9dc59d3.aar.sha1 +1 -0
  37. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250930-9dc59d3/expo.modules.medialibrary-18.3.0-canary-20250930-9dc59d3.aar.sha256 +1 -0
  38. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250930-9dc59d3/expo.modules.medialibrary-18.3.0-canary-20250930-9dc59d3.aar.sha512 +1 -0
  39. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/{18.3.0-canary-20250919-7a31b96/expo.modules.medialibrary-18.3.0-canary-20250919-7a31b96.module → 18.3.0-canary-20250930-9dc59d3/expo.modules.medialibrary-18.3.0-canary-20250930-9dc59d3.module} +27 -27
  40. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250930-9dc59d3/expo.modules.medialibrary-18.3.0-canary-20250930-9dc59d3.module.md5 +1 -0
  41. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250930-9dc59d3/expo.modules.medialibrary-18.3.0-canary-20250930-9dc59d3.module.sha1 +1 -0
  42. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250930-9dc59d3/expo.modules.medialibrary-18.3.0-canary-20250930-9dc59d3.module.sha256 +1 -0
  43. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250930-9dc59d3/expo.modules.medialibrary-18.3.0-canary-20250930-9dc59d3.module.sha512 +1 -0
  44. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/{18.3.0-canary-20250919-7a31b96/expo.modules.medialibrary-18.3.0-canary-20250919-7a31b96.pom → 18.3.0-canary-20250930-9dc59d3/expo.modules.medialibrary-18.3.0-canary-20250930-9dc59d3.pom} +4 -4
  45. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250930-9dc59d3/expo.modules.medialibrary-18.3.0-canary-20250930-9dc59d3.pom.md5 +1 -0
  46. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250930-9dc59d3/expo.modules.medialibrary-18.3.0-canary-20250930-9dc59d3.pom.sha1 +1 -0
  47. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250930-9dc59d3/expo.modules.medialibrary-18.3.0-canary-20250930-9dc59d3.pom.sha256 +1 -0
  48. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250930-9dc59d3/expo.modules.medialibrary-18.3.0-canary-20250930-9dc59d3.pom.sha512 +1 -0
  49. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/maven-metadata.xml +4 -4
  50. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/maven-metadata.xml.md5 +1 -1
  51. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/maven-metadata.xml.sha1 +1 -1
  52. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/maven-metadata.xml.sha256 +1 -1
  53. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/maven-metadata.xml.sha512 +1 -1
  54. package/package.json +3 -3
  55. package/src/next/types/Asset.ts +4 -3
  56. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250919-7a31b96/expo.modules.medialibrary-18.3.0-canary-20250919-7a31b96-sources.jar.md5 +0 -1
  57. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250919-7a31b96/expo.modules.medialibrary-18.3.0-canary-20250919-7a31b96-sources.jar.sha1 +0 -1
  58. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250919-7a31b96/expo.modules.medialibrary-18.3.0-canary-20250919-7a31b96-sources.jar.sha256 +0 -1
  59. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250919-7a31b96/expo.modules.medialibrary-18.3.0-canary-20250919-7a31b96-sources.jar.sha512 +0 -1
  60. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250919-7a31b96/expo.modules.medialibrary-18.3.0-canary-20250919-7a31b96.aar +0 -0
  61. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250919-7a31b96/expo.modules.medialibrary-18.3.0-canary-20250919-7a31b96.aar.md5 +0 -1
  62. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250919-7a31b96/expo.modules.medialibrary-18.3.0-canary-20250919-7a31b96.aar.sha1 +0 -1
  63. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250919-7a31b96/expo.modules.medialibrary-18.3.0-canary-20250919-7a31b96.aar.sha256 +0 -1
  64. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250919-7a31b96/expo.modules.medialibrary-18.3.0-canary-20250919-7a31b96.aar.sha512 +0 -1
  65. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250919-7a31b96/expo.modules.medialibrary-18.3.0-canary-20250919-7a31b96.module.md5 +0 -1
  66. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250919-7a31b96/expo.modules.medialibrary-18.3.0-canary-20250919-7a31b96.module.sha1 +0 -1
  67. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250919-7a31b96/expo.modules.medialibrary-18.3.0-canary-20250919-7a31b96.module.sha256 +0 -1
  68. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250919-7a31b96/expo.modules.medialibrary-18.3.0-canary-20250919-7a31b96.module.sha512 +0 -1
  69. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250919-7a31b96/expo.modules.medialibrary-18.3.0-canary-20250919-7a31b96.pom.md5 +0 -1
  70. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250919-7a31b96/expo.modules.medialibrary-18.3.0-canary-20250919-7a31b96.pom.sha1 +0 -1
  71. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250919-7a31b96/expo.modules.medialibrary-18.3.0-canary-20250919-7a31b96.pom.sha256 +0 -1
  72. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250919-7a31b96/expo.modules.medialibrary-18.3.0-canary-20250919-7a31b96.pom.sha512 +0 -1
package/CHANGELOG.md CHANGED
@@ -10,11 +10,14 @@
10
10
 
11
11
  ### 🐛 Bug fixes
12
12
 
13
+ - [next][iOS] Convert `id` to URI format ([#39920](https://github.com/expo/expo/pull/39920) by [@Wenszel](https://github.com/Wenszel))
14
+ - [next][android] Fix `delete()` throwing security exception ([#39914](https://github.com/expo/expo/pull/39914) by [@Wenszel](https://github.com/Wenszel))
13
15
  - [next][android] Change default root directory to Pictures ([#39716](https://github.com/expo/expo/pull/39716) by [@Wenszel](https://github.com/Wenszel))
14
16
  - [next] Fix `asset.getModificationTime` to return milliseconds ([#39715](https://github.com/expo/expo/pull/39715) by [@Wenszel](https://github.com/Wenszel))
15
17
 
16
18
  ### 💡 Others
17
19
 
20
+ - [next] Add test screens ([#39951](https://github.com/expo/expo/pull/39951) by [@Wenszel](https://github.com/Wenszel))
18
21
  - [next] Add documentation ([#39754](https://github.com/expo/expo/pull/39754) by [@Wenszel](https://github.com/Wenszel))
19
22
 
20
23
  ## 18.2.0 — 2025-09-16
@@ -4,23 +4,23 @@ plugins {
4
4
  }
5
5
 
6
6
  group = 'host.exp.exponent'
7
- version = '18.3.0-canary-20250919-7a31b96'
7
+ version = '18.3.0-canary-20250930-9dc59d3'
8
8
 
9
9
  android {
10
10
  namespace "expo.modules.medialibrary"
11
11
  defaultConfig {
12
12
  versionCode 37
13
- versionName "18.3.0-canary-20250919-7a31b96"
13
+ versionName "18.3.0-canary-20250930-9dc59d3"
14
14
  }
15
15
  }
16
16
 
17
17
  dependencies {
18
- implementation "androidx.annotation:annotation:1.2.0"
19
- api "androidx.exifinterface:exifinterface:1.3.3"
20
- implementation 'androidx.activity:activity-ktx:1.10.1'
18
+ implementation "androidx.annotation:annotation:1.9.1"
19
+ api "androidx.exifinterface:exifinterface:1.4.1"
20
+ implementation 'androidx.activity:activity-ktx:1.11.0'
21
21
 
22
22
  if (project.findProject(':expo-modules-test-core')) {
23
23
  testImplementation project(':expo-modules-test-core')
24
24
  }
25
- testImplementation "org.robolectric:robolectric:4.10"
25
+ testImplementation "org.robolectric:robolectric:4.16"
26
26
  }
@@ -15,6 +15,8 @@ import expo.modules.medialibrary.next.objects.album.AlbumQuery
15
15
  import expo.modules.medialibrary.next.objects.asset.Asset
16
16
  import expo.modules.medialibrary.next.objects.album.factories.AlbumModernFactory
17
17
  import expo.modules.medialibrary.next.objects.album.factories.AlbumLegacyFactory
18
+ import expo.modules.medialibrary.next.objects.asset.deleters.AssetLegacyDeleter
19
+ import expo.modules.medialibrary.next.objects.asset.deleters.AssetModernDeleter
18
20
  import expo.modules.medialibrary.next.objects.asset.factories.AssetModernFactory
19
21
  import expo.modules.medialibrary.next.objects.asset.factories.AssetLegacyFactory
20
22
  import expo.modules.medialibrary.next.objects.query.MediaStoreQueryFormatter
@@ -40,22 +42,30 @@ class MediaLibraryNextModule : Module() {
40
42
  }
41
43
 
42
44
  private val albumQuery by lazy {
43
- AlbumQuery(context)
45
+ AlbumQuery(albumFactory, context)
44
46
  }
45
47
 
46
48
  private val albumFactory by lazy {
47
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
48
- AlbumModernFactory(assetFactory, context)
49
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
50
+ AlbumModernFactory(assetFactory, assetDeleter, context)
49
51
  } else {
50
- AlbumLegacyFactory(assetFactory, context)
52
+ AlbumLegacyFactory(assetFactory, assetDeleter, context)
51
53
  }
52
54
  }
53
55
 
54
56
  private val assetFactory by lazy {
55
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
56
- AssetModernFactory(context)
57
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
58
+ AssetModernFactory(assetDeleter, context)
57
59
  } else {
58
- AssetLegacyFactory(context)
60
+ AssetLegacyFactory(assetDeleter, context)
61
+ }
62
+ }
63
+
64
+ private val assetDeleter by lazy {
65
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
66
+ AssetModernDeleter(mediaStorePermissionsDelegate)
67
+ } else {
68
+ AssetLegacyDeleter(context)
59
69
  }
60
70
  }
61
71
 
@@ -64,7 +74,7 @@ class MediaLibraryNextModule : Module() {
64
74
 
65
75
  Class(Asset::class) {
66
76
  Constructor { contentUri: Uri ->
67
- Asset(contentUri, context)
77
+ assetFactory.create(contentUri)
68
78
  }
69
79
 
70
80
  Property("id") { self: Asset ->
@@ -114,14 +124,13 @@ class MediaLibraryNextModule : Module() {
114
124
 
115
125
  AsyncFunction("delete") Coroutine { self: Asset ->
116
126
  systemPermissionsDelegate.requireSystemPermissions(true)
117
- mediaStorePermissionsDelegate.requestMediaLibraryActionPermission(listOf(self.contentUri), needsDeletePermission = true)
118
127
  self.delete()
119
128
  }
120
129
  }
121
130
 
122
131
  Class(Album::class) {
123
132
  Constructor { id: String ->
124
- Album(id, context)
133
+ Album(id, assetDeleter, assetFactory, context)
125
134
  }
126
135
 
127
136
  Property("id") { self: Album ->
@@ -140,21 +149,19 @@ class MediaLibraryNextModule : Module() {
140
149
 
141
150
  AsyncFunction("add") Coroutine { self: Album, asset: Asset ->
142
151
  systemPermissionsDelegate.requireSystemPermissions(true)
143
- mediaStorePermissionsDelegate.requestMediaLibraryActionPermission(listOf(asset.contentUri))
152
+ mediaStorePermissionsDelegate.requestMediaLibraryWritePermission(listOf(asset.contentUri))
144
153
  self.add(asset)
145
154
  }
146
155
 
147
156
  AsyncFunction("delete") Coroutine { self: Album ->
148
157
  systemPermissionsDelegate.requireSystemPermissions(true)
149
- val assetIdsToDelete = self.getAssets().map { it.contentUri }
150
- mediaStorePermissionsDelegate.requestMediaLibraryActionPermission(assetIdsToDelete, needsDeletePermission = true)
151
158
  self.delete()
152
159
  }
153
160
  }
154
161
 
155
162
  Class(Query::class) {
156
163
  Constructor {
157
- Query(context)
164
+ Query(assetFactory, context)
158
165
  }
159
166
 
160
167
  Function("limit") { self: Query, limit: Int ->
@@ -233,14 +240,16 @@ class MediaLibraryNextModule : Module() {
233
240
 
234
241
  AsyncFunction("deleteAlbums") Coroutine { albums: List<Album> ->
235
242
  systemPermissionsDelegate.requireSystemPermissions(true)
236
- albums.forEach { album -> album.delete() }
243
+ val contentUris = albums
244
+ .map { it.getAssets() }
245
+ .flatten()
246
+ .map { it.contentUri }
247
+ assetDeleter.delete(contentUris)
237
248
  }
238
249
 
239
250
  AsyncFunction("deleteAssets") Coroutine { assets: List<Asset> ->
240
251
  systemPermissionsDelegate.requireSystemPermissions(true)
241
- val assetIdsToDelete = assets.map { it.contentUri }
242
- mediaStorePermissionsDelegate.requestMediaLibraryActionPermission(assetIdsToDelete, needsDeletePermission = true)
243
- assets.forEach { asset -> asset.delete() }
252
+ assetDeleter.delete(assets.map { it.contentUri })
244
253
  }
245
254
 
246
255
  AsyncFunction("requestPermissionsAsync") { writeOnly: Boolean, permissions: List<GranularPermission>?, promise: Promise ->
@@ -3,16 +3,26 @@ package expo.modules.medialibrary.next.extensions.resolver
3
3
  import android.content.ContentUris
4
4
  import android.database.Cursor
5
5
  import android.net.Uri
6
+ import android.os.Build
6
7
  import android.provider.MediaStore
7
8
 
8
9
  fun Cursor.extractAssetContentUri(idColumn: Int, typeColumn: Int): Uri {
9
10
  val id = getLong(idColumn)
10
11
  val mediaType = getInt(typeColumn)
11
- val baseUri = when (mediaType) {
12
- MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI
13
- MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI
14
- MediaStore.Files.FileColumns.MEDIA_TYPE_AUDIO -> MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
15
- else -> EXTERNAL_CONTENT_URI
12
+ val baseUri = if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) {
13
+ when (mediaType) {
14
+ MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE -> MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
15
+ MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO -> MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
16
+ MediaStore.Files.FileColumns.MEDIA_TYPE_AUDIO -> MediaStore.Audio.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
17
+ else -> MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
18
+ }
19
+ } else {
20
+ when (mediaType) {
21
+ MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI
22
+ MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI
23
+ MediaStore.Files.FileColumns.MEDIA_TYPE_AUDIO -> MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
24
+ else -> EXTERNAL_CONTENT_URI
25
+ }
16
26
  }
17
27
  return ContentUris.withAppendedId(baseUri, id)
18
28
  }
@@ -12,14 +12,18 @@ import expo.modules.medialibrary.next.extensions.resolver.queryAlbumFilepath
12
12
  import expo.modules.medialibrary.next.extensions.resolver.queryAlbumRelativePath
13
13
  import expo.modules.medialibrary.next.extensions.resolver.queryAlbumTitle
14
14
  import expo.modules.medialibrary.next.objects.asset.Asset
15
+ import expo.modules.medialibrary.next.objects.asset.deleters.AssetDeleter
16
+ import expo.modules.medialibrary.next.objects.asset.factories.AssetFactory
15
17
  import expo.modules.medialibrary.next.objects.wrappers.RelativePath
16
- import kotlinx.coroutines.async
17
- import kotlinx.coroutines.awaitAll
18
- import kotlinx.coroutines.coroutineScope
19
18
  import java.io.File
20
19
  import java.lang.ref.WeakReference
21
20
 
22
- class Album(val id: String, context: Context) : SharedObject() {
21
+ class Album(
22
+ val id: String,
23
+ val assetDeleter: AssetDeleter,
24
+ val assetFactory: AssetFactory,
25
+ context: Context
26
+ ) : SharedObject() {
23
27
  private val contextRef = WeakReference(context)
24
28
 
25
29
  private val contentResolver
@@ -51,21 +55,16 @@ class Album(val id: String, context: Context) : SharedObject() {
51
55
  return RelativePath(relative)
52
56
  }
53
57
 
54
- suspend fun getAssets(): List<Asset> {
55
- return contentResolver
58
+ suspend fun getAssets(): List<Asset> =
59
+ contentResolver
56
60
  .queryAlbumAssetsContentUris(id)
57
- .map { contentUri -> Asset(contentUri, contextRef.getOrThrow()) }
58
- }
61
+ .map { assetFactory.create(it) }
59
62
 
60
- suspend fun delete() = coroutineScope {
61
- getAssets().map { asset ->
62
- async {
63
- asset.delete()
64
- }
65
- }.awaitAll()
66
- }
63
+ suspend fun delete() =
64
+ assetDeleter.delete(
65
+ getAssets().map { it.contentUri }
66
+ )
67
67
 
68
- suspend fun add(asset: Asset) {
68
+ suspend fun add(asset: Asset) =
69
69
  asset.move(getRelativePath())
70
- }
71
70
  }
@@ -4,9 +4,10 @@ import android.content.Context
4
4
  import expo.modules.medialibrary.next.exceptions.ContentResolverNotObtainedException
5
5
  import expo.modules.medialibrary.next.extensions.getOrThrow
6
6
  import expo.modules.medialibrary.next.extensions.resolver.queryAlbumId
7
+ import expo.modules.medialibrary.next.objects.album.factories.AlbumFactory
7
8
  import java.lang.ref.WeakReference
8
9
 
9
- class AlbumQuery(context: Context) {
10
+ class AlbumQuery(val albumFactory: AlbumFactory, context: Context) {
10
11
  private val contextRef = WeakReference(context)
11
12
 
12
13
  private val contentResolver
@@ -17,6 +18,6 @@ class AlbumQuery(context: Context) {
17
18
  suspend fun getAlbum(title: String): Album? {
18
19
  val id = contentResolver.queryAlbumId(title)
19
20
  ?: return null
20
- return Album(id, contextRef.getOrThrow())
21
+ return albumFactory.create(id)
21
22
  }
22
23
  }
@@ -5,6 +5,7 @@ import expo.modules.medialibrary.next.objects.album.Album
5
5
  import expo.modules.medialibrary.next.objects.asset.Asset
6
6
 
7
7
  interface AlbumFactory {
8
+ fun create(id: String): Album
8
9
  suspend fun createFromAssets(albumName: String, assets: List<Asset>, deleteOriginalAssets: Boolean): Album
9
10
  suspend fun createFromFilePaths(albumName: String, filePaths: List<Uri>): Album
10
11
  }
@@ -12,14 +12,19 @@ import expo.modules.medialibrary.next.extensions.resolver.queryAssetBucketId
12
12
  import expo.modules.medialibrary.next.objects.album.Album
13
13
  import expo.modules.medialibrary.next.objects.wrappers.RelativePath
14
14
  import expo.modules.medialibrary.next.objects.asset.Asset
15
+ import expo.modules.medialibrary.next.objects.asset.deleters.AssetDeleter
15
16
  import expo.modules.medialibrary.next.objects.wrappers.MimeType
16
17
  import expo.modules.medialibrary.next.objects.asset.factories.AssetFactory
17
18
  import java.io.File
18
19
  import java.io.IOException
19
20
  import java.lang.ref.WeakReference
20
21
 
21
- @DeprecatedSinceApi(Build.VERSION_CODES.Q)
22
- class AlbumLegacyFactory(private val assetFactory: AssetFactory, context: Context) : AlbumFactory {
22
+ @DeprecatedSinceApi(Build.VERSION_CODES.R)
23
+ class AlbumLegacyFactory(
24
+ private val assetFactory: AssetFactory,
25
+ private val assetDeleter: AssetDeleter,
26
+ context: Context
27
+ ) : AlbumFactory {
23
28
  private val contextRef = WeakReference(context)
24
29
 
25
30
  private val contentResolver
@@ -27,6 +32,10 @@ class AlbumLegacyFactory(private val assetFactory: AssetFactory, context: Contex
27
32
  .getOrThrow()
28
33
  .contentResolver ?: throw AlbumCouldNotBeCreated("Failed to create album: ContentResolver is unavailable.")
29
34
 
35
+ override fun create(id: String): Album {
36
+ return Album(id, assetDeleter, assetFactory, contextRef.getOrThrow())
37
+ }
38
+
30
39
  override suspend fun createFromAssets(albumName: String, assets: List<Asset>, deleteOriginalAssets: Boolean): Album {
31
40
  try {
32
41
  val firstAsset = assets.firstOrNull()
@@ -37,7 +46,7 @@ class AlbumLegacyFactory(private val assetFactory: AssetFactory, context: Contex
37
46
  processAssetsLocation(assets, relativePath, true)
38
47
  val albumId = contentResolver.queryAssetBucketId(assets[0].contentUri)
39
48
  ?: throw AlbumNotFoundException("Could not find album with filePath: ${relativePath.toFilePath()}")
40
- return Album(albumId.toString(), contextRef.getOrThrow())
49
+ return Album(albumId.toString(), assetDeleter, assetFactory, contextRef.getOrThrow())
41
50
  } catch (e: SecurityException) {
42
51
  throw AlbumCouldNotBeCreated("Missing WRITE_EXTERNAL_STORAGE permission: ${e.message}", e)
43
52
  } catch (e: IOException) {
@@ -55,7 +64,7 @@ class AlbumLegacyFactory(private val assetFactory: AssetFactory, context: Contex
55
64
  }
56
65
  val albumId = contentResolver.queryAssetBucketId(assets[0].contentUri)
57
66
  ?: throw AlbumCouldNotBeCreated("Could not find album with relativePath: $relativePath")
58
- return Album(albumId.toString(), contextRef.getOrThrow())
67
+ return Album(albumId.toString(), assetDeleter, assetFactory, contextRef.getOrThrow())
59
68
  }
60
69
 
61
70
  private suspend fun processAssetsLocation(assets: List<Asset>, relativePath: RelativePath, deleteOriginalAssets: Boolean) {
@@ -11,13 +11,18 @@ import expo.modules.medialibrary.next.extensions.resolver.queryAlbumId
11
11
  import expo.modules.medialibrary.next.objects.album.Album
12
12
  import expo.modules.medialibrary.next.objects.wrappers.RelativePath
13
13
  import expo.modules.medialibrary.next.objects.asset.Asset
14
+ import expo.modules.medialibrary.next.objects.asset.deleters.AssetDeleter
14
15
  import expo.modules.medialibrary.next.objects.wrappers.MimeType
15
16
  import expo.modules.medialibrary.next.objects.asset.factories.AssetFactory
16
17
  import java.io.IOException
17
18
  import java.lang.ref.WeakReference
18
19
 
19
- @RequiresApi(Build.VERSION_CODES.Q)
20
- class AlbumModernFactory(private val assetFactory: AssetFactory, context: Context) : AlbumFactory {
20
+ @RequiresApi(Build.VERSION_CODES.R)
21
+ class AlbumModernFactory(
22
+ private val assetFactory: AssetFactory,
23
+ private val assetDeleter: AssetDeleter,
24
+ context: Context
25
+ ) : AlbumFactory {
21
26
  private val contextRef = WeakReference(context)
22
27
 
23
28
  private val contentResolver
@@ -25,6 +30,10 @@ class AlbumModernFactory(private val assetFactory: AssetFactory, context: Contex
25
30
  .getOrThrow()
26
31
  .contentResolver ?: throw AlbumCouldNotBeCreated("Failed to create album: ContentResolver is unavailable.")
27
32
 
33
+ override fun create(id: String): Album {
34
+ return Album(id, assetDeleter, assetFactory, contextRef.getOrThrow())
35
+ }
36
+
28
37
  override suspend fun createFromAssets(albumName: String, assets: List<Asset>, deleteOriginalAssets: Boolean): Album =
29
38
  try {
30
39
  val mimeTypeOfFirstAsset = assets[0].getMimeType()
@@ -32,7 +41,7 @@ class AlbumModernFactory(private val assetFactory: AssetFactory, context: Contex
32
41
  processAssetsLocation(assets, albumRelativePath, deleteOriginalAssets)
33
42
  val albumId = contentResolver.queryAlbumId(albumRelativePath)
34
43
  ?: throw AlbumNotFoundException("Could not find album with relativePath: $albumRelativePath")
35
- Album(albumId, contextRef.getOrThrow())
44
+ Album(albumId, assetDeleter, assetFactory, contextRef.getOrThrow())
36
45
  } catch (e: SecurityException) {
37
46
  throw AlbumCouldNotBeCreated("Security Exception: ${e.message}", e)
38
47
  } catch (e: IOException) {
@@ -47,7 +56,7 @@ class AlbumModernFactory(private val assetFactory: AssetFactory, context: Contex
47
56
  }
48
57
  val albumId = contentResolver.queryAlbumId(relativePath)
49
58
  ?: throw AlbumCouldNotBeCreated("Failed to create album: newly created album was not found in the MediaStore.")
50
- return Album(albumId, contextRef.getOrThrow())
59
+ return Album(albumId, assetDeleter, assetFactory, contextRef.getOrThrow())
51
60
  }
52
61
 
53
62
  private suspend fun processAssetsLocation(assets: List<Asset>, relativePath: RelativePath, deleteOriginalAssets: Boolean) {
@@ -1,31 +1,15 @@
1
1
  package expo.modules.medialibrary.next.objects.asset
2
2
 
3
- import android.content.Context
4
3
  import android.net.Uri
5
- import android.os.Build
6
4
  import expo.modules.kotlin.sharedobjects.SharedObject
7
- import expo.modules.medialibrary.next.extensions.getOrThrow
5
+ import expo.modules.medialibrary.next.objects.asset.delegates.AssetDelegate
8
6
  import expo.modules.medialibrary.next.objects.wrappers.RelativePath
9
- import expo.modules.medialibrary.next.objects.asset.delegates.AssetLegacyDelegate
10
- import expo.modules.medialibrary.next.objects.asset.delegates.AssetModernDelegate
11
7
  import expo.modules.medialibrary.next.objects.wrappers.MediaType
12
8
  import expo.modules.medialibrary.next.objects.wrappers.MimeType
13
9
  import kotlinx.coroutines.Dispatchers
14
10
  import kotlinx.coroutines.withContext
15
- import java.lang.ref.WeakReference
16
- import kotlin.getValue
17
-
18
- class Asset(contentUri: Uri, context: Context) : SharedObject() {
19
- private val contextRef = WeakReference(context)
20
-
21
- val assetDelegate by lazy {
22
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
23
- AssetModernDelegate(contentUri, contextRef.getOrThrow())
24
- } else {
25
- AssetLegacyDelegate(contentUri, contextRef.getOrThrow())
26
- }
27
- }
28
11
 
12
+ class Asset(val assetDelegate: AssetDelegate) : SharedObject() {
29
13
  val contentUri: Uri get() = assetDelegate.contentUri
30
14
 
31
15
  suspend fun getCreationTime(): Long? =
@@ -6,7 +6,6 @@ import android.net.Uri
6
6
  import android.os.Build
7
7
  import androidx.annotation.DeprecatedSinceApi
8
8
  import androidx.core.net.toUri
9
- import expo.modules.medialibrary.AssetFileException
10
9
  import expo.modules.medialibrary.MediaLibraryUtils
11
10
  import expo.modules.medialibrary.next.exceptions.AssetCouldNotBeCreated
12
11
  import expo.modules.medialibrary.next.exceptions.AssetPropertyNotFoundException
@@ -24,6 +23,7 @@ import expo.modules.medialibrary.next.extensions.safeCopy
24
23
  import expo.modules.medialibrary.next.extensions.safeMove
25
24
  import expo.modules.medialibrary.next.objects.wrappers.RelativePath
26
25
  import expo.modules.medialibrary.next.objects.asset.Asset
26
+ import expo.modules.medialibrary.next.objects.asset.deleters.AssetDeleter
27
27
  import expo.modules.medialibrary.next.objects.wrappers.MediaType
28
28
  import expo.modules.medialibrary.next.objects.wrappers.MimeType
29
29
  import kotlinx.coroutines.Dispatchers
@@ -34,7 +34,11 @@ import kotlin.time.DurationUnit
34
34
  import kotlin.time.toDuration
35
35
 
36
36
  @DeprecatedSinceApi(Build.VERSION_CODES.Q)
37
- class AssetLegacyDelegate(contentUri: Uri, context: Context) : AssetDelegate {
37
+ class AssetLegacyDelegate(
38
+ contentUri: Uri,
39
+ val assetDeleter: AssetDeleter,
40
+ context: Context
41
+ ) : AssetDelegate {
38
42
  private val contextRef = WeakReference(context)
39
43
 
40
44
  private val contentResolver
@@ -111,12 +115,7 @@ class AssetLegacyDelegate(contentUri: Uri, context: Context) : AssetDelegate {
111
115
  }
112
116
 
113
117
  override suspend fun delete(): Unit = withContext(Dispatchers.IO) {
114
- val path = contentResolver.queryAssetPath(contentUri)
115
- ?: throw AssetPropertyNotFoundException("Uri")
116
- if (!File(path).delete()) {
117
- throw AssetFileException("Could not delete file.")
118
- }
119
- contentResolver.delete(contentUri, null, null)
118
+ assetDeleter.delete(contentUri)
120
119
  }
121
120
 
122
121
  override suspend fun getUri(): Uri {
@@ -146,6 +145,7 @@ class AssetLegacyDelegate(contentUri: Uri, context: Context) : AssetDelegate {
146
145
  if (uri == null) {
147
146
  throw AssetCouldNotBeCreated("Could not create a new asset while copying the old one")
148
147
  }
149
- return@withContext Asset(uri, contextRef.getOrThrow())
148
+ val newAssetDelegate = AssetLegacyDelegate(contentUri, assetDeleter, contextRef.getOrThrow())
149
+ return@withContext Asset(newAssetDelegate)
150
150
  }
151
151
  }
@@ -22,6 +22,7 @@ import expo.modules.medialibrary.next.extensions.resolver.queryAssetCreationTime
22
22
  import expo.modules.medialibrary.next.extensions.resolver.updateRelativePath
23
23
  import expo.modules.medialibrary.next.objects.wrappers.RelativePath
24
24
  import expo.modules.medialibrary.next.objects.asset.Asset
25
+ import expo.modules.medialibrary.next.objects.asset.deleters.AssetDeleter
25
26
  import expo.modules.medialibrary.next.objects.wrappers.MediaType
26
27
  import expo.modules.medialibrary.next.objects.wrappers.MimeType
27
28
  import kotlinx.coroutines.Dispatchers
@@ -32,7 +33,11 @@ import kotlin.time.DurationUnit
32
33
  import kotlin.time.toDuration
33
34
 
34
35
  @RequiresApi(Build.VERSION_CODES.Q)
35
- class AssetModernDelegate(override val contentUri: Uri, context: Context) : AssetDelegate {
36
+ class AssetModernDelegate(
37
+ override val contentUri: Uri,
38
+ val assetDeleter: AssetDeleter,
39
+ context: Context
40
+ ) : AssetDelegate {
36
41
  private val contextRef = WeakReference(context)
37
42
 
38
43
  private val contentResolver
@@ -109,8 +114,8 @@ class AssetModernDelegate(override val contentUri: Uri, context: Context) : Asse
109
114
  ?: MimeType.from(getUri())
110
115
  }
111
116
 
112
- override suspend fun delete() {
113
- contentResolver.delete(contentUri, null, null)
117
+ override suspend fun delete() = withContext(Dispatchers.IO) {
118
+ assetDeleter.delete(contentUri)
114
119
  }
115
120
 
116
121
  override suspend fun move(relativePath: RelativePath) {
@@ -121,6 +126,7 @@ class AssetModernDelegate(override val contentUri: Uri, context: Context) : Asse
121
126
  val newAssetUri = contentResolver.insertPendingAsset(getFilename(), getMimeType(), relativePath)
122
127
  contentResolver.copyUriContent(contentUri, newAssetUri)
123
128
  contentResolver.publishPendingAsset(newAssetUri)
124
- return@withContext Asset(newAssetUri, contextRef.getOrThrow())
129
+ val newAssetDelegate = AssetModernDelegate(newAssetUri, assetDeleter, contextRef.getOrThrow())
130
+ return@withContext Asset(newAssetDelegate)
125
131
  }
126
132
  }
@@ -0,0 +1,8 @@
1
+ package expo.modules.medialibrary.next.objects.asset.deleters
2
+
3
+ import android.net.Uri
4
+
5
+ interface AssetDeleter {
6
+ suspend fun delete(contentUri: Uri)
7
+ suspend fun delete(contentUris: List<Uri>)
8
+ }
@@ -0,0 +1,46 @@
1
+ package expo.modules.medialibrary.next.objects.asset.deleters
2
+
3
+ import android.content.Context
4
+ import android.net.Uri
5
+ import android.os.Build
6
+ import androidx.annotation.DeprecatedSinceApi
7
+ import expo.modules.medialibrary.AssetFileException
8
+ import expo.modules.medialibrary.next.exceptions.AssetPropertyNotFoundException
9
+ import expo.modules.medialibrary.next.exceptions.ContentResolverNotObtainedException
10
+ import expo.modules.medialibrary.next.extensions.getOrThrow
11
+ import expo.modules.medialibrary.next.extensions.resolver.queryAssetPath
12
+ import kotlinx.coroutines.Dispatchers
13
+ import kotlinx.coroutines.async
14
+ import kotlinx.coroutines.awaitAll
15
+ import kotlinx.coroutines.withContext
16
+ import java.io.File
17
+ import java.lang.ref.WeakReference
18
+
19
+ @DeprecatedSinceApi(Build.VERSION_CODES.Q)
20
+ class AssetLegacyDeleter(context: Context) : AssetDeleter {
21
+ private val contextRef = WeakReference(context)
22
+
23
+ private val contentResolver
24
+ get() = contextRef
25
+ .getOrThrow()
26
+ .contentResolver ?: throw ContentResolverNotObtainedException()
27
+
28
+ override suspend fun delete(contentUri: Uri): Unit = withContext(Dispatchers.IO) {
29
+ val path = contentResolver.queryAssetPath(contentUri)
30
+ ?: throw AssetPropertyNotFoundException("Uri")
31
+ if (!File(path).delete()) {
32
+ throw AssetFileException("Could not delete a file.")
33
+ }
34
+ contentResolver.delete(contentUri, null, null)
35
+ }
36
+
37
+ override suspend fun delete(contentUris: List<Uri>): Unit = withContext(Dispatchers.IO) {
38
+ contentUris.map { uri ->
39
+ async {
40
+ runCatching {
41
+ delete(uri)
42
+ }
43
+ }
44
+ }.awaitAll()
45
+ }
46
+ }
@@ -0,0 +1,21 @@
1
+ package expo.modules.medialibrary.next.objects.asset.deleters
2
+
3
+ import android.net.Uri
4
+ import android.os.Build
5
+ import androidx.annotation.RequiresApi
6
+ import expo.modules.medialibrary.next.permissions.MediaStorePermissionsDelegate
7
+ import kotlinx.coroutines.Dispatchers
8
+ import kotlinx.coroutines.withContext
9
+
10
+ @RequiresApi(Build.VERSION_CODES.R)
11
+ class AssetModernDeleter(
12
+ val mediaStorePermissionsDelegate: MediaStorePermissionsDelegate
13
+ ) : AssetDeleter {
14
+ override suspend fun delete(contentUri: Uri) = withContext(Dispatchers.IO) {
15
+ mediaStorePermissionsDelegate.launchMediaStoreDeleteRequest(listOf(contentUri))
16
+ }
17
+
18
+ override suspend fun delete(contentUris: List<Uri>) = withContext(Dispatchers.IO) {
19
+ mediaStorePermissionsDelegate.launchMediaStoreDeleteRequest(contentUris)
20
+ }
21
+ }
@@ -4,6 +4,7 @@ import android.net.Uri
4
4
  import expo.modules.medialibrary.next.objects.wrappers.RelativePath
5
5
  import expo.modules.medialibrary.next.objects.asset.Asset
6
6
 
7
- fun interface AssetFactory {
7
+ interface AssetFactory {
8
+ fun create(contentUri: Uri): Asset
8
9
  suspend fun create(filePath: Uri, relativePath: RelativePath?): Asset
9
10
  }