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/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 f1629d2..d3776c1 100644 --- a/src/components/navigation/navigators.js +++ b/src/components/navigation/navigators.js @@ -9,7 +9,7 @@ import TrayJsx from "src/components/navigation/tray"; // Provider for context menus // Allows for establishing global styling of context menus -const ContextJsx = (props) => { +export const ContextJsx = (props) => { return ( { props.children } diff --git a/src/components/pages/feed.js b/src/components/pages/feed.js index b2ee92d..ac9b3e0 100644 --- a/src/components/pages/feed.js +++ b/src/components/pages/feed.js @@ -43,7 +43,9 @@ const FeedJsx = (props) => { - +
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.js b/src/components/posts/post.js index 5fc6e48..2a9905f 100644 --- a/src/components/posts/post.js +++ b/src/components/posts/post.js @@ -9,6 +9,8 @@ import { 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; @@ -18,14 +20,6 @@ const TEST_IMAGE = "https://cache.desktopnexus.com/thumbseg/2255/2255124-bigthum // This will be used in RawPostJsx const { SlideInMenu } = renderers; -function pluralize(n, singular, plural) { - if (n < 2) { - return singular; - } else { - return plural; - } -} - function getAutoHeight(w1, h1, w2) { /* Given the original dimensions and the new width, calculate what would @@ -45,36 +39,6 @@ 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 - */ - - 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"; - } -} - export const RawPostJsx = (props) => { const repliesCount = props.data.replies_count; @@ -125,7 +89,13 @@ export const RawPostJsx = (props) => { { props.data.username } { props.data.content } - + props.navigation.navigate("ViewComments", { + originTab: props.navigation.getParam("originTab"), + postData: props.data + }) + }> { commentsText } @@ -169,7 +139,8 @@ export const PostByDataJsx = (props) => { + height = { state.height } + navigation = { props.navigation }/> : } ); @@ -210,7 +181,7 @@ export const PostByIdJsx = (props) => { }); }); })(); - }); + }, []); return ( @@ -218,7 +189,8 @@ export const PostByIdJsx = (props) => { + height = { state.height } + navigation = { props.navigation }/> : } 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"; + } +}