@symbiote-native/media-library 0.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.
Files changed (58) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +284 -0
  3. package/build/angular/index.d.ts +1 -0
  4. package/build/angular/index.js +1 -0
  5. package/build/core/index.d.ts +3 -0
  6. package/build/core/index.js +3 -0
  7. package/build/core/media-library.d.ts +94 -0
  8. package/build/core/media-library.js +284 -0
  9. package/build/core/native-module.d.ts +29 -0
  10. package/build/core/native-module.js +3 -0
  11. package/build/core/types.d.ts +126 -0
  12. package/build/core/types.js +1 -0
  13. package/build/next/album.d.ts +9 -0
  14. package/build/next/album.js +12 -0
  15. package/build/next/asset.d.ts +13 -0
  16. package/build/next/asset.js +34 -0
  17. package/build/next/index.d.ts +24 -0
  18. package/build/next/index.js +44 -0
  19. package/build/next/native-module.d.ts +78 -0
  20. package/build/next/native-module.js +2 -0
  21. package/build/next/query.d.ts +13 -0
  22. package/build/next/query.js +16 -0
  23. package/build/next/types.d.ts +92 -0
  24. package/build/next/types.js +30 -0
  25. package/build-ngc/angular/index.d.ts +1 -0
  26. package/build-ngc/angular/index.js +2 -0
  27. package/build-ngc/angular/index.js.map +1 -0
  28. package/build-ngc/next/album.d.ts +9 -0
  29. package/build-ngc/next/album.js +13 -0
  30. package/build-ngc/next/album.js.map +1 -0
  31. package/build-ngc/next/asset.d.ts +13 -0
  32. package/build-ngc/next/asset.js +35 -0
  33. package/build-ngc/next/asset.js.map +1 -0
  34. package/build-ngc/next/index.d.ts +24 -0
  35. package/build-ngc/next/index.js +45 -0
  36. package/build-ngc/next/index.js.map +1 -0
  37. package/build-ngc/next/native-module.d.ts +78 -0
  38. package/build-ngc/next/native-module.js +3 -0
  39. package/build-ngc/next/native-module.js.map +1 -0
  40. package/build-ngc/next/query.d.ts +13 -0
  41. package/build-ngc/next/query.js +17 -0
  42. package/build-ngc/next/query.js.map +1 -0
  43. package/build-ngc/next/types.d.ts +92 -0
  44. package/build-ngc/next/types.js +31 -0
  45. package/build-ngc/next/types.js.map +1 -0
  46. package/native-link.json +34 -0
  47. package/package.json +154 -0
  48. package/src/angular/index.ts +1 -0
  49. package/src/core/index.ts +7 -0
  50. package/src/core/media-library.ts +443 -0
  51. package/src/core/native-module.ts +80 -0
  52. package/src/core/types.ts +163 -0
  53. package/src/next/album.ts +13 -0
  54. package/src/next/asset.ts +51 -0
  55. package/src/next/index.ts +82 -0
  56. package/src/next/native-module.ts +116 -0
  57. package/src/next/query.ts +17 -0
  58. package/src/next/types.ts +106 -0
