expo-media-library 18.0.5 → 18.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/android/build.gradle +2 -2
- package/android/src/main/java/expo/modules/medialibrary/MediaLibraryModule.kt +10 -6
- package/android/src/main/java/expo/modules/medialibrary/next/MediaLibraryNextModule.kt +187 -0
- package/android/src/main/java/expo/modules/medialibrary/next/exceptions/AlbumExceptions.kt +15 -0
- package/android/src/main/java/expo/modules/medialibrary/next/exceptions/AssetExceptions.kt +21 -0
- package/android/src/main/java/expo/modules/medialibrary/next/exceptions/ContentResolverExceptions.kt +6 -0
- package/android/src/main/java/expo/modules/medialibrary/next/exceptions/PermissionExceptions.kt +6 -0
- package/android/src/main/java/expo/modules/medialibrary/next/extensions/CursorExtensions.kt +28 -0
- package/android/src/main/java/expo/modules/medialibrary/next/extensions/FileExtensions.kt +41 -0
- package/android/src/main/java/expo/modules/medialibrary/next/extensions/WeakReferenceExtensions.kt +9 -0
- package/android/src/main/java/expo/modules/medialibrary/next/extensions/resolver/AlbumExtensions.kt +68 -0
- package/android/src/main/java/expo/modules/medialibrary/next/extensions/resolver/AssetExtensions.kt +89 -0
- package/android/src/main/java/expo/modules/medialibrary/next/extensions/resolver/CursorExtensions.kt +18 -0
- package/android/src/main/java/expo/modules/medialibrary/next/extensions/resolver/QueryOne.kt +29 -0
- package/android/src/main/java/expo/modules/medialibrary/next/extensions/resolver/TransferExtensions.kt +15 -0
- package/android/src/main/java/expo/modules/medialibrary/next/objects/album/Album.kt +71 -0
- package/android/src/main/java/expo/modules/medialibrary/next/objects/album/factories/AlbumFactory.kt +10 -0
- package/android/src/main/java/expo/modules/medialibrary/next/objects/album/factories/AlbumLegacyFactory.kt +76 -0
- package/android/src/main/java/expo/modules/medialibrary/next/objects/album/factories/AlbumModernFactory.kt +60 -0
- package/android/src/main/java/expo/modules/medialibrary/next/objects/asset/Asset.kt +68 -0
- package/android/src/main/java/expo/modules/medialibrary/next/objects/asset/delegates/AssetDelegate.kt +22 -0
- package/android/src/main/java/expo/modules/medialibrary/next/objects/asset/delegates/AssetLegacyDelegate.kt +146 -0
- package/android/src/main/java/expo/modules/medialibrary/next/objects/asset/delegates/AssetModernDelegate.kt +121 -0
- package/android/src/main/java/expo/modules/medialibrary/next/objects/asset/factories/AssetFactory.kt +9 -0
- package/android/src/main/java/expo/modules/medialibrary/next/objects/asset/factories/AssetLegacyFactory.kt +52 -0
- package/android/src/main/java/expo/modules/medialibrary/next/objects/asset/factories/AssetModernFactory.kt +42 -0
- package/android/src/main/java/expo/modules/medialibrary/next/objects/wrappers/MimeType.kt +54 -0
- package/android/src/main/java/expo/modules/medialibrary/next/objects/wrappers/RelativePath.kt +31 -0
- package/android/src/main/java/expo/modules/medialibrary/next/permissions/MediaLibraryPermissionPromiseWrapper.kt +78 -0
- package/android/src/main/java/expo/modules/medialibrary/next/permissions/MediaStorePermissionsDelegate.kt +61 -0
- package/android/src/main/java/expo/modules/medialibrary/next/permissions/SystemPermissionsDelegate.kt +182 -0
- package/android/src/main/java/expo/modules/medialibrary/next/permissions/contracts/DeleteContract.kt +38 -0
- package/android/src/main/java/expo/modules/medialibrary/next/permissions/contracts/WriteContract.kt +38 -0
- package/android/src/main/java/expo/modules/medialibrary/next/permissions/enums/AccessPrivileges.kt +7 -0
- package/android/src/main/java/expo/modules/medialibrary/next/permissions/enums/GranularPermission.kt +18 -0
- package/build/next/ExpoMediaLibraryNext.d.ts +9 -0
- package/build/next/ExpoMediaLibraryNext.d.ts.map +1 -0
- package/build/next/ExpoMediaLibraryNext.js +3 -0
- package/build/next/ExpoMediaLibraryNext.js.map +1 -0
- package/build/next/MediaLibraryNext.types.d.ts +3 -0
- package/build/next/MediaLibraryNext.types.d.ts.map +1 -0
- package/build/next/MediaLibraryNext.types.js +3 -0
- package/build/next/MediaLibraryNext.types.js.map +1 -0
- package/build/next/index.d.ts +14 -0
- package/build/next/index.d.ts.map +1 -0
- package/build/next/index.js +40 -0
- package/build/next/index.js.map +1 -0
- package/build/next/types/Album.d.ts +91 -0
- package/build/next/types/Album.d.ts.map +1 -0
- package/build/next/types/Album.js +2 -0
- package/build/next/types/Album.js.map +1 -0
- package/build/next/types/Asset.d.ts +91 -0
- package/build/next/types/Asset.d.ts.map +1 -0
- package/build/next/types/Asset.js +2 -0
- package/build/next/types/Asset.js.map +1 -0
- package/expo-module.config.json +6 -3
- package/ios/MediaLibraryModule.swift +22 -18
- package/ios/next/MediaLibraryNextModule.swift +180 -0
- package/ios/next/exceptions/Exceptions.swift +55 -0
- package/ios/next/extensions/PHAssetExtensions.swift +16 -0
- package/ios/next/extensions/PHImageManagerExtensions.swift +15 -0
- package/ios/next/extensions/PHPhotoLibraryExtensions.swift +34 -0
- package/ios/next/objects/album/Album.swift +72 -0
- package/ios/next/objects/asset/Asset.swift +122 -0
- package/ios/next/repository/AssetCollectionRepository.swift +80 -0
- package/ios/next/repository/AssetRepository.swift +111 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.0/expo.modules.medialibrary-18.1.0-sources.jar +0 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.0/expo.modules.medialibrary-18.1.0-sources.jar.md5 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.0/expo.modules.medialibrary-18.1.0-sources.jar.sha1 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.0/expo.modules.medialibrary-18.1.0-sources.jar.sha256 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.0/expo.modules.medialibrary-18.1.0-sources.jar.sha512 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.0/expo.modules.medialibrary-18.1.0.aar +0 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.0/expo.modules.medialibrary-18.1.0.aar.md5 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.0/expo.modules.medialibrary-18.1.0.aar.sha1 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.0/expo.modules.medialibrary-18.1.0.aar.sha256 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.0/expo.modules.medialibrary-18.1.0.aar.sha512 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/{18.0.5/expo.modules.medialibrary-18.0.5.module → 18.1.0/expo.modules.medialibrary-18.1.0.module} +22 -22
- package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.0/expo.modules.medialibrary-18.1.0.module.md5 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.0/expo.modules.medialibrary-18.1.0.module.sha1 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.0/expo.modules.medialibrary-18.1.0.module.sha256 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.0/expo.modules.medialibrary-18.1.0.module.sha512 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/{18.0.5/expo.modules.medialibrary-18.0.5.pom → 18.1.0/expo.modules.medialibrary-18.1.0.pom} +1 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.0/expo.modules.medialibrary-18.1.0.pom.md5 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.0/expo.modules.medialibrary-18.1.0.pom.sha1 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.0/expo.modules.medialibrary-18.1.0.pom.sha256 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.0/expo.modules.medialibrary-18.1.0.pom.sha512 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/maven-metadata.xml +4 -4
- package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/maven-metadata.xml.md5 +1 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/maven-metadata.xml.sha1 +1 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/maven-metadata.xml.sha256 +1 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/maven-metadata.xml.sha512 +1 -1
- package/next.ts +1 -0
- package/package.json +3 -3
- package/src/next/ExpoMediaLibraryNext.ts +10 -0
- package/src/next/MediaLibraryNext.types.ts +2 -0
- package/src/next/index.ts +51 -0
- package/src/next/types/Album.ts +102 -0
- package/src/next/types/Asset.ts +102 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.0.5/expo.modules.medialibrary-18.0.5-sources.jar +0 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.0.5/expo.modules.medialibrary-18.0.5-sources.jar.md5 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.0.5/expo.modules.medialibrary-18.0.5-sources.jar.sha1 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.0.5/expo.modules.medialibrary-18.0.5-sources.jar.sha256 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.0.5/expo.modules.medialibrary-18.0.5-sources.jar.sha512 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.0.5/expo.modules.medialibrary-18.0.5.aar +0 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.0.5/expo.modules.medialibrary-18.0.5.aar.md5 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.0.5/expo.modules.medialibrary-18.0.5.aar.sha1 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.0.5/expo.modules.medialibrary-18.0.5.aar.sha256 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.0.5/expo.modules.medialibrary-18.0.5.aar.sha512 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.0.5/expo.modules.medialibrary-18.0.5.module.md5 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.0.5/expo.modules.medialibrary-18.0.5.module.sha1 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.0.5/expo.modules.medialibrary-18.0.5.module.sha256 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.0.5/expo.modules.medialibrary-18.0.5.module.sha512 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.0.5/expo.modules.medialibrary-18.0.5.pom.md5 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.0.5/expo.modules.medialibrary-18.0.5.pom.sha1 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.0.5/expo.modules.medialibrary-18.0.5.pom.sha256 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.0.5/expo.modules.medialibrary-18.0.5.pom.sha512 +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,18 @@
|
|
|
10
10
|
|
|
11
11
|
### 💡 Others
|
|
12
12
|
|
|
13
|
+
## 18.1.0 — 2025-09-08
|
|
14
|
+
|
|
15
|
+
### 🎉 New features
|
|
16
|
+
|
|
17
|
+
- Add MediaLibrary@Next. ([#38835](https://github.com/expo/expo/pull/38835) by [@Wenszel](https://github.com/wenszel))
|
|
18
|
+
|
|
19
|
+
## 18.0.6 — 2025-09-02
|
|
20
|
+
|
|
21
|
+
### 💡 Others
|
|
22
|
+
|
|
23
|
+
- Change Constants to Constant/Property. ([#38926](https://github.com/expo/expo/pull/38926) by [@jakex7](https://github.com/jakex7))
|
|
24
|
+
|
|
13
25
|
## 18.0.5 — 2025-08-31
|
|
14
26
|
|
|
15
27
|
_This version does not introduce any user-facing changes._
|
package/android/build.gradle
CHANGED
|
@@ -69,12 +69,16 @@ class MediaLibraryModule : Module() {
|
|
|
69
69
|
override fun definition() = ModuleDefinition {
|
|
70
70
|
Name("ExpoMediaLibrary")
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
)
|
|
72
|
+
Constant("MediaType") {
|
|
73
|
+
MediaType.getConstants()
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
Constant("SortBy") {
|
|
77
|
+
SortBy.getConstants()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
Constant("CHANGE_LISTENER_NAME") {
|
|
81
|
+
LIBRARY_DID_CHANGE_EVENT
|
|
78
82
|
}
|
|
79
83
|
|
|
80
84
|
Events(LIBRARY_DID_CHANGE_EVENT)
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
package expo.modules.medialibrary.next
|
|
2
|
+
|
|
3
|
+
import android.net.Uri
|
|
4
|
+
import android.os.Build
|
|
5
|
+
import expo.modules.kotlin.Promise
|
|
6
|
+
import expo.modules.kotlin.apifeatures.EitherType
|
|
7
|
+
import expo.modules.kotlin.exception.Exceptions
|
|
8
|
+
import expo.modules.kotlin.functions.Coroutine
|
|
9
|
+
import expo.modules.kotlin.modules.Module
|
|
10
|
+
import expo.modules.kotlin.modules.ModuleDefinition
|
|
11
|
+
import expo.modules.kotlin.types.Either
|
|
12
|
+
import expo.modules.kotlin.types.toKClass
|
|
13
|
+
import expo.modules.medialibrary.next.objects.album.Album
|
|
14
|
+
import expo.modules.medialibrary.next.objects.asset.Asset
|
|
15
|
+
import expo.modules.medialibrary.next.objects.album.factories.AlbumModernFactory
|
|
16
|
+
import expo.modules.medialibrary.next.objects.album.factories.AlbumLegacyFactory
|
|
17
|
+
import expo.modules.medialibrary.next.objects.asset.factories.AssetModernFactory
|
|
18
|
+
import expo.modules.medialibrary.next.objects.asset.factories.AssetLegacyFactory
|
|
19
|
+
import expo.modules.medialibrary.next.permissions.MediaStorePermissionsDelegate
|
|
20
|
+
import expo.modules.medialibrary.next.permissions.SystemPermissionsDelegate
|
|
21
|
+
import expo.modules.medialibrary.next.permissions.enums.GranularPermission
|
|
22
|
+
|
|
23
|
+
class MediaLibraryNextModule : Module() {
|
|
24
|
+
private val context
|
|
25
|
+
get() = appContext.reactContext ?: throw Exceptions.ReactContextLost()
|
|
26
|
+
|
|
27
|
+
private val systemPermissionsDelegate by lazy {
|
|
28
|
+
SystemPermissionsDelegate(appContext)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private val mediaStorePermissionsDelegate by lazy {
|
|
32
|
+
MediaStorePermissionsDelegate(appContext)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private val albumFactory by lazy {
|
|
36
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
37
|
+
AlbumModernFactory(assetFactory, context)
|
|
38
|
+
} else {
|
|
39
|
+
AlbumLegacyFactory(assetFactory, context)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private val assetFactory by lazy {
|
|
44
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
45
|
+
AssetModernFactory(context)
|
|
46
|
+
} else {
|
|
47
|
+
AssetLegacyFactory(context)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
override fun definition() = ModuleDefinition {
|
|
52
|
+
Name("ExpoMediaLibraryNext")
|
|
53
|
+
|
|
54
|
+
Class(Asset::class) {
|
|
55
|
+
Constructor { contentUri: Uri ->
|
|
56
|
+
Asset(contentUri, context)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
Property("id") { self: Asset ->
|
|
60
|
+
systemPermissionsDelegate.requireSystemPermissions(false)
|
|
61
|
+
self.contentUri
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
AsyncFunction("getCreationTime") Coroutine { self: Asset ->
|
|
65
|
+
systemPermissionsDelegate.requireSystemPermissions(false)
|
|
66
|
+
self.getCreationTime()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
AsyncFunction("getDuration") Coroutine { self: Asset ->
|
|
70
|
+
systemPermissionsDelegate.requireSystemPermissions(false)
|
|
71
|
+
self.getDuration()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
AsyncFunction("getFilename") Coroutine { self: Asset ->
|
|
75
|
+
systemPermissionsDelegate.requireSystemPermissions(false)
|
|
76
|
+
self.getFilename()
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
AsyncFunction("getHeight") Coroutine { self: Asset ->
|
|
80
|
+
systemPermissionsDelegate.requireSystemPermissions(false)
|
|
81
|
+
self.getHeight()
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
AsyncFunction("getMediaType") Coroutine { self: Asset ->
|
|
85
|
+
systemPermissionsDelegate.requireSystemPermissions(false)
|
|
86
|
+
self.getMediaType()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
AsyncFunction("getModificationTime") Coroutine { self: Asset ->
|
|
90
|
+
systemPermissionsDelegate.requireSystemPermissions(false)
|
|
91
|
+
self.getModificationTime()
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
AsyncFunction("getUri") Coroutine { self: Asset ->
|
|
95
|
+
systemPermissionsDelegate.requireSystemPermissions(false)
|
|
96
|
+
self.getUri()
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
AsyncFunction("getWidth") Coroutine { self: Asset ->
|
|
100
|
+
systemPermissionsDelegate.requireSystemPermissions(false)
|
|
101
|
+
self.getWidth()
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
AsyncFunction("delete") Coroutine { self: Asset ->
|
|
105
|
+
systemPermissionsDelegate.requireSystemPermissions(true)
|
|
106
|
+
mediaStorePermissionsDelegate.requestMediaLibraryActionPermission(listOf(self.contentUri), needsDeletePermission = true)
|
|
107
|
+
self.delete()
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
Class(Album::class) {
|
|
112
|
+
Constructor { id: String ->
|
|
113
|
+
Album(id, context)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
Property("id") { self: Album ->
|
|
117
|
+
self.id
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
AsyncFunction("getTitle") Coroutine { self: Album ->
|
|
121
|
+
systemPermissionsDelegate.requireSystemPermissions(false)
|
|
122
|
+
self.getTitle()
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
AsyncFunction("getAssets") Coroutine { self: Album ->
|
|
126
|
+
systemPermissionsDelegate.requireSystemPermissions(false)
|
|
127
|
+
self.getAssets()
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
AsyncFunction("add") Coroutine { self: Album, asset: Asset ->
|
|
131
|
+
systemPermissionsDelegate.requireSystemPermissions(true)
|
|
132
|
+
mediaStorePermissionsDelegate.requestMediaLibraryActionPermission(listOf(asset.contentUri))
|
|
133
|
+
self.add(asset)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
AsyncFunction("delete") Coroutine { self: Album ->
|
|
137
|
+
systemPermissionsDelegate.requireSystemPermissions(true)
|
|
138
|
+
val assetIdsToDelete = self.getAssets().map { it.contentUri }
|
|
139
|
+
mediaStorePermissionsDelegate.requestMediaLibraryActionPermission(assetIdsToDelete, needsDeletePermission = true)
|
|
140
|
+
self.delete()
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
AsyncFunction("createAsset") Coroutine { filePath: Uri, album: Album? ->
|
|
145
|
+
systemPermissionsDelegate.requireSystemPermissions(true)
|
|
146
|
+
return@Coroutine assetFactory.create(filePath, album?.getRelativePath())
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
@OptIn(EitherType::class)
|
|
150
|
+
AsyncFunction("createAlbum") Coroutine { name: String, assetRefs: Either<List<Asset>, List<Uri>>, move: Boolean ->
|
|
151
|
+
systemPermissionsDelegate.requireSystemPermissions(true)
|
|
152
|
+
val assetListKClass = toKClass<List<Asset>>()
|
|
153
|
+
if (assetRefs.`is`(assetListKClass)) {
|
|
154
|
+
val assetList = assetRefs.get(assetListKClass)
|
|
155
|
+
return@Coroutine albumFactory.createFromAssets(name, assetList, move)
|
|
156
|
+
}
|
|
157
|
+
val assetPaths = assetRefs.get(toKClass<List<Uri>>())
|
|
158
|
+
return@Coroutine albumFactory.createFromFilePaths(name, assetPaths)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
AsyncFunction("deleteAlbums") Coroutine { albums: List<Album> ->
|
|
162
|
+
systemPermissionsDelegate.requireSystemPermissions(true)
|
|
163
|
+
albums.forEach { album -> album.delete() }
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
AsyncFunction("deleteAssets") Coroutine { assets: List<Asset> ->
|
|
167
|
+
systemPermissionsDelegate.requireSystemPermissions(true)
|
|
168
|
+
val assetIdsToDelete = assets.map { it.contentUri }
|
|
169
|
+
mediaStorePermissionsDelegate.requestMediaLibraryActionPermission(assetIdsToDelete, needsDeletePermission = true)
|
|
170
|
+
assets.forEach { asset -> asset.delete() }
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
AsyncFunction("requestPermissionsAsync") { writeOnly: Boolean, permissions: List<GranularPermission>?, promise: Promise ->
|
|
174
|
+
systemPermissionsDelegate.requestPermissions(writeOnly, permissions, promise)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
AsyncFunction("getPermissionsAsync") { writeOnly: Boolean, permissions: List<GranularPermission>?, promise: Promise ->
|
|
178
|
+
systemPermissionsDelegate.getPermissions(writeOnly, permissions, promise)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
RegisterActivityContracts {
|
|
182
|
+
with(mediaStorePermissionsDelegate) {
|
|
183
|
+
registerMediaStoreContracts(this@MediaLibraryNextModule)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
package expo.modules.medialibrary.next.exceptions
|
|
2
|
+
|
|
3
|
+
import expo.modules.kotlin.exception.CodedException
|
|
4
|
+
|
|
5
|
+
class AlbumNotFoundException(message: String, cause: Throwable? = null) :
|
|
6
|
+
CodedException(message, cause)
|
|
7
|
+
|
|
8
|
+
class AlbumAlreadyExistsException(message: String, cause: Throwable? = null) :
|
|
9
|
+
CodedException(message, cause)
|
|
10
|
+
|
|
11
|
+
class AlbumPropertyNotFoundException(message: String, cause: Throwable? = null) :
|
|
12
|
+
CodedException(message, cause)
|
|
13
|
+
|
|
14
|
+
class AlbumCouldNotBeCreated(message: String, cause: Throwable? = null) :
|
|
15
|
+
CodedException(message, cause)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
package expo.modules.medialibrary.next.exceptions
|
|
2
|
+
|
|
3
|
+
import expo.modules.kotlin.exception.CodedException
|
|
4
|
+
|
|
5
|
+
class PermissionsException(message: String, cause: Throwable? = null) :
|
|
6
|
+
CodedException(message, cause)
|
|
7
|
+
|
|
8
|
+
class UnableToSaveException(message: String) :
|
|
9
|
+
CodedException(message)
|
|
10
|
+
|
|
11
|
+
class AssetNotFoundException(message: String, cause: Throwable? = null) :
|
|
12
|
+
CodedException(message, cause)
|
|
13
|
+
|
|
14
|
+
class AssetPropertyNotFoundException(propertyName: String, cause: Throwable? = null) :
|
|
15
|
+
CodedException("$propertyName not found. The asset may have been deleted or is no longer accessible.", cause)
|
|
16
|
+
|
|
17
|
+
class AssetCouldNotBeCreated(message: String, cause: Throwable? = null) :
|
|
18
|
+
CodedException(message, cause)
|
|
19
|
+
|
|
20
|
+
class AssetInitializationException(message: String, cause: Throwable? = null) :
|
|
21
|
+
CodedException(message, cause)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
package expo.modules.medialibrary.next.extensions
|
|
2
|
+
|
|
3
|
+
import android.database.Cursor
|
|
4
|
+
|
|
5
|
+
fun Cursor.asIterable(): Iterable<Cursor> {
|
|
6
|
+
return object : Iterable<Cursor> {
|
|
7
|
+
override fun iterator(): Iterator<Cursor> = object : Iterator<Cursor> {
|
|
8
|
+
private var hasNextCalled = false
|
|
9
|
+
private var hasNextCache = false
|
|
10
|
+
|
|
11
|
+
override fun hasNext(): Boolean {
|
|
12
|
+
if (!hasNextCalled) {
|
|
13
|
+
hasNextCache = moveToNext()
|
|
14
|
+
hasNextCalled = true
|
|
15
|
+
}
|
|
16
|
+
return hasNextCache
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
override fun next(): Cursor {
|
|
20
|
+
if (!hasNextCalled) {
|
|
21
|
+
if (!moveToNext()) throw NoSuchElementException()
|
|
22
|
+
}
|
|
23
|
+
hasNextCalled = false
|
|
24
|
+
return this@asIterable
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
package expo.modules.medialibrary.next.extensions
|
|
2
|
+
|
|
3
|
+
import expo.modules.medialibrary.MediaLibraryUtils.getFileNameAndExtension
|
|
4
|
+
import java.io.File
|
|
5
|
+
import java.io.FileInputStream
|
|
6
|
+
import java.io.FileOutputStream
|
|
7
|
+
import java.io.IOException
|
|
8
|
+
|
|
9
|
+
fun File.safeMove(destinationDirectory: File): File =
|
|
10
|
+
safeCopy(destinationDirectory).also {
|
|
11
|
+
delete()
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
fun File.safeCopy(destinationDirectory: File): File {
|
|
15
|
+
val newFile = createUniqueFileIn(destinationDirectory, name)
|
|
16
|
+
FileInputStream(this).channel.use { input ->
|
|
17
|
+
FileOutputStream(newFile).channel.use { output ->
|
|
18
|
+
val transferred = input.transferTo(0, input.size(), output)
|
|
19
|
+
if (transferred != input.size()) {
|
|
20
|
+
newFile.delete()
|
|
21
|
+
throw IOException("Could not save file to $destinationDirectory Not enough space.")
|
|
22
|
+
}
|
|
23
|
+
return newFile
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private fun createUniqueFileIn(directory: File, newFileName: String): File {
|
|
29
|
+
var newFile = File(directory, newFileName)
|
|
30
|
+
var suffix = 2
|
|
31
|
+
val (filename, extension) = getFileNameAndExtension(newFileName)
|
|
32
|
+
val suffixLimit = Short.MAX_VALUE.toInt()
|
|
33
|
+
while (newFile.exists()) {
|
|
34
|
+
newFile = File(directory, filename + "_" + suffix + extension)
|
|
35
|
+
suffix++
|
|
36
|
+
if (suffix > suffixLimit) {
|
|
37
|
+
throw IOException("File name suffix limit reached ($suffixLimit)")
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return newFile
|
|
41
|
+
}
|
package/android/src/main/java/expo/modules/medialibrary/next/extensions/WeakReferenceExtensions.kt
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
package expo.modules.medialibrary.next.extensions
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import expo.modules.kotlin.exception.Exceptions
|
|
5
|
+
import java.lang.ref.WeakReference
|
|
6
|
+
|
|
7
|
+
fun WeakReference<Context>.getOrThrow(): Context {
|
|
8
|
+
return get() ?: throw Exceptions.ReactContextLost()
|
|
9
|
+
}
|
package/android/src/main/java/expo/modules/medialibrary/next/extensions/resolver/AlbumExtensions.kt
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
package expo.modules.medialibrary.next.extensions.resolver
|
|
2
|
+
|
|
3
|
+
import android.content.ContentResolver
|
|
4
|
+
import android.database.Cursor
|
|
5
|
+
import android.net.Uri
|
|
6
|
+
import android.provider.MediaStore
|
|
7
|
+
import expo.modules.medialibrary.next.extensions.asIterable
|
|
8
|
+
import expo.modules.medialibrary.next.objects.wrappers.RelativePath
|
|
9
|
+
import kotlinx.coroutines.Dispatchers
|
|
10
|
+
import kotlinx.coroutines.ensureActive
|
|
11
|
+
import kotlinx.coroutines.withContext
|
|
12
|
+
|
|
13
|
+
val EXTERNAL_CONTENT_URI: Uri = MediaStore.Files.getContentUri("external")
|
|
14
|
+
|
|
15
|
+
suspend fun ContentResolver.queryAlbumTitle(bucketId: String): String? =
|
|
16
|
+
queryOne(
|
|
17
|
+
EXTERNAL_CONTENT_URI,
|
|
18
|
+
MediaStore.MediaColumns.BUCKET_DISPLAY_NAME,
|
|
19
|
+
Cursor::getString,
|
|
20
|
+
"${MediaStore.MediaColumns.BUCKET_ID} = ?",
|
|
21
|
+
arrayOf(bucketId)
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
suspend fun ContentResolver.queryAlbumRelativePath(bucketId: String): RelativePath? =
|
|
25
|
+
queryOne(
|
|
26
|
+
EXTERNAL_CONTENT_URI,
|
|
27
|
+
MediaStore.MediaColumns.RELATIVE_PATH,
|
|
28
|
+
{ index -> RelativePath(getString(index)) },
|
|
29
|
+
"${MediaStore.MediaColumns.BUCKET_ID} = ?",
|
|
30
|
+
arrayOf(bucketId)
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
suspend fun ContentResolver.queryAlbumFilepath(bucketId: String): String? =
|
|
34
|
+
queryOne(
|
|
35
|
+
EXTERNAL_CONTENT_URI,
|
|
36
|
+
MediaStore.MediaColumns.DATA,
|
|
37
|
+
Cursor::getString,
|
|
38
|
+
"${MediaStore.Files.FileColumns.BUCKET_ID} = ?",
|
|
39
|
+
arrayOf(bucketId)
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
suspend fun ContentResolver.queryAlbumId(relativePath: RelativePath): String? =
|
|
43
|
+
queryOne(
|
|
44
|
+
EXTERNAL_CONTENT_URI,
|
|
45
|
+
MediaStore.Files.FileColumns.BUCKET_ID,
|
|
46
|
+
Cursor::getString,
|
|
47
|
+
"${MediaStore.Files.FileColumns.RELATIVE_PATH} = ?",
|
|
48
|
+
arrayOf(relativePath.value)
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
suspend fun ContentResolver.queryAlbumAssetsContentUris(bucketId: String): List<Uri> =
|
|
52
|
+
withContext(Dispatchers.IO) {
|
|
53
|
+
val projection = arrayOf(
|
|
54
|
+
MediaStore.Files.FileColumns._ID,
|
|
55
|
+
MediaStore.Files.FileColumns.MEDIA_TYPE
|
|
56
|
+
)
|
|
57
|
+
val selection = "${MediaStore.Files.FileColumns.BUCKET_ID} = ?"
|
|
58
|
+
|
|
59
|
+
query(EXTERNAL_CONTENT_URI, projection, selection, arrayOf(bucketId), null)?.use { cursor ->
|
|
60
|
+
ensureActive()
|
|
61
|
+
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID)
|
|
62
|
+
val typeColumn = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.MEDIA_TYPE)
|
|
63
|
+
cursor
|
|
64
|
+
.asIterable()
|
|
65
|
+
.map { it.extractAssetContentUri(idColumn, typeColumn) }
|
|
66
|
+
.toList()
|
|
67
|
+
} ?: emptyList()
|
|
68
|
+
}
|
package/android/src/main/java/expo/modules/medialibrary/next/extensions/resolver/AssetExtensions.kt
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
package expo.modules.medialibrary.next.extensions.resolver
|
|
2
|
+
|
|
3
|
+
import android.content.ContentResolver
|
|
4
|
+
import android.content.ContentValues
|
|
5
|
+
import android.database.Cursor
|
|
6
|
+
import android.net.Uri
|
|
7
|
+
import android.os.Build
|
|
8
|
+
import android.provider.MediaStore
|
|
9
|
+
import androidx.annotation.RequiresApi
|
|
10
|
+
import expo.modules.medialibrary.EXTERNAL_CONTENT_URI
|
|
11
|
+
import expo.modules.medialibrary.next.exceptions.AssetCouldNotBeCreated
|
|
12
|
+
import expo.modules.medialibrary.next.objects.wrappers.RelativePath
|
|
13
|
+
import expo.modules.medialibrary.next.objects.wrappers.MimeType
|
|
14
|
+
import kotlinx.coroutines.Dispatchers
|
|
15
|
+
import kotlinx.coroutines.withContext
|
|
16
|
+
|
|
17
|
+
suspend fun ContentResolver.queryAssetDisplayName(contentUri: Uri): String? =
|
|
18
|
+
queryOne(contentUri, MediaStore.MediaColumns.DISPLAY_NAME, Cursor::getString)
|
|
19
|
+
|
|
20
|
+
suspend fun ContentResolver.queryGetCreationTime(contentUri: Uri): Long? =
|
|
21
|
+
queryOne(contentUri, MediaStore.Images.Media.DATE_TAKEN, Cursor::getLong)
|
|
22
|
+
|
|
23
|
+
suspend fun ContentResolver.queryAssetModificationTime(contentUri: Uri): Long? =
|
|
24
|
+
queryOne(contentUri, MediaStore.Images.Media.DATE_MODIFIED, Cursor::getLong)
|
|
25
|
+
|
|
26
|
+
suspend fun ContentResolver.queryAssetDuration(contentUri: Uri): Long? =
|
|
27
|
+
queryOne(contentUri, MediaStore.Video.VideoColumns.DURATION, Cursor::getLong)
|
|
28
|
+
|
|
29
|
+
suspend fun ContentResolver.queryAssetWidth(contentUri: Uri): Int? =
|
|
30
|
+
queryOne(contentUri, MediaStore.Images.Media.WIDTH, Cursor::getInt)
|
|
31
|
+
|
|
32
|
+
suspend fun ContentResolver.queryAssetHeight(contentUri: Uri): Int? =
|
|
33
|
+
queryOne(contentUri, MediaStore.Images.Media.HEIGHT, Cursor::getInt)
|
|
34
|
+
|
|
35
|
+
suspend fun ContentResolver.queryAssetPath(contentUri: Uri): String? =
|
|
36
|
+
queryOne(contentUri, MediaStore.Files.FileColumns.DATA, Cursor::getString)
|
|
37
|
+
|
|
38
|
+
suspend fun ContentResolver.queryAssetBucketId(contentUri: Uri): Int? =
|
|
39
|
+
queryOne(contentUri, MediaStore.Images.Media.BUCKET_ID, Cursor::getInt)
|
|
40
|
+
|
|
41
|
+
suspend fun ContentResolver.insertPendingAsset(
|
|
42
|
+
displayName: String,
|
|
43
|
+
mimeType: MimeType,
|
|
44
|
+
relativePath: RelativePath
|
|
45
|
+
): Uri = withContext(Dispatchers.IO) {
|
|
46
|
+
val contentValues = ContentValues().apply {
|
|
47
|
+
put(MediaStore.MediaColumns.DISPLAY_NAME, displayName)
|
|
48
|
+
put(MediaStore.MediaColumns.MIME_TYPE, mimeType.value)
|
|
49
|
+
put(MediaStore.MediaColumns.RELATIVE_PATH, relativePath.value)
|
|
50
|
+
put(MediaStore.MediaColumns.IS_PENDING, 1)
|
|
51
|
+
}
|
|
52
|
+
val collectionUri = mimeType.mediaCollectionUri()
|
|
53
|
+
return@withContext insert(collectionUri, contentValues)
|
|
54
|
+
?: throw AssetCouldNotBeCreated("Failed to create asset: contentResolver.insert() returned null.")
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@RequiresApi(Build.VERSION_CODES.Q)
|
|
58
|
+
fun ContentResolver.publishPendingAsset(uri: Uri) {
|
|
59
|
+
val contentValues = ContentValues().apply {
|
|
60
|
+
put(MediaStore.MediaColumns.IS_PENDING, 0)
|
|
61
|
+
}
|
|
62
|
+
safeUpdate(uri, contentValues)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
fun ContentResolver.safeUpdate(
|
|
66
|
+
uri: Uri,
|
|
67
|
+
values: ContentValues
|
|
68
|
+
): Int {
|
|
69
|
+
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
70
|
+
update(uri, values, null)
|
|
71
|
+
} else {
|
|
72
|
+
update(uri, values, null, null)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
fun ContentResolver.updateRelativePath(contentUri: Uri, newRelativePath: RelativePath) {
|
|
77
|
+
val contentValues = ContentValues().apply {
|
|
78
|
+
put(MediaStore.MediaColumns.RELATIVE_PATH, newRelativePath.value)
|
|
79
|
+
}
|
|
80
|
+
update(contentUri, contentValues, null, null)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
fun ContentResolver.deleteBy(assetPath: String) {
|
|
84
|
+
delete(
|
|
85
|
+
EXTERNAL_CONTENT_URI,
|
|
86
|
+
"${MediaStore.MediaColumns.DATA}=?",
|
|
87
|
+
arrayOf(assetPath)
|
|
88
|
+
)
|
|
89
|
+
}
|
package/android/src/main/java/expo/modules/medialibrary/next/extensions/resolver/CursorExtensions.kt
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
package expo.modules.medialibrary.next.extensions.resolver
|
|
2
|
+
|
|
3
|
+
import android.content.ContentUris
|
|
4
|
+
import android.database.Cursor
|
|
5
|
+
import android.net.Uri
|
|
6
|
+
import android.provider.MediaStore
|
|
7
|
+
|
|
8
|
+
fun Cursor.extractAssetContentUri(idColumn: Int, typeColumn: Int): Uri {
|
|
9
|
+
val id = getLong(idColumn)
|
|
10
|
+
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
|
|
16
|
+
}
|
|
17
|
+
return ContentUris.withAppendedId(baseUri, id)
|
|
18
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
package expo.modules.medialibrary.next.extensions.resolver
|
|
2
|
+
|
|
3
|
+
import android.content.ContentResolver
|
|
4
|
+
import android.database.Cursor
|
|
5
|
+
import android.net.Uri
|
|
6
|
+
import kotlinx.coroutines.Dispatchers
|
|
7
|
+
import kotlinx.coroutines.ensureActive
|
|
8
|
+
import kotlinx.coroutines.withContext
|
|
9
|
+
|
|
10
|
+
suspend fun <T> ContentResolver.queryOne(
|
|
11
|
+
contentUri: Uri,
|
|
12
|
+
column: String,
|
|
13
|
+
extractor: Cursor.(index: Int) -> T,
|
|
14
|
+
selection: String? = null,
|
|
15
|
+
selectionArgs: Array<String>? = null,
|
|
16
|
+
sortOrder: String? = null
|
|
17
|
+
): T? = withContext(Dispatchers.IO) {
|
|
18
|
+
val projection = arrayOf(column)
|
|
19
|
+
query(contentUri, projection, selection, selectionArgs, sortOrder)?.use { cursor ->
|
|
20
|
+
ensureActive()
|
|
21
|
+
val index = cursor.getColumnIndexOrThrow(column)
|
|
22
|
+
return@withContext if (cursor.moveToFirst()) {
|
|
23
|
+
extractor(cursor, index)
|
|
24
|
+
} else {
|
|
25
|
+
null
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return@withContext null
|
|
29
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
package expo.modules.medialibrary.next.extensions.resolver
|
|
2
|
+
|
|
3
|
+
import android.content.ContentResolver
|
|
4
|
+
import android.net.Uri
|
|
5
|
+
import kotlin.io.copyTo
|
|
6
|
+
|
|
7
|
+
fun ContentResolver.copyUriContent(from: Uri, to: Uri) {
|
|
8
|
+
openInputStream(from).use { input ->
|
|
9
|
+
openOutputStream(to).use { output ->
|
|
10
|
+
if (input != null && output != null) {
|
|
11
|
+
input.copyTo(output)
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
package expo.modules.medialibrary.next.objects.album
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.os.Build
|
|
5
|
+
import android.os.Environment
|
|
6
|
+
import expo.modules.kotlin.sharedobjects.SharedObject
|
|
7
|
+
import expo.modules.medialibrary.next.exceptions.AlbumPropertyNotFoundException
|
|
8
|
+
import expo.modules.medialibrary.next.exceptions.ContentResolverNotObtainedException
|
|
9
|
+
import expo.modules.medialibrary.next.extensions.getOrThrow
|
|
10
|
+
import expo.modules.medialibrary.next.extensions.resolver.queryAlbumAssetsContentUris
|
|
11
|
+
import expo.modules.medialibrary.next.extensions.resolver.queryAlbumFilepath
|
|
12
|
+
import expo.modules.medialibrary.next.extensions.resolver.queryAlbumRelativePath
|
|
13
|
+
import expo.modules.medialibrary.next.extensions.resolver.queryAlbumTitle
|
|
14
|
+
import expo.modules.medialibrary.next.objects.asset.Asset
|
|
15
|
+
import expo.modules.medialibrary.next.objects.wrappers.RelativePath
|
|
16
|
+
import kotlinx.coroutines.async
|
|
17
|
+
import kotlinx.coroutines.awaitAll
|
|
18
|
+
import kotlinx.coroutines.coroutineScope
|
|
19
|
+
import java.io.File
|
|
20
|
+
import java.lang.ref.WeakReference
|
|
21
|
+
|
|
22
|
+
class Album(val id: String, context: Context) : SharedObject() {
|
|
23
|
+
private val contextRef = WeakReference(context)
|
|
24
|
+
|
|
25
|
+
private val contentResolver
|
|
26
|
+
get() = contextRef
|
|
27
|
+
.getOrThrow()
|
|
28
|
+
.contentResolver ?: throw ContentResolverNotObtainedException()
|
|
29
|
+
|
|
30
|
+
suspend fun getTitle(): String {
|
|
31
|
+
return contentResolver.queryAlbumTitle(id)
|
|
32
|
+
?: throw AlbumPropertyNotFoundException("Album with ID=$id does not exist in MediaStore")
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
suspend fun getRelativePath(): RelativePath {
|
|
36
|
+
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
37
|
+
contentResolver.queryAlbumRelativePath(id)
|
|
38
|
+
?: throw AlbumPropertyNotFoundException("Album with ID=$id does not exist in MediaStore")
|
|
39
|
+
} else {
|
|
40
|
+
val filePath = contentResolver.queryAlbumFilepath(id)
|
|
41
|
+
?: throw AlbumPropertyNotFoundException("Album with ID=$id does not exist in MediaStore")
|
|
42
|
+
createRelativePathFrom(filePath)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private fun createRelativePathFrom(filePath: String): RelativePath {
|
|
47
|
+
val albumDir = File(filePath).parent
|
|
48
|
+
?: throw AlbumPropertyNotFoundException("Could get a relative path for the album")
|
|
49
|
+
val externalRoot = Environment.getExternalStorageDirectory().absolutePath
|
|
50
|
+
val relative = albumDir.removePrefix(externalRoot).trimStart('/').plus('/')
|
|
51
|
+
return RelativePath(relative)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
suspend fun getAssets(): List<Asset> {
|
|
55
|
+
return contentResolver
|
|
56
|
+
.queryAlbumAssetsContentUris(id)
|
|
57
|
+
.map { contentUri -> Asset(contentUri, contextRef.getOrThrow()) }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
suspend fun delete() = coroutineScope {
|
|
61
|
+
getAssets().map { asset ->
|
|
62
|
+
async {
|
|
63
|
+
asset.delete()
|
|
64
|
+
}
|
|
65
|
+
}.awaitAll()
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
suspend fun add(asset: Asset) {
|
|
69
|
+
asset.move(getRelativePath())
|
|
70
|
+
}
|
|
71
|
+
}
|