Add new page direct.js and conversation.js
This commit is contained in:
parent
341fc61116
commit
314de081de
|
@ -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 },
|
||||
|
|
|
@ -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;
|
|
@ -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>
|
||||
|
|
|
@ -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 };
|
|
@ -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 };
|
Loading…
Reference in New Issue