Add new page direct.js and conversation.js

This commit is contained in:
Nat 2021-04-15 14:43:45 -03:00
parent 341fc61116
commit 314de081de
5 changed files with 528 additions and 14 deletions

View File

@ -15,6 +15,8 @@ import ProfileJsx, { ViewProfileJsx } from "src/components/pages/profile";
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 DirectJsx from "src/components/pages/direct";
import ConversationJsx from "src/components/pages/direct/conversation";
import NotificationsJsx from 'src/components/pages/profile/notifications';
import UserListJsx from "src/components/pages/user-list.js";
import SettingsJsx from "src/components/pages/profile/settings.js";
@ -23,6 +25,8 @@ const Stack = createStackNavigator({
Authenticate: { screen: AuthenticateJsx },
Feed: { screen: FeedJsx, },
Discover: { screen: DiscoverJsx },
Direct: { screen: DirectJsx },
Conversation: { screen: ConversationJsx },
Notifications: { screen: NotificationsJsx },
Profile: { screen: ProfileJsx, },
Settings: { screen: SettingsJsx },

View File

@ -1,33 +1,44 @@
import React from "react";
import { Image } from "react-native";
import { TouchableWithoutFeedback, View } from "react-native";
import { TouchableOpacity, View } from "react-native";
const BackBarJsx = (props) => {
const backIcon = require("assets/eva-icons/back.png");
return (
<View style = { styles.nav }>
<TouchableWithoutFeedback
onPress = { () => props.navigation.goBack() }>
<TouchableOpacity
onPress = { () => props.navigation.goBack() }
style = { styles.button }>
<Image
style = { styles.button }
style = { styles.chevron }
source = { backIcon }/>
</TouchableWithoutFeedback>
</TouchableOpacity>
<View style = { styles.rest }>
{ props.children }
</View>
</View>
);
};
const styles = {
nav: {
padding: 15,
borderBottom: "2px solid #CCC",
backgroundColor: "white"
backgroundColor: "white",
flexDirection: "row",
alignItems: "center",
},
rest: {
flexGrow: 1,
},
chevron: {
width: 30,
height: 30,
},
button: {
width: 30,
height: 30
}
padding: 10,
},
}
export default BackBarJsx;

View File

@ -36,7 +36,12 @@ export const ScreenWithBackBarJsx = (props) => {
return (
<ContextJsx>
<View style = { { flex: 1 } }>
<BackBarJsx navigation = { props.navigation } />
<BackBarJsx navigation = { props.navigation }>
{ props.renderBackBar != undefined
? props.renderBackBar()
: <></>
}
</BackBarJsx>
<ScrollView>
{ props.children }
</ScrollView>
@ -49,7 +54,12 @@ export const ScreenWithFullNavigationJsx = (props) => {
return (
<ContextJsx>
<View style = { { flex: 1 } }>
<BackBarJsx navigation = { props.navigation } />
<BackBarJsx navigation = { props.navigation }>
{ props.renderBackBar != undefined
? props.renderBackBar()
: <></>
}
</BackBarJsx>
<ScrollView>
{ props.children }
</ScrollView>

View File

@ -0,0 +1,195 @@
import React, { useState, useEffect } from "react";
import {
View,
Text,
TouchableOpacity,
Image,
FlatList,
TextInput,
Dimensions,
} from "react-native";
import { Ionicons } from "@expo/vector-icons";
import { ScreenWithTrayJsx } from "src/components/navigation/navigators";
import ModerateMenuJsx from "src/components/moderate-menu.js";
const TEST_IMAGE_1 = "https://cache.desktopnexus.com/thumbseg/2255/2255124-bigthumbnail.jpg";
const TEST_IMAGE_2 = "https://natureproducts.net/Forest_Products/Cutflowers/Musella_cut.jpg";
const TEST_ACCOUNT_1 = { acct: "njms", display_name: "Nat🔆", avatar: TEST_IMAGE_1 };
const TEST_ACCOUNT_2 = { acct: "someone", display_name: "Some person", avatar: TEST_IMAGE_2 };
const TEST_STATUS = {
id: 1,
account: TEST_ACCOUNT_1,
content: "This is a direct message",
};
function filterConversations(convs, query) {
return convs.filter(conv => {
const accts = conv.accounts.map(account => account.acct).join();
const names = conv.accounts.map(account => account.display_name).join();
return accts.includes(query) || names.includes(query)
});
}
const DirectJsx = ({ navigation }) => {
const [state, setState] = useState({
loaded: false,
query: ""
});
useEffect(() => {
setState({...state,
loaded: true,
conversations: [
{
id: 1,
unread: true,
accounts: [TEST_ACCOUNT_1],
last_status: TEST_STATUS,
},
{
id: 2,
unread: false,
accounts: [TEST_ACCOUNT_1, TEST_ACCOUNT_2],
last_status: TEST_STATUS,
}
],
});
}, []);
const onPressConversationFactory = (conv) => {
return () => {
navigation.navigate("Conversation", {
conversation: conv,
});
}
};
const renderConversation = ({ item }) => {
const boldIfUnread = item.unread ? styles.bold : {};
return <View style = { [styles.row, styles.conv.container] }>
<TouchableOpacity
style = { [styles.row, styles.conv.containerButton] }
onPress = {
onPressConversationFactory(item)
}>
<View style = { styles.conv.avatar.container }>
<Image
source = { { uri: item.accounts[0].avatar } }
style = { styles.conv.avatar.image }/>
</View>
<View style = { styles.conv.body }>
<Text style = { boldIfUnread }>
{ item.accounts.map(account => account.acct).join(", ") }
</Text>
<Text style = { boldIfUnread }>
{
// Prefix message with acct
[
item.accounts.length > 1 ?
item.last_status.account.acct + ": "
: "",
item.last_status.content,
].join("")
}
</Text>
</View>
</TouchableOpacity>
<View style = { styles.conv.context }>
<ModerateMenuJsx
triggerStyle = { styles.menu.trigger } />
</View>
</View>
};
return (
<ScreenWithTrayJsx
navigation = { navigation }
originTab = "Direct">
<View style = { [ styles.row, styles.form.container ] }>
<TextInput
placeholder = "Search..."
value = { state.query }
style = { styles.form.searchBar }
onChangeText = {
(value) => {
setState({...state,
query: value,
});
}
}/>
<TouchableOpacity
style = { styles.form.compose }>
<Ionicons name = "md-create" size = { 24 } color = "black"/>
</TouchableOpacity>
</View>
{ state.loaded ?
<FlatList
data = { filterConversations(state.conversations, state.query) }
renderItem = { renderConversation }
keyExtractor = { conv => conv.id }/>
: <></>
}
</ScreenWithTrayJsx>
);
};
const SCREEN_WIDTH = Dimensions.get("window").width;
const styles = {
row: {
flexDirection: "row",
alignItems: "center",
},
form: {
container: {
marginLeft: 20,
marginTop: 20,
marginBottom: 20,
},
searchBar: {
padding: 10,
width: SCREEN_WIDTH * 3 / 4,
borderWidth: 1,
borderRadius: 5,
borderColor: "#888",
},
compose: {
marginLeft: "auto",
marginRight: "auto",
},
},
conv: {
container: {
paddingBottom: 20,
paddingLeft: 10,
paddingRight: 20,
},
containerButton: { flexGrow: 1 },
avatar: {
image: {
width: 40,
height: 40,
borderRadius: 20,
}
},
body: {
marginLeft: 10,
},
context: {
marginLeft: "auto",
}
},
menu: {
trigger: {
width: 20,
height: 20,
},
},
bold: { fontWeight: "bold" },
};
export { DirectJsx as default };

View File

@ -0,0 +1,294 @@
import React, { useState, useEffect } from "react";
import {
View,
Text,
Image,
TextInput,
FlatList,
ScrollView,
Dimensions,
TouchableOpacity,
} from "react-native";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { Ionicons } from "@expo/vector-icons";
import {
Menu,
MenuOptions,
MenuOption,
MenuTrigger,
renderers
} from "react-native-popup-menu";
const { SlideInMenu } = renderers;
import BackBarJsx from "src/components/navigation/back-bar";
import { ContextJsx } from "src/components/navigation/navigators";
import { timeToAge } from "src/interface/rendering";
const TEST_IMAGE_1 = "https://cache.desktopnexus.com/thumbseg/2255/2255124-bigthumbnail.jpg";
const TEST_IMAGE_2 = "https://natureproducts.net/Forest_Products/Cutflowers/Musella_cut.jpg";
const TEST_ACCOUNT_1 = { acct: "someone", display_name: "Someone", avatar: TEST_IMAGE_1 };
const TEST_ACCOUNT_2 = { acct: "someone_else", display_name: "Another person", avatar: TEST_IMAGE_2 };
const TEST_STATUS = {
account: TEST_ACCOUNT_1,
content: "This is a direct message",
created_at: 1596745156000,
};
const TEST_MESSAGES = [
{ ...TEST_STATUS, id: 1 },
{ ...TEST_STATUS, id: 2, account: TEST_ACCOUNT_2 },
{ ...TEST_STATUS, id: 3 },
{ ...TEST_STATUS, id: 4, account: { acct: "njms" } },
{ ...TEST_STATUS, id: 5 },
];
const ConversationJsx = ({ navigation }) => {
const conversation = navigation.getParam("conversation", {});
const [state, setState] = useState({
loaded: false,
newMessage: "",
});
useEffect(() => {
// Get the context of last_status, then profile from AsyncStorage
AsyncStorage.getItem("@user_profile").then((profile) => {
setState({...state,
loaded: true,
profile: JSON.parse(profile),
messages: TEST_MESSAGES,
});
});
}, []);
const accountListOptionsStyles = {
optionWrapper: { // The wrapper around a single option
flexDirection: "row",
alignItems: "center",
paddingLeft: SCREEN_WIDTH / 15,
paddingTop: SCREEN_WIDTH / 30,
paddingBottom: SCREEN_WIDTH / 30
},
optionsWrapper: { // The wrapper around all options
marginTop: SCREEN_WIDTH / 20,
marginBottom: SCREEN_WIDTH / 20,
},
optionsContainer: { // The Animated.View
borderTopLeftRadius: 10,
borderTopRightRadius: 10
}
};
const renderBackBar = () => (
<View style = { [ styles.row, styles.backBar.container ] }>
<Menu renderer = { SlideInMenu }>
<MenuTrigger>
<View style = { styles.row }>
<Image
source = { { uri: conversation.last_status.account.avatar } }
style = { styles.backBar.avatar } />
<Text style = { styles.bold }>
{
conversation.accounts
.slice(0, 3) // Take first 3 accounts only
.map(account => account.acct)
.join(", ")
}
</Text>
</View>
</MenuTrigger>
<MenuOptions customStyles = { accountListOptionsStyles }>
{
conversation.accounts.map(account =>
<MenuOption>
<Image
source = { { uri: account.avatar } }
style = { styles.backBar.accountList.avatar }/>
<View>
<Text style = { styles.bold }>
@{ account.acct }
</Text>
<Text>
{ account.display_name }
</Text>
</View>
</MenuOption>
)
}
</MenuOptions>
</Menu>
</View>
);
const renderMessage = ({ item }) => {
const yours = state.profile.acct == item.account.acct;
return <>
{ !yours
? <Text style = { styles.message.acct }>
{ item.account.acct }
</Text>
: <></>
}
<View style = { styles.message.container }>
{ !yours
? <Image
source = { { uri: item.account.avatar } }
style = { styles.message.avatar }/>
: <></>
}
<View
style = {
[
yours
? styles.message.yourBubble
: {},
styles.message.bubble,
]
}>
<Text style = { yours ? styles.message.yourText : {} }>
<Text>
{ item.content + "\n" }
</Text>
<Text style = { styles.message.age }>
{ timeToAge(item.created_at) }
</Text>
</Text>
</View>
</View>
</>;
};
return (
<ContextJsx>
<View style = { { flex: 1 } }>
<BackBarJsx navigation = { navigation }>
{ renderBackBar() }
</BackBarJsx>
<ScrollView>
{ state.loaded
? <FlatList
data = { state.messages }
renderItem = { renderMessage }
keyExtractor = { item => item.id }/>
: <></>
}
</ScrollView>
<View style = { [ styles.row, styles.send.container ] }>
<TextInput
placeholder = "Say something..."
multiline
value = { state.newMessage }
style = { styles.send.input }
onChangeText = {
value => {
setState({...state,
newMessage: value,
});
}
}/>
<TouchableOpacity style = { styles.send.button }>
<Ionicons
name = "ios-send"
size = { 24 }
color = "black" />
</TouchableOpacity>
</View>
</View>
</ContextJsx>
);
};
const SCREEN_WIDTH = Dimensions.get("window").width;
const styles = {
row: {
flexDirection: "row",
alignItems: "center",
},
backBar: {
accountList: {
avatar: {
width: 40,
height: 40,
borderRadius: 20,
marginRight: 10,
},
},
container: {
marginLeft: 20,
paddingTop: 10,
paddingBottom: 10,
},
avatar: {
width: 40,
height: 40,
borderRadius: 20,
marginRight: 10,
},
},
message: {
container: {
paddingTop: 5,
paddingBottom: 10,
paddingLeft: 10,
paddingRight: 10,
flexDirection: "row",
},
acct: {
paddingLeft: 60,
fontSize: 12,
color: "#888",
},
avatar: {
width: 40,
height: 40,
borderRadius: 20,
marginRight: 10,
},
bubble: {
width: SCREEN_WIDTH * 3/4,
borderWidth: 1,
borderColor: "#888",
borderRadius: 10,
padding: 10,
},
yourBubble: {
backgroundColor: "#CCC",
marginLeft: "auto",
},
yourText: {
//color: "white",
textAlign: "right",
},
age: {
fontSize: 10,
color: "#888",
},
},
send: {
container: {
marginTop: 10,
marginBottom: 10,
marginLeft: 10,
},
input: {
padding: 10,
borderWidth: 1,
borderColor: "#888",
borderRadius: 5,
flexGrow: 1,
},
button: {
marginLeft: 10,
marginRight: 10,
}
},
bold: { fontWeight: "bold", },
};
export { ConversationJsx as default };