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