{"id":18782875,"url":"https://github.com/rescript-react-native/cameraroll","last_synced_at":"2025-04-13T12:09:01.149Z","repository":{"id":42003295,"uuid":"251401372","full_name":"rescript-react-native/cameraroll","owner":"rescript-react-native","description":"ReScript bindings for @react-native-community/cameraroll","archived":false,"fork":false,"pushed_at":"2023-02-09T03:18:01.000Z","size":1073,"stargazers_count":2,"open_issues_count":3,"forks_count":1,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-04-13T12:08:56.010Z","etag":null,"topics":["camera-roll","photo-gallery","react","react-native","rescript","rescript-react","rescript-react-native","saving-photos"],"latest_commit_sha":null,"homepage":"","language":"ReScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rescript-react-native.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2020-03-30T19:01:12.000Z","updated_at":"2024-02-20T00:56:34.000Z","dependencies_parsed_at":"2023-02-08T06:31:50.597Z","dependency_job_id":"d3a35cc3-8a88-46be-bb84-8f857ceeaabe","html_url":"https://github.com/rescript-react-native/cameraroll","commit_stats":{"total_commits":25,"total_committers":3,"mean_commits":8.333333333333334,"dds":0.07999999999999996,"last_synced_commit":"ee9b60cf8c8b78693c0d692b3c0e003db80fd86e"},"previous_names":["reason-react-native/cameraroll"],"tags_count":4,"template":false,"template_full_name":"rescript-react-native/__template__","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rescript-react-native%2Fcameraroll","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rescript-react-native%2Fcameraroll/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rescript-react-native%2Fcameraroll/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rescript-react-native%2Fcameraroll/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rescript-react-native","download_url":"https://codeload.github.com/rescript-react-native/cameraroll/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248710433,"owners_count":21149190,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["camera-roll","photo-gallery","react","react-native","rescript","rescript-react","rescript-react-native","saving-photos"],"created_at":"2024-11-07T20:37:21.372Z","updated_at":"2025-04-13T12:09:01.130Z","avatar_url":"https://github.com/rescript-react-native.png","language":"ReScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# `@rescript-react-native/cameraroll`\n\n[![Build Status](https://github.com/rescript-react-native/cameraroll/workflows/Build/badge.svg)](https://github.com/rescript-react-native/cameraroll/actions)\n[![Version](https://img.shields.io/npm/v/@rescript-react-native/cameraroll.svg)](https://www.npmjs.com/@rescript-react-native/cameraroll)\n[![ReScript Forum](https://img.shields.io/discourse/posts?color=e6484f\u0026label=ReScript%20Forum\u0026server=https%3A%2F%2Fforum.rescript-lang.org)](https://forum.rescript-lang.org/)\n\n[ReScript](https://rescript-lang.org) bindings for\n[`@react-native-community/cameraroll`](https://github.com/react-native-cameraroll/react-native-cameraroll).\n\nExposed as `ReactNativeCameraRoll` module.\n\n`@rescript-react-native/cameraroll` X.y.\\* means it's compatible with\n`@react-native-community/cameraroll` X.y.\\*\n\n## Installation\n\nWhen\n[`@react-native-community/cameraroll`](https://github.com/react-native-cameraroll/react-native-cameraroll)\nis properly installed \u0026 configured by following their installation instructions,\nyou can install the bindings:\n\n```console\nnpm install @rescript-react-native/cameraroll\n# or\nyarn add @rescript-react-native/cameraroll\n```\n\n`@rescript-react-native/cameraroll` should be added to `bs-dependencies` in your\n`bsconfig.json`:\n\n```diff\n{\n  //...\n  \"bs-dependencies\": [\n    \"@rescript/react\",\n    \"rescript-react-native\",\n    // ...\n+    \"@rescript-react-native/cameraroll\"\n  ],\n  //...\n}\n```\n\n## Methods\n\n### `save`\n\nAllows saving photos and videos to the Camera Roll or Photo Gallery, similar to\n[`saveToCameraRoll`](#savetocameraroll).\n\nThe function will return the URI for the saved file as a string wrapped in a\nPromise.\n\n```rescript\nsave: string =\u003e Js.Promise.t(string)\n```\n\n### `saveWithOptions`\n\nAllows saving photos and videos to the Camera Roll or Photo Gallery, similar to\n[`saveToCameraRoll`](#savetocameraroll), however, a particular album may be\nspecified in a `saveOptions` object.\n\nThe function will return the URI for the saved file as a string wrapped in a\nPromise.\n\n```rescript\nsaveWithOptions: (string, saveOptions) =\u003e Js.Promise.t(string)\n```\n\n### `saveToCameraRoll`\n\nAllows saving photos and videos to the Camera Roll or Photo Gallery. File to be\nsaved is specified as a tag (of type `string`) which can be\n\n- on Android, a local image or video URI, such as \"file:///sdcard/img.png\"\n- on iOS, a local video URI or any image URI (local, remote asset-library, or\n  base64 data URIs)\n\nMedia type (photo or video) will be automatically inferred; any file will be\ninferred to be a photo, unless the file extension is `mov` or `mp4`, then it\nwill be inferred to be a video.\n\nThe function will return the URI for the saved file as a string wrapped in a\nPromise.\n\n```rescript\nsaveToCameraRoll: string =\u003e Js.Promise.t(string)\n```\n\n### `saveToCameraRollWithType`\n\nAllows saving photos and videos to the Camera Roll, where the tag will be\nspecified as above, overriding the automatically determined type by specifying\none of the polymorphic variants `` `photo `` or `` `video ``.\n\nThe function will return the URI for the saved file as a string wrapped in a\nPromise.\n\n```rescript\nsaveToCameraRollWithType: (string, [ | #photo | #video]) =\u003e Js.Promise.t(string)\n```\n\n### `deletePhotos`\n\nTo request deletion of photos (as `array(string)`) from the Camera Roll. Returns\n`bool` wrapped in a Promise. The Promise will be rejected if deletion does not\nsucceed; on _Android_ that would imply a system error whereas on _iOS_ the user\nmay have cancelled the request to delete.\n\nOn _Android_, the uri must be a local image or video URI, such as\n\"file:///sdcard/img.png\".\n\nOn _iOS_, the uri can be any image URI (including local, remote asset-library\nand base64 data URIs) or a local video file URI. The user will be presented with\na dialog box that showing the asset(s) and asked for confirmation. This cannot\nbe bypassed as per Apple Developer guidelines.\n\n```rescript\ndeletePhotos: array(string) =\u003e Js.Promise.t(bool)\n```\n\n### `getAlbums`\n\nReturns a list of albums wrapped in a Promise.\n\n```rescript\ngetAlbums: unit =\u003e Js.Promise.t(array(album))\n```\n\n### `getAlbumsWithParams`\n\nReturns a list of albums of type specified in a `getAlbumsParams` object,\nwrapped in a Promise.\n\n```rescript\ngetAlbums: getAlbumsParams =\u003e Js.Promise.t(array(album))\n```\n\n### `getPhotos`\n\nAllows searching for photos or videos matching given parameters.\n\nTakes as argument `getPhotosParams` and returns a `photoIdentifiersPage` object\nwrapped in a Promise. `edges` key of the `photoIdentifiersPage` object would be\nof type `array(photoIdentifier)`, where each `photoIdentifier` object would\ncontain details of each photo or video matching parameters provided in the\n`getPhotosParam` object.\n\n```rescript\ngetPhotos: getPhotosParams =\u003e Js.Promise.t(photoIdentifiersPage)\n```\n\n## Types\n\n### `album`\n\n```rescript\ntype album = {\n  title: string,\n  count: int\n};\n```\n\n### `getAlbumsParams`;\n\ncan be constructed with the constructor of the same name\n\n```rescript\ngetAlbumsParams:\n  (\n    ~assetType: [\n                   | #All\n                   | #Photos\n                   | #Videos\n                 ]\n  ) =\u003e\n  getAlbumsParams\n```\n\n### `getPhotosParams`\n\ncan be constructed with the constructor of the same name\n\n- `first` takes an integer which specifies the number of files for which details\n  will be returned. Files will match in reverse order (i.e. most recent first)\n- `after` takes a string which should be obtained from `photoIdentifiersPage`\n  returned in a previous `getPhotos` call, under the `end_cursor` key contained\n  in turn under the `page_info` key.\n\n```rescript\ngetPhotosParams:\n  (\n    ~first: int,\n    ~after: string=?,\n    ~groupTypes: [\n                   | #Album\n                   | #All\n                   | #Event\n                   | #Faces\n                   | #Library\n                   | #PhotoStream\n                   | #SavedPhotos\n                 ]\n                   =?,\n    ~groupName: string=?,\n    ~assetType: [ | #All | #Videos | #Photos]=?,\n    ~mimeTypes: array(string)=?,\n    ~fromTime: float=?,\n    ~toTime: float=?,\n    ~include_: array(string)=?,\n    unit\n  ) =\u003e getPhotosParams\n```\n\n### `image`\n\n```rescript\ntype image = {\n  filename: Js.Nullable.t(string),\n  uri: string,\n  height: Js.Nullable.t(float),\n  width: Js.Nullable.t(float),\n  fileSize: Js.Nullable.t(float),\n  playableDuration: Js.Nullable.t(float),\n}\n```\n\n### `location`\n\n```rescript\ntype location = {\n  latitude: Js.Nullable.t(float),\n  longitude: Js.Nullable.t(float),\n  altitude: Js.Nullable.t(float),\n  heading: Js.Nullable.t(float),\n  speed: Js.Nullable.t(float),\n};\n```\n\n### `node`\n\n```rescript\ntype node = {\n  \\\"type\": string,\n  \\\"group_name\": string,\n  image,\n  timestamp: float,\n  location: Js.Nullable.t(location),\n}\n```\n\n### `pageInfo`\n\n```rescript\ntype pageInfo = {\n  \\\"has_next_page\": bool,\n  \\\"start_cursor\": Js.Nullable.t(string),\n  \\\"end_curson\": Js.Nullable.t(string),\n};\n\n```\n\n### `photoIdentifiersPage`\n\n```rescript\ntype photoIdentifiersPage = {\n  edges: array(photoIdentifier),\n  \\\"page_info\",\n}\n```\n\n### `photoIdentifier`\n\n```rescript\n  type photoIdentifier = {node}\n```\n\n### `saveOptions`\n\ncan be constructed with the constructor of the same name\n\n```rescript\nsaveOptions:\n  (\n    ~_type: [\n      | #auto\n      | #photo\n      | #video\n    ],\n    ~album: string\n  ) =\u003e\n  saveOptions\n```\n\n## Example\n\n```rescript\nopen ReactNative;\nopen ReactNativeCameraRoll;\n\nlet windowWidth = Dimensions.get(`window)##width;\nlet windowHeight = Dimensions.get(`window)##height;\n\ntype state = {\n  tag: string,\n  path: option(string),\n  photos: array(photoIdentifier),\n};\n\ntype action =\n  | SetTag(string)\n  | SetPath(string)\n  | SetPhotos(array(photoIdentifier));\n\nlet styles = Style.(\n  StyleSheet.create({\n    \"container\":\n      style(\n        ~flex=1.,\n        ~flexDirection=`column,\n        ~alignItems=`center,\n        ~justifyContent=`spaceBetween,\n        (),\n      ),\n    \"getPhotosExample\":\n      style(\n        ~height=(0.25 *. windowHeight)-\u003edp,\n        ~justifyContent=`spaceBetween,\n        (),\n      ),\n    \"saveToCameraRollExample\":\n      style(\n        ~width=(0.8 *. windowWidth)-\u003edp,\n        ~height=(0.6 *. windowHeight)-\u003edp,\n        ~margin=(0.1 *. windowWidth)-\u003edp,\n        ~justifyContent=`spaceAround,\n        (),\n      ),\n    \"image\":\n      style(\n        ~width=(0.8 *. windowWidth)-\u003edp,\n        ~height=(0.56 *. windowWidth)-\u003edp,\n        (),\n      ),\n    \"imageContainer\":\n      style(\n        ~width=(0.8 *. windowWidth)-\u003edp,\n        ~height=(0.56 *. windowWidth)-\u003edp,\n        ~borderWidth=1.,\n        ~justifyContent=`center,\n        (),\n      ),\n    \"text\": style(~textAlign=`center, ()),\n    \"textInput\":\n      style(\n        ~textAlign=`center,\n        ~borderRadius=8.,\n        ~padding=4.-\u003edp,\n        ~borderWidth=1.,\n        ~backgroundColor=Color.linen,\n        (),\n      ),\n    \"thumbnail\":\n      style(\n        ~width=(0.21 *. windowHeight)-\u003edp,\n        ~height=(0.15 *. windowHeight)-\u003edp,\n        (),\n      ),\n    \"thumbnails\":\n      style(\n        ~height=(0.15 *. windowHeight)-\u003edp,\n        (),\n      ),\n  })\n);\n\nlet styledText = s =\u003e {\n  \u003cText style=styles##text\u003e s-\u003eReact.string \u003c/Text\u003e;\n};\n\nlet thumbnails = photos =\u003e {\n  \u003cView style=styles##thumbnails\u003e\n    \u003cScrollView horizontal=true\u003e\n      {photos\n       -\u003eBelt.Array.mapWithIndex((i, s) =\u003e\n           \u003cImage\n             style=styles##thumbnail\n             source={Image.Source.fromUriSource(\n               Image.uriSource(~uri=s.node.image.uri, ()),\n             )}\n             key={string_of_int(i)}\n           /\u003e\n         )\n       -\u003eReact.array}\n    \u003c/ScrollView\u003e\n  \u003c/View\u003e;\n};\n\nlet inputBox = (tag, dispatch) =\u003e {\n  \u003cTextInput\n    style=styles##textInput\n    multiline=true\n    defaultValue=\"https://images.unsplash.com/photo-1520453803296-c39eabe2dab4\"\n    value=tag\n    onChangeText={s =\u003e dispatch(SetTag(s))}\n  /\u003e;\n};\n\n[@react.component]\nlet make = () =\u003e {\n  let (state, dispatch) =\n    React.useReducer(\n      (state, action) =\u003e\n        switch (action) {\n        | SetTag(uri) =\u003e {...state, tag: uri}\n        | SetPath(uri) =\u003e {...state, path: Some(uri)}\n        | SetPhotos(a) =\u003e {...state, photos: a}\n        },\n      {\n        tag: \"https://images.unsplash.com/photo-1520453803296-c39eabe2dab4\",\n        path: None,\n        photos: [||],\n      },\n    );\n\n  let getPhotos = () =\u003e {\n    Js.Promise.(\n      getPhotos(\n        getPhotosParams(\n          ~first=20,\n          ~assetType=`Photos,\n          ~groupTypes=`All,\n          (),\n        ),\n      )\n      |\u003e then_(r =\u003e resolve(dispatch(SetPhotos(r.edges))))\n      |\u003e catch(err =\u003e resolve(Js.Console.warn(err)))\n      |\u003e ignore\n    );\n  };\n\n  let savePhoto = uri =\u003e {\n    Js.Promise.(\n      saveToCameraRoll(uri)\n      |\u003e then_(r =\u003e resolve(dispatch(SetPath(r))))\n      |\u003e catch(err =\u003e resolve(Js.Console.warn(err)))\n      |\u003e ignore\n    );\n  };\n\n  \u003cView style=styles##container\u003e\n    \u003cView style=styles##saveToCameraRollExample\u003e\n      \u003cView style=styles##imageContainer\u003e\n        {switch (state.path) {\n         | None =\u003e\n           // default view, before a photo is saved to the Camera Roll or Photo Library\n           \u003cView\u003e\n             {styledText(\"Press the Save Photo button\")}\n             {styledText(\"below to load photo\")}\n           \u003c/View\u003e\n         | Some(p) =\u003e\n           // Once a photo is saved to the Camera Roll or Photo Library, it will be displayed in this view\n           \u003cImage\n             style=styles##image\n             source={Image.Source.fromUriSource(Image.uriSource(~uri=p, ()))}\n           /\u003e\n         }}\n      \u003c/View\u003e\n      \u003cView\u003e\n        {styledText(\"Enter a path for a photo to save,\")}\n        {styledText(\"or try the example given below\")}\n        // TextInput box to try other photo sources\n        {inputBox(state.tag, dispatch)}\n        // An attempt will be made to save the photo file specified in the TextInput box to the Camera Roll or Photo Library once the button below is pressed\n        \u003cButton\n          onPress={_ =\u003e savePhoto(state.tag)}\n          title={js|Save a Photo|js}\n        /\u003e\n      \u003c/View\u003e\n    \u003c/View\u003e\n    \u003cView style=styles##getPhotosExample\u003e\n      \u003cView\u003e\n        {styledText(\"Saved photo will appear in the Camera Roll\")}\n        // An attempt will be made to get the most recent 20 photos from the Camera Roll or Photo Library\n        \u003cButton onPress={_ =\u003e getPhotos()} title={js|Open CameraRoll|js} /\u003e\n      \u003c/View\u003e\n      {thumbnails(state.photos)}\n    \u003c/View\u003e\n  \u003c/View\u003e;\n};\n\n```\n\n---\n\n## Changelog\n\nCheck the [changelog](./CHANGELOG.md) for more informations about recent\nreleases.\n\n---\n\n## Contribute\n\nRead the\n[contribution guidelines](https://github.com/rescript-react-native/.github/blob/master/CONTRIBUTING.md)\nbefore contributing.\n\n## Code of Conduct\n\nWe want this community to be friendly and respectful to each other. Please read\n[our full code of conduct](https://github.com/rescript-react-native/.github/blob/master/CODE_OF_CONDUCT.md)\nso that you can understand what actions will and will not be tolerated.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frescript-react-native%2Fcameraroll","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frescript-react-native%2Fcameraroll","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frescript-react-native%2Fcameraroll/lists"}