[APP-716] Add 'save image' button to the lightbox (#926)
* Add 'save image' button to the lightbox * Fix types * Fix typeszio/stable
parent
5fcca17129
commit
c72e24f841
|
@ -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(
|
||||||
|
|
|
@ -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')
|
||||||
}
|
}
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in New Issue