1import { StackScreenProps } from '@react-navigation/stack';
2import * as MediaLibrary from 'expo-media-library';
3import React from 'react';
4import { Image, ScrollView, StyleSheet, View, Alert } from 'react-native';
5
6import Button from '../../components/Button';
7import HeadingText from '../../components/HeadingText';
8import MonoText from '../../components/MonoText';
9
10const EXPO_ALBUM_NAME = 'Expo';
11
12type Links = {
13  MediaDetails: { asset: MediaLibrary.Asset; onGoBack: () => void; album: MediaLibrary.Album };
14};
15
16type Props = StackScreenProps<Links, 'MediaDetails'>;
17
18export default class MediaDetailsScreen extends React.Component<Props> {
19  static navigationOptions = {
20    title: 'MediaLibrary Asset',
21  };
22
23  state = {
24    details: null,
25    detailsWithoutDownloadingFromNetwork: null,
26  };
27
28  componentDidMount() {
29    const { asset } = this.props.route.params;
30    MediaLibrary.getAssetInfoAsync(asset, { shouldDownloadFromNetwork: false }).then(details => {
31      this.setState({ detailsWithoutDownloadingFromNetwork: details });
32    });
33    MediaLibrary.getAssetInfoAsync(asset).then(details => {
34      this.setState({ details });
35    });
36  }
37
38  goBack() {
39    const { navigation, route } = this.props;
40    const { onGoBack } = route.params;
41
42    if (onGoBack) {
43      onGoBack();
44    }
45    navigation.goBack();
46  }
47
48  deleteAsset = async () => {
49    const { asset } = this.props.route.params!;
50
51    await MediaLibrary.deleteAssetsAsync([asset]);
52    this.goBack();
53  };
54
55  addToAlbum = async () => {
56    const permissions = await MediaLibrary.getPermissionsAsync();
57    if (permissions?.accessPrivileges && permissions.accessPrivileges !== 'all') {
58      Alert.alert('Access to all photos is required to do this operation');
59      return;
60    }
61
62    const { asset } = this.props.route.params!;
63    const expoAlbum = await MediaLibrary.getAlbumAsync(EXPO_ALBUM_NAME);
64
65    if (expoAlbum) {
66      await MediaLibrary.addAssetsToAlbumAsync(asset, expoAlbum);
67    } else {
68      await MediaLibrary.createAlbumAsync(EXPO_ALBUM_NAME, asset);
69    }
70
71    alert('Successfully added asset to Expo album!');
72  };
73
74  removeFromAlbum = async () => {
75    const { asset, album } = this.props.route.params!;
76
77    if (album) {
78      await MediaLibrary.removeAssetsFromAlbumAsync(asset.id, album.id);
79      this.goBack();
80    }
81  };
82
83  renderAsset(asset: MediaLibrary.Asset) {
84    const aspectRatio = asset.height ? asset.width / asset.height : 1;
85
86    switch (asset.mediaType) {
87      case MediaLibrary.MediaType.photo:
88      case MediaLibrary.MediaType.video: // TODO: render Expo.Video component
89        return (
90          <Image
91            style={[styles.image, { aspectRatio }]}
92            source={{ uri: asset.uri }}
93            resizeMode="cover"
94          />
95        );
96      default:
97        return null;
98    }
99  }
100
101  render() {
102    const { details, detailsWithoutDownloadingFromNetwork } = this.state;
103    const { asset, album } = this.props.route.params!;
104
105    return (
106      <ScrollView style={styles.container}>
107        <View style={styles.buttons}>
108          <Button
109            style={styles.button}
110            buttonStyle={{ backgroundColor: 'red' }}
111            title="Delete asset"
112            onPress={this.deleteAsset}
113          />
114          {album && (
115            <Button
116              style={styles.button}
117              buttonStyle={{ backgroundColor: 'red' }}
118              title={`Remove from ${album.title} album`}
119              onPress={this.removeFromAlbum}
120            />
121          )}
122        </View>
123        {(!album || album.title !== EXPO_ALBUM_NAME) && (
124          <View style={styles.buttons}>
125            <Button style={styles.button} title="Add to Expo album" onPress={this.addToAlbum} />
126          </View>
127        )}
128
129        <View style={styles.imageContainer}>{this.renderAsset(asset)}</View>
130
131        <HeadingText>Base asset data</HeadingText>
132        <MonoText>{JSON.stringify(asset, null, 2)}</MonoText>
133
134        {details && (
135          <View style={styles.details}>
136            <HeadingText>MediaLibrary.getAssetInfoAsync(assetId)</HeadingText>
137            <MonoText>{JSON.stringify(details, null, 2)}</MonoText>
138          </View>
139        )}
140
141        {detailsWithoutDownloadingFromNetwork && (
142          <View style={styles.details}>
143            <HeadingText>{`MediaLibrary.getAssetInfoAsync(assetId, { shouldDownloadFromNetwork: false })`}</HeadingText>
144            <MonoText>{JSON.stringify(detailsWithoutDownloadingFromNetwork, null, 2)}</MonoText>
145          </View>
146        )}
147      </ScrollView>
148    );
149  }
150}
151
152const styles = StyleSheet.create({
153  container: {
154    paddingHorizontal: 10,
155    paddingVertical: 16,
156    flex: 1,
157  },
158  buttons: {
159    marginBottom: 10,
160    flexDirection: 'row',
161    justifyContent: 'center',
162    alignItems: 'center',
163  },
164  button: {
165    marginHorizontal: 5,
166  },
167  imageContainer: {
168    width: '100%',
169    paddingVertical: 10,
170    paddingHorizontal: 25,
171    justifyContent: 'center',
172  },
173  image: {
174    flex: 1,
175  },
176  details: {
177    marginBottom: 10,
178  },
179});
180