-
Notifications
You must be signed in to change notification settings - Fork 1.4k
feat: Room Invited State #6857
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Room Invited State #6857
Changes from 10 commits
dd395dd
9bf3b5b
6d1891c
4789b85
ced347c
1928efc
7ea0ad1
2165314
622dbde
20802e7
c9f7a5e
0283fe7
2f3bc23
b384e01
a36ea13
a61df23
54b7a6a
ea3312c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| import { type IInviteSubscription } from '../../definitions'; | ||
| import I18n from '../../i18n'; | ||
| import { getRoomTitle } from './helpers'; | ||
| import { replyRoomInvite } from './replyRoomInvite'; | ||
|
|
||
| export const getInvitationData = (room: IInviteSubscription) => { | ||
| const title = | ||
| room.t === 'd' | ||
| ? I18n.t('invited_room_title_dm') | ||
| : I18n.t('invited_room_title_channel', { room_name: getRoomTitle(room).slice(0, 30) }); | ||
|
|
||
| const description = room.t === 'd' ? I18n.t('invited_room_description_dm') : I18n.t('invited_room_description_channel'); | ||
|
|
||
| return { | ||
| title, | ||
| description, | ||
| inviter: room.inviter, | ||
| accept: () => replyRoomInvite(room.id, 'accept'), | ||
| reject: () => replyRoomInvite(room.id, 'reject') | ||
| }; | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| import i18n from '../../i18n'; | ||
| import { sendInvitationReply } from '../services/restApi'; | ||
| import { showErrorAlert } from './helpers'; | ||
| import log from './helpers/log'; | ||
|
|
||
| export const replyRoomInvite = async (rid: string, action: 'accept' | 'reject') => { | ||
| try { | ||
| await sendInvitationReply(rid, action); | ||
| } catch (e) { | ||
| showErrorAlert(i18n.t('error-invitation-reply-action')); | ||
| log(e); | ||
| } | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| import React, { type ReactElement } from 'react'; | ||
| import { StyleSheet, Text, View } from 'react-native'; | ||
|
|
||
| import { useTheme } from '../../../theme'; | ||
| import { CustomIcon } from '../../../containers/CustomIcon'; | ||
| import Button from '../../../containers/Button'; | ||
| import sharedStyles from '../../Styles'; | ||
| import I18n from '../../../i18n'; | ||
| import type { IInviteSubscription } from '../../../definitions'; | ||
| import Chip from '../../../containers/Chip'; | ||
|
|
||
| const GAP = 32; | ||
|
|
||
| type InvitedRoomProps = { | ||
| title: string; | ||
| description: string; | ||
| inviter: IInviteSubscription['inviter']; | ||
| loading?: boolean; | ||
| onAccept: () => Promise<void>; | ||
| onReject: () => Promise<void>; | ||
| }; | ||
|
|
||
| export const InvitedRoom = ({ title, description, inviter, loading, onAccept, onReject }: InvitedRoomProps): ReactElement => { | ||
| const { colors } = useTheme(); | ||
| const styles = useStyle(); | ||
|
|
||
| return ( | ||
| <View style={styles.root}> | ||
| <View style={styles.container}> | ||
| <View style={styles.textView}> | ||
| <View style={styles.icon}> | ||
| <CustomIcon name='mail' size={42} color={colors.fontSecondaryInfo} /> | ||
| </View> | ||
| <Text style={styles.title}>{title}</Text> | ||
| <Text style={styles.description}>{description}</Text> | ||
| <View style={styles.username}> | ||
| <Chip avatar={inviter.username} text={inviter.name || inviter.username} /> | ||
| </View> | ||
| </View> | ||
| <Button title={I18n.t('accept')} loading={loading} onPress={onAccept} /> | ||
| <Button | ||
| title={I18n.t('reject')} | ||
| type='secondary' | ||
| loading={loading} | ||
| backgroundColor={colors.surfaceTint} | ||
| onPress={onReject} | ||
| /> | ||
| </View> | ||
| </View> | ||
| ); | ||
| }; | ||
|
Comment on lines
23
to
51
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Loading state is not wired up in the parent component. The
🔎 Example: internal loading state-export const InvitedRoom = ({ title, description, inviter, loading, onAccept, onReject }: InvitedRoomProps): ReactElement => {
+export const InvitedRoom = ({ title, description, inviter, onAccept, onReject }: InvitedRoomProps): ReactElement => {
const { colors } = useTheme();
const styles = useStyle();
+ const [loading, setLoading] = React.useState(false);
+
+ const handleAccept = async () => {
+ setLoading(true);
+ try {
+ await onAccept();
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const handleReject = async () => {
+ setLoading(true);
+ try {
+ await onReject();
+ } finally {
+ setLoading(false);
+ }
+ };
return (
// ...
- <Button title={I18n.t('accept')} loading={loading} onPress={onAccept} />
+ <Button title={I18n.t('accept')} loading={loading} onPress={handleAccept} />
<Button
title={I18n.t('reject')}
type='secondary'
loading={loading}
backgroundColor={colors.surfaceTint}
- onPress={onReject}
+ onPress={handleReject}
/>
// ...
);
};🤖 Prompt for AI Agents |
||
|
|
||
| const useStyle = () => { | ||
| const { colors } = useTheme(); | ||
| const styles = StyleSheet.create({ | ||
| root: { | ||
| flex: 1, | ||
| backgroundColor: colors.surfaceRoom | ||
| }, | ||
| container: { | ||
| flex: 1, | ||
| marginHorizontal: 24, | ||
| justifyContent: 'center' | ||
| }, | ||
| textView: { alignItems: 'center' }, | ||
| icon: { | ||
| width: 58, | ||
| height: 58, | ||
| borderRadius: 30, | ||
| marginBottom: GAP, | ||
| backgroundColor: colors.surfaceNeutral, | ||
| alignItems: 'center', | ||
| justifyContent: 'center' | ||
| }, | ||
| title: { | ||
| ...sharedStyles.textBold, | ||
| fontSize: 24, | ||
| lineHeight: 32, | ||
| textAlign: 'center', | ||
| color: colors.fontTitlesLabels, | ||
| marginBottom: GAP | ||
| }, | ||
| description: { | ||
| ...sharedStyles.textRegular, | ||
| fontSize: 16, | ||
| lineHeight: 24, | ||
| textAlign: 'center', | ||
| color: colors.fontDefault | ||
| }, | ||
| username: { | ||
| ...sharedStyles.textRegular, | ||
| fontSize: 16, | ||
| lineHeight: 24, | ||
| textAlign: 'center', | ||
| color: colors.fontDefault, | ||
| marginBottom: GAP | ||
| } | ||
| }); | ||
| return styles; | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -66,7 +66,8 @@ import { | |
| type TSubscriptionModel, | ||
| type IEmoji, | ||
| type TGetCustomEmoji, | ||
| type RoomType | ||
| type RoomType, | ||
| isInviteSubscription | ||
| } from '../../definitions'; | ||
| import { E2E_MESSAGE_TYPE, E2E_STATUS } from '../../lib/constants/keys'; | ||
| import { MESSAGE_TYPE_ANY_LOAD, MessageTypeLoad } from '../../lib/constants/messageTypeLoad'; | ||
|
|
@@ -102,6 +103,8 @@ import UserPreferences from '../../lib/methods/userPreferences'; | |
| import { type IRoomViewProps, type IRoomViewState } from './definitions'; | ||
| import { roomAttrsUpdate, stateAttrsUpdate } from './constants'; | ||
| import { EncryptedRoom, MissingRoomE2EEKey } from './components'; | ||
| import { InvitedRoom } from './components/InvitedRoom'; | ||
| import { getInvitationData } from '../../lib/methods/getInvitationData'; | ||
|
|
||
| class RoomView extends React.Component<IRoomViewProps, IRoomViewState> { | ||
| private rid?: string; | ||
|
|
@@ -328,6 +331,11 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> { | |
| ) { | ||
| this.updateE2EEState(); | ||
| } | ||
|
|
||
| // init() is skipped for invite subscriptions. Initialize when invite has been accepted | ||
| if (prevState.roomUpdate.status === 'INVITED' && roomUpdate.status !== 'INVITED') { | ||
| this.init(); | ||
| } | ||
| } | ||
|
|
||
| updateOmnichannel = async () => { | ||
|
|
@@ -535,6 +543,7 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> { | |
| onPress={this.goRoomActionsView} | ||
| testID={`room-view-title-${title}`} | ||
| sourceType={sourceType} | ||
| disabled={isInviteSubscription(iSubRoom)} | ||
| /> | ||
| ), | ||
| headerRight: () => ( | ||
|
|
@@ -637,6 +646,12 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> { | |
| if (!this.rid) { | ||
| return; | ||
| } | ||
|
|
||
| if ('id' in room && isInviteSubscription(room)) { | ||
| this.setState({ loading: false }); | ||
| return; | ||
| } | ||
|
|
||
| if (this.tmid) { | ||
| await loadThreadMessages({ tmid: this.tmid, rid: this.rid }); | ||
| } else { | ||
|
|
@@ -1563,6 +1578,16 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> { | |
| ({ bannerClosed, announcement } = room); | ||
| } | ||
|
|
||
| if ('id' in room && isInviteSubscription(room)) { | ||
| const { title, description, inviter, accept, reject } = getInvitationData(room); | ||
|
|
||
| return ( | ||
| <SafeAreaView style={{ backgroundColor: themes[theme].surfaceRoom }} testID='room-view-invited'> | ||
| <InvitedRoom title={title} description={description} inviter={inviter} onAccept={accept} onReject={reject} /> | ||
| </SafeAreaView> | ||
| ); | ||
| } | ||
|
Comment on lines
+1581
to
+1589
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add loading state management for invitation actions. The 🔎 Suggested implementationAdd state for tracking invitation reply loading: const { title, description, inviter, accept, reject } = getInvitationData(room);
+ const [invitationLoading, setInvitationLoading] = React.useState(false);
+
+ const handleAccept = async () => {
+ setInvitationLoading(true);
+ try {
+ await accept();
+ } finally {
+ setInvitationLoading(false);
+ }
+ };
+
+ const handleReject = async () => {
+ setInvitationLoading(true);
+ try {
+ await reject();
+ } finally {
+ setInvitationLoading(false);
+ }
+ };
return (
<SafeAreaView style={{ backgroundColor: themes[theme].surfaceRoom }} testID='room-view-invited'>
- <InvitedRoom title={title} description={description} inviter={inviter} onAccept={accept} onReject={reject} />
+ <InvitedRoom
+ title={title}
+ description={description}
+ inviter={inviter}
+ loading={invitationLoading}
+ onAccept={handleAccept}
+ onReject={handleReject}
+ />
</SafeAreaView>
);Note: If
🤖 Prompt for AI Agents |
||
|
|
||
| if ('encrypted' in room) { | ||
| // Missing room encryption key | ||
| if (showMissingE2EEKey) { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.