Create interface for publishing new photos
This commit is contained in:
parent
80ec215be3
commit
cae19ce896
|
@ -13,6 +13,7 @@ import ViewCommentsJsx from "src/components/pages/view-comments.js";
|
|||
|
||||
import AuthenticateJsx from "src/components/pages/authenticate";
|
||||
import FeedJsx from "src/components/pages/feed";
|
||||
import PublishJsx from "src/components/pages/publish";
|
||||
import OlderPostsJsx from "src/components/pages/feed/older-posts";
|
||||
import ProfileJsx, { ViewProfileJsx } from "src/components/pages/profile";
|
||||
import DiscoverJsx from 'src/components/pages/discover';
|
||||
|
@ -32,6 +33,7 @@ const Stack = createStackNavigator({
|
|||
Feed: { screen: FeedJsx, },
|
||||
OlderPosts: { screen: OlderPostsJsx },
|
||||
Discover: { screen: DiscoverJsx },
|
||||
Publish: { screen: PublishJsx },
|
||||
Direct: { screen: DirectJsx },
|
||||
Compose: { screen: ComposeJsx },
|
||||
Conversation: { screen: ConversationJsx },
|
||||
|
|
|
@ -90,15 +90,18 @@ const SettingsJsx = (props) => {
|
|||
const _handleChangeProfilePhoto = async () => {
|
||||
await ImagePicker.getCameraRollPermissionsAsync()
|
||||
|
||||
const { base64, uri } = await ImagePicker.launchImageLibraryAsync({
|
||||
const { uri, type } = await ImagePicker.launchImageLibraryAsync({
|
||||
allowsEditing: true,
|
||||
aspect: [1, 1],
|
||||
});
|
||||
|
||||
const name = uri.split("/").slice(-1)[0];
|
||||
|
||||
setState({...state,
|
||||
newAvatar: {
|
||||
base64,
|
||||
uri,
|
||||
type,
|
||||
name,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
@ -109,11 +112,10 @@ const SettingsJsx = (props) => {
|
|||
note: state.note,
|
||||
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(
|
||||
|
|
|
@ -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 } }>
|
||||
|
||||
{ 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 };
|
|
@ -8,7 +8,11 @@ import {
|
|||
ScrollView,
|
||||
} 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 * 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 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) {
|
||||
return uris.map(attachment => new Promise(resolve => {
|
||||
Image.getSize(attachment.url, (width, height) => {
|
||||
|
|
|
@ -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) {
|
||||
/*
|
||||
Output a friendly string to describe the age of a post, where `time1` and
|
||||
|
|
|
@ -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
|
||||
let headers = {};
|
||||
|
||||
if (token) headers["Authorization"] = `Bearer ${token}`;
|
||||
if (contentType) headers["Content-Type"] = contentType;
|
||||
|
||||
const resp = await fetch(url, {
|
||||
method: "POST",
|
||||
body: data ? objectToForm(data): {},
|
||||
headers: token
|
||||
? { "Authorization": `Bearer ${token}`, }
|
||||
: {},
|
||||
body: data ? objectToForm(data) : undefined,
|
||||
headers,
|
||||
});
|
||||
|
||||
return resp;
|
||||
|
@ -123,6 +126,11 @@ export async function unblockAccount(domain, id, token) {
|
|||
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) {
|
||||
const resp = await postForm(`https://${domain}/api/v1/statuses`, params, token);
|
||||
return resp.json();
|
||||
|
|
Loading…
Reference in New Issue