Create interface for publishing new photos

This commit is contained in:
Nat 2021-05-30 09:50:50 -03:00
parent 80ec215be3
commit cae19ce896
6 changed files with 285 additions and 31 deletions

View File

@ -13,6 +13,7 @@ import ViewCommentsJsx from "src/components/pages/view-comments.js";
import AuthenticateJsx from "src/components/pages/authenticate"; import AuthenticateJsx from "src/components/pages/authenticate";
import FeedJsx from "src/components/pages/feed"; import FeedJsx from "src/components/pages/feed";
import PublishJsx from "src/components/pages/publish";
import OlderPostsJsx from "src/components/pages/feed/older-posts"; import OlderPostsJsx from "src/components/pages/feed/older-posts";
import ProfileJsx, { ViewProfileJsx } from "src/components/pages/profile"; import ProfileJsx, { ViewProfileJsx } from "src/components/pages/profile";
import DiscoverJsx from 'src/components/pages/discover'; import DiscoverJsx from 'src/components/pages/discover';
@ -32,6 +33,7 @@ const Stack = createStackNavigator({
Feed: { screen: FeedJsx, }, Feed: { screen: FeedJsx, },
OlderPosts: { screen: OlderPostsJsx }, OlderPosts: { screen: OlderPostsJsx },
Discover: { screen: DiscoverJsx }, Discover: { screen: DiscoverJsx },
Publish: { screen: PublishJsx },
Direct: { screen: DirectJsx }, Direct: { screen: DirectJsx },
Compose: { screen: ComposeJsx }, Compose: { screen: ComposeJsx },
Conversation: { screen: ConversationJsx }, Conversation: { screen: ConversationJsx },

View File

@ -90,15 +90,18 @@ const SettingsJsx = (props) => {
const _handleChangeProfilePhoto = async () => { const _handleChangeProfilePhoto = async () => {
await ImagePicker.getCameraRollPermissionsAsync() await ImagePicker.getCameraRollPermissionsAsync()
const { base64, uri } = await ImagePicker.launchImageLibraryAsync({ const { uri, type } = await ImagePicker.launchImageLibraryAsync({
allowsEditing: true, allowsEditing: true,
aspect: [1, 1], aspect: [1, 1],
}); });
const name = uri.split("/").slice(-1)[0];
setState({...state, setState({...state,
newAvatar: { newAvatar: {
base64,
uri, uri,
type,
name,
}, },
}); });
}; };
@ -109,11 +112,10 @@ const SettingsJsx = (props) => {
note: state.note, note: state.note,
locked: state.locked, locked: state.locked,
}; };
if (state.newAvatar.base64) {
let blob = fetch(state.newAvatar.base64).then(res => res.blob());
let filename = uri.split("/")[uri.split("/").length - 1];
params.avatar = new File([blob], filename); // In other words, if a picture has been selected...
if (state.newAvatar.name) {
params.avatar = state.newAvatar;
} }
const newProfile = await fetch( const newProfile = await fetch(

View File

@ -0,0 +1,238 @@
import React, { useState, useEffect } from "react";
import {
Dimensions,
View,
Image,
Text,
TextInput,
} from "react-native";
import { Ionicons } from '@expo/vector-icons';
import { getAutoHeight } from "src/interface/rendering";
import { ScreenWithTrayJsx } from "src/components/navigation/navigators";
import { TouchableOpacity } from "react-native-gesture-handler";
import * as ImagePicker from 'expo-image-picker';
import * as Permissions from "expo-permissions";
import AsyncStorage from "@react-native-async-storage/async-storage";
import * as requests from "src/requests";
const PublishJsx = ({ navigation }) => {
const [ state, setState ] = useState({
loaded: false,
});
useEffect(() => {
let instance, accessToken;
AsyncStorage
.multiGet([
"@user_instance",
"@user_token",
])
.then(([ instancePair, tokenPair ]) => {
instance = instancePair[1];
accessToken = JSON.parse(tokenPair[1]).access_token;
return Permissions.askAsync(Permissions.CAMERA_ROLL);
})
.then(({ granted }) => {
if (granted) {
return ImagePicker.launchImageLibraryAsync({
allowsEditing: true,
});
} else {
navigation.goBack();
}
})
.then((imageData) => {
if (!imageData.cancelled) {
return imageData;
} else {
navigation.goBack();
}
})
.then(({ uri, type, width, height }) => {
const name = uri.split("/").slice(-1)[0];
const newWidth = SCREEN_WIDTH * (3/4);
setState({...state,
loaded: true,
instance,
accessToken,
visibility: "public",
image: {
data: {
uri,
type,
name,
},
width: newWidth,
height: getAutoHeight(width, height, newWidth),
},
});
});
}, []);
const _handlePublish = async () => {
const mediaAttachment = await requests.publishMediaAttachment(
state.instance,
state.accessToken,
{ file: state.image.data }
);
console.warn(mediaAttachment);
if(mediaAttachment.type == "unknown") return;
const params = {
status: state.caption,
media_ids: [mediaAttachment.id],
visibility: state.visibility,
};
const newStatus = await requests.publishStatus(
state.instance,
state.accessToken,
params
);
console.warn(newStatus);
navigation.navigate("Feed");
};
const Selector = (props) => {
const color = props.active == props.visibility ? "black" : "#888";
return (
<TouchableOpacity
style = { styles.form.option.button }
onPress = {
() => setState({ ...state, visibility: props.visibility })
}>
<View style = { styles.form.option.inner }>
<Ionicons
name = { props.icon }
color = { color }
size={24} />
<Text style = { { color } }>
&nbsp;
{ props.message }
</Text>
</View>
</TouchableOpacity>
);
};
return (
<>
{ state.loaded
? <ScreenWithTrayJsx
active = "Publish"
navigation = { navigation } >
<View style = { styles.preview.container }>
<Image
style = {[
styles.preview.image,
{
width: state.image.width,
height: state.image.height,
},
]}
source = { { uri: state.image.data.uri } } />
</View>
<View style = { styles.form.container }>
<TextInput
placeholder = "Caption this post"
value = { state.caption }
multiline
onChangeText = {
caption => setState({ ...state, caption })
}
style = { [ styles.form.input, { height: 100, } ] } />
<Text style = { styles.form.label }>Visibility</Text>
<Selector
visibility = "public"
active = { state.visibility }
icon = "md-planet"
message = "Anyone can see this post" />
<Selector
visibility = "unlisted"
active = { state.visibility }
icon = "md-unlock"
message = "Keep this post off public timelines" />
<Selector
visibility = "private"
active = { state.visibility }
icon = "md-lock"
message = "Only share this with my followers" />
<TouchableOpacity
onPress = { _handlePublish }
style = { styles.form.button.container }>
<Text style = { styles.form.button.label }>
Publish
</Text>
</TouchableOpacity>
</View>
</ScreenWithTrayJsx>
: <></>
}
</>
);
};
const SCREEN_WIDTH = Dimensions.get("window").width;
const styles = {
preview: {
container: {
paddingTop: 10,
},
image: {
marginLeft: "auto",
marginRight: "auto",
},
},
form: {
container: {
padding: 10,
},
input: {
borderBottomWidth: 1,
borderBottomColor: "#888",
textAlignVertical: "top",
padding: 10,
},
label: {
marginTop: 20,
fontSize: 15,
color: "#666",
},
option: {
button: {},
inner: {
marginTop: 10,
flexDirection: "row",
alignItems: "center",
},
},
button: {
container: {
width: SCREEN_WIDTH * (3/4),
marginTop: 30,
marginLeft: "auto",
marginRight: "auto",
padding: 20,
borderWidth: 1,
borderColor: "#666",
borderRadius: 10,
},
label: {
textAlign: "center",
},
},
},
};
export { PublishJsx as default };

View File

@ -8,7 +8,11 @@ import {
ScrollView, ScrollView,
} from "react-native"; } from "react-native";
import { pluralize, timeToAge } from "src/interface/rendering" import {
pluralize,
timeToAge,
getAutoHeight,
} from "src/interface/rendering";
import AsyncStorage from "@react-native-async-storage/async-storage"; import AsyncStorage from "@react-native-async-storage/async-storage";
import * as requests from "src/requests"; import * as requests from "src/requests";
@ -21,25 +25,6 @@ import ContextMenuJsx from "src/components/context-menu.js";
const SCREEN_WIDTH = Dimensions.get("window").width; const SCREEN_WIDTH = Dimensions.get("window").width;
const TEST_IMAGE = "https://cache.desktopnexus.com/thumbseg/2255/2255124-bigthumbnail.jpg"; const TEST_IMAGE = "https://cache.desktopnexus.com/thumbseg/2255/2255124-bigthumbnail.jpg";
function getAutoHeight(w1, h1, w2) {
/*
Given the original dimensions and the new width, calculate what would
otherwise be the "auto" height of the image.
Just so that nobody has to ever work out this algebra again:
Let {w1, h1} = the width and height of the static image,
w2 = the new width,
h2 = the "auto" height of the scaled image of width w2:
w1/h1 = w2/h2
h2 * w1/h1 = w2
h2 = w2 / w1/h1
h2 = w2 * h1/w1
*/
return w2 * (h1 / w1)
}
function getDimensionsPromises(uris) { function getDimensionsPromises(uris) {
return uris.map(attachment => new Promise(resolve => { return uris.map(attachment => new Promise(resolve => {
Image.getSize(attachment.url, (width, height) => { Image.getSize(attachment.url, (width, height) => {

View File

@ -22,6 +22,25 @@ export function pluralize(n, singular, plural) {
} }
} }
export function getAutoHeight(w1, h1, w2) {
/*
Given the original dimensions and the new width, calculate what would
otherwise be the "auto" height of the image.
Just so that nobody has to ever work out this algebra again:
Let {w1, h1} = the width and height of the static image,
w2 = the new width,
h2 = the "auto" height of the scaled image of width w2:
w1/h1 = w2/h2
h2 * w1/h1 = w2
h2 = w2 / w1/h1
h2 = w2 * h1/w1
*/
return w2 * (h1 / w1)
}
export function timeToAge(time1, time2) { export function timeToAge(time1, time2) {
/* /*
Output a friendly string to describe the age of a post, where `time1` and Output a friendly string to describe the age of a post, where `time1` and

View File

@ -39,14 +39,17 @@ export async function checkUnreadNotifications() {
} }
} }
export async function postForm(url, data = false, token = false) { export async function postForm(url, data = false, token = false, contentType = false) {
// Send a POST request with data formatted with FormData returning JSON // Send a POST request with data formatted with FormData returning JSON
let headers = {};
if (token) headers["Authorization"] = `Bearer ${token}`;
if (contentType) headers["Content-Type"] = contentType;
const resp = await fetch(url, { const resp = await fetch(url, {
method: "POST", method: "POST",
body: data ? objectToForm(data): {}, body: data ? objectToForm(data) : undefined,
headers: token headers,
? { "Authorization": `Bearer ${token}`, }
: {},
}); });
return resp; return resp;
@ -123,6 +126,11 @@ export async function unblockAccount(domain, id, token) {
return resp.json(); return resp.json();
} }
export async function publishMediaAttachment(domain, token, params) {
const resp = await postForm(`https://${domain}/api/v1/media`, params, token, "multipart/form-data");
return resp.json();
}
export async function publishStatus(domain, token, params) { export async function publishStatus(domain, token, params) {
const resp = await postForm(`https://${domain}/api/v1/statuses`, params, token); const resp = await postForm(`https://${domain}/api/v1/statuses`, params, token);
return resp.json(); return resp.json();