init
|
@ -0,0 +1,14 @@
|
|||
node_modules/**/*
|
||||
.expo/*
|
||||
.expo-shared/*
|
||||
npm-debug.*
|
||||
*.jks
|
||||
*.p8
|
||||
*.p12
|
||||
*.key
|
||||
*.mobileprovision
|
||||
*.orig.*
|
||||
web-build/
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"expo": {
|
||||
"entryPoint": "./src/App.js",
|
||||
"name": "Resin",
|
||||
"slug": "Resin",
|
||||
"version": "1.0.0",
|
||||
"orientation": "portrait",
|
||||
"icon": "./assets/icon.png",
|
||||
"splash": {
|
||||
"image": "./assets/splash.png",
|
||||
"resizeMode": "contain",
|
||||
"backgroundColor": "#ffffff"
|
||||
},
|
||||
"updates": {
|
||||
"fallbackToCacheTimeout": 0
|
||||
},
|
||||
"assetBundlePatterns": [
|
||||
"**/*"
|
||||
],
|
||||
"ios": {
|
||||
"supportsTablet": true
|
||||
},
|
||||
"web": {
|
||||
"favicon": "./assets/favicon.png"
|
||||
}
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 7.2 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 7.5 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 5.1 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 7.4 KiB |
After Width: | Height: | Size: 6.9 KiB |
After Width: | Height: | Size: 7.4 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 4.8 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g data-name="Layer 2"><g data-name="home"><rect width="24" height="24" opacity="0"/><path d="M20.42 10.18L12.71 2.3a1 1 0 0 0-1.42 0l-7.71 7.89A2 2 0 0 0 3 11.62V20a2 2 0 0 0 1.89 2h14.22A2 2 0 0 0 21 20v-8.38a2.07 2.07 0 0 0-.58-1.44zM10 20v-6h4v6zm9 0h-3v-7a1 1 0 0 0-1-1H9a1 1 0 0 0-1 1v7H5v-8.42l7-7.15 7 7.19z"/></g></g></svg>
|
After Width: | Height: | Size: 392 B |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 5.6 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1013 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 986 B |
After Width: | Height: | Size: 814 B |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 6.0 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 7.8 KiB |
After Width: | Height: | Size: 642 B |
After Width: | Height: | Size: 389 KiB |
|
@ -0,0 +1,18 @@
|
|||
module.exports = function(api) {
|
||||
api.cache(true);
|
||||
return {
|
||||
presets: ['babel-preset-expo'],
|
||||
plugins: [
|
||||
[
|
||||
require.resolve('babel-plugin-module-resolver'),
|
||||
{
|
||||
root: ["./"],
|
||||
alias: {
|
||||
"assets": "./assets",
|
||||
"src": "./src"
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
};
|
||||
};
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"main": "node_modules/expo/AppEntry.js",
|
||||
"scripts": {
|
||||
"start": "expo start",
|
||||
"android": "expo start --android",
|
||||
"ios": "expo start --ios",
|
||||
"web": "expo start --web",
|
||||
"eject": "expo eject"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-native-community/masked-view": "0.1.10",
|
||||
"@react-navigation/native": "5.1.1",
|
||||
"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-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"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.8.6",
|
||||
"babel-plugin-module-resolver": "^4.0.0",
|
||||
"babel-preset-expo": "~8.1.0"
|
||||
},
|
||||
"private": true
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
import 'react-native-gesture-handler';
|
||||
|
||||
|
||||
import { createAppContainer } from 'react-navigation';
|
||||
import { createStackNavigator } from "react-navigation-stack";
|
||||
|
||||
import { registerRootComponent } from 'expo';
|
||||
|
||||
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';
|
||||
import NotificationsJsx from 'src/components/pages/profile/notifications';
|
||||
|
||||
const Stack = createStackNavigator({
|
||||
Feed: { screen: FeedJsx, },
|
||||
Discover: { screen: DiscoverJsx },
|
||||
Notifications: { screen: NotificationsJsx },
|
||||
Profile: { screen: ProfileJsx, },
|
||||
Search: { screen: SearchJsx },
|
||||
ViewPost: { screen: ViewPostJsx },
|
||||
ViewProfile: { screen: ViewProfileJsx },
|
||||
ViewHashtag: { screen: ViewHashtagJsx }
|
||||
}, {
|
||||
initialRouteKey: "Feed",
|
||||
headerMode: "none",
|
||||
navigationOptions: {
|
||||
headerVisible: false
|
||||
}
|
||||
});
|
||||
|
||||
const App = createAppContainer(Stack);
|
||||
|
||||
export default registerRootComponent(App);
|
|
@ -0,0 +1,33 @@
|
|||
import React from "react";
|
||||
import { Image } from "react-native";
|
||||
import { TouchableWithoutFeedback } from "react-native";
|
||||
|
||||
const BackBarJsx = (props) => {
|
||||
const backIcon = require("assets/eva-icons/back.png");
|
||||
|
||||
return (
|
||||
<nav style = { styles.nav }>
|
||||
<TouchableWithoutFeedback
|
||||
onPress = { () => props.navigation.goBack() }>
|
||||
<Image
|
||||
style = { styles.button }
|
||||
source = { backIcon }/>
|
||||
</TouchableWithoutFeedback>
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = {
|
||||
nav: {
|
||||
padding: 15,
|
||||
|
||||
borderBottom: "2px solid #CCC",
|
||||
backgroundColor: "white"
|
||||
},
|
||||
button: {
|
||||
width: 30,
|
||||
height: 30
|
||||
}
|
||||
}
|
||||
|
||||
export default BackBarJsx;
|
|
@ -0,0 +1,43 @@
|
|||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import { ScrollView } from "react-native-gesture-handler";
|
||||
import BackBarJsx from "./back-bar";
|
||||
import TrayJsx from "src/components/navigation/tray";
|
||||
|
||||
export const ScreenWithTrayJsx = (props) => {
|
||||
return (
|
||||
<View style = { { flex: 1 } }>
|
||||
<ScrollView>
|
||||
{ props.children }
|
||||
</ScrollView>
|
||||
<TrayJsx
|
||||
active = { props.active }
|
||||
navigation = { props.navigation } />
|
||||
</View>
|
||||
)
|
||||
};
|
||||
|
||||
export const ScreenWithBackBarJsx = (props) => {
|
||||
return (
|
||||
<View style = { { flex: 1 } }>
|
||||
<BackBarJsx navigation = { props.navigation } />
|
||||
<ScrollView>
|
||||
{ props.children }
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export const ScreenWithFullNavigationJsx = (props) => {
|
||||
return (
|
||||
<View style = { { flex: 1 } }>
|
||||
<BackBarJsx navigation = { props.navigation } />
|
||||
<ScrollView>
|
||||
{ props.children }
|
||||
</ScrollView>
|
||||
<TrayJsx
|
||||
active = { props.active }
|
||||
navigation = { props.navigation } />
|
||||
</View>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
import React from "react";
|
||||
import { Image } from "react-native";
|
||||
import { activeOrNot } from "src/interface/interactions"
|
||||
import { TouchableWithoutFeedback } from "react-native";
|
||||
|
||||
const TrayButtonJsx = (props) => {
|
||||
return (
|
||||
<TouchableWithoutFeedback
|
||||
onPress={ () => props.nav.navigate(props.where, {}) }>
|
||||
<Image
|
||||
source = {
|
||||
activeOrNot(props.active == props.where, props.pack)
|
||||
}
|
||||
style = { styles.icon } />
|
||||
</TouchableWithoutFeedback>
|
||||
);
|
||||
}
|
||||
|
||||
const TrayJsx = (props) => {
|
||||
const nav = props.navigation;
|
||||
|
||||
const icons = {
|
||||
feed: {
|
||||
active: require("assets/eva-icons/home-black.png"),
|
||||
inactive: require("assets/eva-icons/home-grey.png")
|
||||
},
|
||||
discover: {
|
||||
active: require("assets/eva-icons/search-black.png"),
|
||||
inactive: require("assets/eva-icons/search-grey.png")
|
||||
},
|
||||
publish: {
|
||||
active: require("assets/eva-icons/camera-black.png"),
|
||||
inactive: require("assets/eva-icons/camera-grey.png")
|
||||
},
|
||||
direct: {
|
||||
active: require("assets/eva-icons/email-black.png"),
|
||||
inactive: require("assets/eva-icons/email-grey.png")
|
||||
},
|
||||
profile: {
|
||||
active: require("assets/eva-icons/person-black.png"),
|
||||
inactive: require("assets/eva-icons/person-grey.png")
|
||||
},
|
||||
}
|
||||
|
||||
return (
|
||||
<nav style = { styles.tray }>
|
||||
<ul style = { styles.iconList }>
|
||||
<li>
|
||||
<TrayButtonJsx
|
||||
where = "Feed"
|
||||
pack = { icons.feed }
|
||||
active = { props.active }
|
||||
nav = { nav } />
|
||||
</li>
|
||||
<li>
|
||||
<TrayButtonJsx
|
||||
where = "Discover"
|
||||
pack = { icons.discover }
|
||||
active = { props.active }
|
||||
nav = { nav } />
|
||||
</li>
|
||||
<li>
|
||||
<TrayButtonJsx
|
||||
where = "Publish"
|
||||
pack = { icons.publish }
|
||||
active = { props.active }
|
||||
nav = { nav } />
|
||||
</li>
|
||||
<li>
|
||||
<TrayButtonJsx
|
||||
where = "Direct"
|
||||
pack = { icons.direct }
|
||||
active = { props.active }
|
||||
nav = { nav } />
|
||||
</li>
|
||||
<li>
|
||||
<TrayButtonJsx
|
||||
where = "Profile"
|
||||
pack = { icons.profile }
|
||||
active = { props.active }
|
||||
nav = { nav } />
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
|
||||
const iconSize = 30;
|
||||
|
||||
const styles = {
|
||||
tray: {
|
||||
width: "100%",
|
||||
paddingTop: iconSize / 2,
|
||||
paddingBottom: iconSize / 2,
|
||||
|
||||
borderTop: "2px solid #CCC",
|
||||
backgroundColor: "white"
|
||||
},
|
||||
iconList: {
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-evenly",
|
||||
alignItems: "center",
|
||||
|
||||
margin: 0,
|
||||
paddingLeft: 0,
|
||||
|
||||
listStyle: "none",
|
||||
},
|
||||
icon: {
|
||||
width: iconSize,
|
||||
height: iconSize
|
||||
}
|
||||
};
|
||||
|
||||
export default TrayJsx;
|
|
@ -0,0 +1,44 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { View, TextInput, Text, Dimensions } from "react-native";
|
||||
|
||||
import PagedGridJsx from "src/components/posts/paged-grid";
|
||||
import { ScreenWithTrayJsx } from "src/components/navigation/navigators";
|
||||
import { TouchableWithoutFeedback } from "react-native-gesture-handler";
|
||||
|
||||
const DiscoverJsx = (props) => {
|
||||
return (
|
||||
<ScreenWithTrayJsx
|
||||
active = "Discover"
|
||||
navigation = { props.navigation }>
|
||||
<TouchableWithoutFeedback
|
||||
onPress = { () => props.navigation.navigate("Search") }>
|
||||
<View style = { styles.form }>
|
||||
<View style = { styles.searchBarContainer }>
|
||||
<Text style = { styles.searchBar }>
|
||||
Search...
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
<PagedGridJsx
|
||||
navigation = { props.navigation }
|
||||
originTab = "Discover" />
|
||||
</ScreenWithTrayJsx>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = {
|
||||
form: {
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
backgroundColor: "white",
|
||||
padding: 20
|
||||
},
|
||||
searchBar: {
|
||||
padding: 10,
|
||||
fontSize: "1.1em",
|
||||
color: "#888"
|
||||
},
|
||||
};
|
||||
|
||||
export default DiscoverJsx;
|
|
@ -0,0 +1,181 @@
|
|||
import React, { useState } from "react";
|
||||
import { View, TextInput, Text, Dimensions, Image } from "react-native";
|
||||
import { ScreenWithTrayJsx } from "src/components/navigation/navigators";
|
||||
import { TouchableWithoutFeedback } from "react-native-gesture-handler";
|
||||
|
||||
const TEST_IMAGE = "https://cache.desktopnexus.com/thumbseg/2255/2255124-bigthumbnail.jpg";
|
||||
const TEST_ACCOUNTS = [
|
||||
{
|
||||
id: 1,
|
||||
avatar: TEST_IMAGE,
|
||||
username: "njms",
|
||||
acct: "njms",
|
||||
display_name: "Nathan Scott"
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
avatar: TEST_IMAGE,
|
||||
username: "njms",
|
||||
acct: "njms",
|
||||
display_name: "Nathan Scott"
|
||||
}
|
||||
];
|
||||
|
||||
const TEST_HASHTAGS = [
|
||||
{
|
||||
name: "hashtag1"
|
||||
},
|
||||
{
|
||||
name: "hashtag2"
|
||||
},
|
||||
];
|
||||
|
||||
function navCallbackFactory(navigation, route) {
|
||||
return params => {
|
||||
console.log("test")
|
||||
navigation.navigate(route, params);
|
||||
}
|
||||
}
|
||||
|
||||
const SearchJsx = ({navigation}) => {
|
||||
let [state, setState] = useState({
|
||||
query: "",
|
||||
});
|
||||
|
||||
const accountCallback = navCallbackFactory(navigation, "ViewProfile");
|
||||
const hashtagCallback = navCallbackFactory(navigation, "ViewHashtag");
|
||||
|
||||
return (
|
||||
<ScreenWithTrayJsx
|
||||
active = "Discover"
|
||||
navigation = { navigation }>
|
||||
<View style = { styles.form }>
|
||||
<TextInput
|
||||
style = { styles.searchBar }
|
||||
placeholder = "Search..."
|
||||
autoFocus
|
||||
onChangeText = { q => setState({query: q}) }
|
||||
onBlur = {
|
||||
() => {
|
||||
if (state.query == "") {
|
||||
navigation.navigate("Discover");
|
||||
}
|
||||
}
|
||||
}
|
||||
value = { state.query } />
|
||||
</View>
|
||||
{ state.query == "" ?
|
||||
<View></View>
|
||||
: <View>
|
||||
<Text>Accounts</Text>
|
||||
<AccountsListJsx
|
||||
data = { TEST_ACCOUNTS }
|
||||
callback = { accountCallback } />
|
||||
<Text>Hashtags</Text>
|
||||
<HashtagListJsx
|
||||
data = { TEST_HASHTAGS }
|
||||
callback = { hashtagCallback } />
|
||||
</View>
|
||||
}
|
||||
</ScreenWithTrayJsx>
|
||||
);
|
||||
};
|
||||
|
||||
const SearchItemJsx = (props) => {
|
||||
return (
|
||||
<TouchableWithoutFeedback
|
||||
onPress = { () => props.callback(props.params) }>
|
||||
<li style = { styles.searchResultContainer }>
|
||||
<Image
|
||||
style = { styles.thumbnail }
|
||||
source = { props.thumbnail } />
|
||||
<View style = { styles.queried }>
|
||||
{ props.children }
|
||||
</View>
|
||||
</li>
|
||||
</TouchableWithoutFeedback>
|
||||
);
|
||||
};
|
||||
|
||||
const AccountsListJsx = (props) => {
|
||||
return (
|
||||
<ul style = { styles.searchList }>
|
||||
{
|
||||
props.data.map(item => {
|
||||
return (
|
||||
<SearchItemJsx
|
||||
key = { item.id }
|
||||
thumbnail = { { uri: item.avatar } }
|
||||
callback = { props.callback }
|
||||
params = { { acct: item.acct } }>
|
||||
<Text style = { styles.username }>
|
||||
{ item.username }
|
||||
</Text>
|
||||
<Text style = { styles.displayName }>
|
||||
{ item.display_name }
|
||||
</Text>
|
||||
</SearchItemJsx>
|
||||
);
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
|
||||
const HashtagListJsx = (props) => {
|
||||
return (
|
||||
<ul style = { styles.searchList }>
|
||||
{
|
||||
props.data.map(item => {
|
||||
return (
|
||||
<SearchItemJsx
|
||||
key = { item.id }
|
||||
thumbnail = { require("assets/hashtag.png") }
|
||||
callback = { props.callback }
|
||||
params = { { name: item.name } }>
|
||||
<Text style = { styles.username }>
|
||||
#{ item.name }
|
||||
</Text>
|
||||
</SearchItemJsx>
|
||||
);
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = {
|
||||
form: {
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
backgroundColor: "white",
|
||||
padding: 20
|
||||
},
|
||||
searchBar: {
|
||||
padding: 10,
|
||||
fontSize: "1.1em",
|
||||
color: "#888"
|
||||
},
|
||||
|
||||
searchList: { padding: 0 },
|
||||
searchResultContainer: {
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
padding: 5,
|
||||
paddingLeft: 20,
|
||||
},
|
||||
queried: {
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
},
|
||||
username: { fontWeight: "bold" },
|
||||
displayName: { color: "#888" },
|
||||
thumbnail: {
|
||||
width: 50,
|
||||
height: 50,
|
||||
borderRadius: "100%",
|
||||
marginRight: 10,
|
||||
}
|
||||
}
|
||||
|
||||
export default SearchJsx;
|
|
@ -0,0 +1,99 @@
|
|||
import React, { useState } from "react";
|
||||
import { View, Image, Dimensions, Text } from "react-native";
|
||||
import { ScreenWithFullNavigationJsx } from "src/components/navigation/navigators";
|
||||
import PagedGridJsx from "src/components/posts/paged-grid";
|
||||
import { TouchableWithoutFeedback } from "react-native-gesture-handler";
|
||||
|
||||
const FollowHashtagButtonJsx = ({followed, onPress}) => {
|
||||
return (
|
||||
<TouchableWithoutFeedback
|
||||
style = {
|
||||
[
|
||||
styles.button,
|
||||
followed ? { backgroundColor: "black" } : {}
|
||||
]
|
||||
}
|
||||
onPress = { onPress }>
|
||||
<Text
|
||||
style = { followed ? { color: "white" } : {} }>
|
||||
{ followed ? "Followed" : "Follow" }
|
||||
</Text>
|
||||
</TouchableWithoutFeedback>
|
||||
);
|
||||
};
|
||||
|
||||
const ViewHashtagJsx = ({navigation}) => {
|
||||
let [state, setState] = useState({
|
||||
name: navigation.getParam("name", ""),
|
||||
posts: [],
|
||||
nPosts: 0,
|
||||
followed: false,
|
||||
loaded: false,
|
||||
});
|
||||
return (
|
||||
<ScreenWithFullNavigationJsx
|
||||
active = "Discover"
|
||||
navigation = { navigation }>
|
||||
<View>
|
||||
<View style = { styles.headerContainer }>
|
||||
<View>
|
||||
<Image
|
||||
style = { styles.image }
|
||||
source = { require("assets/hashtag.png") } />
|
||||
</View>
|
||||
<View style = { styles.headerText }>
|
||||
<Text style = { styles.hashtag}>
|
||||
#{ state.name }
|
||||
</Text>
|
||||
<Text>
|
||||
<strong>{ state.nPosts }</strong> posts
|
||||
</Text>
|
||||
|
||||
<FollowHashtagButtonJsx
|
||||
followed = { state.followed }
|
||||
onPress = { () => {
|
||||
// Send request to follow hashtag and such...
|
||||
setState({ ...state, followed: !state.followed});
|
||||
}
|
||||
}/>
|
||||
</View>
|
||||
</View>
|
||||
<PagedGridJsx
|
||||
navigation = { navigation }
|
||||
originTab = "Discover" />
|
||||
</View>
|
||||
</ScreenWithFullNavigationJsx>
|
||||
);
|
||||
};
|
||||
|
||||
const screen_width = Dimensions.get("window").width;
|
||||
const styles = {
|
||||
headerContainer: {
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
padding: 15,
|
||||
borderBottom: "2px solid black"
|
||||
},
|
||||
image: {
|
||||
width: screen_width / 3,
|
||||
height: screen_width / 3,
|
||||
border: "2px solid black",
|
||||
borderRadius: "100%",
|
||||
marginRight: 20
|
||||
},
|
||||
hashtag: {
|
||||
fontWeight: "bold",
|
||||
fontSize: "1.2em"
|
||||
},
|
||||
button: {
|
||||
border: "2px solid black",
|
||||
borderRadius: 5,
|
||||
padding: 10,
|
||||
paddingLeft: 30,
|
||||
paddingRight: 30,
|
||||
marginTop: 10
|
||||
},
|
||||
}
|
||||
|
||||
export default ViewHashtagJsx;
|
|
@ -0,0 +1,101 @@
|
|||
import React from "react";
|
||||
import { Dimensions, View, Image, Text } from "react-native";
|
||||
|
||||
import TimelineViewJsx from "src/components/posts/timeline-view";
|
||||
import { ScreenWithTrayJsx } from "src/components/navigation/navigators";
|
||||
import { TouchableWithoutFeedback } from "react-native-gesture-handler";
|
||||
|
||||
const TEST_IMAGE = "https://cache.desktopnexus.com/thumbseg/2255/2255124-bigthumbnail.jpg";
|
||||
|
||||
const TEST_POSTS = [
|
||||
{
|
||||
id: 1,
|
||||
avatar: TEST_IMAGE,
|
||||
username: "njms",
|
||||
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}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
avatar: TEST_IMAGE,
|
||||
username: "njms",
|
||||
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}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const FeedJsx = (props) => {
|
||||
const checkmark = require("assets/eva-icons/checkmark-circle-large.png");
|
||||
|
||||
return (
|
||||
<ScreenWithTrayJsx
|
||||
active = "Feed"
|
||||
navigation = { props.navigation }>
|
||||
<TimelineViewJsx posts = { TEST_POSTS } />
|
||||
<div style = { styles.interruptionOuter }>
|
||||
<View style = { styles.interruption }>
|
||||
<Image
|
||||
source = { checkmark }
|
||||
style = { styles.checkmark }/>
|
||||
|
||||
<Text style = { styles.interruptionHeader }>
|
||||
You're all caught up.
|
||||
</Text>
|
||||
<br />
|
||||
<Text> Wow, it sure is a lovely day outside 🌳 </Text>
|
||||
|
||||
<TouchableWithoutFeedback
|
||||
style = { styles.buttonOlder }>
|
||||
<Text> See older posts </Text>
|
||||
</TouchableWithoutFeedback>
|
||||
</View>
|
||||
</div>
|
||||
</ScreenWithTrayJsx>
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
const screen_width = Dimensions.get("window").width;
|
||||
const screen_height = Dimensions.get("window").height;
|
||||
const styles = {
|
||||
timeline: {
|
||||
height: screen_height - (screen_height / 12)
|
||||
},
|
||||
interruptionOuter: {
|
||||
borderTop: "2px solid #CCC",
|
||||
},
|
||||
interruption: {
|
||||
marginTop: 10,
|
||||
marginBottom: 10,
|
||||
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
},
|
||||
interruptionHeader: {
|
||||
fontSize: "1.3em"
|
||||
},
|
||||
checkmark: {
|
||||
width: screen_width * 0.3,
|
||||
height: screen_width * 0.3
|
||||
},
|
||||
buttonOlder: {
|
||||
border: "2px solid black",
|
||||
borderRadius: 5,
|
||||
|
||||
margin: 30,
|
||||
padding: 5
|
||||
}
|
||||
};
|
||||
|
||||
export default FeedJsx;
|
|
@ -0,0 +1,217 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import { View, Dimensions, Image, Text, TouchableWithoutFeedback } from "react-native";
|
||||
|
||||
import { activeOrNot } from "src/interface/interactions"
|
||||
|
||||
import GridViewJsx from "src/components/posts/grid-view";
|
||||
import {
|
||||
ScreenWithTrayJsx,
|
||||
ScreenWithFullNavigationJsx
|
||||
} from "src/components/navigation/navigators";
|
||||
|
||||
const TEST_IMAGE = "https://cache.desktopnexus.com/thumbseg/2255/2255124-bigthumbnail.jpg";
|
||||
const TEST_POSTS = [
|
||||
{
|
||||
id: 1,
|
||||
media_attachments: [
|
||||
{preview_url: TEST_IMAGE}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
media_attachments: [
|
||||
{preview_url: TEST_IMAGE}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
media_attachments: [
|
||||
{preview_url: TEST_IMAGE}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
media_attachments: [
|
||||
{preview_url: TEST_IMAGE}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const ProfileJsx = ({navigation}) => {
|
||||
return (
|
||||
<ScreenWithTrayJsx
|
||||
active = "Profile"
|
||||
navigation = { navigation }
|
||||
active = "Profile">
|
||||
<ProfileDisplayJsx navigation = { navigation }/>
|
||||
</ScreenWithTrayJsx>
|
||||
);
|
||||
};
|
||||
|
||||
const ViewProfileJsx = ({navigation}) => {
|
||||
return (
|
||||
<ScreenWithFullNavigationJsx
|
||||
active = { navigation.getParam("originTab") }
|
||||
navigation = { navigation }>
|
||||
<ProfileDisplayJsx navigation = { navigation } />
|
||||
</ScreenWithFullNavigationJsx>
|
||||
);
|
||||
}
|
||||
|
||||
const ProfileDisplayJsx = ({navigation}) => {
|
||||
const accountName = navigation.getParam("acct", "");
|
||||
let [state, setState] = useState({
|
||||
avatar: "",
|
||||
displayName: "Somebody",
|
||||
username: "somebody",
|
||||
statusesCount: 0,
|
||||
followersCount: 0,
|
||||
followingCount: 0,
|
||||
note: "Not much here...",
|
||||
unread_notifications: false,
|
||||
own: false,
|
||||
loaded: false
|
||||
});
|
||||
|
||||
const notif_pack = {
|
||||
active: require("assets/eva-icons/bell-unread.png"),
|
||||
inactive: require("assets/eva-icons/bell-black.png")
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// do something to get the profile based on given account name
|
||||
if (!state.loaded) {
|
||||
setState({
|
||||
avatar: TEST_IMAGE,
|
||||
displayName: "Nat🔆",
|
||||
username: "njms",
|
||||
statusesCount: 334,
|
||||
followersCount: "1 jillion",
|
||||
followingCount: 7,
|
||||
note: "Yeah heart emoji.",
|
||||
own: true,
|
||||
unread_notifs: false,
|
||||
loaded: true
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
let profileButton;
|
||||
if (state.own) {
|
||||
profileButton = (
|
||||
<TouchableWithoutFeedback>
|
||||
<View style = { styles.button }>
|
||||
<Text style = { styles.buttonText }>Edit profile</Text>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
);
|
||||
} else {
|
||||
profileButton = (
|
||||
<TouchableWithoutFeedback>
|
||||
<View style = { styles.button }>
|
||||
<Text style = { styles.buttonText }>Follow</Text>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<View>
|
||||
<View style = { styles.jumbotron }>
|
||||
<View style = { styles.profileHeader }>
|
||||
<Image
|
||||
source = { { uri: state.avatar } }
|
||||
style = { styles.avatar } />
|
||||
<View>
|
||||
<Text
|
||||
style = { styles.displayName }>
|
||||
{state.displayName}
|
||||
</Text>
|
||||
<Text><strong> @{state.username} </strong></Text>
|
||||
</View>
|
||||
<TouchableWithoutFeedback>
|
||||
<Image
|
||||
source = { activeOrNot(state.unread_notifs, notif_pack) }
|
||||
style = { styles.bell } />
|
||||
</TouchableWithoutFeedback>
|
||||
</View>
|
||||
<Text style = { styles.accountStats }>
|
||||
{ state.statusesCount } posts •
|
||||
{ state.followersCount } followers •
|
||||
{ state.followingCount } following
|
||||
</Text>
|
||||
<Text style = { styles.note }>
|
||||
{state.note}
|
||||
</Text>
|
||||
{profileButton}
|
||||
</View>
|
||||
|
||||
<GridViewJsx
|
||||
posts = { TEST_POSTS }
|
||||
openPostCallback = {
|
||||
(id) => {
|
||||
navigation.navigate("ViewPost", {
|
||||
id: id,
|
||||
originTab: "Profile"
|
||||
});
|
||||
}
|
||||
} />
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const screen_width = Dimensions.get("screen").width;
|
||||
const screen_height = Dimensions.get("screen").height;
|
||||
|
||||
const styles = {
|
||||
jumbotron: {
|
||||
padding: screen_width / 20,
|
||||
},
|
||||
profileHeader: {
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
|
||||
marginBottom: screen_width / 20
|
||||
},
|
||||
displayName: {
|
||||
fontSize: "1.5em"
|
||||
},
|
||||
avatar: {
|
||||
width: screen_width / 5,
|
||||
height: screen_width / 5,
|
||||
|
||||
borderRadius: "100%",
|
||||
marginRight: screen_width / 20
|
||||
},
|
||||
bell: {
|
||||
width: screen_width / 12,
|
||||
height: screen_width / 12,
|
||||
|
||||
marginLeft: "auto",
|
||||
marginRight: screen_width / 15
|
||||
},
|
||||
accountStats: {
|
||||
fontSize: "0.8em",
|
||||
fontWeight: "bold"
|
||||
},
|
||||
note: {
|
||||
fontSize: "1em",
|
||||
marginTop: 10,
|
||||
},
|
||||
button: {
|
||||
borderWidth: 1,
|
||||
borderStyle: "solid",
|
||||
borderColor: "#888",
|
||||
borderRadius: 5,
|
||||
|
||||
padding: 10,
|
||||
marginTop: 10
|
||||
},
|
||||
buttonText: {
|
||||
textAlign: "center"
|
||||
}
|
||||
};
|
||||
|
||||
export { ViewProfileJsx, ProfileDisplayJsx };
|
||||
export default ProfileJsx;
|
|
@ -0,0 +1,14 @@
|
|||
import React, { useState } from "react";
|
||||
import { ScreenWithTrayJsx } from "src/components/navigation/navigators";
|
||||
|
||||
const NotificationsJsx = ({navigation}) => {
|
||||
return (
|
||||
<ScreenWithTrayJsx
|
||||
active = "Notifications"
|
||||
navigation = { navigation }>
|
||||
|
||||
</ScreenWithTrayJsx>
|
||||
);
|
||||
}
|
||||
|
||||
export default NotificationsJsx;
|
|
@ -0,0 +1,16 @@
|
|||
import React from "react";
|
||||
|
||||
import { ScreenWithFullNavigationJsx } from "src/components/navigation/navigators";
|
||||
import { PostByIdJsx } from "src/components/posts/post";
|
||||
|
||||
const ViewPostJsx = (props) => {
|
||||
return (
|
||||
<ScreenWithFullNavigationJsx
|
||||
active = { props.navigation.getParam("originTab", "Timeline") }
|
||||
navigation = { props.navigation }>
|
||||
<PostByIdJsx id = { props.id } />
|
||||
</ScreenWithFullNavigationJsx>
|
||||
);
|
||||
}
|
||||
|
||||
export default ViewPostJsx;
|
|
@ -0,0 +1,23 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { Image, Dimensions, TouchableWithoutFeedback } from "react-native";
|
||||
|
||||
const GridPostJsx = (props) => {
|
||||
return (
|
||||
<TouchableWithoutFeedback
|
||||
onPress={ () => props.openPostCallback(props.id)}>
|
||||
<Image
|
||||
source = { { uri: props.previewUrl } }
|
||||
style = { styles.gridImage } />
|
||||
</TouchableWithoutFeedback>
|
||||
)
|
||||
}
|
||||
|
||||
const screen_width = Dimensions.get("window").width;
|
||||
const styles = {
|
||||
gridImage: {
|
||||
width: screen_width / 3,
|
||||
height: screen_width / 3
|
||||
}
|
||||
};
|
||||
|
||||
export default GridPostJsx;
|
|
@ -0,0 +1,65 @@
|
|||
import React from "react";
|
||||
import { View, Dimensions, Image } from "react-native";
|
||||
|
||||
import GridPostJsx from "src/components/posts/grid-post"
|
||||
|
||||
function partition(arr, size) {
|
||||
let newArray = [];
|
||||
for (let i = 0; i < arr.length; i += size) {
|
||||
const part = arr.slice(i, i + 3);
|
||||
newArray.push(part);
|
||||
}
|
||||
|
||||
return newArray
|
||||
}
|
||||
|
||||
const GridViewJsx = (props) => {
|
||||
let rows = partition(props.posts, 3);
|
||||
return (
|
||||
<View>
|
||||
{
|
||||
rows.map((row, i) => {
|
||||
return (
|
||||
<ul style = { styles.gridRow }
|
||||
key = { i }>
|
||||
{
|
||||
row.map((post) => {
|
||||
const post_url = post
|
||||
.media_attachments[0]
|
||||
.preview_url;
|
||||
|
||||
return (
|
||||
<li key = { post.id }>
|
||||
<GridPostJsx
|
||||
id = { post.id }
|
||||
previewUrl = { post_url }
|
||||
openPostCallback = {
|
||||
(id) => props.openPostCallback(id)
|
||||
}
|
||||
/>
|
||||
</li>
|
||||
);
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
)
|
||||
})
|
||||
}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const screen_width = Dimensions.get("window").width
|
||||
const styles = {
|
||||
gridRow: {
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
|
||||
listStyle: "none",
|
||||
|
||||
display: "flex",
|
||||
flexDirection: "row"
|
||||
}
|
||||
};
|
||||
|
||||
export default GridViewJsx;
|
|
@ -0,0 +1,92 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import { View, TouchableWithoutFeedback, Text } from "react-native";
|
||||
|
||||
import GridViewJsx from "src/components/posts/grid-view";
|
||||
|
||||
const TEST_IMAGE = "https://cache.desktopnexus.com/thumbseg/2255/2255124-bigthumbnail.jpg";
|
||||
const TEST_POSTS = [
|
||||
{
|
||||
id: 1,
|
||||
media_attachments: [
|
||||
{preview_url: TEST_IMAGE}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
media_attachments: [
|
||||
{preview_url: TEST_IMAGE}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
media_attachments: [
|
||||
{preview_url: TEST_IMAGE}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
media_attachments: [
|
||||
{preview_url: TEST_IMAGE}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const PagedGridJSX = (props) => {
|
||||
let [state, setState] = useState({
|
||||
posts: [],
|
||||
loaded: false
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!state.loaded) {
|
||||
// TODO: actually get posts :)
|
||||
setState({
|
||||
posts: TEST_POSTS,
|
||||
loaded: true
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<View>
|
||||
<GridViewJsx
|
||||
posts = { state.posts }
|
||||
openPostCallback = {
|
||||
(id) => {
|
||||
props.navigation.navigate("ViewPost", {
|
||||
id: id,
|
||||
originTab: props.navigation.getParam("originTab")
|
||||
});
|
||||
}
|
||||
} />
|
||||
<View style = { styles.buttonContainer }>
|
||||
<TouchableWithoutFeedback
|
||||
onPress = { () => {
|
||||
// TODO: actually get more posts :)
|
||||
let morePosts = state.posts.concat(TEST_POSTS);
|
||||
setState({ posts: morePosts, loaded: true });
|
||||
} }>
|
||||
<View style = { styles.buttonMore }>
|
||||
<Text>Show more?</Text>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = {
|
||||
buttonContainer: {
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center"
|
||||
},
|
||||
buttonMore: {
|
||||
border: "2px solid black",
|
||||
borderRadius: 5,
|
||||
padding: 10,
|
||||
margin: 20
|
||||
}
|
||||
}
|
||||
|
||||
export default PagedGridJSX;
|
|
@ -0,0 +1,117 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import {
|
||||
Image,
|
||||
View,
|
||||
Dimensions,
|
||||
TouchableWithoutFeedback
|
||||
} from "react-native";
|
||||
import { activeOrNot } from "src/interface/interactions";
|
||||
|
||||
function invertField (field, state, updater) {
|
||||
// Takes a function (like `setState`) and uses it to invert the given field of `state`
|
||||
let newState = state;
|
||||
newState[field] = !newState[field];
|
||||
updater(newState);
|
||||
}
|
||||
|
||||
// These callbacks will eventually make calls to the instance's API
|
||||
function favouritedCallback(state, updater) {
|
||||
invertField("favourited", state, updater);
|
||||
}
|
||||
|
||||
function commentCallback(state, updater) {
|
||||
invertField("commenting", state, updater);
|
||||
}
|
||||
|
||||
function reblogCallback(state, updater) {
|
||||
invertField("reblogged", state, updater);
|
||||
}
|
||||
|
||||
function downloadCallback(state, updater) {
|
||||
let newState = state;
|
||||
newState.downloaded = true;
|
||||
updater(newState);
|
||||
}
|
||||
|
||||
const PostActionJsx = (props) => {
|
||||
return (
|
||||
<TouchableWithoutFeedback
|
||||
onPress = { props.callback }>
|
||||
<Image
|
||||
source = {
|
||||
activeOrNot(props.state[props.field], props.pack)
|
||||
}
|
||||
style = { styles.icon } />
|
||||
</TouchableWithoutFeedback>
|
||||
)
|
||||
}
|
||||
|
||||
const PostActionBarJsx = (props) => {
|
||||
let [state, setState] = useState({
|
||||
favourited: props.favourited,
|
||||
commenting: false,
|
||||
reblogged: props.reblogged,
|
||||
downloaded: false
|
||||
});
|
||||
|
||||
const icons = {
|
||||
heart: {
|
||||
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")
|
||||
}
|
||||
}
|
||||
return (
|
||||
<View style = { styles.flexContainer }>
|
||||
<PostActionJsx
|
||||
field = "favourited"
|
||||
pack = { icons.heart }
|
||||
state = { state }
|
||||
callback = { () => favouritedCallback(state, setState) } />
|
||||
|
||||
<PostActionJsx
|
||||
field = "commenting"
|
||||
pack = { icons.comment }
|
||||
state = { state }
|
||||
callback = { () => commentCallback(state, setState) } />
|
||||
|
||||
<PostActionJsx
|
||||
field = "reblogged"
|
||||
pack = { icons.reblog }
|
||||
state = { state }
|
||||
callback = { () => reblogCallback(state, setState) } />
|
||||
|
||||
<PostActionJsx
|
||||
field = "downloaded"
|
||||
pack = { icons.download }
|
||||
state = { state }
|
||||
callback = { () => downloadCallback(state, setState) } />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = {
|
||||
flexContainer: {
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
padding: Dimensions.get("window").width / 40
|
||||
},
|
||||
icon: {
|
||||
width: 30,
|
||||
height: 30,
|
||||
marginRight: Dimensions.get("window").width / 20
|
||||
}
|
||||
}
|
||||
|
||||
export default PostActionBarJsx;
|
|
@ -0,0 +1,211 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { Image, View, Text, Dimensions } from "react-native";
|
||||
|
||||
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";
|
||||
|
||||
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 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 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";
|
||||
}
|
||||
}
|
||||
|
||||
export const RawPostJsx = (props) => {
|
||||
return (
|
||||
<View>
|
||||
<View style = { styles.postHeader }>
|
||||
<Image
|
||||
style = { styles.pfp }
|
||||
source = { { uri: props.data.avatar } } />
|
||||
<Text
|
||||
style = { styles.postHeaderName }>{ props.data.username }</Text>
|
||||
</View>
|
||||
{ /* TODO: support for more than one image per post */ }
|
||||
<Image
|
||||
source = { { uri: TEST_IMAGE/* props.data.media_attachments[0] */ } }
|
||||
style = { {
|
||||
flex: 1,
|
||||
width: SCREEN_WIDTH,
|
||||
height: getAutoHeight(props.width, props.height, SCREEN_WIDTH)
|
||||
} } />
|
||||
<PostActionBarJsx
|
||||
favourited = { props.data.favourited }
|
||||
reblogged = {props.data.reblogged } />
|
||||
<View style = { styles.caption }>
|
||||
<Text>
|
||||
<strong>{ props.data.username }</strong> { props.data.content }
|
||||
</Text>
|
||||
<Text style = { styles.captionDate }>
|
||||
{ timeToAge((new Date()).getTime(), props.data.timestamp) }
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
export const PostByDataJsx = (props) => {
|
||||
/*
|
||||
Renders a post where the data is supplied directly to the element through
|
||||
its properties, as it is in a timeline.
|
||||
*/
|
||||
|
||||
let [state, setState] = useState({
|
||||
width: 0,
|
||||
height: 0,
|
||||
loaded: false
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
Image.getSize(TEST_IMAGE, (width, height) => {
|
||||
const newHeight = getAutoHeight(width, height, SCREEN_WIDTH)
|
||||
|
||||
setState({
|
||||
width: SCREEN_WIDTH,
|
||||
height: newHeight,
|
||||
loaded: true
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<View>
|
||||
{ state.loaded ?
|
||||
<RawPostJsx
|
||||
data = { props.data }
|
||||
width = { state.width }
|
||||
height = { state.height } />
|
||||
: <View></View> }
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
export const PostByIdJsx = (props) => {
|
||||
/*
|
||||
Renders a post given the post's ID in the properties, as is done when
|
||||
retrieving an individual post on someone's profile.
|
||||
*/
|
||||
let [state, setState] = useState({
|
||||
avatar: "",
|
||||
username: "",
|
||||
media_attachments: [],
|
||||
favourited: false,
|
||||
reblogged: false,
|
||||
content: "",
|
||||
timestamp: 0,
|
||||
});
|
||||
|
||||
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)
|
||||
|
||||
setState({
|
||||
avatar: TEST_IMAGE,
|
||||
username: "njms",
|
||||
media_attachments: [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
|
||||
});
|
||||
});
|
||||
})();
|
||||
});
|
||||
|
||||
return (
|
||||
<View>
|
||||
{ state.loaded ?
|
||||
<RawPostJsx
|
||||
data = { state }
|
||||
width = { state.width }
|
||||
height = { state.height } />
|
||||
: <View></View>
|
||||
}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = {
|
||||
postHeader: {
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
marginTop: SCREEN_WIDTH / 28,
|
||||
marginBottom: SCREEN_WIDTH / 28,
|
||||
marginLeft: SCREEN_WIDTH / 36,
|
||||
marginRight: SCREEN_WIDTH / 36
|
||||
},
|
||||
postHeaderName: {
|
||||
fontSize: "1em",
|
||||
fontWeight: "bold",
|
||||
marginTop: -2
|
||||
},
|
||||
pfp: {
|
||||
width: SCREEN_WIDTH / 10,
|
||||
height: SCREEN_WIDTH / 10,
|
||||
marginRight: SCREEN_WIDTH / 28,
|
||||
borderRadius: "100%"
|
||||
},
|
||||
photo: {
|
||||
flex: 1,
|
||||
},
|
||||
|
||||
caption: {
|
||||
padding: SCREEN_WIDTH / 24,
|
||||
},
|
||||
captionDate: {
|
||||
fontSize: "0.8em",
|
||||
color: "#666",
|
||||
paddingTop: 10
|
||||
}
|
||||
};
|
|
@ -0,0 +1,20 @@
|
|||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
|
||||
import { PostByDataJsx } from "src/components/posts/post";
|
||||
|
||||
const TimelineViewJsx = (props) => {
|
||||
return (
|
||||
<View>
|
||||
{ props.posts.map((post, i) => {
|
||||
return (
|
||||
<View key = { i } >
|
||||
<PostByDataJsx data = { post } />
|
||||
</View>
|
||||
);
|
||||
}) }
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default TimelineViewJsx;
|
|
@ -0,0 +1,7 @@
|
|||
export function activeOrNot(condition, pack) {
|
||||
return condition ? pack.active : pack.inactive;
|
||||
}
|
||||
|
||||
export function updateTabBuilder(nav) {
|
||||
return (tab, params) => nav.navigate(tab, params)
|
||||
}
|