From ad4e0c4674140fb399b9ec2a812599a8f622106b Mon Sep 17 00:00:00 2001 From: Rexogamer Date: Sun, 8 Oct 2023 20:03:50 +0100 Subject: [PATCH] feat: switch to RNBS --- package.json | 1 + src/Generic.tsx | 6 +- src/Modals.tsx | 229 +--- src/components/common/BottomSheet.tsx | 43 + src/components/sheets/ChannelInfoSheet.tsx | 103 +- src/components/sheets/MemberListSheet.tsx | 86 +- src/components/sheets/MessageMenuSheet.tsx | 288 +++-- src/components/sheets/ProfileSheet.tsx | 1232 +++++++++++--------- src/components/sheets/ReportSheet.tsx | 657 ++++++----- src/components/sheets/ServerInfoSheet.tsx | 262 +++-- src/components/sheets/StatusSheet.tsx | 119 +- src/components/sheets/UserMenuSheet.tsx | 66 -- src/components/sheets/index.tsx | 3 +- src/components/views/ChannelView.tsx | 9 +- yarn.lock | 5 + 15 files changed, 1593 insertions(+), 1516 deletions(-) create mode 100644 src/components/common/BottomSheet.tsx delete mode 100644 src/components/sheets/UserMenuSheet.tsx diff --git a/package.json b/package.json index b8dcf0c..7f4ef8b 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "@notifee/react-native": "^7.8.0", "@react-native-async-storage/async-storage": "^1.19.3", "@react-native-clipboard/clipboard": "^1.11.2", + "@react-native-community/hooks": "^3.0.0", "@react-native/eslint-config": "^0.73.0", "@react-native/metro-config": "^0.73.0", "@tradle/react-native-http": "^2.0.1", diff --git a/src/Generic.tsx b/src/Generic.tsx index 864f4d2..30d1346 100644 --- a/src/Generic.tsx +++ b/src/Generic.tsx @@ -7,7 +7,7 @@ import FastImage from 'react-native-fast-image'; import MaterialIcon from 'react-native-vector-icons/MaterialIcons'; import MaterialCommunityIcon from 'react-native-vector-icons/MaterialCommunityIcons'; -import {API, Channel, Client, Server, User} from 'revolt.js'; +import {API, Channel, Client, Server} from 'revolt.js'; import {currentTheme, setTheme, themes, styles} from './Theme'; import {Button, Text} from './components/common/atoms'; @@ -310,10 +310,10 @@ export const app = { pushToQueue: m => {}, joinInvite: async (i: API.InviteResponse) => {}, logOut: () => {}, - openMemberList: (c: Channel | Server | null, u: User[] | null) => {}, + openMemberList: (data: Channel | Server | null) => {}, openChannelContextMenu: (c: Channel | null) => {}, openStatusMenu: (state: boolean) => {}, - openReportMenu: (object: ReportedObject| null) => {}, + openReportMenu: (object: ReportedObject | null) => {}, }; export function setFunction(name: string, func: any) { diff --git a/src/Modals.tsx b/src/Modals.tsx index f68379e..4eb1027 100644 --- a/src/Modals.tsx +++ b/src/Modals.tsx @@ -1,16 +1,14 @@ import React from 'react'; -import {Modal, Pressable, ScrollView, View} from 'react-native'; +import {Modal, Pressable, View} from 'react-native'; import {observer} from 'mobx-react-lite'; -import BottomSheet from '@gorhom/bottom-sheet'; -import Modal2 from 'react-native-modal'; import ImageViewer from 'react-native-image-zoom-viewer'; import MaterialCommunityIcon from 'react-native-vector-icons/MaterialCommunityIcons'; -import {API, Channel, Message, Server, User} from 'revolt.js'; +import {API, Channel, User} from 'revolt.js'; import {app, client, openUrl, setFunction} from './Generic'; -import {styles, currentTheme} from './Theme'; +import {currentTheme} from './Theme'; import {GapView} from './components/layout'; import { BotInviteSheet, @@ -24,88 +22,12 @@ import { SettingsSheet, StatusSheet, } from './components/sheets/'; -import type {ReportedObject} from './lib/types'; - -const MBottomSheet = observer( - ({ - sheetKey, - visible, - callback, - includeScrollView, - children, - }: { - sheetKey: string; - visible: boolean; - callback: Function; - includeScrollView?: boolean; - children: any; - }) => { - return ( - callback()} - onBackButtonPress={() => callback()} - swipeDirection={'down'} - onSwipeComplete={() => callback()} - propagateSwipe - style={{ - width: '100%', - marginHorizontal: 0, - marginBottom: 0, - justifyContent: 'flex-end', - }}> - - - {includeScrollView ? {children} : children} - - - ); - }, -); export const Modals = observer(() => { - const [messageMenuState, setMessageMenuState] = React.useState({ - showMessageMenu: false, - contextMenuMessage: null, - } as {showMessageMenu: boolean; contextMenuMessage: Message | null}); - const [statusMenuState, setStatusMenuState] = React.useState(false); - const [profileMenuState, setProfileMenuState] = React.useState({ - showUserMenu: false, - contextMenuUser: null, - contextMenuUserServer: null, - } as { - showUserMenu: boolean; - contextMenuUser: User | null; - contextMenuUserServer: Server | null; - }); const [imageViewerState, setImageViewerState] = React.useState({ i: null as any, }); const [settingsVisibility, setSettingsVisibility] = React.useState(false); - const [reportMenuState, setReportMenuState] = React.useState({ - showReportMenu: false, - reportObject: null, - } as { - showReportMenu: boolean; - reportObject: ReportedObject | null; - }); - const [channelMenuState, setChannelMenuState] = React.useState({ - showChannelMenu: false, - channelMenuChannel: null, - } as { - showChannelMenu: boolean; - channelMenuChannel: Channel | null; - }); const [inviteServer, setInviteServer] = React.useState({ inviteServer: null, inviteServerCode: '', @@ -114,49 +36,9 @@ export const Modals = observer(() => { inviteServerCode: string; }); const [inviteBot, setInviteBot] = React.useState(null as User | null); - const [serverMenuState, setServerMenuState] = React.useState({ - showServerMenu: false, - contextMenuServer: null, - } as { - showServerMenu: boolean; - contextMenuServer: Server | null; - }); - const [memberListState, setMemberListState] = React.useState({ - showMemberList: false, - memberListContext: null, - memberListUsers: null, - } as { - showMemberList: boolean; - memberListContext: Channel | /* Server | */ null; - memberListUsers: User[] | null; - }); - setFunction('openMessage', async (m: Message | null) => { - setMessageMenuState( - m - ? {showMessageMenu: true, contextMenuMessage: m} - : {...messageMenuState, showMessageMenu: false}, - ); - }); - setFunction('openStatusMenu', async (show: boolean) => { - setStatusMenuState(show); - }); - setFunction('openProfile', async (u: User | null, s: Server | null) => { - setProfileMenuState( - u - ? { - showUserMenu: true, - contextMenuUser: u, - contextMenuUserServer: s, - } - : {...profileMenuState, showUserMenu: false}, - ); - }); setFunction('openDirectMessage', async (dm: Channel) => { - setProfileMenuState({ - ...profileMenuState, - showUserMenu: false, - }); + app.openProfile(null); app.openChannel(dm); }); setFunction('openImage', async (a: any) => { @@ -165,20 +47,6 @@ export const Modals = observer(() => { setFunction('openSettings', async (o: boolean) => { setSettingsVisibility(o); }); - setFunction('openReportMenu', async (object: ReportedObject | null) => { - setReportMenuState( - object - ? {showReportMenu: true, reportObject: object} - : {...reportMenuState, showReportMenu: false}, - ); - }); - setFunction('openChannelContextMenu', async (channel: Channel | null) => { - setChannelMenuState( - channel - ? {showChannelMenu: true, channelMenuChannel: channel} - : {...channelMenuState, showChannelMenu: false}, - ); - }); setFunction('openInvite', async (i: string) => { try { let community = await client.fetchInvite(i); @@ -195,59 +63,16 @@ export const Modals = observer(() => { setFunction('openBotInvite', async (id: string) => { setInviteBot(await client.bots.fetchPublic(id).catch(e => e)); }); - setFunction('openServerContextMenu', async (s: Server | null) => { - setServerMenuState( - s - ? {showServerMenu: true, contextMenuServer: s} - : {...serverMenuState, showServerMenu: false}, - ); - }); - setFunction( - 'openMemberList', - async (context: Channel | /* Server | */ null, users: User[] | null) => { - setMemberListState( - context - ? { - showMemberList: true, - memberListContext: context, - memberListUsers: users, - } - : {...memberListState, showMemberList: false}, - ); - }, - ); return ( <> - app.openMessage(null)}> - { - app.openMessage(null); - }} - message={messageMenuState.contextMenuMessage!} - /> - - { - app.openStatusMenu(false); - }}> - - - app.openProfile(null)} - includeScrollView> - - + + + + + + + { onRequestClose={() => setSettingsVisibility(false)}> setSettingsVisibility(false)} /> - app.openReportMenu(null)}> - - - app.openChannelContextMenu(null)}> - - { bot={inviteBot!} /> - app.openServerContextMenu(null)}> - { - app.openServerContextMenu(null); - }} - server={serverMenuState.contextMenuServer!} - /> - - app.openMemberList(null, null)}> - - ); }); diff --git a/src/components/common/BottomSheet.tsx b/src/components/common/BottomSheet.tsx new file mode 100644 index 0000000..b4cc3b8 --- /dev/null +++ b/src/components/common/BottomSheet.tsx @@ -0,0 +1,43 @@ +import React, {useMemo} from 'react'; +import {StyleSheet} from 'react-native'; + +import BottomSheetCore, { + BottomSheetBackdrop, + BottomSheetScrollView, +} from '@gorhom/bottom-sheet'; +import {observer} from 'mobx-react-lite'; + +import {currentTheme} from '../../Theme'; + +export const BottomSheet = observer( + ({sheetRef, children}: {sheetRef: any; children: any}) => { + const snapPoints = useMemo(() => ['50%', '70%', '90%'], []); + + return ( + + {children} + + ); + }, +); + +const localStyles = StyleSheet.create({ + sheetBackground: { + backgroundColor: currentTheme.backgroundSecondary, + }, + handleIndicator: { + backgroundColor: currentTheme.foregroundPrimary, + width: '25%', + padding: 3, + marginVertical: 8, + }, +}); diff --git a/src/components/sheets/ChannelInfoSheet.tsx b/src/components/sheets/ChannelInfoSheet.tsx index 45fbadd..d70ae0c 100644 --- a/src/components/sheets/ChannelInfoSheet.tsx +++ b/src/components/sheets/ChannelInfoSheet.tsx @@ -1,18 +1,43 @@ -import React from 'react'; -import {ScrollView, View} from 'react-native'; +import React, {useRef} from 'react'; +import {View} from 'react-native'; import {observer} from 'mobx-react-lite'; +import BottomSheetCore from '@gorhom/bottom-sheet'; +import {useBackHandler} from '@react-native-community/hooks'; + import {Channel, User} from 'revolt.js'; +import {setFunction} from '../../Generic'; import {currentTheme} from '../../Theme'; import {Text} from '../common/atoms'; +import {BottomSheet} from '../common/BottomSheet'; import {MarkdownView} from '../common/MarkdownView'; -export const ChannelInfoSheet = observer(({channel}: {channel: Channel}) => { +export const ChannelInfoSheet = observer(() => { + const [channel, setChannel] = React.useState(null as Channel | null); const [groupMembers, setGroupMembers] = React.useState([] as User[]); + const sheetRef = useRef(null); + + useBackHandler(() => { + if (channel) { + sheetRef.current?.close(); + return true; + } + + return false; + }); + + setFunction('openChannelContextMenu', async (c: Channel | null) => { + setChannel(c); + c ? sheetRef.current?.expand() : sheetRef.current?.close(); + }); + React.useEffect(() => { async function fetchMembers() { + if (!channel) { + return; + } const m = channel.channel_type === 'Group' ? await channel.fetchMembers() : []; setGroupMembers(m); @@ -20,38 +45,46 @@ export const ChannelInfoSheet = observer(({channel}: {channel: Channel}) => { fetchMembers(); }, [channel]); return ( - - - {channel.name} - - {channel.channel_type === 'Group' - ? `Group (${groupMembers.length} ${ - groupMembers.length === 1 ? 'member' : 'members' - })` - : 'Regular channel'} - - {channel.description ? ( - - - {channel.description} - - - ) : null} + + + {!channel ? ( + <> + ) : ( + <> + + {channel.name} + + {channel.channel_type === 'Group' + ? `Group (${groupMembers.length} ${ + groupMembers.length === 1 ? 'member' : 'members' + })` + : 'Regular channel'} + + {channel.description ? ( + + + {channel.description} + + + ) : null} + + + )} - + ); }); diff --git a/src/components/sheets/MemberListSheet.tsx b/src/components/sheets/MemberListSheet.tsx index b46c40c..1547b93 100644 --- a/src/components/sheets/MemberListSheet.tsx +++ b/src/components/sheets/MemberListSheet.tsx @@ -1,30 +1,68 @@ -import React from 'react'; -import {ScrollView, View} from 'react-native'; +import React, {useEffect, useRef} from 'react'; +import {View} from 'react-native'; import {observer} from 'mobx-react-lite'; -import {Channel, Server, User} from 'revolt.js'; // TODO: add Member support +import BottomSheetCore from '@gorhom/bottom-sheet'; +import {useBackHandler} from '@react-native-community/hooks'; +import {Channel, Server, User} from 'revolt.js'; + +import {setFunction} from '../../Generic'; import {Text} from '../common/atoms'; +import {BottomSheet} from '../common/BottomSheet'; import {UserList} from '../navigation/UserList'; -interface ServerMemberList { - context: Server; - users: User[]; // Member[]; -} - -interface ChannelMemberList { - context: Channel; - users: User[]; -} - -export const MemberListSheet = observer( - ({context, users}: ServerMemberList | ChannelMemberList) => { - return ( - - {context.name ?? context._id} members - - - - ); - }, -); +export const MemberListSheet = observer(() => { + const [context, setContext] = React.useState(null as Channel | Server | null); + const [users, setUsers] = React.useState([] as User[]); + + const sheetRef = useRef(null); + + useBackHandler(() => { + if (context) { + sheetRef.current?.close(); + return true; + } + + return false; + }); + + setFunction('openMemberList', async (ctx: Channel | Server | null) => { + if (ctx !== context) { + setUsers([]); + } + setContext(ctx); + ctx ? sheetRef.current?.expand() : sheetRef.current?.close(); + }); + + useEffect(() => { + async function getUsers() { + if (!context) { + return; + } + const u = + context instanceof Server + ? (await context.fetchMembers()).users + : await context.fetchMembers(); + + setUsers(u); + } + getUsers(); + }, [context]); + + return ( + + + {!context ? ( + <> + ) : ( + <> + {context.name ?? context._id} members + + + + )} + + + ); +}); diff --git a/src/components/sheets/MessageMenuSheet.tsx b/src/components/sheets/MessageMenuSheet.tsx index dacd5cf..ede614e 100644 --- a/src/components/sheets/MessageMenuSheet.tsx +++ b/src/components/sheets/MessageMenuSheet.tsx @@ -1,141 +1,171 @@ -import React from 'react'; -import {ScrollView, View} from 'react-native'; +import React, {useRef} from 'react'; +import {View} from 'react-native'; import {observer} from 'mobx-react-lite'; +import BottomSheetCore from '@gorhom/bottom-sheet'; import Clipboard from '@react-native-clipboard/clipboard'; +import {useBackHandler} from '@react-native-community/hooks'; import MaterialIcon from 'react-native-vector-icons/MaterialIcons'; import {Message} from 'revolt.js'; -import {app} from '../../Generic'; +import {app, setFunction} from '../../Generic'; import {currentTheme, styles} from '../../Theme'; import {ContextButton, CopyIDButton, Text} from '../common/atoms'; +import {BottomSheet} from '../common/BottomSheet'; import {ReplyMessage} from '../common/messaging'; -export const MessageMenuSheet = observer( - ({setState, message}: {setState: Function; message: Message}) => { - return ( - <> - - - - {message?.channel?.havePermission('SendMessage') ? ( - { - let replyingMessages = [...app.getReplyingMessages()]; - if ( - replyingMessages.filter(m => m.message._id === message._id) - .length > 0 - ) { - return; - } - if (replyingMessages.length >= 5) { - return; - } - if (app.getEditingMessage()) { - return; - } - replyingMessages.push({ - message: message, - mentions: false, - }); - app.setReplyingMessages(replyingMessages); - setState(); - }}> - - - - Reply - - ) : null} - {message.content ? ( - { - Clipboard.setString(message.content!); - }}> - - - - Copy content - - ) : null} - {app.settings.get('ui.showDeveloperFeatures') ? ( - - ) : null} - { - Clipboard.setString(message.url); - }}> - - - - Copy message link - - {message?.author?.relationship === 'User' ? ( - { - app.setMessageBoxInput(message?.content); - app.setEditingMessage(message); - app.setReplyingMessages([]); - setState(); - }}> - - - - Edit - - ) : null} - {message?.channel?.havePermission('ManageMessages') || - message?.author?.relationship === 'User' ? ( - { - message.delete(); - setState(); - }}> - - - - Delete - - ) : null} - {message?.author?.relationship !== 'User' ? ( - { - app.openReportMenu({object: message, type: 'Message'}); - setState(); - }}> - - +export const MessageMenuSheet = observer(() => { + const [message, setMessage] = React.useState(null as Message | null); + + const sheetRef = useRef(null); + + useBackHandler(() => { + if (message) { + sheetRef.current?.close(); + return true; + } + + return false; + }); + + setFunction('openMessage', async (m: Message | null) => { + setMessage(m); + m ? sheetRef.current?.expand() : sheetRef.current?.close(); + }); + return ( + + + {!message ? ( + <> + ) : ( + <> + + - Report Message - - ) : null} - - - ); - }, -); + {message?.channel?.havePermission('SendMessage') ? ( + { + let replyingMessages = [...app.getReplyingMessages()]; + if ( + replyingMessages.filter(m => m.message._id === message._id) + .length > 0 + ) { + return; + } + if (replyingMessages.length >= 5) { + return; + } + if (app.getEditingMessage()) { + return; + } + replyingMessages.push({ + message: message, + mentions: false, + }); + app.setReplyingMessages(replyingMessages); + app.openMessage(null); + }}> + + + + Reply + + ) : null} + {message.content ? ( + { + Clipboard.setString(message.content!); + }}> + + + + Copy content + + ) : null} + {app.settings.get('ui.showDeveloperFeatures') ? ( + + ) : null} + { + Clipboard.setString(message.url); + }}> + + + + Copy message link + + {message?.author?.relationship === 'User' ? ( + { + app.setMessageBoxInput(message?.content); + app.setEditingMessage(message); + app.setReplyingMessages([]); + app.openMessage(null); + }}> + + + + Edit + + ) : null} + {message?.channel?.havePermission('ManageMessages') || + message?.author?.relationship === 'User' ? ( + { + message.delete(); + app.openMessage(null); + }}> + + + + Delete + + ) : null} + {message?.author?.relationship !== 'User' ? ( + { + app.openReportMenu({object: message, type: 'Message'}); + app.openMessage(null); + }}> + + + + Report Message + + ) : null} + + + )} + + + ); +}); diff --git a/src/components/sheets/ProfileSheet.tsx b/src/components/sheets/ProfileSheet.tsx index b7a3f94..2409d57 100644 --- a/src/components/sheets/ProfileSheet.tsx +++ b/src/components/sheets/ProfileSheet.tsx @@ -1,8 +1,10 @@ /* eslint-disable no-bitwise */ -import React, {useEffect} from 'react'; -import {ScrollView, TouchableOpacity, View} from 'react-native'; +import React, {useEffect, useRef} from 'react'; +import {Pressable, ScrollView, TouchableOpacity, View} from 'react-native'; import {observer} from 'mobx-react-lite'; +import BottomSheetCore from '@gorhom/bottom-sheet'; +import {useBackHandler} from '@react-native-community/hooks'; // import FastImage from 'react-native-fast-image'; import MaterialIcon from 'react-native-vector-icons/MaterialIcons'; import MaterialCommunityIcon from 'react-native-vector-icons/MaterialCommunityIcons'; @@ -10,624 +12,700 @@ import FA5Icon from 'react-native-vector-icons/FontAwesome5'; import {User, Server} from 'revolt.js'; -import {app, client, GeneralAvatar, openUrl} from '../../Generic'; +import {app, client, GeneralAvatar, openUrl, setFunction} from '../../Generic'; import {BADGES, USER_IDS} from '../../lib/consts'; import {parseRevoltNodes, showToast} from '../../lib/utils'; import {Avatar, MiniProfile, RoleView} from '../../Profile'; -import {currentTheme} from '../../Theme'; -import {Button, ContextButton, Link, Text, Username} from '../common/atoms'; +import {currentTheme, styles} from '../../Theme'; +import { + Button, + ContextButton, + CopyIDButton, + Link, + Text, + Username, +} from '../common/atoms'; +import {BottomSheet} from '../common/BottomSheet'; import {MarkdownView} from '../common/MarkdownView'; -import {UserMenuSheet} from './index'; import {UserList} from '../navigation/UserList'; // const Image = FastImage; -export const ProfileSheet = observer( - ({user, server}: {user: User; server?: Server}) => { - const [section, setSection] = React.useState('Profile'); - const [profile, setProfile] = React.useState( - {} as {content?: string | null | undefined}, - ); - const [mutual, setMutual] = React.useState( - {} as {users: User[]; servers: Server[]}, - ); - const [showMenu, setShowMenu] = React.useState(false); +export const ProfileSheet = observer(() => { + const [user, setUser] = React.useState(null as User | null); + const [server, setServer] = React.useState(null as Server | null); - useEffect(() => { - async function getInfo() { - const p = await user.fetchProfile(); - const rawMutuals = - user.relationship !== 'User' - ? await user.fetchMutual() - : {users: [] as string[], servers: [] as string[]}; + const sheetRef = useRef(null); - const fetchedMutualUsers: User[] = []; - for (const u of rawMutuals.users) { - fetchedMutualUsers.push(await client.users.fetch(u)); - } + useBackHandler(() => { + if (user) { + sheetRef.current?.close(); + return true; + } - const fetchedMutualServers: Server[] = []; - for (const s of rawMutuals.servers) { - fetchedMutualServers.push(await client.servers.fetch(s)); - } + return false; + }); - const m = {servers: fetchedMutualServers, users: fetchedMutualUsers}; + setFunction('openProfile', async (u: User | null, s: Server | null) => { + if (u !== user) { + setProfile({}); + setMutual({users: [], servers: []}); + setUser(u); + } + setServer(s); + u ? sheetRef.current?.expand() : sheetRef.current?.close(); + }); - setProfile(p); - setMutual(m); + const [section, setSection] = React.useState('Profile'); + const [profile, setProfile] = React.useState( + {} as {content?: string | null | undefined}, + ); + const [mutual, setMutual] = React.useState( + {} as {users: User[]; servers: Server[]}, + ); + const [showMenu, setShowMenu] = React.useState(false); + + useEffect(() => { + async function getInfo() { + if (!user) { + return; + } + const p = await user.fetchProfile(); + const rawMutuals = + user.relationship !== 'User' + ? await user.fetchMutual() + : {users: [] as string[], servers: [] as string[]}; + + const fetchedMutualUsers: User[] = []; + for (const u of rawMutuals.users) { + fetchedMutualUsers.push(await client.users.fetch(u)); + } + + const fetchedMutualServers: Server[] = []; + for (const s of rawMutuals.servers) { + fetchedMutualServers.push(await client.servers.fetch(s)); } - getInfo(); - }, [user]); - return showMenu ? ( - - ) : ( - <> - - - - setShowMenu(true)}> + + const m = {servers: fetchedMutualServers, users: fetchedMutualUsers}; + + setProfile(p); + setMutual(m); + } + getInfo(); + }, [user]); + + return ( + + + {!user ? ( + <> + ) : showMenu ? ( + <> + { + setShowMenu(false); + }}> - - - - - - - - {server ? ( - <> - {client.members.getKey({ - server: server?._id, - user: user._id, - })?.avatar?._id !== user.avatar?._id ? ( - <> - - - @ - - - ) : ( - @ - )} - - - ) : null} - - {user.status?.text ? {user.status?.text} : <>} - - - {user.flags ? ( - user.flags & 1 ? ( - User is suspended - ) : user.flags & 2 ? ( - User deleted their account - ) : user.flags & 4 ? ( - User is banned - ) : null - ) : null} - {user.relationship !== 'User' ? ( + + Return to Profile + + + {app.settings.get('ui.showDeveloperFeatures') ? ( + + ) : null} + {user.relationship !== 'User' ? ( + { + app.openReportMenu({object: user, type: 'User'}); + setShowMenu(false); + app.openProfile(null); + }}> + + + + Report User + + ) : null} + + ) : ( <> - + + - {!user.bot ? ( - user.relationship === 'Friend' ? ( - - ) : user.relationship === 'Incoming' ? ( + setShowMenu(true)}> + + + + + + + + + {server ? ( <> - - + {client.members.getKey({ + server: server?._id, + user: user._id, + })?.avatar?._id !== user.avatar?._id ? ( + <> + + + @ + + + ) : ( + @ + )} + - ) : user.relationship === 'Outgoing' ? ( - - ) : user.relationship !== 'Blocked' && - user.relationship !== 'BlockedOther' ? ( - - ) : null - ) : ( - <> - )} + ) : null} + + {user.status?.text ? {user.status?.text} : <>} - - - - - - - - - ) : null} - {section === 'Profile' ? ( - - {user.relationship === 'User' ? ( - <> - STATUS - - Status settings have moved. + {user.flags ? ( + user.flags & 1 ? ( + User is suspended + ) : user.flags & 2 ? ( + + User deleted their account + ) : user.flags & 4 ? ( + User is banned + ) : null + ) : null} + {user.relationship !== 'User' ? ( + <> - { - app.openStatusMenu(true); }}> - - Open status menu - - + {!user.bot ? ( + user.relationship === 'Friend' ? ( + + ) : user.relationship === 'Incoming' ? ( + <> + + + + ) : user.relationship === 'Outgoing' ? ( + + ) : user.relationship !== 'Blocked' && + user.relationship !== 'BlockedOther' ? ( + + ) : null + ) : ( + <> + )} + - - ) : user.bot ? ( - <> - BOT OWNER - {user.bot.owner && client.users.get(user.bot.owner) ? ( + - ) : ( - - Unloaded user - - )} - - ) : null} - {server && } - {user.badges ? ( - <> + + - BADGES {'('} - - {')'} + - + + ) : null} + {section === 'Profile' ? ( + + {user.bot ? ( <> - {Object.keys(BADGES).map(b => { - if (user.badges! & BADGES[b]) { - return ( - - {(() => { - switch (b) { - case 'Founder': - return ( - showToast('Founder')}> - - - ); - case 'Developer': - return ( - - showToast('Revolt Developer') - }> - - - ); - case 'Translator': - return ( - showToast('Translator')}> - - - ); - case 'Supporter': - return ( - showToast('Donator')} - onLongPress={() => - openUrl('https://insrt.uk/donate') - }> - - - ); - case 'ResponsibleDisclosure': - return ( - - showToast( - 'Responisbly disclosed a security issue', - ) - }> - - - ); - case 'EarlyAdopter': - return ( - - showToast('Early Adopter') - }> - - - ); - case 'PlatformModeration': - return ( - - showToast('Platform Moderator') - }> - - - ); - case 'Paw': - return ( - showToast("Insert's Paw")}> - ✌️ - - ); - case 'ReservedRelevantJokeBadge1': - return ( - showToast('amogus')}> - 📮 - - ); - case 'ReservedRelevantJokeBadge2': - return ( - - showToast("It's Morbin Time") - }> - 🦇 - - ); - default: - return ( - showToast(b)}> - - [{b}] - - - ); - } - })()} - - ); - } - })} - {USER_IDS.developers.includes(user._id) ? ( - showToast('RVMob Developer')}> - - - RV - - - - ) : null} - {user._id === USER_IDS.teamMembers.lea ? ( - showToast("Lea's Paw")}> - - BOT OWNER + {user.bot.owner && client.users.get(user.bot.owner) ? ( + - ); - })} - - ); - } - - function SuccessScreen({reportedObject}: {reportedObject: ReportedObject}) { - return ( - <> - - Your report has been sent to the Revolt team for review - thank you - for helping us keep Revolt safe. - - Next steps - {reportedObject.type === 'Message' || reportedObject.type === 'User' ? ( - <> - - You can also block{' '} - {reportedObject.type === 'Message' - ? 'the author of this message' - : 'this user'} - . - - {(reportedObject.type === 'Message' - ? reportedObject.object.author! - : reportedObject.object - ).relationship === 'Blocked' ? ( - - ) : ( - - )} - - ) : ( - <> - - You can also leave this server. Other members will not be - notified. - + ) : ( - - )} - If not, tap above the sheet to close it. - - ); - } + )} + + ) : ( + <> + + You can also leave this server. Other members will not be notified. + + + + )} + + + ); +} + +function ReasonsSelector({ + reportedObject, + reasons, + setReason, +}: { + reportedObject: ReportedObject; + reasons: Reason[]; + setReason: Function; +}) { + return ( + <> + + Why are you reporting this {reportedObject.type.toLowerCase()}? You can + add more context after selecting a reason. + + {reasons.map(r => { + return ( + + ); + })} + + ); +} + +export const ReportSheet = observer(() => { + const [obj, setObj] = React.useState(null as ReportedObject | null); + + const sheetRef = useRef(null); + + useBackHandler(() => { + if (obj) { + sheetRef.current?.close(); + return true; + } + + return false; + }); + + setFunction('openReportMenu', async (o: ReportedObject | null) => { + setObj(o); + o ? sheetRef.current?.expand() : sheetRef.current?.close(); + }); + + const [additionalContext, setAdditionalContext] = React.useState(''); + const [reason, setReason] = React.useState({} as Reason); + const [status, setStatus] = React.useState({} as Status); + + const messageReasons = useMemo( + () => + [ + {label: 'This message breaks one or more laws', reason: 'Illegal'}, + {label: 'This message promotes harm', reason: 'PromotesHarm'}, + { + label: 'This message is spam or abuse of the platform', + reason: 'SpamAbuse', + }, + { + label: 'This message contains malware or dangerous links', + reason: 'Malware', + }, + { + label: 'This message is harassing me or someone else', + reason: 'Harassment', + }, + {label: 'Something else not listed here', reason: 'NoneSpecified'}, + ] as Reason[], + [], + ); + const serverReasons = useMemo( + () => + [ + {label: 'This server breaks one or more laws', reason: 'Illegal'}, + {label: 'This server promotes harm', reason: 'PromotesHarm'}, + { + label: 'This server is spamming or abusing the platform', + reason: 'SpamAbuse', + }, + { + label: 'This server contains malware or dangerous links', + reason: 'Malware', + }, + { + label: 'This server is harassing me or someone else', + reason: 'Harassment', + }, + {label: 'Something else not listed here', reason: 'NoneSpecified'}, + ] as Reason[], + [], + ); + const userReasons = useMemo( + () => + [ + { + label: "This user's profile is inappropriate for a general audience", + reason: 'InappropriateProfile', + }, + { + label: 'This user is spamming or abusing the platform', + reason: 'SpamAbuse', + }, + { + label: 'This user is impersonating me or someone else', + reason: 'Impersonation', + }, + { + label: 'This user is evading a ban', + reason: 'BanEvasion', + }, + { + label: 'This user is too young to be using Revolt', + reason: 'Underage', + }, + {label: 'Something else not listed here', reason: 'NoneSpecified'}, + ] as Reason[], + [], + ); let output = <>; - switch (obj.type) { - case 'Message': - let msg = object as Message; - const isLikelyBridged = - msg.author?._id === USER_IDS.automod && msg.masquerade !== null; - console.log(isLikelyBridged, msg.author?._id, msg.masquerade?.name); - output = ( - <> - Report message - - - - - + Report message + + + - {msg.content ? ( - {msg.content} - ) : msg.attachments ? ( - - Sent an attachment - - ) : msg.embeds ? ( - - Sent an embed - - ) : ( - - No content - - )} + + + {msg.content ? ( + {msg.content} + ) : msg.attachments ? ( + + Sent an attachment + + ) : msg.embeds ? ( + + Sent an embed + + ) : ( + + No content + + )} + - - - {isLikelyBridged && ( - <> - - NOTE: This message may - have been sent from another platform. If so, we recommend - reporting it there as well. - - - )} - {reason.reason && !status.status && ( - <> - You can add more context to your report here. - { - setAdditionalContext(c); - }} - placeholder={'Add more context (optional)'} + + {isLikelyBridged && ( + <> + + NOTE: This message + may have been sent from another platform. If so, we recommend + reporting it there as well. + + + )} + {reason.reason && !status.status && ( + <> + You can add more context to your report here. + { + setAdditionalContext(c); + }} + placeholder={`Add more context (${ + reason.reason === 'NoneSpecified' + ? 'recommended' + : 'optional' + })`} + /> + + + )} + {!reason.reason && ( + - - - )} - {!reason.reason && } - {status.status === 'success' && ( - - )} - - ); - break; - case 'User': - output = ( - <> - Report user - {reason.reason && !status.status && ( - <> - You can add more context to your report here. - { - setAdditionalContext(c); - }} - placeholder={'Add more context (optional)'} + )} + {status.status === 'success' && ( + + )} + + ); + break; + case 'User': + output = ( + <> + Report user + {reason.reason && !status.status && ( + <> + You can add more context to your report here. + { + setAdditionalContext(c); + }} + placeholder={'Add more context (optional)'} + /> + + + )} + {!reason.reason && ( + - - - )} - {!reason.reason && } - {status.status === 'success' && ( - - )} - - ); - break; - case 'Server': - output = ( - <> - Report server - {reason.reason && !status.status && ( - <> - You can add more context to your report here. - { - setAdditionalContext(c); - }} - placeholder={'Add more context (optional)'} + )} + {status.status === 'success' && ( + + )} + + ); + break; + case 'Server': + output = ( + <> + Report server + {reason.reason && !status.status && ( + <> + You can add more context to your report here. + { + setAdditionalContext(c); + }} + placeholder={'Add more context (optional)'} + /> + + + )} + {!reason.reason && ( + - - - )} - {!reason.reason && } - {status.status === 'success' && ( - - )} - - ); - break; + )} + {status.status === 'success' && ( + + )} + + ); + break; + } } - return {output}; + return ( + + {output} + + ); }); diff --git a/src/components/sheets/ServerInfoSheet.tsx b/src/components/sheets/ServerInfoSheet.tsx index fe9b419..f47d5f4 100644 --- a/src/components/sheets/ServerInfoSheet.tsx +++ b/src/components/sheets/ServerInfoSheet.tsx @@ -1,137 +1,165 @@ -import React, {useEffect} from 'react'; -import {ScrollView, View} from 'react-native'; +import React, {useEffect, useRef} from 'react'; +import {View} from 'react-native'; import {observer} from 'mobx-react-lite'; +import BottomSheetCore from '@gorhom/bottom-sheet'; +import {useBackHandler} from '@react-native-community/hooks'; import FastImage from 'react-native-fast-image'; import MaterialIcon from 'react-native-vector-icons/MaterialIcons'; -import {Member, Server, User} from 'revolt.js'; +import {Member, Server} from 'revolt.js'; -import {GeneralAvatar, app, client} from '../../Generic'; +import {GeneralAvatar, app, client, setFunction} from '../../Generic'; import {SPECIAL_SERVERS} from '../../lib/consts'; import {currentTheme, styles} from '../../Theme'; import {ContextButton, CopyIDButton, Text} from '../common/atoms'; +import {BottomSheet} from '../common/BottomSheet'; import {MarkdownView} from '../common/MarkdownView'; const Image = FastImage; -export const ServerInfoSheet = observer( - ({setState, server}: {setState: Function; server: Server}) => { - const [members, setMembers] = React.useState( - {} as {members: Member[]; users: User[]}, - ); +export const ServerInfoSheet = observer(() => { + const [server, setServer] = React.useState(null as Server | null); + const [members, setMembers] = React.useState(null as Member[] | null); - useEffect(() => { - async function fetchMembers() { - if (server._id !== SPECIAL_SERVERS.lounge.id) { - const start = new Date().getTime(); - console.log(`[SERVERINFOSHEET] Fetching members... (${start})`); - const m = await server.fetchMembers(); - const mid = new Date().getTime(); - console.log(`[SERVERINFOSHEET] Fetched members (${mid})`); - setMembers(m); - const end = new Date().getTime(); - console.log(`[SERVERINFOSHEET] Set members (${end})`); - } + const sheetRef = useRef(null); + + useBackHandler(() => { + if (server) { + sheetRef.current?.close(); + return true; + } + + return false; + }); + + setFunction('openServerContextMenu', async (s: Server | null) => { + if (s !== server) { + setMembers(null); + } + setServer(s); + s ? sheetRef.current?.expand() : sheetRef.current?.close(); + }); + + useEffect(() => { + async function fetchMembers() { + if (!server || server._id === SPECIAL_SERVERS.lounge.id) { + return; } - fetchMembers(); - }, [server]); + // const start = new Date().getTime(); + // console.log(`[SERVERINFOSHEET] Fetching members... (${start})`); + const m = await server.fetchMembers(); + // const mid = new Date().getTime(); + // console.log(`[SERVERINFOSHEET] Fetched members (${mid})`); + setMembers(m.members); + // const end = new Date().getTime(); + // console.log(`[SERVERINFOSHEET] Set members (${end})`); + } + fetchMembers(); + }, [server]); - return ( - - - {server.banner ? ( - - ) : null} - {server.icon ? ( - - ) : null} - - {server.name} - - - {server._id === SPECIAL_SERVERS.lounge.id - ? 'Member count disabled for this server' - : members.members - ? `${members.members.length} ${ - members.members.length === 1 ? 'member' : 'members' - }` - : 'Fetching member count...'} - - {server.description ? ( - - + + {!server ? ( + <> + ) : ( + <> + + {server.banner ? ( + + ) : null} + {server.icon ? ( + + ) : null} + - {server.description} - - - ) : null} - - - {app.settings.get('ui.showDeveloperFeatures') ? ( - - ) : null} - {server.owner !== client.user?._id ? ( - <> - { - await app.openServer(); - setState(); - server.delete(); + marginBottom: 0, + fontSize: 24, }}> - - - - Leave Server - - { - app.openReportMenu({object: server, type: 'Server'}); + {server.name} + + - - + {server._id === SPECIAL_SERVERS.lounge.id + ? 'Member count disabled for this server' + : members + ? `${members.length} ${ + members.length === 1 ? 'member' : 'members' + }` + : 'Fetching member count...'} + + {server.description ? ( + + + {server.description} + - Report Server - - - ) : null} - - - ); - }, -); + ) : null} + + + {app.settings.get('ui.showDeveloperFeatures') ? ( + + ) : null} + {server.owner !== client.user?._id ? ( + <> + { + app.openServer(); + app.openServerContextMenu(null); + server.delete(); + }}> + + + + Leave Server + + { + app.openReportMenu({object: server, type: 'Server'}); + }}> + + + + Report Server + + + ) : null} + + + )} + + + ); +}); diff --git a/src/components/sheets/StatusSheet.tsx b/src/components/sheets/StatusSheet.tsx index b47bd48..98a949c 100644 --- a/src/components/sheets/StatusSheet.tsx +++ b/src/components/sheets/StatusSheet.tsx @@ -1,60 +1,83 @@ -import React from 'react'; +import React, {useRef} from 'react'; import {View} from 'react-native'; import {observer} from 'mobx-react-lite'; -import {client, InputWithButton} from '../../Generic'; +import BottomSheetCore from '@gorhom/bottom-sheet'; +import {useBackHandler} from '@react-native-community/hooks'; + +import {client, InputWithButton, setFunction} from '../../Generic'; import {STATUSES} from '../../lib/consts'; import {currentTheme} from '../../Theme'; import {ContextButton, Text} from '../common/atoms'; +import {BottomSheet} from '../common/BottomSheet'; export const StatusSheet = observer(() => { + const [isOpen, setIsOpen] = React.useState(false); + const sheetRef = useRef(null); + + useBackHandler(() => { + if (isOpen) { + sheetRef.current?.close(); + setIsOpen(false); + return true; + } + + return false; + }); + + setFunction('openStatusMenu', async (show: boolean) => { + show ? sheetRef.current?.expand() : sheetRef.current?.close(); + setIsOpen(show); + }); return ( - - - Status - - - {STATUSES.map(s => ( - { - client.users.edit({ - status: {...client.user?.status, presence: s}, - }); - }}> - - - {s} - - - ))} + + + + Status + + + {STATUSES.map(s => ( + { + client.users.edit({ + status: {...client.user?.status, presence: s}, + }); + }}> + + + {s} + + + ))} + + + Status text + + { + client.users.edit({ + status: { + ...client.user?.status, + text: v ? v : undefined, + }, + }); + }} + buttonLabel="Set text" + backgroundColor={currentTheme.backgroundPrimary} + /> - - Status text - - { - client.users.edit({ - status: { - ...client.user?.status, - text: v ? v : undefined, - }, - }); - }} - buttonLabel="Set text" - backgroundColor={currentTheme.backgroundPrimary} - /> - + ); }); diff --git a/src/components/sheets/UserMenuSheet.tsx b/src/components/sheets/UserMenuSheet.tsx deleted file mode 100644 index 571a239..0000000 --- a/src/components/sheets/UserMenuSheet.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import React from 'react'; -import {Pressable, ScrollView, View} from 'react-native'; -import {observer} from 'mobx-react-lite'; - -import MaterialCommunityIcon from 'react-native-vector-icons/MaterialCommunityIcons'; -import MaterialIcon from 'react-native-vector-icons/MaterialIcons'; - -import {User} from 'revolt.js'; - -import {app} from '../../Generic'; -import {currentTheme, styles} from '../../Theme'; -import {ContextButton, CopyIDButton, Text} from '../common/atoms'; - -export const UserMenuSheet = observer( - ({state, user}: {state: any; user: User}) => { - return ( - <> - - { - state(false); - }}> - - - Close - - - {app.settings.get('ui.showDeveloperFeatures') ? ( - - ) : null} - {user.relationship !== 'User' ? ( - { - app.openReportMenu({object: user, type: 'User'}); - state(false); - }}> - - - - Report User - - ) : null} - - - - ); - }, -); diff --git a/src/components/sheets/index.tsx b/src/components/sheets/index.tsx index b2ca8d7..63d575c 100644 --- a/src/components/sheets/index.tsx +++ b/src/components/sheets/index.tsx @@ -3,9 +3,8 @@ export {ChannelInfoSheet} from './ChannelInfoSheet'; export {MemberListSheet} from './MemberListSheet'; export {MessageMenuSheet} from './MessageMenuSheet'; export {ProfileSheet} from './ProfileSheet'; -export {ReportModal as ReportSheet} from './ReportSheet'; +export {ReportSheet} from './ReportSheet'; export {ServerInfoSheet} from './ServerInfoSheet'; export {ServerInviteSheet} from './ServerInviteSheet'; export {SettingsSheet} from './SettingsSheet'; export {StatusSheet} from './StatusSheet'; -export {UserMenuSheet} from './UserMenuSheet'; diff --git a/src/components/views/ChannelView.tsx b/src/components/views/ChannelView.tsx index af52259..8578cd9 100644 --- a/src/components/views/ChannelView.tsx +++ b/src/components/views/ChannelView.tsx @@ -106,12 +106,9 @@ export const ChannelView = observer( {channel.channel_type === 'Group' ? ( - app.openMemberList( - channel, - await channel.fetchMembers(), - ) - }> + onPress={() => { + app.openMemberList(channel); + }}>