@@ -0,0 +1,163 @@
1
+ import type { PermissionResponse } from 'expo-modules-core';
2
+
3
+ export type IGranularPermission = 'audio' | 'photo' | 'video';
4
+
5
+ export type IMediaLibraryMediaTypeValue =
6
+ 'audio' | 'photo' | 'video' | 'unknown' | 'pairedVideo';
7
+
8
+ /** @platform android 14+ */
9
+ export type IMediaLibraryMediaTypeFilter = 'photo' | 'video';
10
+
11
+ export type IMediaLibrarySortByKey =
12
+ | 'default'
13
+ | 'mediaType'
14
+ | 'width'
15
+ | 'height'
16
+ | 'creationTime'
17
+ | 'modificationTime'
18
+ | 'duration';
19
+
20
+ export type IMediaLibrarySortByValue =
21
+ [IMediaLibrarySortByKey, boolean] | IMediaLibrarySortByKey;
22
+
23
+ export type IMediaLibraryMediaTypeObject = {
24
+ audio: 'audio';
25
+ photo: 'photo';
26
+ video: 'video';
27
+ unknown: 'unknown';
28
+ };
29
+
30
+ export type IMediaLibrarySortByObject = {
31
+ default: 'default';
32
+ mediaType: 'mediaType';
33
+ width: 'width';
34
+ height: 'height';
35
+ creationTime: 'creationTime';
36
+ modificationTime: 'modificationTime';
37
+ duration: 'duration';
38
+ };
39
+
40
+ /** @platform ios */
41
+ export type IMediaLibraryMediaSubtype =
42
+ | 'depthEffect'
43
+ | 'hdr'
44
+ | 'highFrameRate'
45
+ | 'livePhoto'
46
+ | 'panorama'
47
+ | 'screenshot'
48
+ | 'stream'
49
+ | 'timelapse'
50
+ | 'spatialMedia'
51
+ | 'videoCinematic';
52
+
53
+ export type IMediaLibraryLocation = {
54
+ latitude: number;
55
+ longitude: number;
56
+ };
57
+
58
+ export type IMediaLibraryAsset = {
59
+ id: string;
60
+ filename: string;
61
+ /** `ph://*` (iOS), `file://*` (Android) */
62
+ uri: string;
63
+ mediaType: IMediaLibraryMediaTypeValue;
64
+ /** @platform ios */
65
+ mediaSubtypes?: IMediaLibraryMediaSubtype[];
66
+ width: number;
67
+ height: number;
68
+ creationTime: number;
69
+ modificationTime: number;
70
+ /** Seconds, for audio/video assets. */
71
+ duration: number;
72
+ /** @platform android */
73
+ albumId?: string;
74
+ };
75
+
76
+ export type IMediaLibraryAssetInfo = IMediaLibraryAsset & {
77
+ localUri?: string;
78
+ location?: IMediaLibraryLocation;
79
+ exif?: object;
80
+ /** @platform ios */
81
+ isFavorite?: boolean;
82
+ /** Only set when `shouldDownloadFromNetwork` is `false`. @platform ios */
83
+ isNetworkAsset?: boolean;
84
+ /** 1-8, see the EXIF orientation spec. Only for `mediaType: 'photo'`. @platform ios */
85
+ orientation?: number;
86
+ /** The video paired with a Live Photo. @platform ios */
87
+ pairedVideoAsset?: IMediaLibraryAsset | null;
88
+ };
89
+
90
+ export type IMediaLibraryAssetInfoQueryOptions = {
91
+ /** @default true — iOS iCloud assets only */
92
+ shouldDownloadFromNetwork?: boolean;
93
+ };
94
+
95
+ export type IMediaLibraryAssetsChangeEvent = {
96
+ /** `false` on Android — the platform never reports incremental detail. */
97
+ hasIncrementalChanges: boolean;
98
+ insertedAssets?: IMediaLibraryAsset[];
99
+ deletedAssets?: IMediaLibraryAsset[];
100
+ updatedAssets?: IMediaLibraryAsset[];
101
+ };
102
+
103
+ export type IMediaLibraryAlbumType = 'album' | 'moment' | 'smartAlbum';
104
+
105
+ export type IMediaLibraryAlbum = {
106
+ id: string;
107
+ title: string;
108
+ assetCount: number;
109
+ /** @platform ios */
110
+ type?: IMediaLibraryAlbumType;
111
+ /** Only for `type: 'moment'`. @platform ios */
112
+ startTime: number;
113
+ /** Only for `type: 'moment'`. @platform ios */
114
+ endTime: number;
115
+ /** Only for `type: 'moment'`. @platform ios */
116
+ approximateLocation?: IMediaLibraryLocation;
117
+ /** Only for `type: 'moment'`. @platform ios */
118
+ locationNames?: string[];
119
+ };
120
+
121
+ export type IMediaLibraryAlbumsOptions = {
122
+ includeSmartAlbums?: boolean;
123
+ };
124
+
125
+ export type IMediaLibraryAssetRef = IMediaLibraryAsset | string;
126
+ export type IMediaLibraryAlbumRef = IMediaLibraryAlbum | string;
127
+
128
+ export type IMediaLibraryAssetsOptions = {
129
+ /** @default 20 */
130
+ first?: number;
131
+ /** Pass the previous page's `endCursor` to fetch the next page. */
132
+ after?: IMediaLibraryAssetRef;
133
+ album?: IMediaLibraryAlbumRef;
134
+ /** All keys sort descending by default; pass `[key, true]` for ascending. */
135
+ sortBy?: IMediaLibrarySortByValue[] | IMediaLibrarySortByValue;
136
+ /** @default MediaType.photo */
137
+ mediaType?: IMediaLibraryMediaTypeValue[] | IMediaLibraryMediaTypeValue;
138
+ /** @platform ios */
139
+ mediaSubtypes?: IMediaLibraryMediaSubtype[] | IMediaLibraryMediaSubtype;
140
+ createdAfter?: Date | number;
141
+ createdBefore?: Date | number;
142
+ /** @default false @platform android */
143
+ resolveWithFullInfo?: boolean;
144
+ };
145
+
146
+ export type IMediaLibraryPagedInfo<T> = {
147
+ assets: T[];
148
+ /** iOS: the ID of the last fetched asset. Android: its index. */
149
+ endCursor: string;
150
+ hasNextPage: boolean;
151
+ totalCount: number;
152
+ };
153
+
154
+ export type IMediaLibraryPermissionResponse = PermissionResponse & {
155
+ /**
156
+ * - `'all'` — full library access
157
+ * - `'limited'` — only selected photos (Android 14+, iOS 14+)
158
+ * - `'none'` — denied or not yet granted
159
+ */
160
+ accessPrivileges?: 'all' | 'limited' | 'none';
161
+ };
162
+
163
+ export type { PermissionResponse };
@@ -0,0 +1,13 @@
1
+ import { expoMediaLibraryNext } from './native-module';
2
+ import { Asset } from './asset';
3
+
4
+ /**
5
+ * A media album (collection of assets) on the device, matching upstream's own trivial subclass
6
+ * for every method but `getAssets()` — same reason as Query.exe(), see query.ts.
7
+ */
8
+ export class Album extends expoMediaLibraryNext.Album {
9
+ override async getAssets(): Promise<Asset[]> {
10
+ const results = await super.getAssets();
11
+ return results.map(result => new Asset(result.id));
12
+ }
13
+ }
@@ -0,0 +1,51 @@
1
+ import { Platform } from 'react-native';
2
+ import { UnavailabilityError } from 'expo-modules-core';
3
+ import { expoMediaLibraryNext } from './native-module';
4
+ import type { MediaSubtype } from './types';
5
+
6
+ /**
7
+ * A single media asset on the device (image, video, or audio). Four getters are iOS-only on
8
+ * upstream's own native side; guarded here to fail with the same `UnavailabilityError` upstream
9
+ * throws rather than whatever the Android native method happens to do when called anyway.
10
+ */
11
+ export class Asset extends expoMediaLibraryNext.Asset {
12
+ override getMediaSubtypes(): Promise<MediaSubtype[]> {
13
+ if (Platform.OS !== 'ios') {
14
+ throw new UnavailabilityError(
15
+ 'MediaLibrary',
16
+ 'getMediaSubtypes is only available on iOS',
17
+ );
18
+ }
19
+ return super.getMediaSubtypes() as Promise<MediaSubtype[]>;
20
+ }
21
+
22
+ override getLivePhotoVideoUri(): Promise<string | null> {
23
+ if (Platform.OS !== 'ios') {
24
+ throw new UnavailabilityError(
25
+ 'MediaLibrary',
26
+ 'getLivePhotoVideoUri is only available on iOS',
27
+ );
28
+ }
29
+ return super.getLivePhotoVideoUri();
30
+ }
31
+
32
+ override getIsInCloud(): Promise<boolean> {
33
+ if (Platform.OS !== 'ios') {
34
+ throw new UnavailabilityError(
35
+ 'MediaLibrary',
36
+ 'getIsInCloud is only available on iOS',
37
+ );
38
+ }
39
+ return super.getIsInCloud();
40
+ }
41
+
42
+ override getOrientation(): Promise<number | null> {
43
+ if (Platform.OS !== 'ios') {
44
+ throw new UnavailabilityError(
45
+ 'MediaLibrary',
46
+ 'getOrientation is only available on iOS',
47
+ );
48
+ }
49
+ return super.getOrientation();
50
+ }
51
+ }
@@ -0,0 +1,82 @@
1
+ import { Platform } from 'react-native';
2
+ import {
3
+ createPermissionHook,
4
+ type EventSubscription,
5
+ } from 'expo-modules-core';
6
+ import { expoMediaLibraryNext } from './native-module';
7
+ import type {
8
+ IGranularPermission,
9
+ IMediaLibraryAssetsChangeEvent,
10
+ IMediaLibraryNextPermissionResponse,
11
+ IMediaTypeFilter,
12
+ IPermissionHookOptions,
13
+ } from './types';
14
+
15
+ export { Query } from './query';
16
+ export { Asset } from './asset';
17
+ export { Album } from './album';
18
+ export * from './types';
19
+
20
+ /**
21
+ * @param granularPermissions Only has an effect on Android 13+. Defaults to every possible
22
+ * permission — make sure your app manifest requests everything you pass here.
23
+ */
24
+ export async function requestPermissionsAsync(
25
+ writeOnly: boolean = false,
26
+ granularPermissions?: IGranularPermission[],
27
+ ): Promise<IMediaLibraryNextPermissionResponse> {
28
+ if (Platform.OS === 'android') {
29
+ return expoMediaLibraryNext.requestPermissionsAsync(
30
+ writeOnly,
31
+ granularPermissions,
32
+ );
33
+ }
34
+ return expoMediaLibraryNext.requestPermissionsAsync(writeOnly);
35
+ }
36
+
37
+ export async function getPermissionsAsync(
38
+ writeOnly: boolean = false,
39
+ granularPermissions?: IGranularPermission[],
40
+ ): Promise<IMediaLibraryNextPermissionResponse> {
41
+ if (Platform.OS === 'android') {
42
+ return expoMediaLibraryNext.getPermissionsAsync(
43
+ writeOnly,
44
+ granularPermissions,
45
+ );
46
+ }
47
+ return expoMediaLibraryNext.getPermissionsAsync(writeOnly);
48
+ }
49
+
50
+ export const usePermissions = createPermissionHook<
51
+ IMediaLibraryNextPermissionResponse,
52
+ IPermissionHookOptions
53
+ >({
54
+ getMethod: (options?: IPermissionHookOptions) =>
55
+ getPermissionsAsync(options?.writeOnly, options?.granularPermissions),
56
+ requestMethod: (options?: IPermissionHookOptions) =>
57
+ requestPermissionsAsync(options?.writeOnly, options?.granularPermissions),
58
+ });
59
+
60
+ /**
61
+ * Allows the user to update the assets your app has access to. Only a no-op unless the user
62
+ * previously granted `'limited'` access.
63
+ */
64
+ export async function presentPermissionsPicker(
65
+ mediaTypes?: IMediaTypeFilter[],
66
+ ): Promise<void> {
67
+ return expoMediaLibraryNext.presentPermissionsPicker(mediaTypes);
68
+ }
69
+
70
+ /**
71
+ * Subscribes for updates in the user's media library. On Android it's invoked with an empty
72
+ * object — only iOS reports `insertedAssets`/`deletedAssets`/`updatedAssets`.
73
+ */
74
+ export function addListener(
75
+ listener: (event: IMediaLibraryAssetsChangeEvent) => void,
76
+ ): EventSubscription {
77
+ return expoMediaLibraryNext.addListener('mediaLibraryDidChange', listener);
78
+ }
79
+
80
+ export function removeAllListeners(): void {
81
+ expoMediaLibraryNext.removeAllListeners('mediaLibraryDidChange');
82
+ }
@@ -0,0 +1,116 @@
1
+ import { requireNativeModule, type EventSubscription } from 'expo-modules-core';
2
+ import type {
3
+ AssetField,
4
+ IAssetFieldValueMap,
5
+ IAssetInfo,
6
+ IAssetMetadata,
7
+ ISortDescriptor,
8
+ } from './types';
9
+
10
+ // `declare class` (matching upstream's own ExpoMediaLibraryNext.ts pattern), not a flat object
11
+ // type with a self-referential `new(...): T` construct signature — the latter makes TypeScript
12
+ // report spurious `override` errors on the JS classes that `extends` these
13
+ // (packages/file-system/src/next/native-module.ts carries the same note, found the hard way there).
14
+
15
+ // Every chained filter method returns `this`, not the concrete `NativeMediaLibraryQuery` type
16
+ // upstream's own ambient declaration uses (harmless for them - they never subclass `Query`).
17
+ // Our own `Query` (query.ts) DOES subclass it to override `exe()`, and a `this`-typed return is
18
+ // what lets that override survive a chain: `new Query().orderBy(...).limit(...)` would otherwise
19
+ // statically widen back to the base `NativeMediaLibraryQuery` after the first chained call,
20
+ // silently routing `.exe()` to the base implementation instead of our override.
21
+ export declare class NativeMediaLibraryQuery {
22
+ constructor();
23
+ eq<T extends AssetField>(field: T, value: IAssetFieldValueMap[T]): this;
24
+ within<T extends AssetField>(field: T, value: IAssetFieldValueMap[T][]): this;
25
+ gt(field: AssetField, value: number): this;
26
+ gte(field: AssetField, value: number): this;
27
+ lt(field: AssetField, value: number): this;
28
+ lte(field: AssetField, value: number): this;
29
+ limit(limit: number): this;
30
+ offset(offset: number): this;
31
+ orderBy(sortDescriptors: ISortDescriptor | AssetField): this;
32
+ album(album: NativeMediaLibraryAlbum): this;
33
+ exe(): Promise<NativeMediaLibraryAsset[]>;
34
+ exeForMetadata(): Promise<IAssetMetadata[]>;
35
+ }
36
+
37
+ export declare class NativeMediaLibraryAsset {
38
+ constructor(id: string);
39
+ id: string;
40
+ getCreationTime(): Promise<number | null>;
41
+ getDuration(): Promise<number | null>;
42
+ getFilename(): Promise<string>;
43
+ getHeight(): Promise<number>;
44
+ getMediaType(): Promise<string>;
45
+ getMediaSubtypes(): Promise<string[]>;
46
+ getLivePhotoVideoUri(): Promise<string | null>;
47
+ getIsInCloud(): Promise<boolean>;
48
+ getOrientation(): Promise<number | null>;
49
+ getModificationTime(): Promise<number | null>;
50
+ getShape(): Promise<{ width: number; height: number } | null>;
51
+ getUri(): Promise<string>;
52
+ getWidth(): Promise<number>;
53
+ getInfo(): Promise<IAssetInfo>;
54
+ getAlbums(): Promise<NativeMediaLibraryAlbum[]>;
55
+ getLocation(): Promise<{ latitude: number; longitude: number } | null>;
56
+ getExif(): Promise<Record<string, unknown>>;
57
+ delete(): Promise<void>;
58
+ getFavorite(): Promise<boolean>;
59
+ setFavorite(isFavorite: boolean): Promise<void>;
60
+ static create(
61
+ filePath: string,
62
+ album?: NativeMediaLibraryAlbum,
63
+ ): Promise<NativeMediaLibraryAsset>;
64
+ static delete(assets: NativeMediaLibraryAsset[]): Promise<void>;
65
+ }
66
+
67
+ export declare class NativeMediaLibraryAlbum {
68
+ constructor(id: string);
69
+ id: string;
70
+ getAssets(): Promise<NativeMediaLibraryAsset[]>;
71
+ getTitle(): Promise<string>;
72
+ delete(): Promise<void>;
73
+ add(
74
+ assets: NativeMediaLibraryAsset | NativeMediaLibraryAsset[],
75
+ ): Promise<void>;
76
+ removeAssets(assets: NativeMediaLibraryAsset[]): Promise<void>;
77
+ static create(
78
+ name: string,
79
+ assetsRefs: string[] | NativeMediaLibraryAsset[],
80
+ moveAssets?: boolean,
81
+ ): Promise<NativeMediaLibraryAlbum>;
82
+ static delete(
83
+ albums: NativeMediaLibraryAlbum[],
84
+ deleteAssets?: boolean,
85
+ ): Promise<void>;
86
+ static get(title: string): Promise<NativeMediaLibraryAlbum | null>;
87
+ static getAll(): Promise<NativeMediaLibraryAlbum[]>;
88
+ }
89
+
90
+ export type INativeMediaLibraryNextModule = {
91
+ Query: typeof NativeMediaLibraryQuery;
92
+ Asset: typeof NativeMediaLibraryAsset;
93
+ Album: typeof NativeMediaLibraryAlbum;
94
+ getPermissionsAsync(
95
+ writeOnly?: boolean,
96
+ granularPermissions?: string[],
97
+ ): Promise<import('expo-modules-core').PermissionResponse>;
98
+ requestPermissionsAsync(
99
+ writeOnly?: boolean,
100
+ granularPermissions?: string[],
101
+ ): Promise<import('expo-modules-core').PermissionResponse>;
102
+ presentPermissionsPicker(mediaTypes?: string[]): Promise<void>;
103
+ addListener(
104
+ eventName: 'mediaLibraryDidChange',
105
+ listener: (event: {
106
+ hasIncrementalChanges: boolean;
107
+ insertedAssets?: string[];
108
+ deletedAssets?: string[];
109
+ updatedAssets?: string[];
110
+ }) => void,
111
+ ): EventSubscription;
112
+ removeAllListeners(eventName: 'mediaLibraryDidChange'): void;
113
+ };
114
+
115
+ export const expoMediaLibraryNext =
116
+ requireNativeModule<INativeMediaLibraryNextModule>('ExpoMediaLibraryNext');
@@ -0,0 +1,17 @@
1
+ import { expoMediaLibraryNext } from './native-module';
2
+ import { Asset } from './asset';
3
+
4
+ /**
5
+ * A query builder over the media library, matching upstream's own trivial subclass
6
+ * (`export class Query extends ExpoMediaLibraryNext.Query {}`) for every method but `exe()`:
7
+ * the native call constructs its results off the BASE native asset class, never our own `Asset`
8
+ * (asset.ts) — so without this override, a query result's iOS-only getters silently skip the
9
+ * platform guard every other `Asset` gets. Re-wrapping by id is cheap (a shared-object handle,
10
+ * not a data copy) and keeps `exe()` consistent with every other way of obtaining an `Asset`.
11
+ */
12
+ export class Query extends expoMediaLibraryNext.Query {
13
+ override async exe(): Promise<Asset[]> {
14
+ const results = await super.exe();
15
+ return results.map(result => new Asset(result.id));
16
+ }
17
+ }
@@ -0,0 +1,106 @@
1
+ import type { PermissionResponse } from 'expo-modules-core';
2
+
3
+ export enum AssetField {
4
+ CREATION_TIME = 'creationTime',
5
+ MODIFICATION_TIME = 'modificationTime',
6
+ MEDIA_TYPE = 'mediaType',
7
+ WIDTH = 'width',
8
+ HEIGHT = 'height',
9
+ DURATION = 'duration',
10
+ IS_FAVORITE = 'isFavorite',
11
+ }
12
+
13
+ export enum MediaType {
14
+ UNKNOWN = 'unknown',
15
+ IMAGE = 'image',
16
+ AUDIO = 'audio',
17
+ VIDEO = 'video',
18
+ }
19
+
20
+ export enum MediaSubtype {
21
+ DEPTH_EFFECT = 'depthEffect',
22
+ HDR = 'hdr',
23
+ HIGH_FRAME_RATE = 'highFrameRate',
24
+ LIVE_PHOTO = 'livePhoto',
25
+ PANORAMA = 'panorama',
26
+ SCREENSHOT = 'screenshot',
27
+ STREAM = 'stream',
28
+ TIME_LAPSE = 'timelapse',
29
+ SPATIAL_MEDIA = 'spatialMedia',
30
+ VIDEO_CINEMATIC = 'videoCinematic',
31
+ }
32
+
33
+ export type IAssetFieldValueMap = {
34
+ [AssetField.CREATION_TIME]: number;
35
+ [AssetField.MODIFICATION_TIME]: number;
36
+ [AssetField.MEDIA_TYPE]: MediaType;
37
+ [AssetField.WIDTH]: number;
38
+ [AssetField.HEIGHT]: number;
39
+ [AssetField.DURATION]: number;
40
+ [AssetField.IS_FAVORITE]: boolean;
41
+ };
42
+
43
+ export type ISortDescriptor = {
44
+ key: AssetField;
45
+ ascending?: boolean;
46
+ };
47
+
48
+ export type IShape = {
49
+ width: number;
50
+ height: number;
51
+ };
52
+
53
+ export type ILocation = {
54
+ latitude: number;
55
+ longitude: number;
56
+ };
57
+
58
+ export type IAssetInfo = {
59
+ id: string;
60
+ filename: string;
61
+ uri: string;
62
+ mediaType: MediaType;
63
+ width: number;
64
+ height: number;
65
+ duration: number | null;
66
+ creationTime: number | null;
67
+ modificationTime: number | null;
68
+ isFavorite: boolean;
69
+ };
70
+
71
+ export type IAssetMetadata = {
72
+ id: string;
73
+ filename: string | null;
74
+ mediaType: MediaType;
75
+ width: number | null;
76
+ height: number | null;
77
+ duration: number | null;
78
+ creationTime: number | null;
79
+ modificationTime: number | null;
80
+ isFavorite: boolean;
81
+ };
82
+
83
+ export type IGranularPermission = 'audio' | 'photo' | 'video';
84
+
85
+ export type IMediaTypeFilter = 'photo' | 'video';
86
+
87
+ export type IMediaLibraryAssetsChangeEvent = {
88
+ hasIncrementalChanges: boolean;
89
+ insertedAssets?: string[];
90
+ deletedAssets?: string[];
91
+ updatedAssets?: string[];
92
+ };
93
+
94
+ export type IPermissionHookOptions = {
95
+ writeOnly?: boolean;
96
+ granularPermissions?: IGranularPermission[];
97
+ };
98
+
99
+ export type IMediaLibraryNextPermissionResponse = PermissionResponse & {
100
+ /**
101
+ * - `'all'` — full library access
102
+ * - `'limited'` — only selected photos (Android 14+, iOS 14+)
103
+ * - `'none'` — denied or not yet granted
104
+ */
105
+ accessPrivileges?: 'all' | 'limited' | 'none';
106
+ };