[APP-716] Add 'save image' button to the lightbox (#926)

* Add 'save image' button to the lightbox

* Fix types

* Fix types
zio/stable
Paul Frazee 2023-06-30 11:34:04 -05:00 committed by GitHub
parent 5fcca17129
commit c72e24f841
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 60 additions and 4 deletions

View File

@ -5,6 +5,7 @@ import {Image} from 'react-native-image-crop-picker'
import * as RNFS from 'react-native-fs' import * as RNFS from 'react-native-fs'
import uuid from 'react-native-uuid' import uuid from 'react-native-uuid'
import * as Sharing from 'expo-sharing' import * as Sharing from 'expo-sharing'
import * as MediaLibrary from 'expo-media-library'
import {Dimensions} from './types' import {Dimensions} from './types'
import {isAndroid, isIOS} from 'platform/detection' import {isAndroid, isIOS} from 'platform/detection'
@ -75,7 +76,7 @@ export async function downloadAndResize(opts: DownloadAndResizeOpts) {
} }
} }
export async function saveImageModal({uri}: {uri: string}) { export async function shareImageModal({uri}: {uri: string}) {
if (!(await Sharing.isAvailableAsync())) { if (!(await Sharing.isAvailableAsync())) {
// TODO might need to give an error to the user in this case -prf // TODO might need to give an error to the user in this case -prf
return return
@ -107,6 +108,34 @@ export async function saveImageModal({uri}: {uri: string}) {
RNFS.unlink(imagePath) RNFS.unlink(imagePath)
} }
export async function saveImageToAlbum({
uri,
album,
}: {
uri: string
album: string
}) {
// download the file to cache
// NOTE
// assuming PNG
// we're currently relying on the fact our CDN only serves pngs
// -prf
const downloadResponse = await RNFetchBlob.config({
fileCache: true,
}).fetch('GET', uri)
let imagePath = downloadResponse.path()
imagePath = normalizePath(await moveToPermanentPath(imagePath, '.png'), true)
// save to the album (creating as needed)
const assetRef = await MediaLibrary.createAssetAsync(imagePath)
const albumRef = await MediaLibrary.getAlbumAsync(album)
if (albumRef) {
await MediaLibrary.addAssetsToAlbumAsync(assetRef, albumRef)
} else {
await MediaLibrary.createAlbumAsync(album, assetRef)
}
}
export function getImageDim(path: string): Promise<Dimensions> { export function getImageDim(path: string): Promise<Dimensions> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
RNImage.getSize( RNImage.getSize(

View File

@ -37,7 +37,12 @@ export async function downloadAndResize(opts: DownloadAndResizeOpts) {
return await doResize(dataUri, opts) return await doResize(dataUri, opts)
} }
export async function saveImageModal(_opts: {uri: string}) { export async function shareImageModal(_opts: {uri: string}) {
// TODO
throw new Error('TODO')
}
export async function saveImageToAlbum(_opts: {uri: string; album: string}) {
// TODO // TODO
throw new Error('TODO') throw new Error('TODO')
} }

View File

@ -5,7 +5,8 @@ import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import ImageView from './ImageViewing' import ImageView from './ImageViewing'
import {useStores} from 'state/index' import {useStores} from 'state/index'
import * as models from 'state/models/ui/shell' import * as models from 'state/models/ui/shell'
import {saveImageModal} from 'lib/media/manip' import {shareImageModal, saveImageToAlbum} from 'lib/media/manip'
import * as Toast from '../util/Toast'
import {Text} from '../util/text/Text' import {Text} from '../util/text/Text'
import {s, colors} from 'lib/styles' import {s, colors} from 'lib/styles'
import {Button} from '../util/forms/Button' import {Button} from '../util/forms/Button'
@ -54,7 +55,16 @@ export const Lightbox = observer(function Lightbox() {
<Button <Button
type="primary-outline" type="primary-outline"
style={styles.footerBtn} style={styles.footerBtn}
onPress={() => saveImageModal({uri})}> onPress={() => saveImageToAlbumWithToasts(uri)}>
<FontAwesomeIcon icon={['far', 'floppy-disk']} style={s.white} />
<Text type="xl" style={s.white}>
Save
</Text>
</Button>
<Button
type="primary-outline"
style={styles.footerBtn}
onPress={() => shareImageModal({uri})}>
<FontAwesomeIcon icon="arrow-up-from-bracket" style={s.white} /> <FontAwesomeIcon icon="arrow-up-from-bracket" style={s.white} />
<Text type="xl" style={s.white}> <Text type="xl" style={s.white}>
Share Share
@ -96,6 +106,15 @@ export const Lightbox = observer(function Lightbox() {
} }
}) })
async function saveImageToAlbumWithToasts(uri: string) {
try {
await saveImageToAlbum({uri, album: 'Bluesky'})
Toast.show('Saved to the "Bluesky" album.')
} catch (e: any) {
Toast.show(`Failed to save image: ${String(e)}`)
}
}
const styles = StyleSheet.create({ const styles = StyleSheet.create({
footer: { footer: {
paddingTop: 16, paddingTop: 16,
@ -109,6 +128,7 @@ const styles = StyleSheet.create({
footerBtns: { footerBtns: {
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'center', justifyContent: 'center',
gap: 8,
}, },
footerBtn: { footerBtn: {
flexDirection: 'row', flexDirection: 'row',

View File

@ -37,6 +37,7 @@ import {faEnvelope} from '@fortawesome/free-solid-svg-icons/faEnvelope'
import {faExclamation} from '@fortawesome/free-solid-svg-icons/faExclamation' import {faExclamation} from '@fortawesome/free-solid-svg-icons/faExclamation'
import {faEye} from '@fortawesome/free-solid-svg-icons/faEye' import {faEye} from '@fortawesome/free-solid-svg-icons/faEye'
import {faEyeSlash as farEyeSlash} from '@fortawesome/free-regular-svg-icons/faEyeSlash' import {faEyeSlash as farEyeSlash} from '@fortawesome/free-regular-svg-icons/faEyeSlash'
import {faFloppyDisk} from '@fortawesome/free-regular-svg-icons/faFloppyDisk'
import {faGear} from '@fortawesome/free-solid-svg-icons/faGear' import {faGear} from '@fortawesome/free-solid-svg-icons/faGear'
import {faGlobe} from '@fortawesome/free-solid-svg-icons/faGlobe' import {faGlobe} from '@fortawesome/free-solid-svg-icons/faGlobe'
import {faHand} from '@fortawesome/free-solid-svg-icons/faHand' import {faHand} from '@fortawesome/free-solid-svg-icons/faHand'
@ -124,6 +125,7 @@ export function setup() {
faEye, faEye,
faExclamation, faExclamation,
farEyeSlash, farEyeSlash,
faFloppyDisk,
faGear, faGear,
faGlobe, faGlobe,
faHand, faHand,