diff --git a/.gitignore b/.gitignore
index 0ba4ecc..0a98f3d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,6 @@ web-build/
# macOS
.DS_Store
+
+# Vim
+*.sw[klmnop]
diff --git a/assets/eva-icons/ellipsis.png b/assets/eva-icons/ellipsis.png
new file mode 100644
index 0000000..7bce4cf
Binary files /dev/null and b/assets/eva-icons/ellipsis.png differ
diff --git a/assets/eva-icons/paper-plane.png b/assets/eva-icons/paper-plane.png
new file mode 100644
index 0000000..1432e16
Binary files /dev/null and b/assets/eva-icons/paper-plane.png differ
diff --git a/assets/eva-icons/post-actions/bookmark-active.png b/assets/eva-icons/post-actions/bookmark-active.png
new file mode 100644
index 0000000..123d321
Binary files /dev/null and b/assets/eva-icons/post-actions/bookmark-active.png differ
diff --git a/assets/eva-icons/post-actions/bookmark-inactive.png b/assets/eva-icons/post-actions/bookmark-inactive.png
new file mode 100644
index 0000000..142f644
Binary files /dev/null and b/assets/eva-icons/post-actions/bookmark-inactive.png differ
diff --git a/assets/eva-icons/post-actions/comment-active.png b/assets/eva-icons/post-actions/comment-active.png
deleted file mode 100644
index c9a5a10..0000000
Binary files a/assets/eva-icons/post-actions/comment-active.png and /dev/null differ
diff --git a/assets/eva-icons/post-actions/comment-full.png b/assets/eva-icons/post-actions/comment-full.png
deleted file mode 100644
index 00951c8..0000000
Binary files a/assets/eva-icons/post-actions/comment-full.png and /dev/null differ
diff --git a/assets/eva-icons/post-actions/comment-inactive.png b/assets/eva-icons/post-actions/comment-inactive.png
deleted file mode 100644
index 6a44b47..0000000
Binary files a/assets/eva-icons/post-actions/comment-inactive.png and /dev/null differ
diff --git a/assets/eva-icons/post-actions/comment.png b/assets/eva-icons/post-actions/comment.png
deleted file mode 100644
index 87eb205..0000000
Binary files a/assets/eva-icons/post-actions/comment.png and /dev/null differ
diff --git a/package-lock.json b/package-lock.json
index df5c627..3739d97 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6129,6 +6129,11 @@
"resolved": "https://registry.npmjs.org/react-native-iphone-x-helper/-/react-native-iphone-x-helper-1.2.1.tgz",
"integrity": "sha512-/VbpIEp8tSNNHIvstuA3Swx610whci1Zpc9mqNkqn14DkMbw+ORviln2u0XyHG1kPvvwTNGZY6QpeFwxYaSdbQ=="
},
+ "react-native-popup-menu": {
+ "version": "0.15.10",
+ "resolved": "https://registry.npmjs.org/react-native-popup-menu/-/react-native-popup-menu-0.15.10.tgz",
+ "integrity": "sha512-w7MaicsfpclK7g/omjMchNaXwhMi0apt/DC734AbHuJTWCfv5mF3JgL1UzRW19ncFMBRfQeYapPy/zUyJCGgEQ=="
+ },
"react-native-reanimated": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-1.9.0.tgz",
diff --git a/package.json b/package.json
index b48ad8a..bb34cfb 100644
--- a/package.json
+++ b/package.json
@@ -9,21 +9,22 @@
},
"dependencies": {
"@react-native-community/masked-view": "0.1.10",
+ "@react-navigation/core": "5.2.3",
"@react-navigation/native": "5.1.1",
+ "@react-navigation/stack": "5.2.3",
"expo": "^38.0.9",
"expo-status-bar": "^1.0.2",
"react": "~16.11.0",
"react-dom": "~16.11.0",
"react-native": "https://github.com/expo/react-native/archive/sdk-38.0.2.tar.gz",
"react-native-gesture-handler": "~1.6.0",
+ "react-native-popup-menu": "^0.15.10",
"react-native-reanimated": "~1.9.0",
"react-native-safe-area-context": "~3.0.7",
"react-native-screens": "~2.9.0",
"react-native-web": "~0.11.7",
"react-navigation": "^4.4.0",
- "react-navigation-stack": "^2.8.2",
- "@react-navigation/stack": "5.2.3",
- "@react-navigation/core": "5.2.3"
+ "react-navigation-stack": "^2.8.2"
},
"devDependencies": {
"@babel/core": "^7.8.6",
diff --git a/src/App.js b/src/App.js
index 5a25377..9b7d6db 100644
--- a/src/App.js
+++ b/src/App.js
@@ -6,9 +6,11 @@ import { createStackNavigator } from "react-navigation-stack";
import { registerRootComponent } from 'expo';
+import ViewPostJsx from "src/components/pages/view-post";
+import ViewCommentsJsx from "src/components/pages/view-comments.js";
+
import FeedJsx from "src/components/pages/feed";
import ProfileJsx, { ViewProfileJsx } from "src/components/pages/profile";
-import ViewPostJsx from "src/components/pages/view-post";
import DiscoverJsx from 'src/components/pages/discover';
import SearchJsx from 'src/components/pages/discover/search';
import ViewHashtagJsx from 'src/components/pages/discover/view-hashtag';
@@ -21,9 +23,10 @@ const Stack = createStackNavigator({
Profile: { screen: ProfileJsx, },
Search: { screen: SearchJsx },
ViewPost: { screen: ViewPostJsx },
+ ViewComments: { screen: ViewCommentsJsx },
ViewProfile: { screen: ViewProfileJsx },
ViewHashtag: { screen: ViewHashtagJsx }
-}, {
+}, {
initialRouteKey: "Feed",
headerMode: "none",
navigationOptions: {
diff --git a/src/components/navigation/navigators.js b/src/components/navigation/navigators.js
index b7ba237..d3776c1 100644
--- a/src/components/navigation/navigators.js
+++ b/src/components/navigation/navigators.js
@@ -1,43 +1,69 @@
import React from "react";
import { View } from "react-native";
import { ScrollView } from "react-native-gesture-handler";
+
+import { MenuProvider } from "react-native-popup-menu";
+
import BackBarJsx from "./back-bar";
import TrayJsx from "src/components/navigation/tray";
+// Provider for context menus
+// Allows for establishing global styling of context menus
+export const ContextJsx = (props) => {
+ return (
+
+ { props.children }
+
+ );
+};
+
export const ScreenWithTrayJsx = (props) => {
return (
-
-
- { props.children }
+
+
+
+ { props.children }
-
- )
+ active = { props.active }
+ navigation = { props.navigation } />
+
+
+ );
};
export const ScreenWithBackBarJsx = (props) => {
return (
-
-
-
- { props.children }
-
-
+
+
+
+
+ { props.children }
+
+
+
);
};
export const ScreenWithFullNavigationJsx = (props) => {
return (
-
-
-
- { props.children }
-
-
-
+
+
+
+
+ { props.children }
+
+
+
+
);
-}
\ No newline at end of file
+};
+
+const providerStyles = {
+ backdrop: {
+ backgroundColor: "black",
+ opacity: 0.5
+ }
+}
diff --git a/src/components/pages/feed.js b/src/components/pages/feed.js
index c742724..e507703 100644
--- a/src/components/pages/feed.js
+++ b/src/components/pages/feed.js
@@ -12,12 +12,13 @@ const TEST_POSTS = [
id: 1,
avatar: TEST_IMAGE,
username: "njms",
+ replies_count: 3,
favourited: false,
reblogged: false,
content: "Also learning Claire de Lune feels a lot like reading the communist manifesto",
timestamp: 1596745156000,
media_attachments: [
- {preview_url: TEST_IMAGE}
+ {url: TEST_IMAGE}
]
},
{
@@ -26,10 +27,13 @@ const TEST_POSTS = [
username: "njms",
favourited: false,
reblogged: false,
+ replies_count: 0,
content: "Also learning Claire de Lune feels a lot like reading the communist manifesto",
timestamp: 1596745156000,
media_attachments: [
- {preview_url: TEST_IMAGE}
+ { url: "https://college.mayo.edu/media/mccms/content-assets/campus-amp-community/arizona/mayo-clinic-phoenix-arizona-is453080663-hero-mobile.jpg" },
+ { url: TEST_IMAGE },
+ { url: TEST_IMAGE }
]
}
];
@@ -41,13 +45,17 @@ const FeedJsx = (props) => {
-
+
+
+
-
+
You're all caught up.
@@ -60,7 +68,7 @@ const FeedJsx = (props) => {
-
+
);
};
@@ -97,4 +105,4 @@ const styles = {
}
};
-export default FeedJsx;
\ No newline at end of file
+export default FeedJsx;
diff --git a/src/components/pages/view-comments.js b/src/components/pages/view-comments.js
new file mode 100644
index 0000000..a385d5f
--- /dev/null
+++ b/src/components/pages/view-comments.js
@@ -0,0 +1,361 @@
+import React, { useEffect, useState } from "react";
+import { Dimensions, View, Image, TextInput, Text } from "react-native";
+import { ScrollView } from "react-native-gesture-handler";
+
+import { timeToAge } from "src/interface/rendering";
+import { activeOrNot } from "src/interface/interactions";
+
+import TimelineViewJsx from "src/components/posts/timeline-view";
+import { ContextJsx } from "src/components/navigation/navigators";
+import BackBarJsx from "src/components/navigation/back-bar";
+import { TouchableWithoutFeedback } from "react-native-gesture-handler";
+
+const TEST_IMAGE = "https://cache.desktopnexus.com/thumbseg/2255/2255124-bigthumbnail.jpg";
+
+const TEST_CONTEXT = {
+ ancestors: [],
+ descendants: [
+ {
+ id: "1",
+ in_reply_to_id: "0",
+ username: "respondant1",
+ avatar: TEST_IMAGE,
+ content: "This is a comment",
+ favourited: false,
+ created_at: 1596745156000
+ },
+ {
+ id: "2",
+ in_reply_to_id: "0",
+ username: "respondant2",
+ avatar: TEST_IMAGE,
+ content: "This is a comment",
+ favourited: true,
+ created_at: 1596745156000
+ },
+ {
+ id: "3",
+ in_reply_to_id: "2",
+ username: "respondant3",
+ avatar: TEST_IMAGE,
+ content: "This is a comment",
+ favourited: false,
+ created_at: 1596745156000
+ },
+ {
+ id: "4",
+ in_reply_to_id: "2",
+ username: "respondant2",
+ avatar: TEST_IMAGE,
+ content: "This is a comment",
+ favourited: false,
+ created_at: 1596745156000
+ },
+ {
+ id: "5",
+ in_reply_to_id: "1",
+ username: "respondant4",
+ avatar: TEST_IMAGE,
+ content: "This is a comment",
+ favourited: false,
+ created_at: 1596745156000
+ },
+ {
+ id: "6",
+ in_reply_to_id: "4",
+ username: "respondant5",
+ avatar: TEST_IMAGE,
+ content: "This is a comment",
+ favourited: false,
+ created_at: 1596745156000
+ },
+ ]
+}
+
+function chunkWhile(arr, fun) {
+ /*
+ * Chunk a list into partitions while fun returns something truthy
+ * > chunkWhile([1,1,1,2,2], (a, b) => a == b)
+ * [[1,1,1], [2,2]]
+ */
+
+ let parts;
+
+ if (arr == []) {
+ return []
+ } else {
+ parts = [[arr[0]]];
+ }
+
+ let tail = arr.slice(1);
+
+ if (tail == []) {
+ return parts;
+ }
+
+ for (let i = 0; i < tail.length; i++) {
+ let lastPart = parts[parts.length - 1];
+ if (fun(tail[i], lastPart[lastPart.length - 1])) {
+ // If fun returns something truthy, push tail[i] to the end of the
+ // partition at the end of the new array.
+ parts[parts.length - 1].push(tail[i])
+ } else {
+ // Create a new partition starting with tail[i]
+ parts.push([tail[i]])
+ }
+ }
+
+ return parts;
+}
+
+function threadify(descendants, parentID) {
+ /*
+ * Take a list of descendants and sort them into a 2D matrix.
+ * The first item is the direct descendant of parentID post and the rest
+ * are all the descendants of the direct descendant in order of id, the
+ * way Instagram displays conversations in comments.
+ * i.e. [[first level comment, ...descendants]]
+ */
+
+ if (descendants == []) {
+ return [];
+ }
+
+ // Sort comments in order of increasing reply id
+ const comments = descendants.sort((first, second) => {
+ return first.in_reply_to_id - second.in_reply_to_id;
+ });
+
+ // Return partitions of comments based on their reply id
+ const byReply = chunkWhile(comments, (a, b) => {
+ return a.in_reply_to_id == b.in_reply_to_id;
+ });
+
+ // Start with just the first level comments.
+ // All these elements should be in singleton arrays so they can be
+ // appended to.
+ let sorted = byReply[0].map(x => [x]);
+
+ let sub = byReply.slice(1); // All sub-comments
+
+ // Repeate the procedure until sub is empty (i.e all comments have been
+ // sorted)
+ while (sub.length > 0) {
+ sorted.forEach((thread, threadIndex) => {
+ for (let i = 0; i < thread.length; i++) {
+ const id = thread[i].id;
+
+ // Search for comment groups with that id
+ for(let subIndex = 0; subIndex < sub.length; subIndex++) {
+ // All items in each partition should have the same reply id
+ if(id == sub[subIndex][0].in_reply_to_id) {
+ // Move the newly found thread contents to thread in
+ // sorted
+ sorted[threadIndex] = sorted[threadIndex].concat(sub[subIndex]);
+ sub.splice(subIndex, 1);
+ }
+ }
+ }
+ });
+}
+
+return sorted;
+}
+
+const CommentJsx = (props) => {
+ const packs = {
+ favourited: {
+ active: require("assets/eva-icons/post-actions/heart-active.png"),
+ inactive: require("assets/eva-icons/post-actions/heart-inactive.png")
+ }
+ };
+
+ return (
+
+
+
+
+ { props.data.username }
+ { props.data.content }
+
+
+
+
+ { timeToAge((new Date()).getTime(), props.data.created_at) }
+
+
+
+
+
+ Reply
+
+
+
+
+
+
+
+
+
+ );
+}
+
+const ViewCommentsJsx = (props) => {
+ let [state, setState] = useState({
+ postData: undefined,
+ loaded: false,
+ reply: ""
+ });
+
+ useEffect(() => {
+ (() => { // Some magical function that will get all the data needed
+ setState({ ...state,
+ descendants: threadify(TEST_CONTEXT.descendants),
+ postData: props.navigation.getParam("postData"),
+ loaded: true,
+ });
+ })();
+ }, []);
+
+ return (
+
+
+
+
+ { state.loaded ?
+
+
+
+
+
+ {
+ state.descendants.map((thread, i) => {
+ const comment = thread[0];
+ const subs = thread.slice(1);
+
+ return (
+
+
+ {
+ subs.map((sub, j) => {
+ return (
+
+
+
+ )
+ })
+ }
+
+ );
+ })
+ }
+
+
+ :
+ }
+
+
+
+ setState({...state, reply: c }) }/>
+
+
+
+
+
+
+
+
+ );
+}
+
+const SCREEN_WIDTH = Dimensions.get("window").width;
+
+const styles = {
+ bold: {
+ fontWeight: "bold",
+ },
+ container: {
+ display: "flex",
+ flexDirection: "row",
+ flexShrink: 1,
+ marginTop: 10,
+ marginBottom: 10,
+ marginRight: 20,
+ },
+ avatar: {
+ marginLeft: 20,
+ marginRight: 20,
+ width: 50,
+ height: 50,
+ borderRadius: "100%"
+ },
+ contentContainer: {
+ flexShrink: 1
+ },
+ parentPost: {
+ borderBottomWidth: 1,
+ borderBottomColor: "#CCC",
+ marginBottom: 10
+ },
+ sub: {
+ marginLeft: SCREEN_WIDTH / 8
+ },
+ commentActions: {
+ display: "flex",
+ flexDirection: "row",
+ alignItems: "center",
+ },
+ actionText: {
+ fontSize: 13,
+ color: "#666",
+ paddingRight: 10
+ },
+ heart: {
+ width: 15,
+ height: 15
+ },
+
+ commentForm: {
+ flexDirection: "row",
+ alignItems: "center",
+ backgroundColor: "white",
+
+ borderTopWidth: 1,
+ borderTopColor: "#CCC",
+
+ paddingTop: 10,
+ paddingBottom: 10
+ },
+ commentInput: {
+ borderWidth: 0,
+ padding: 10,
+ flexGrow: 3,
+ marginRight: 20
+ },
+ submitContainer: {
+ marginLeft: "auto",
+ marginRight: 20
+ },
+ commentSubmit: {
+ width: 30,
+ height: 30,
+ }
+};
+
+export default ViewCommentsJsx;
diff --git a/src/components/pages/view-post.js b/src/components/pages/view-post.js
index 106fcd7..5cc87ce 100644
--- a/src/components/pages/view-post.js
+++ b/src/components/pages/view-post.js
@@ -8,9 +8,11 @@ const ViewPostJsx = (props) => {
-
+
);
}
-export default ViewPostJsx;
\ No newline at end of file
+export default ViewPostJsx;
diff --git a/src/components/posts/paged-grid.js b/src/components/posts/paged-grid.js
index 25a0542..50e964c 100644
--- a/src/components/posts/paged-grid.js
+++ b/src/components/posts/paged-grid.js
@@ -89,4 +89,4 @@ const styles = {
}
}
-export default PagedGridJSX;
\ No newline at end of file
+export default PagedGridJSX;
diff --git a/src/components/posts/post-action-bar.js b/src/components/posts/post-action-bar.js
index 543c7c0..458cc9d 100644
--- a/src/components/posts/post-action-bar.js
+++ b/src/components/posts/post-action-bar.js
@@ -27,10 +27,8 @@ function reblogCallback(state, updater) {
invertField("reblogged", state, updater);
}
-function downloadCallback(state, updater) {
- let newState = state;
- newState.downloaded = true;
- updater(newState);
+function bookmarkCallback(state, updater) {
+ invertField("bookmarked", state, updater);
}
const PostActionJsx = (props) => {
@@ -41,7 +39,12 @@ const PostActionJsx = (props) => {
source = {
activeOrNot(props.state[props.field], props.pack)
}
- style = { styles.icon } />
+ style = {
+ [
+ styles.icon,
+ props.last ? styles.lastIcon : {}
+ ]
+ } />
)
}
@@ -51,7 +54,7 @@ const PostActionBarJsx = (props) => {
favourited: props.favourited,
commenting: false,
reblogged: props.reblogged,
- downloaded: false
+ bookmarked: false
});
const icons = {
@@ -59,17 +62,13 @@ const PostActionBarJsx = (props) => {
active: require("assets/eva-icons/post-actions/heart-active.png"),
inactive: require("assets/eva-icons/post-actions/heart-inactive.png")
},
- comment: {
- active: require("assets/eva-icons/post-actions/comment-active.png"),
- inactive: require("assets/eva-icons/post-actions/comment-inactive.png")
- },
reblog: {
active: require("assets/eva-icons/post-actions/reblog-active.png"),
inactive: require("assets/eva-icons/post-actions/reblog-inactive.png")
},
- download: {
- active: require("assets/eva-icons/post-actions/download-active.png"),
- inactive: require("assets/eva-icons/post-actions/download-inactive.png")
+ bookmark: {
+ active: require("assets/eva-icons/post-actions/bookmark-active.png"),
+ inactive: require("assets/eva-icons/post-actions/bookmark-inactive.png")
}
}
return (
@@ -79,13 +78,7 @@ const PostActionBarJsx = (props) => {
pack = { icons.heart }
state = { state }
callback = { () => favouritedCallback(state, setState) } />
-
- commentCallback(state, setState) } />
-
+
{
callback = { () => reblogCallback(state, setState) } />
downloadCallback(state, setState) } />
+ callback = { () => bookmarkCallback(state, setState) } />
)
}
+const SCREEN_WIDTH = Dimensions.get("window").width;
const styles = {
flexContainer: {
display: "flex",
flexDirection: "row",
- padding: Dimensions.get("window").width / 40
+ padding: SCREEN_WIDTH / 40
},
icon: {
width: 30,
height: 30,
- marginRight: Dimensions.get("window").width / 20
+ marginRight: SCREEN_WIDTH / 20
+ },
+ lastIcon: {
+ marginLeft: "auto"
}
}
-export default PostActionBarJsx;
\ No newline at end of file
+export default PostActionBarJsx;
diff --git a/src/components/posts/post.js b/src/components/posts/post.js
index cd6dde5..b8fc264 100644
--- a/src/components/posts/post.js
+++ b/src/components/posts/post.js
@@ -1,11 +1,33 @@
import React, { useEffect, useState } from "react";
-import { Image, View, Text, Dimensions } from "react-native";
+import {
+ Image,
+ View,
+ Text,
+ Dimensions,
+ TouchableWithoutFeedback,
+ ScrollView
+} from "react-native";
+
+import {
+ Menu,
+ MenuOptions,
+ MenuOption,
+ MenuTrigger,
+ renderers
+} from "react-native-popup-menu";
+
+
+import { pluralize, timeToAge } from "src/interface/rendering"
import PostActionBarJsx from "src/components/posts/post-action-bar";
const SCREEN_WIDTH = Dimensions.get("window").width;
const TEST_IMAGE = "https://cache.desktopnexus.com/thumbseg/2255/2255124-bigthumbnail.jpg";
+// Extract the SlideInMenu function from `renderers`
+// This will be used in RawPostJsx
+const { SlideInMenu } = renderers;
+
function getAutoHeight(w1, h1, w2) {
/*
Given the original dimensions and the new width, calculate what would
@@ -25,62 +47,107 @@ function getAutoHeight(w1, h1, w2) {
return w2 * (h1 / w1)
}
-function timeToAge(time1, time2) {
- /*
- Output a friendly string to describe the age of a post, where `time1` and
- `time2` are in milliseconds
- */
+function getDimensionsPromises(uris) {
+ return uris.map(attachment => new Promise(resolve => {
+ Image.getSize(attachment.url, (width, height) => {
+ const autoHeight = getAutoHeight(width, height, SCREEN_WIDTH)
- const between = (n, lower, upper) => n >= lower && n < upper;
- const pluralize = (n, singular, plural) => n < 2 ? singular : plural;
-
- const diff = time1 - time2;
-
- if (diff < 60000) {
- return "Seconds ago"
- } else if (between(diff, 60000, 3600000)) {
- const nMin = Math.floor(diff / 60000);
- return nMin + " " + pluralize(nMin, "minute", "minutes") + " ago";
- } else if (between(diff, 3600000, 86400000)) {
- const nHours = Math.floor(diff / 3600000);
- return nHours + " " + pluralize(nHours, "hour", "hours") + " ago";
- } else if (between(diff, 86400000, 2629800000)) {
- const nDays = Math.floor(diff / 86400000);
- return nDays + " " + pluralize(nDays, "day", "days") + " ago";
- } else if (between(diff, 2629800000, 31557600000)) {
- const nMonths = Math.floor(diff / 2629800000);
- return nMonths + " " + pluralize(nMonths, "month", "months") + " ago";
- } else {
- const nYears = Math.floor(diff / 31557600000);
- return nYears + " " + pluralize(nYears, "year", "years") + " ago";
- }
+ resolve([SCREEN_WIDTH, autoHeight]);
+ });
+ }));
}
+const PostImageJsx = (props) => {
+ return
+};
+
export const RawPostJsx = (props) => {
+ const repliesCount = props.data.replies_count;
+
+ let commentsText;
+ if (repliesCount == 0) {
+ commentsText = "View comments";
+ } else {
+ commentsText = "View "
+ + repliesCount
+ + pluralize(repliesCount, " comment", " comments");
+ }
+
return (
-
- { props.data.username }
+
+
+
- { /* TODO: support for more than one image per post */ }
-
- 1 ?
+
+ {
+ props.data.media_attachments
+ .map((attachment, i) => {
+ return ();
+ })
+ }
+
+ :
+ }
+
+ reblogged = { props.data.reblogged } />
{ props.data.username } { props.data.content }
+ props.navigation.navigate("ViewComments", {
+ originTab: props.navigation.getParam("originTab"),
+ postData: props.data
+ })
+ }>
+
+ { commentsText }
+
+
+
{ timeToAge((new Date()).getTime(), props.data.timestamp) }
@@ -96,19 +163,16 @@ export const PostByDataJsx = (props) => {
*/
let [state, setState] = useState({
- width: 0,
- height: 0,
- loaded: false
+ loaded: false,
+ dimensions: []
});
useEffect(() => {
- Image.getSize(TEST_IMAGE, (width, height) => {
- const newHeight = getAutoHeight(width, height, SCREEN_WIDTH)
-
+ Promise.all(getDimensionsPromises(props.data.media_attachments))
+ .then(dimensions => {
setState({
- width: SCREEN_WIDTH,
- height: newHeight,
- loaded: true
+ dimensions: dimensions,
+ loaded: true
});
});
});
@@ -118,8 +182,8 @@ export const PostByDataJsx = (props) => {
{ state.loaded ?
+ dimensions = { state.dimensions }
+ navigation = { props.navigation }/>
: }
);
@@ -138,25 +202,25 @@ export const PostByIdJsx = (props) => {
reblogged: false,
content: "",
timestamp: 0,
+ loaded: false,
+ dimensions: []
});
useEffect(() => {
// TODO: Make API request using props.id, set it as the state
- ((/* This would be the data retrieved */) => {
- Image.getSize(TEST_IMAGE, (width, height) => {
- const newHeight = getAutoHeight(width, height, SCREEN_WIDTH)
-
+ (() => {
+ Promise.all(getDimensionsPromises([{ url: TEST_IMAGE }]))
+ .then(dimensions => {
setState({
avatar: TEST_IMAGE,
username: "njms",
- media_attachments: [TEST_IMAGE],
+ media_attachments: [{ url: TEST_IMAGE }],
favourited: false,
reblogged: false,
content: "Also learning Claire de Lune feels a lot like reading the communist manifesto",
timestamp: 1596745156000,
- width: SCREEN_WIDTH,
- height: newHeight,
- loaded: true
+ loaded: true,
+ dimensions: dimensions
});
});
})();
@@ -164,11 +228,11 @@ export const PostByIdJsx = (props) => {
return (
- { state.loaded ?
-
+ dimensions = { state.dimensions }
+ navigation = { props.navigation }/>
:
}
@@ -191,19 +255,38 @@ const styles = {
color: "#000",
marginTop: -2
},
+ menu: {
+ marginLeft: "auto",
+ marginRight: SCREEN_WIDTH / 30
+ },
pfp: {
width: SCREEN_WIDTH / 10,
height: SCREEN_WIDTH / 10,
marginRight: SCREEN_WIDTH / 28,
borderRadius: 50
},
+ ellipsis: {
+ width: SCREEN_WIDTH / 15,
+ height: SCREEN_WIDTH / 15
+ },
photo: {
flex: 1,
},
-
+ carousel: {
+ width: SCREEN_WIDTH,
+ height: SCREEN_WIDTH,
+ },
+ carouselContainer: {
+ display: "flex",
+ alignItems: "center"
+ },
caption: {
padding: SCREEN_WIDTH / 24,
},
+ comments: {
+ paddingTop: SCREEN_WIDTH / 50,
+ color: "#666",
+ },
captionDate: {
fontSize: 0.8,
color: "#666",
@@ -211,6 +294,28 @@ const styles = {
},
strong: {
fontWeight: 'bold',
- color: "#666",
}
-};
\ No newline at end of file
+};
+
+// customStyles for react-native-popup-menu should be defined in particular
+// objects to be interpreted correctly.
+
+//const menuStyles = {
+// menuProviderWrapper
+//}
+
+const optionsStyles = {
+ optionWrapper: { // The wrapper around a single option
+ paddingLeft: SCREEN_WIDTH / 15,
+ paddingTop: SCREEN_WIDTH / 30,
+ paddingBottom: SCREEN_WIDTH / 30
+ },
+ optionsWrapper: { // The wrapper around all options
+ marginTop: SCREEN_WIDTH / 20,
+ marginBottom: SCREEN_WIDTH / 20,
+ },
+ optionsContainer: { // The Animated.View
+ borderTopLeftRadius: 10,
+ borderTopRightRadius: 10
+ }
+}
diff --git a/src/components/posts/timeline-view.js b/src/components/posts/timeline-view.js
index a66736b..70a158f 100644
--- a/src/components/posts/timeline-view.js
+++ b/src/components/posts/timeline-view.js
@@ -9,7 +9,9 @@ const TimelineViewJsx = (props) => {
{ props.posts.map((post, i) => {
return (
-
+
);
}) }
@@ -17,4 +19,4 @@ const TimelineViewJsx = (props) => {
);
};
-export default TimelineViewJsx;
\ No newline at end of file
+export default TimelineViewJsx;
diff --git a/src/interface/rendering.js b/src/interface/rendering.js
new file mode 100644
index 0000000..6dee69e
--- /dev/null
+++ b/src/interface/rendering.js
@@ -0,0 +1,37 @@
+export function pluralize(n, singular, plural) {
+ if (n < 2) {
+ return singular;
+ } else {
+ return plural;
+ }
+}
+
+export function timeToAge(time1, time2) {
+ /*
+ Output a friendly string to describe the age of a post, where `time1` and
+ `time2` are in milliseconds
+ */
+
+ const between = (n, lower, upper) => n >= lower && n < upper;
+
+ const diff = time1 - time2;
+
+ if (diff < 60000) {
+ return "Seconds ago"
+ } else if (between(diff, 60000, 3600000)) {
+ const nMin = Math.floor(diff / 60000);
+ return nMin + " " + pluralize(nMin, "minute", "minutes") + " ago";
+ } else if (between(diff, 3600000, 86400000)) {
+ const nHours = Math.floor(diff / 3600000);
+ return nHours + " " + pluralize(nHours, "hour", "hours") + " ago";
+ } else if (between(diff, 86400000, 2629800000)) {
+ const nDays = Math.floor(diff / 86400000);
+ return nDays + " " + pluralize(nDays, "day", "days") + " ago";
+ } else if (between(diff, 2629800000, 31557600000)) {
+ const nMonths = Math.floor(diff / 2629800000);
+ return nMonths + " " + pluralize(nMonths, "month", "months") + " ago";
+ } else {
+ const nYears = Math.floor(diff / 31557600000);
+ return nYears + " " + pluralize(nYears, "year", "years") + " ago";
+ }
+}