diff --git a/src/App.js b/src/App.js
index f5bd15c..4184d8c 100644
--- a/src/App.js
+++ b/src/App.js
@@ -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 },
diff --git a/src/components/pages/profile/settings.js b/src/components/pages/profile/settings.js
index 34ea97d..5b53b07 100644
--- a/src/components/pages/profile/settings.js
+++ b/src/components/pages/profile/settings.js
@@ -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(
diff --git a/src/components/pages/publish.js b/src/components/pages/publish.js
new file mode 100644
index 0000000..09a6f18
--- /dev/null
+++ b/src/components/pages/publish.js
@@ -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 (
+ setState({ ...state, visibility: props.visibility })
+ }>
+
+
+
+
+ { props.message }
+
+
+
+ );
+ };
+
+ return (
+ <>
+ { state.loaded
+ ?
+
+
+
+
+ setState({ ...state, caption })
+ }
+ style = { [ styles.form.input, { height: 100, } ] } />
+
+ Visibility
+
+
+
+
+
+
+ Publish
+
+
+
+
+ : <>>
+ }
+ >
+ );
+};
+
+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 };
diff --git a/src/components/posts/post.js b/src/components/posts/post.js
index 857e517..c463fdc 100644
--- a/src/components/posts/post.js
+++ b/src/components/posts/post.js
@@ -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) => {
diff --git a/src/interface/rendering.js b/src/interface/rendering.js
index d8f55b2..ceb5a3a 100644
--- a/src/interface/rendering.js
+++ b/src/interface/rendering.js
@@ -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
diff --git a/src/requests.js b/src/requests.js
index 258aea4..7061771 100644
--- a/src/requests.js
+++ b/src/requests.js
@@ -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();