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 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 },
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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,
|
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) => {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
|
|
Loading…
Reference in New Issue