This commit is contained in:
Nat 2021-02-26 12:05:46 -04:00
commit 7cf3dc709b
68 changed files with 9131 additions and 0 deletions

14
.gitignore vendored Normal file
View File

@ -0,0 +1,14 @@
node_modules/**/*
.expo/*
.expo-shared/*
npm-debug.*
*.jks
*.p8
*.p12
*.key
*.mobileprovision
*.orig.*
web-build/
# macOS
.DS_Store

27
app.json Normal file
View File

@ -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"
}
}
}

BIN
assets/eva-icons/back.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1013 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 986 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 814 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

BIN
assets/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
assets/hashtag.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

BIN
assets/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 642 B

BIN
assets/test.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 KiB

18
babel.config.js Normal file
View File

@ -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"
}
}
]
]
};
};

7602
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

34
package.json Normal file
View File

@ -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
}

36
src/App.js Normal file
View File

@ -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);

View File

@ -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;

View File

@ -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>
);
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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 &#8226;&nbsp;
{ state.followersCount } followers &#8226;&nbsp;
{ 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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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>&nbsp;{ 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
}
};

View File

@ -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;

View File

@ -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)
}