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