Merge pull request #18 from natjms/discover

Implementing discover
This commit is contained in:
Nat 2021-04-10 13:51:37 -03:00 committed by GitHub
commit 341fc61116
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 666 additions and 64 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

10
package-lock.json generated
View File

@ -6137,6 +6137,11 @@
"resolved": "https://registry.npmjs.org/react-native-iphone-x-helper/-/react-native-iphone-x-helper-1.2.1.tgz",
"integrity": "sha512-/VbpIEp8tSNNHIvstuA3Swx610whci1Zpc9mqNkqn14DkMbw+ORviln2u0XyHG1kPvvwTNGZY6QpeFwxYaSdbQ=="
},
"react-native-pager-view": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/react-native-pager-view/-/react-native-pager-view-5.1.2.tgz",
"integrity": "sha512-UvPvjtuIkiI9Ti8NoMH+fiFj0ehfFv4WkNUGM46dOJfOxmE6Z/hoyJjymOHU//iLkQSMO+YNherZs0HcijdA2A=="
},
"react-native-popup-menu": {
"version": "0.15.10",
"resolved": "https://registry.npmjs.org/react-native-popup-menu/-/react-native-popup-menu-0.15.10.tgz",
@ -6168,6 +6173,11 @@
"resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-2.9.0.tgz",
"integrity": "sha512-5MaiUD6HA3nzY3JbVI8l3V7pKedtxQF3d8qktTVI0WmWXTI4QzqOU8r8fPVvfKo3MhOXwhWBjr+kQ7DZaIQQeg=="
},
"react-native-tab-view": {
"version": "2.16.0",
"resolved": "https://registry.npmjs.org/react-native-tab-view/-/react-native-tab-view-2.16.0.tgz",
"integrity": "sha512-ac2DmT7+l13wzIFqtbfXn4wwfgtPoKzWjjZyrK1t+T8sdemuUvD4zIt+UImg03fu3s3VD8Wh/fBrIdcqQyZJWg=="
},
"react-native-web": {
"version": "0.11.7",
"resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.11.7.tgz",

View File

@ -19,10 +19,12 @@
"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-pager-view": "^5.1.2",
"react-native-popup-menu": "^0.15.10",
"react-native-reanimated": "~1.9.0",
"react-native-safe-area-context": "~3.0.7",
"react-native-screens": "~2.9.0",
"react-native-tab-view": "^2.16.0",
"react-native-web": "~0.11.7",
"react-navigation": "^4.4.0",
"react-navigation-stack": "^2.8.2"

View File

@ -1,5 +1,8 @@
import React from "react";
import React, { useState, useEffect } from "react";
import { Image } from "react-native";
import { checkUnreadNotifications } from "src/requests";
import { activeOrNot } from "src/interface/interactions"
import { TouchableWithoutFeedback, View } from "react-native";
@ -18,6 +21,19 @@ const TrayButtonJsx = (props) => {
const TrayJsx = (props) => {
const nav = props.navigation;
const [state, setState] = useState({
unreadNotifications: false
});
useEffect(() => {
checkUnreadNotifications()
.then(isUnread => {
setState({...state,
unreadNotifications: isUnread,
});
});
}, []);
const icons = {
feed: {
@ -40,6 +56,10 @@ const TrayJsx = (props) => {
active: require("assets/eva-icons/person-black.png"),
inactive: require("assets/eva-icons/person-grey.png")
},
profileNotif: {
active: require("assets/eva-icons/person-black-notif.png"),
inactive: require("assets/eva-icons/person-grey-notif.png")
},
}
return (
@ -67,7 +87,11 @@ const TrayJsx = (props) => {
nav = { nav } />
<TrayButtonJsx
where = "Profile"
pack = { icons.profile }
pack = {
state.unreadNotifications ?
icons.profileNotif
: icons.profile
}
active = { props.active }
nav = { nav } />
</View>

View File

@ -44,18 +44,28 @@ const AuthenticateJsx = ({navigation}) => {
});
useEffect(() => {
const profile = AsyncStorage.getItem("@user_profile").then((profile) => {
if (profile != null) {
navigation.navigate("feed");
AsyncStorage.getItem("@user_profile").then((profile) => {
if (profile) {
navigation.navigate("Feed");
}
setState({...state, authChecked: true});
});
}, []);
const loginCallback = async () => {
const profileJSON = JSON.stringify(TEST_PROFILE);
AsyncStorage.setItem("@user_profile", profileJSON).then(() => {
const loginCallback = () => {
const initialization = [
[ "@user_profile", JSON.stringify(TEST_PROFILE) ],
[
"@user_notifications",
JSON.stringify({
unread: false,
memory: [{ id: 1 }, { id: 2 }],
})
]
];
AsyncStorage.multiSet(initialization).then(() => {
navigation.navigate("Feed");
});
};

View File

@ -1,11 +1,61 @@
import React, { useEffect, useState } from "react";
import { View, TextInput, Text, Dimensions } from "react-native";
import { TabView, TabBar, SceneMap } from "react-native-tab-view";
import { Ionicons } from "@expo/vector-icons";
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) => {
const [index, setIndex] = useState(0);
const [routes] = useState([
{
key: "home",
icon: "md-home",
},
{
key: "federated",
icon: "md-planet",
},
]);
const HomeTimeline = () => (
<PagedGridJsx
navigation = { props.navigation }
originTab = "Discover" />
);
const FederatedTimeline = () => (
<PagedGridJsx
navigation = { props.navigation }
originTab = "Discover" />
);
const renderScene = SceneMap({
home: HomeTimeline,
federated: FederatedTimeline,
});
const renderTabBar = (props) => (
<TabBar
{...props}
indicatorStyle = { styles.tabBar.indicator }
activeColor = "#000"
inactiveColor = "#888"
renderIcon = { renderIcon }
style = { styles.tabBar.tab } />
);
const renderIcon = ({ route, color }) => (
<Ionicons
name = { route.icon }
size = { 24 }
color = { color } />
);
return (
<ScreenWithTrayJsx
active = "Discover"
@ -20,13 +70,17 @@ const DiscoverJsx = (props) => {
</View>
</View>
</TouchableWithoutFeedback>
<PagedGridJsx
navigation = { props.navigation }
originTab = "Discover" />
<TabView
navigationState = { { index, routes } }
renderScene = { renderScene }
renderTabBar = { renderTabBar }
onIndexChange = { setIndex }
initialLayout = { { width: SCREEN_WIDTH } } />
</ScreenWithTrayJsx>
);
};
const SCREEN_WIDTH = Dimensions.get("window").width;
const styles = {
form: {
display: "flex",
@ -37,7 +91,14 @@ const styles = {
searchBar: {
padding: 10,
fontSize: 17,
color: "#888"
color: "#888",
borderBottomWidth: 1,
borderBottomColor: "#CCC",
},
tabBar: {
indicator: { backgroundColor: "black" },
tab: { backgroundColor: "white" },
},
};

View File

@ -67,11 +67,11 @@ const SearchJsx = ({navigation}) => {
{ state.query == "" ?
<View></View>
: <View>
<Text>Accounts</Text>
<Text style = { styles.label }>Accounts</Text>
<AccountsListJsx
data = { TEST_ACCOUNTS }
callback = { accountCallback } />
<Text>Hashtags</Text>
<Text style = { styles.label }>Hashtags</Text>
<HashtagListJsx
data = { TEST_HASHTAGS }
callback = { hashtagCallback } />
@ -156,6 +156,10 @@ const styles = {
fontSize: 17,
color: "#888"
},
label: {
padding: 10,
fontSize: 15,
},
searchList: { padding: 0 },
searchResultContainer: {
display: "flex",

View File

@ -2,15 +2,15 @@ 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";
import { TouchableOpacity } from "react-native-gesture-handler";
const FollowHashtagButtonJsx = ({followed, onPress}) => {
return (
<TouchableWithoutFeedback
<TouchableOpacity
style = {
[
styles.button,
followed ? { backgroundColor: "black" } : {}
followed ? { backgroundColor: "#888" } : {}
]
}
onPress = { onPress }>
@ -18,7 +18,7 @@ const FollowHashtagButtonJsx = ({followed, onPress}) => {
style = { followed ? { color: "white" } : {} }>
{ followed ? "Followed" : "Follow" }
</Text>
</TouchableWithoutFeedback>
</TouchableOpacity>
);
};
@ -69,33 +69,33 @@ const ViewHashtagJsx = ({navigation}) => {
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",
borderWidth: 1,
borderColor: "#888",
borderRadius: "100%",
marginRight: 20
marginRight: 20,
},
hashtag: {
fontWeight: "bold",
fontSize: 20
},
button: {
border: "2px solid black",
borderWidth: 1,
borderColor: "#888",
borderRadius: 5,
padding: 10,
paddingLeft: 30,
paddingRight: 30,
marginTop: 10
marginTop: 10,
},
strong: {
fontWeight: "bold"
fontWeight: "bold",
},
}

View File

@ -150,14 +150,21 @@ const ProfileDisplayJsx = ({navigation}) => {
}
useEffect(() => {
AsyncStorage.getItem("@user_profile").then((profileJSON) => {
setState({
profile: JSON.parse(profileJSON),
mutuals: getMutuals(TEST_YOUR_FOLLOWERS, TEST_THEIR_FOLLOWERS),
own: true,
loaded: true,
AsyncStorage.multiGet(["@user_profile", "@user_notifications"])
.then(values => {
const [profileJSON, notificationsJSON] = values;
const profile = JSON.parse(profileJSON[1]);
const notifications = JSON.parse(notificationsJSON[1]);
console.log(notifications);
setState({
profile: profile,
unreadNotifications: notifications.unread,
mutuals: getMutuals(TEST_YOUR_FOLLOWERS, TEST_THEIR_FOLLOWERS),
own: true,
loaded: true,
});
});
});
}, []);
let profileButton;
@ -205,9 +212,19 @@ const ProfileDisplayJsx = ({navigation}) => {
{
state.own ?
<View style = { styles.profileContextContainer }>
<TouchableOpacity>
<TouchableOpacity
onPress = {
() => {
navigation.navigate("Notifications");
}
}>
<Image
source = { activeOrNot(state.unread_notifs, notif_pack) }
source = {
activeOrNot(
state.unreadNotifications,
notif_pack
)
}
style = { styles.profileHeaderIcon } />
</TouchableOpacity>
</View>

View File

@ -1,14 +1,439 @@
import React, { useState } from "react";
import { ScreenWithTrayJsx } from "src/components/navigation/navigators";
import React, { useState, useEffect } from "react";
import {
Dimensions,
View,
TouchableOpacity,
Image,
Text,
} from "react-native";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { ScreenWithBackBarJsx } from "src/components/navigation/navigators";
const TEST_IMAGE = "https://cache.desktopnexus.com/thumbseg/2255/2255124-bigthumbnail.jpg";
const TEST_NOTIFICATIONS = [
{
id: 1,
type: "follow",
account: {
acct: "njms",
avatar: TEST_IMAGE,
},
},
{
id: 2,
type: "follow_request",
account: {
acct: "njms",
avatar: TEST_IMAGE,
},
},
{
id: 3,
type: "mention",
account: {
acct: "njms",
avatar: TEST_IMAGE,
},
status: {
id: 1,
media_attachments: [],
content: "This is a message",
}
},
{
id: 4,
type: "mention",
account: {
acct: "njms",
avatar: TEST_IMAGE,
},
status: {
id: 1,
media_attachments: [
{ url: TEST_IMAGE }
],
content: "This is a message",
}
},
{
id: 5,
type: "mention",
account: {
acct: "njms",
avatar: TEST_IMAGE,
},
status: {
id: 1,
media_attachments: [
{ url: TEST_IMAGE }
],
content: "This is a really really really really really really"
+ " really really really really really really long message",
}
},
{
id: 6,
type: "reblog",
account: {
acct: "njms",
avatar: TEST_IMAGE,
},
status: {
id: 1,
media_attachments: [
{ url: TEST_IMAGE }
],
}
},
{
id: 7,
type: "favourite",
account: {
acct: "njms",
avatar: TEST_IMAGE,
},
status: {
id: 1,
media_attachments: [
{ url: TEST_IMAGE }
],
}
},
{
id: 8,
type: "status",
account: {
acct: "njms",
avatar: TEST_IMAGE,
},
status: {
id: 1,
media_attachments: [
{ url: TEST_IMAGE }
],
}
},
]
function navigateProfileFactory(nav, acct) {
return () => {
nav.navigate("ViewProfile", {
acct: acct,
});
};
}
function navigatePostFactory(nav, id) {
return () => {
nav.navigate("ViewPost", {
originTab: "Profile",
id: id,
});
}
}
function renderNotification(notif, navigation) {
switch(notif.type) {
case "follow":
return <FollowJsx
data = { notif }
key = { notif.id }
navigation = { navigation } />
case "follow_request":
return <FollowRequestJsx
data = { notif }
key = { notif.id }
navigation = { navigation } />
case "mention":
return <MentionJsx
data = { notif }
key = { notif.id }
navigation = { navigation } />
case "reblog":
return <ReblogJsx
data = { notif }
key = { notif.id }
navigation = { navigation } />
case "favourite":
return <FavouriteJsx
data = { notif }
key = { notif.id }
navigation = { navigation } />
case "status":
return <StatusJsx
data = { notif }
key = { notif.id }
navigation = { navigation } />
default:
// We're not expecting polls to be super popular on Pixelfed
return <></>
}
}
const UserTextJsx = (props) => {
return (
<Text
style = { styles.bold }
onPress = {
() => {
props.navigation.navigate("ViewProfile", {
acct: props.acct
});
}
}>
{ props.acct }&nbsp;
</Text>
);
};
const NotificationJsx = (props) => {
return (
<View style = { styles.notif.container }>
<View style = { styles.notif.thumbnailContainer }>
<TouchableOpacity
onPress = { props.thumbnailPressCallback }>
<Image
style = {
[
styles.notif.thumbnail,
props.thumbnailStyles
]
}
source = { { uri: props.thumbnail } } />
</TouchableOpacity>
</View>
<View style = { styles.notif.contentContainer }>
{ props.children }
</View>
{ props.button ?
<View style = { styles.notif.buttonContainer }>
<TouchableOpacity
style = { styles.notif.button }
onPress = { props.buttonCallback }>
<Text>{ props.buttonLabel }</Text>
</TouchableOpacity>
</View>
: <></>
}
</View>
);
};
const FollowJsx = (props) => {
return (
<NotificationJsx
thumbnail = { props.data.account.avatar }
thumbnailStyles = { styles.notif.circularThumbnail }
thumbnailPressCallback = {
navigateProfileFactory(props.navigation, props.data.account.acct)
}>
<Text style = { styles.notif.content }>
<UserTextJsx acct = { props.data.account.acct } />
has followed you.
</Text>
</NotificationJsx>
);
};
const FollowRequestJsx = (props) => {
return (
<NotificationJsx
thumbnail = { props.data.account.avatar }
thumbnailStyles = { styles.notif.circularThumbnail }
thumbnailPressCallback = {
navigateProfileFactory(props.navigation, props.data.account.acct)
}
button = { true }
buttonLabel = { "Accept" }
buttonCallback = { () => console.log("Request accepted") }>
<Text style = { styles.notif.content }>
<UserTextJsx acct = { props.data.account.acct } />
has requested to follow you.
</Text>
</NotificationJsx>
);
};
const MentionJsx = (props) => {
let uri;
let imageStyle;
let thumbnailCallback;
if (props.data.status.media_attachments.length > 0) {
// If it's a comment...
uri = props.data.status.media_attachments[0].url;
imageStyle = {};
thumbnailCallback = navigatePostFactory(
props.navigation,
props.data.status.id
);
} else {
// If it's a reply to your comment...
uri = props.data.account.avatar;
imageStyle = styles.notif.circularThumbnail;
thumbnailCallback = navigateProfileFactory(
props.navigation,
props.data.account.acct
);
}
return (
<NotificationJsx
thumbnail = { uri }
thumbnailStyles = { imageStyle }i
thumbnailPressCallback = { thumbnailCallback }>
<Text style = { styles.notif.content }>
<UserTextJsx acct = { props.data.account.acct } />
mentioned you:
<Text style = { styles.notif.status }>
"{ props.data.status.content }"
</Text>
</Text>
</NotificationJsx>
);
};
const ReblogJsx = (props) => {
return (
<NotificationJsx
thumbnail = { props.data.status.media_attachments[0].url }
thumbnailPressCallback = {
navigatePostFactory(props.navigation, props.data.status.id)
}>
<Image
style = { styles.notif.inlineIcon }
source = { require("assets/eva-icons/post-actions/boost-full.png") } />
<Text style = { styles.notif.content }>
<UserTextJsx acct = { props.data.account.acct } />
shared your post.
</Text>
</NotificationJsx>
);
};
const FavouriteJsx = (props) => {
return (
<NotificationJsx
thumbnail = { props.data.status.media_attachments[0].url }
thumbnailPressCallback = {
navigatePostFactory(props.navigation, props.data.status.id)
}>
<Image
style = { styles.notif.inlineIcon }
source = { require("assets/eva-icons/post-actions/heart-active.png") } />
<Text style = { styles.notif.content }>
<UserTextJsx acct = { props.data.account.acct } />
liked your post.
</Text>
</NotificationJsx>
);
};
const StatusJsx = (props) => {
return (
<NotificationJsx
thumbnail = { props.data.status.media_attachments[0].url }
thumbnailPressCallback = {
navigatePostFactory(props.navigation, props.data.status.id)
}>
<Text style = { styles.notif.content }>
<UserTextJsx acct = { props.data.account.acct } />
just posted.
</Text>
</NotificationJsx>
);
};
const NotificationsJsx = ({navigation}) => {
return (
<ScreenWithTrayJsx
active = "Notifications"
navigation = { navigation }>
const [state, setState] = useState({
loaded: false,
});
</ScreenWithTrayJsx>
useEffect(() => {
const read = JSON.stringify({
unread: false,
memory: [
{ id: 1 },
{ id: 2 },
{ id: 3 },
]
});
AsyncStorage.mergeItem("@user_notifications", read)
.then(() => {
setState({...state,
notifications: TEST_NOTIFICATIONS,
loaded: true
})
});
}, []);
return (
<ScreenWithBackBarJsx
navigation = { navigation }>
{ state.loaded ?
<View>
{
state.notifications.map(notif =>
renderNotification(notif, navigation)
)
}
</View>
: <></>
}
</ScreenWithBackBarJsx>
);
}
const SCREEN_WIDTH = Dimensions.get("window").width;
const styles = {
notif: {
container: {
flexDirection: "row",
alignItems: "center",
paddingLeft: 20,
marginTop: 10,
marginBottom: 10,
},
circularThumbnail: { borderRadius: SCREEN_WIDTH / 16 },
thumbnailContainer: {
marginRight: 10,
},
thumbnail: {
width: SCREEN_WIDTH / 8,
height: SCREEN_WIDTH / 8,
},
contentContainer: {
flexShrink: 1,
flexDirection: "row",
alignItems: "center",
},
inlineIcon: {
width: 20,
height: 20,
marginRight: 10,
},
status: { fontStyle: "italic" },
buttonContainer: {
marginLeft: "auto",
marginRight: 10,
},
button: {
borderWidth: 1,
borderColor: "#888",
borderRadius: 10,
padding: 10,
},
},
bold: { fontWeight: "bold" },
};
export default NotificationsJsx;

View File

@ -10,6 +10,8 @@ import {
Dimensions,
} from "react-native";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { withoutHTML } from "src/interface/rendering";
import { ScreenWithBackBarJsx } from "src/components/navigation/navigators";
@ -203,7 +205,17 @@ const SettingsJsx = (props) => {
<TouchableOpacity style = { styles.largeButton }>
<Text> Save Profile </Text>
</TouchableOpacity>
<TouchableOpacity style = { styles.largeButton }>
<TouchableOpacity
style = { styles.largeButton }
onPress = {
() => {
AsyncStorage.multiRemove(
["@user_profile", "@user_notifications"]
).then(() => {
props.navigation.navigate("Authenticate");
});
}
}>
<Text style = { styles.textWarning }> Log out </Text>
</TouchableOpacity>
</View>

View File

@ -3,14 +3,20 @@ import React from "react";
import { ScreenWithFullNavigationJsx } from "src/components/navigation/navigators";
import { PostByIdJsx } from "src/components/posts/post";
const ViewPostJsx = (props) => {
const ViewPostJsx = ({navigation}) => {
const id = navigation.getParam("id", undefined);
if (id == undefined) {
throw Error("ID not specified when navigating to ViewPost!");
}
return (
<ScreenWithFullNavigationJsx
active = { props.navigation.getParam("originTab", "Timeline") }
navigation = { props.navigation }>
active = { navigation.getParam("originTab", "Timeline") }
navigation = { navigation }>
<PostByIdJsx
navigation = { props.navigation }
id = { props.id } />
navigation = { navigation }
id = { id } />
</ScreenWithFullNavigationJsx>
);
}

View File

@ -1,7 +1,7 @@
import React from "react";
import { View, Dimensions, Image } from "react-native";
import GridPostJsx from "src/components/posts/grid-post"
import GridPostJsx from "src/components/posts/grid-post";
function partition(arr, size) {
let newArray = [];

View File

@ -1,5 +1,5 @@
import React, { useState, useEffect } from "react";
import { View, TouchableWithoutFeedback, Text } from "react-native";
import { View, TouchableOpacity, Text } from "react-native";
import GridViewJsx from "src/components/posts/grid-view";
@ -60,16 +60,16 @@ const PagedGridJSX = (props) => {
}
} />
<View style = { styles.buttonContainer }>
<TouchableWithoutFeedback
<TouchableOpacity
onPress = { () => {
// TODO: actually get more posts :)
let morePosts = state.posts.concat(TEST_POSTS);
setState({ posts: morePosts, loaded: true });
setState({...state, posts: morePosts});
} }>
<View style = { styles.buttonMore }>
<Text>Show more?</Text>
</View>
</TouchableWithoutFeedback>
</TouchableOpacity>
</View>
</View>
);
@ -82,7 +82,8 @@ const styles = {
alignItems: "center"
},
buttonMore: {
border: "2px solid black",
borderWidth: 1,
borderColor: "#888",
borderRadius: 5,
padding: 10,
margin: 20

30
src/requests.js Normal file
View File

@ -0,0 +1,30 @@
import AsyncStorage from "@react-native-async-storage/async-storage";
const TEST_NOTIFICATIONS = [{ id: 1 }, { id: 2 }];
const TEST_NEW_NOTIFICATIONS_1 = [{ id: 1 }, { id: 2 }];
const TEST_NEW_NOTIFICATIONS_2 = [{ id: 1 }, { id: 2 }, { id: 3 }];
export async function checkUnreadNotifications() {
// If the check has already been made since the last time notifications.js
// has been opened
const notifications = JSON.parse(await AsyncStorage.getItem("@user_notifications"));
if (notifications.unread) {
return true;
} else {
// Some promise to get new notifications
const newNotifs = await Promise.resolve(TEST_NEW_NOTIFICATIONS_2);
const isUnread = JSON.stringify(newNotifs) != JSON.stringify(notifications.memory);
// Update stored notifications
await AsyncStorage.setItem(
"@user_notifications",
JSON.stringify({...notifications,
unread: isUnread,
})
);
return isUnread;
}
}