feat: mobile troubleshoot notifications (#5330)

* feat: troubleshoot notification (#5198)

* navigation done

* create the icon inside roomslistview, navigation to push troubleshot and layout push troubleshoot

* custom header

* fix the rooms list view header icon

* layout done

* update the pt-br i18n

* tweak on colors

* feat: create notification in room view (#5250)

* button and simple navigation done, missing master detail

* navigation

* add withTheme and colors to rightuttons

* fix e2e test

* feat: add troubleshooting to notifications pages (#5276)

* feat: add troubleshooting to notifications pages

* fix e2e test

* feat: device notification settings (#5277)

* iOS go to device notification setting to change the configuration

* go to notification settings with android

* add notifee

* add the reducer and action

* saga request done

* add the setInAlert action

* tweak at name and add focus to dispatch the request

* use the foreground inside pushTroubleShoot to request the notification and fix the icon color

* add the request at roomslistview didmount

* remove the notification modulo from android

* add patch

* minor tweak

* feat: test push notification (#5329)

* feat: test push notification

* restApi and definition

* push.info and change properly the troubleshootingNotification

* use the finally at try/catch

* minor tweak

* alert and push.info just for 6.6

* fix the react-native.config

* minor tweaks

* minor tweak

* push.test as rest api

* change the name from inAlertNotification to highlightTroubleshooting

* feat: push quota

* refactor the percentage state

* removed the push quota feature

* minor tweaks

* update the link to push notification

* the notification icon in the room header will appear if notifications are disabled or highlight troubleshoot is true

* remove push quota texts

* updated some of the push quota texts

* chore: rename highlightTroubleshooting

* chore: better prop naming

* wip

* chore: fix function name

* chore: fix colors

* fix: copy

* chore: 💅

* chore: use fork

* chore: naming

* chore: fix init

* chore: naming

* chore: naming

* Comment CE code

* Use put on troubleshooting saga

* Add db column

* fix: check notification payload

* action: organized translations

* fix: push init

---------

Co-authored-by: GleidsonDaniel <gleidson10daniel@hotmail.com>
Co-authored-by: Diego Mello <diegolmello@gmail.com>
Co-authored-by: GleidsonDaniel <GleidsonDaniel@users.noreply.github.com>
This commit is contained in:
Reinaldo Neto 2024-03-04 08:27:24 -03:00 committed by GitHub
parent eefb8793a5
commit 4c8caf0bfd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
42 changed files with 737 additions and 79 deletions

View File

@ -96,5 +96,6 @@ export const VIDEO_CONF = createRequestTypes('VIDEO_CONF', [
'ACCEPT_CALL',
'SET_CALLING'
]);
export const TROUBLESHOOTING_NOTIFICATION = createRequestTypes('TROUBLESHOOTING_NOTIFICATION', ['INIT', 'SET']);
export const SUPPORTED_VERSIONS = createRequestTypes('SUPPORTED_VERSIONS', ['SET']);
export const IN_APP_FEEDBACK = createRequestTypes('IN_APP_FEEDBACK', ['SET', 'REMOVE', 'CLEAR']);

View File

@ -0,0 +1,21 @@
import { Action } from 'redux';
import { TROUBLESHOOTING_NOTIFICATION } from './actionsTypes';
import { ITroubleshootingNotification } from '../reducers/troubleshootingNotification';
type TSetTroubleshootingNotification = Action & { payload: Partial<ITroubleshootingNotification> };
export type TActionTroubleshootingNotification = Action & TSetTroubleshootingNotification;
export function initTroubleshootingNotification(): Action {
return {
type: TROUBLESHOOTING_NOTIFICATION.INIT
};
}
export function setTroubleshootingNotification(payload: Partial<ITroubleshootingNotification>): TSetTroubleshootingNotification {
return {
type: TROUBLESHOOTING_NOTIFICATION.SET,
payload
};
}

View File

@ -36,7 +36,7 @@ export const VideoConferenceBaseContainer = ({ variant, children }: VideoConfMes
},
issue: {
icon: 'phone-issue',
color: colors.statusFontOnWarning,
color: colors.statusFontWarning,
backgroundColor: colors.statusBackgroundWarning,
label: i18n.t('Call_issue')
}

View File

@ -110,6 +110,7 @@ export interface ISubscription {
threads: RelationModified<TThreadModel>;
threadMessages: RelationModified<TThreadMessageModel>;
uploads: RelationModified<TUploadModel>;
disableNotifications?: boolean;
}
export type TSubscriptionModel = ISubscription &

View File

@ -40,6 +40,8 @@ import { IEnterpriseModules } from '../../reducers/enterpriseModules';
import { IVideoConf } from '../../reducers/videoConf';
import { TActionUsersRoles } from '../../actions/usersRoles';
import { TUsersRoles } from '../../reducers/usersRoles';
import { ITroubleshootingNotification } from '../../reducers/troubleshootingNotification';
import { TActionTroubleshootingNotification } from '../../actions/troubleshootingNotification';
import { ISupportedVersionsState } from '../../reducers/supportedVersions';
import { IInAppFeedbackState } from '../../reducers/inAppFeedback';
@ -67,6 +69,7 @@ export interface IApplicationState {
roles: IRoles;
videoConf: IVideoConf;
usersRoles: TUsersRoles;
troubleshootingNotification: ITroubleshootingNotification;
supportedVersions: ISupportedVersionsState;
inAppFeedback: IInAppFeedbackState;
}
@ -90,5 +93,6 @@ export type TApplicationActions = TActionActiveUsers &
TActionEnterpriseModules &
TActionVideoConf &
TActionUsersRoles &
TActionTroubleshootingNotification &
TActionSupportedVersions &
TInAppFeedbackAction;

View File

@ -17,7 +17,7 @@ import { E2eEndpoints } from './e2e';
import { SubscriptionsEndpoints } from './subscriptions';
import { VideoConferenceEndpoints } from './videoConference';
import { CommandsEndpoints } from './commands';
import { PushTokenEndpoints } from './pushToken';
import { PushEndpoints } from './push';
import { DirectoryEndpoint } from './directory';
import { AutoTranslateEndpoints } from './autotranslate';
import { ModerationEndpoints } from './moderation';
@ -41,7 +41,7 @@ export type Endpoints = ChannelsEndpoints &
SubscriptionsEndpoints &
VideoConferenceEndpoints &
CommandsEndpoints &
PushTokenEndpoints &
PushEndpoints &
DirectoryEndpoint &
AutoTranslateEndpoints &
ModerationEndpoints;

View File

@ -0,0 +1,24 @@
type TPushInfo = {
pushGatewayEnabled: boolean;
defaultPushGateway: boolean;
success: boolean;
};
export type PushEndpoints = {
'push.token': {
POST: (params: { value: string; type: string; appName: string }) => {
result: {
id: string;
token: string;
appName: string;
userId: string;
};
};
};
'push.info': {
GET: () => TPushInfo;
};
'push.test': {
POST: () => { tokensCount: number };
};
};

View File

@ -1,12 +0,0 @@
export type PushTokenEndpoints = {
'push.token': {
POST: (params: { value: string; type: string; appName: string }) => {
result: {
id: string;
token: string;
appName: string;
userId: string;
};
};
};
};

View File

@ -24,6 +24,7 @@
"All_users_in_the_channel_can_write_new_messages": "All users in the channel can write new messages",
"All_users_in_the_team_can_write_new_messages": "All users in the team can write new messages",
"Allow_Reactions": "Allow reactions",
"Allow_push_notifications_for_rocket_chat": "Allow push notifications for Rocket.Chat",
"Also_send_thread_message_to_channel_behavior": "Also send thread message to channel",
"Announcement": "Announcement",
"App_users_are_not_allowed_to_log_in_directly": "App users are not allowed to log in directly.",
@ -102,6 +103,7 @@
"Code_block": "Code block",
"Code_or_password_invalid": "Code or password invalid",
"Collaborative": "Collaborative",
"Community_edition_push_quota": "Community push quota",
"Condensed": "Condensed",
"Confirm": "Confirm",
"Confirmation": "Confirmation",
@ -134,6 +136,8 @@
"Create_a_new_workspace": "Create a new workspace",
"Create_account": "Create an account",
"Created_snippet": "created a snippet",
"Custom_push_gateway_connected_description": "Your workspace uses a custom push notification gateway. Check with your workspace administrator for any issues.",
"Custom_push_gateway_connection": "Custom Gateway Connection",
"DELETE": "DELETE",
"Dark": "Dark",
"Dark_level": "Dark level",
@ -156,6 +160,9 @@
"Description": "Description",
"Desktop_Alert_info": "These notifications are delivered in desktop",
"Desktop_Notifications": "Desktop notifications",
"Device_notification_settings": "Device notification settings",
"Device_notifications_alert_description": "Please go to your settings app and enable notifications for Rocket.Chat",
"Device_notifications_alert_title": "Notifications disabled",
"Direct_Messages": "Direct messages",
"Direct_message": "Direct message",
"Direct_message_someone": "Direct message someone",
@ -174,6 +181,7 @@
"Do_you_have_a_certificate": "Do you have a certificate?",
"Do_you_have_an_account": "Do you have an account?",
"Do_you_really_want_to_key_this_room_question_mark": "Do you really want to {{key}} this room?",
"Documentation": "Documentation",
"Dont_Have_An_Account": "Don't you have an account?",
"Dont_activate": "Don't activate now",
"Downloaded_file": "Downloaded file",
@ -387,6 +395,7 @@
"No_channels_in_team": "No Channels on this team",
"No_discussions": "No discussions",
"No_files": "No files",
"No_further_action_is_needed": "No further action is needed",
"No_label_provided": "No {{label}} provided.",
"No_limit": "No limit",
"No_match_found": "No match found.",
@ -404,6 +413,8 @@
"Nothing": "Nothing",
"Nothing_to_save": "Nothing to save!",
"Notification_Preferences": "Notification preferences",
"Notification_delay": "Notification delay",
"Notification_delay_description": "There are factors that can contribute to delayed notifications. Learn more in Rocket.Chat's docs.",
"Notifications": "Notifications",
"Notify_active_in_this_room": "Notify active users in this room",
"Notify_all_in_this_room": "Notify all in this room",
@ -461,6 +472,10 @@
"Public": "Public",
"Push_Notifications": "Push notifications",
"Push_Notifications_Alert_Info": "These notifications are delivered to you when the app is not open",
"Push_Troubleshooting": "Push Troubleshooting",
"Push_gateway_connected_description": "Send a push notification to yourself to check if the gateway is working",
"Push_gateway_connection": "Push Gateway Connection",
"Push_gateway_not_connected_description": "We're not able to connect to the push gateway. If this issue persists please check with your workspace administrator.",
"Queued_chats": "Queued chats",
"Quote": "Quote",
"RESET": "RESET",
@ -605,6 +620,7 @@
"Team_not_found": "Team not found",
"Teams": "Teams",
"Terms_of_Service": " Terms of service ",
"Test_push_notification": "Test push notification",
"The_maximum_number_of_users_has_been_reached": "The maximum number of users has been reached.",
"The_room_does_not_exist": "The room does not exist or you may not have access permission",
"The_user_will_be_able_to_type_in_roomName": "The user will be able to type in {{roomName}}",
@ -625,6 +641,7 @@
"Token_expired": "Your session has expired. Please log in again.",
"Topic": "Topic",
"Translate": "Translate",
"Troubleshooting": "Troubleshooting",
"Try_again": "Try again",
"Two_Factor_Authentication": "Two-factor authentication",
"Type_message": "Type message",
@ -690,6 +707,8 @@
"Wi_Fi_and_mobile_data": "Wi-Fi and mobile data",
"Without_Servers": "Without workspaces",
"Workspace_URL_Example": "Ex. your-company.rocket.chat",
"Workspace_consumption": "Workspace consumption",
"Workspace_consumption_description": "Theres a set amount of push notifications per month",
"Workspaces": "Workspaces",
"Would_like_to_place_on_hold": "Would you like to place this chat on hold?",
"Would_you_like_to_return_the_inquiry": "Would you like to return the inquiry?",
@ -720,6 +739,7 @@
"Your_invite_link_will_expire_on__date__or_after__usesLeft__uses": "Your invite link will expire on {{date}} or after {{usesLeft}} uses.",
"Your_invite_link_will_never_expire": "Your invite link will never expire.",
"Your_password_is": "Your password is",
"Your_push_was_sent_to_s_devices": "Your push was sent to {{s}} devices",
"Your_workspace": "Your workspace",
"__count__empty_room_will_be_removed_automatically": "{{count}} empty room will be deleted.",
"__count__empty_rooms_will_be_removed_automatically": "{{count}} empty rooms will be deleted.",
@ -761,6 +781,7 @@
"error-invalid-file-type": "Invalid file type",
"error-invalid-password": "Invalid password",
"error-invalid-room-name": "{{room_name}} is not a valid room name",
"error-no-tokens-for-this-user": "There are no tokens for this user",
"error-not-allowed": "Not allowed",
"error-not-permission-to-upload-file": "You don't have permission to upload files",
"error-save-image": "Error while saving image",

View File

@ -23,6 +23,7 @@
"All_users_in_the_channel_can_write_new_messages": "Todos usuários no canal podem enviar mensagens novas",
"All_users_in_the_team_can_write_new_messages": "Todos usuários no canal podem enviar mensagens novas",
"Allow_Reactions": "Permitir reagir",
"Allow_push_notifications_for_rocket_chat": "Nenhuma ação adicional é necessária",
"Also_send_thread_message_to_channel_behavior": "Também enviar mensagem do tópico para o canal",
"Announcement": "Anúncio",
"App_users_are_not_allowed_to_log_in_directly": "Usuários do aplicativo não estão autorizados a fazer login diretamente.",
@ -55,7 +56,7 @@
"Call_issue": "Chamada com problemas",
"Call_ongoing": "Chamada em andamento",
"Call_rejected": "Chamada rejeitada",
"Call_started": "Chamada iniciada",
"Call_started": "Chamada Iniciada",
"Call_was_not_answered": "A chamada não foi atendida",
"Calling": "Chamando",
"Cancel": "Cancelar",
@ -99,6 +100,7 @@
"Close_emoji_selector": "Fechar seletor de emojis",
"Code_or_password_invalid": "Código ou senha inválido",
"Collaborative": "Colaborativo",
"Community_edition_push_quota": "Cota de notificações push Community Edition",
"Condensed": "Condensado",
"Confirm": "Confirmar",
"Confirmation": "Confirmação",
@ -131,6 +133,8 @@
"Create_a_new_workspace": "Criar nova área de trabalho",
"Create_account": "Criar conta",
"Created_snippet": "criou um snippet",
"Custom_push_gateway_connected_description": "Seu workspace utiliza um gateway de notificação push personalizado. Verifique com o administrador do seu workspace se há algum problema.",
"Custom_push_gateway_connection": "Conexão Personalizada com o Gateway",
"DELETE": "EXCLUIR",
"Dark": "Escuro",
"Dark_level": "Nível escuro",
@ -153,6 +157,9 @@
"Description": "Descrição",
"Desktop_Alert_info": "Essas notificações são entregues a você na área de trabalho",
"Desktop_Notifications": "Notificações da área de trabalho",
"Device_notification_settings": "Configurações de notificações do dispositivo",
"Device_notifications_alert_description": "Por favor, vá para o aplicativo de configurações e habilite as notificações para o Rocket.Chat.",
"Device_notifications_alert_title": "Notificações desativadas",
"Direct_Messages": "Mensagens diretas",
"Direct_message": "Mensagem direta",
"Direct_message_someone": "Enviar mensagem direta para alguém",
@ -171,6 +178,7 @@
"Do_you_have_a_certificate": "Você tem um certificado?",
"Do_you_have_an_account": "Você tem uma conta?",
"Do_you_really_want_to_key_this_room_question_mark": "Você quer realmente {{key}} esta sala?",
"Documentation": "Documentação",
"Dont_Have_An_Account": "Não tem uma conta?",
"Dont_activate": "Não ativar agora",
"Downloaded_file": "Arquivo baixado",
@ -267,6 +275,7 @@
"Jitsi_authentication_before_making_calls_admin": "Jitsi pode exigir autenticação antes de fazer chamadas. Para saber mais sobre as políticas deles, visite o site do Jitsi. Você também pode atualizar o aplicativo padrão para chamadas de vídeo nas preferências.",
"Jitsi_authentication_before_making_calls_ask_admin": "Se você acredita que há problemas com o Jitsi e sua autenticação, peça ajuda a um administrador do espaço de trabalho.",
"Jitsi_may_require_authentication": "O Jitsi pode exigir autenticação",
"Jitsi_may_requires_authentication": "Jitsi pode exigir autenticação",
"Join": "Entrar",
"Join_Code": "Insira o código da sala",
"Join_our_open_workspace": "Entrar na nossa workspace pública",
@ -381,6 +390,7 @@
"No_channels_in_team": "Nenhum canal nesta equipe",
"No_discussions": "Sem discussões",
"No_files": "Não há arquivos",
"No_further_action_is_needed": "Ir para configurações do dispositivo",
"No_label_provided": "Sem {{label}}.",
"No_limit": "Sem limite",
"No_match_found": "Nenhum resultado encontrado.",
@ -397,6 +407,8 @@
"Nothing": "Nada",
"Nothing_to_save": "Nada para salvar!",
"Notification_Preferences": "Preferências de notificação",
"Notification_delay": "Atraso de notificação",
"Notification_delay_description": "Existem fatores que podem contribuir para atrasos nas notificações. Saiba mais na documentação do Rocket.Chat.",
"Notifications": "Notificações",
"Notify_active_in_this_room": "Notificar usuários ativos nesta sala",
"Notify_all_in_this_room": "Notificar todos nesta sala",
@ -452,6 +464,10 @@
"Public": "Público",
"Push_Notifications": "Notificações push",
"Push_Notifications_Alert_Info": "Essas notificações são entregues a você quando o aplicativo não está aberto",
"Push_Troubleshooting": "Solucionar Problemas de Push",
"Push_gateway_connected_description": "Envie uma notificação push para si mesmo para verificar se o gateway está funcionando.",
"Push_gateway_connection": "Conexão com o Gateway de Push",
"Push_gateway_not_connected_description": "Não conseguimos conectar ao gateway de push. Se esse problema persistir, por favor, verifique com o administrador do seu workspace.",
"Queued_chats": "Bate-papos na fila",
"Quote": "Citar",
"RESET": "RESETAR",
@ -593,6 +609,7 @@
"Team_not_found": "Time não encontrado",
"Teams": "Times",
"Terms_of_Service": " Termos de serviço ",
"Test_push_notification": "Testar notificação push",
"The_maximum_number_of_users_has_been_reached": "O número máximo de usuários foi atingido.",
"The_room_does_not_exist": "A sala não existe ou você pode não ter permissão de acesso",
"The_user_will_be_able_to_type_in_roomName": "O usuário poderá digitar em {{roomName}}",
@ -678,6 +695,8 @@
"Wi_Fi_and_mobile_data": "Wi-Fi e dados móveis",
"Without_Servers": "Sem workspaces",
"Workspace_URL_Example": "Ex. sua-empresa.rocket.chat",
"Workspace_consumption": "Consumo do Workspace",
"Workspace_consumption_description": "Existe uma quantidade definida de notificações push por mês",
"Workspaces": "Workspaces",
"Would_like_to_place_on_hold": "Gostaria de colocar essa conversa em espera?",
"Would_you_like_to_return_the_inquiry": "Deseja retornar a consulta?",
@ -708,6 +727,7 @@
"Your_invite_link_will_expire_on__date__or_after__usesLeft__uses": "Seu link de convite irá vencer em {{date}} ou depois de {{usesLeft}} usos.",
"Your_invite_link_will_never_expire": "Seu link de convite nunca irá vencer.",
"Your_password_is": "Sua senha é",
"Your_push_was_sent_to_s_devices": "A sua notificação foi enviada para {{s}} dispositivos",
"Your_workspace": "Sua workspace",
"__count__empty_room_will_be_removed_automatically": "{{count}} sala vazia será excluída.",
"__count__empty_rooms_will_be_removed_automatically": "{{count}} salas vazias serão excluídas.",
@ -749,6 +769,7 @@
"error-invalid-file-type": "Tipo de arquivo inválido",
"error-invalid-password": "Senha inválida",
"error-invalid-room-name": "{{room_name}} não é um nome de sala válido",
"error-no-tokens-for-this-user": "Não existem tokens para este usuário",
"error-not-allowed": "Não permitido",
"error-not-permission-to-upload-file": "Você não tem permissão para enviar arquivos",
"error-save-image": "Erro ao salvar imagem",

View File

@ -287,7 +287,6 @@ export const colors = {
gray100: '#CBCED1',
n900: '#1F2329',
statusBackgroundWarning: '#FFECAD',
statusFontOnWarning: '#B88D00',
overlayColor: '#1F2329CC',
taskBoxColor: '#9297a2',
...mentions,
@ -369,7 +368,6 @@ export const colors = {
gray100: '#CBCED1',
n900: '#FFFFFF',
statusBackgroundWarning: '#FFECAD',
statusFontOnWarning: '#B88D00',
overlayColor: '#1F2329CC',
taskBoxColor: '#9297a2',
...mentions,
@ -451,7 +449,6 @@ export const colors = {
gray100: '#CBCED1',
n900: '#FFFFFF',
statusBackgroundWarning: '#FFECAD',
statusFontOnWarning: '#B88D00',
overlayColor: '#1F2329CC',
taskBoxColor: '#9297a2',
...mentions,

View File

@ -145,6 +145,8 @@ export default class Subscription extends Model {
@json('source', sanitizer) source;
@field('disable_notifications') disableNotifications;
asPlain() {
return {
_id: this._id,
@ -207,7 +209,8 @@ export default class Subscription extends Model {
teamMain: this.teamMain,
onHold: this.onHold,
usersCount: this.usersCount,
source: this.source
source: this.source,
disableNotifications: this.disableNotifications
};
}
}

View File

@ -284,6 +284,15 @@ export default schemaMigrations({
columns: [{ name: 'unmuted', type: 'string', isOptional: true }]
})
]
},
{
toVersion: 24,
steps: [
addColumns({
table: 'subscriptions',
columns: [{ name: 'disable_notifications', type: 'boolean', isOptional: true }]
})
]
}
]
});

View File

@ -1,7 +1,7 @@
import { appSchema, tableSchema } from '@nozbe/watermelondb';
export default appSchema({
version: 23,
version: 24,
tables: [
tableSchema({
name: 'subscriptions',
@ -66,7 +66,8 @@ export default appSchema({
{ name: 'source', type: 'string', isOptional: true },
{ name: 'hide_mention_status', type: 'boolean', isOptional: true },
{ name: 'users_count', type: 'number', isOptional: true },
{ name: 'unmuted', type: 'string', isOptional: true }
{ name: 'unmuted', type: 'string', isOptional: true },
{ name: 'disable_notifications', type: 'boolean', isOptional: true }
]
}),
tableSchema({

View File

@ -60,7 +60,8 @@ export const SUPPORTED_PERMISSIONS = [
'view-canned-responses',
'mobile-upload-file',
'delete-own-message',
'call-management'
'call-management',
'test-push-notifications'
] as const;
export async function setPermissions(): Promise<void> {

View File

@ -5,12 +5,17 @@ import I18n from '../../../i18n';
export const showErrorAlert = (message: string, title?: string, onPress = () => {}): void =>
Alert.alert(title || '', message, [{ text: 'OK', onPress }], { cancelable: true });
export const showErrorAlertWithEMessage = (e: any): void => {
const messageError =
e.data && e.data.error.includes('[error-too-many-requests]')
? I18n.t('error-too-many-requests', { seconds: e.data.error.replace(/\D/g, '') })
: e.data.errorType;
showErrorAlert(messageError);
export const showErrorAlertWithEMessage = (e: any, title?: string): void => {
let errorMessage: string = e?.data?.error;
if (errorMessage.includes('[error-too-many-requests]')) {
const seconds = errorMessage.replace(/\D/g, '');
errorMessage = I18n.t('error-too-many-requests', { seconds });
} else {
errorMessage = I18n.isTranslated(errorMessage) ? I18n.t(errorMessage) : errorMessage;
}
showErrorAlert(errorMessage, title);
};
interface IShowConfirmationAlert {

View File

@ -1,5 +1,6 @@
import EJSON from 'ejson';
import { appInit } from '../../actions/app';
import { deepLinkingClickCallPush, deepLinkingOpen } from '../../actions/deepLinking';
import { INotification, SubscriptionType } from '../../definitions';
import { isFDroidBuild } from '../constants';
@ -18,39 +19,43 @@ interface IEjson {
export const onNotification = (push: INotification): void => {
const identifier = String(push?.payload?.action?.identifier);
if (identifier === 'ACCEPT_ACTION' || identifier === 'DECLINE_ACTION') {
if (push.payload) {
const notification = EJSON.parse(push.payload.ejson);
if (push?.payload && push?.payload?.ejson) {
const notification = EJSON.parse(push?.payload?.ejson);
store.dispatch(deepLinkingClickCallPush({ ...notification, event: identifier === 'ACCEPT_ACTION' ? 'accept' : 'decline' }));
return;
}
}
if (push.payload) {
if (push?.payload) {
try {
const notification = push.payload;
const { rid, name, sender, type, host, messageId }: IEjson = EJSON.parse(notification.ejson);
const notification = push?.payload;
if (notification.ejson) {
const { rid, name, sender, type, host, messageId }: IEjson = EJSON.parse(notification.ejson);
const types: Record<string, string> = {
c: 'channel',
d: 'direct',
p: 'group',
l: 'channels'
};
let roomName = type === SubscriptionType.DIRECT ? sender.username : name;
if (type === SubscriptionType.OMNICHANNEL) {
roomName = sender.name;
const types: Record<string, string> = {
c: 'channel',
d: 'direct',
p: 'group',
l: 'channels'
};
let roomName = type === SubscriptionType.DIRECT ? sender.username : name;
if (type === SubscriptionType.OMNICHANNEL) {
roomName = sender.name;
}
const params = {
host,
rid,
messageId,
path: `${types[type]}/${roomName}`
};
store.dispatch(deepLinkingOpen(params));
return;
}
const params = {
host,
rid,
messageId,
path: `${types[type]}/${roomName}`
};
store.dispatch(deepLinkingOpen(params));
} catch (e) {
console.warn(e);
}
}
store.dispatch(appInit());
};
export const getDeviceToken = (): string => deviceToken;

View File

@ -105,13 +105,15 @@ const displayVideoConferenceNotification = async (notification: NotificationData
const setBackgroundNotificationHandler = () => {
createChannel();
messaging().setBackgroundMessageHandler(async message => {
const notification: NotificationData = ejson.parse(message?.data?.ejson as string);
if (notification?.notificationType === VIDEO_CONF_TYPE) {
if (notification.status === 0) {
await displayVideoConferenceNotification(notification);
} else if (notification.status === 4) {
const id = `${notification.rid}${notification.caller?._id}`.replace(/[^A-Za-z0-9]/g, '');
await notifee.cancelNotification(id);
if (message?.data?.ejson) {
const notification: NotificationData = ejson.parse(message?.data?.ejson as string);
if (notification?.notificationType === VIDEO_CONF_TYPE) {
if (notification.status === 0) {
await displayVideoConferenceNotification(notification);
} else if (notification.status === 4) {
const id = `${notification.rid}${notification.caller?._id}`.replace(/[^A-Za-z0-9]/g, '');
await notifee.cancelNotification(id);
}
}
}

View File

@ -905,6 +905,12 @@ export const removePushToken = (): Promise<boolean | void> => {
return Promise.resolve();
};
// RC 6.6.0
export const pushTest = () => sdk.post('push.test');
// RC 6.5.0
export const pushInfo = () => sdk.get('push.info');
export const sendEmailCode = () => {
const { username } = reduxStore.getState().login.user as IUser;
// RC 3.1.0

View File

@ -23,6 +23,7 @@ import permissions from './permissions';
import roles from './roles';
import videoConf from './videoConf';
import usersRoles from './usersRoles';
import troubleshootingNotification from './troubleshootingNotification';
import supportedVersions from './supportedVersions';
import inAppFeedback from './inAppFeedback';
@ -50,6 +51,7 @@ export default combineReducers({
roles,
videoConf,
usersRoles,
troubleshootingNotification,
supportedVersions,
inAppFeedback
});

View File

@ -0,0 +1,30 @@
import { setTroubleshootingNotification, initTroubleshootingNotification } from '../actions/troubleshootingNotification';
import { mockedStore } from './mockedStore';
import { ITroubleshootingNotification, initialState } from './troubleshootingNotification';
describe('test troubleshootingNotification reducer', () => {
it('should return initial state', () => {
const state = mockedStore.getState().troubleshootingNotification;
expect(state).toEqual(initialState);
});
it('should return correctly the value after call initTroubleshootingNotification action', () => {
mockedStore.dispatch(initTroubleshootingNotification());
const state = mockedStore.getState().troubleshootingNotification;
expect(state).toEqual(initialState);
});
it('should return correctly value after call troubleshootingNotification action', () => {
const payload: ITroubleshootingNotification = {
deviceNotificationEnabled: true,
issuesWithNotifications: false,
defaultPushGateway: true,
pushGatewayEnabled: true,
consumptionPercentage: 0,
isCommunityEdition: false
};
mockedStore.dispatch(setTroubleshootingNotification(payload));
const state = mockedStore.getState().troubleshootingNotification;
expect(state).toEqual(payload);
});
});

View File

@ -0,0 +1,32 @@
import { TROUBLESHOOTING_NOTIFICATION } from '../actions/actionsTypes';
import { TActionTroubleshootingNotification } from '../actions/troubleshootingNotification';
export interface ITroubleshootingNotification {
deviceNotificationEnabled: boolean;
pushGatewayEnabled: boolean;
defaultPushGateway: boolean;
issuesWithNotifications: boolean;
consumptionPercentage: number;
isCommunityEdition: boolean;
}
export const initialState: ITroubleshootingNotification = {
deviceNotificationEnabled: false,
pushGatewayEnabled: false,
defaultPushGateway: false,
issuesWithNotifications: false,
consumptionPercentage: 0,
isCommunityEdition: false
};
export default (state = initialState, action: TActionTroubleshootingNotification): ITroubleshootingNotification => {
switch (action.type) {
case TROUBLESHOOTING_NOTIFICATION.SET:
return {
...state,
...action.payload
};
default:
return state;
}
};

View File

@ -14,6 +14,7 @@ import inviteLinks from './inviteLinks';
import createDiscussion from './createDiscussion';
import encryption from './encryption';
import videoConf from './videoConf';
import troubleshootingNotification from './troubleshootingNotification';
const root = function* root() {
yield all([
@ -30,7 +31,8 @@ const root = function* root() {
createDiscussion(),
inquiry(),
encryption(),
videoConf()
videoConf(),
troubleshootingNotification()
]);
};

View File

@ -17,6 +17,7 @@ import { inviteLinksRequest } from '../actions/inviteLinks';
import { showErrorAlert } from '../lib/methods/helpers/info';
import { localAuthenticate } from '../lib/methods/helpers/localAuthentication';
import { encryptionInit, encryptionStop } from '../actions/encryption';
import { initTroubleshootingNotification } from '../actions/troubleshootingNotification';
import UserPreferences from '../lib/methods/userPreferences';
import { inquiryRequest, inquiryReset } from '../ee/omnichannel/actions/inquiry';
import { isOmnichannelStatusAvailable } from '../ee/omnichannel/lib';
@ -236,6 +237,7 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) {
yield put(inviteLinksRequest(inviteLinkToken));
}
yield showSupportedVersionsWarning(server);
yield put(initTroubleshootingNotification());
} catch (e) {
log(e);
}

View File

@ -0,0 +1,54 @@
import { Action } from 'redux';
import { call, takeLatest, put } from 'typed-redux-saga';
import notifee, { AuthorizationStatus } from '@notifee/react-native';
import { TROUBLESHOOTING_NOTIFICATION } from '../actions/actionsTypes';
import { setTroubleshootingNotification } from '../actions/troubleshootingNotification';
import { pushInfo } from '../lib/services/restApi';
import log from '../lib/methods/helpers/log';
import { appSelector } from '../lib/hooks';
import { compareServerVersion } from '../lib/methods/helpers';
interface IGenericAction extends Action {
type: string;
}
function* init() {
const serverVersion = yield* appSelector(state => state.server.version);
let deviceNotificationEnabled = false;
let defaultPushGateway = false;
let pushGatewayEnabled = false;
try {
const { authorizationStatus } = yield* call(notifee.getNotificationSettings);
deviceNotificationEnabled = authorizationStatus > AuthorizationStatus.DENIED;
} catch (e) {
log(e);
}
try {
if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '6.5.0')) {
const pushInfoResult = yield* call(pushInfo);
if (pushInfoResult.success) {
pushGatewayEnabled = pushInfoResult.pushGatewayEnabled;
defaultPushGateway = pushInfoResult.defaultPushGateway;
}
}
} catch (e) {
log(e);
}
const issuesWithNotifications =
!deviceNotificationEnabled || (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '6.6.0') && !pushGatewayEnabled);
yield put(
setTroubleshootingNotification({
deviceNotificationEnabled,
defaultPushGateway,
pushGatewayEnabled,
issuesWithNotifications
})
);
}
export default function* root(): Generator {
yield takeLatest<IGenericAction>(TROUBLESHOOTING_NOTIFICATION.INIT, init);
}

View File

@ -42,6 +42,7 @@ import DisplayPrefsView from '../views/DisplayPrefsView';
// Settings Stack
import SettingsView from '../views/SettingsView';
import SecurityPrivacyView from '../views/SecurityPrivacyView';
import PushTroubleshootView from '../views/PushTroubleshootView';
import E2EEncryptionSecurityView from '../views/E2EEncryptionSecurityView';
import LanguageView from '../views/LanguageView';
import ThemeView from '../views/ThemeView';
@ -118,6 +119,7 @@ const ChatsStackNavigator = () => {
<ChatsStack.Screen name='AutoTranslateView' component={AutoTranslateView} />
<ChatsStack.Screen name='DirectoryView' component={DirectoryView} options={DirectoryView.navigationOptions} />
<ChatsStack.Screen name='NotificationPrefView' component={NotificationPrefView} />
<ChatsStack.Screen name='PushTroubleshootView' component={PushTroubleshootView} />
<ChatsStack.Screen name='ForwardLivechatView' component={ForwardLivechatView} />
<ChatsStack.Screen name='CloseLivechatView' component={CloseLivechatView} />
<ChatsStack.Screen name='LivechatEditView' component={LivechatEditView} options={LivechatEditView.navigationOptions} />
@ -155,6 +157,7 @@ const ProfileStackNavigator = () => {
<ProfileStack.Screen name='UserPreferencesView' component={UserPreferencesView} />
<ProfileStack.Screen name='ChangeAvatarView' component={ChangeAvatarView} />
<ProfileStack.Screen name='UserNotificationPrefView' component={UserNotificationPrefView} />
<ProfileStack.Screen name='PushTroubleshootView' component={PushTroubleshootView} />
<ProfileStack.Screen name='PickerView' component={PickerView} />
</ProfileStack.Navigator>
);
@ -171,6 +174,7 @@ const SettingsStackNavigator = () => {
>
<SettingsStack.Screen name='SettingsView' component={SettingsView} />
<SettingsStack.Screen name='SecurityPrivacyView' component={SecurityPrivacyView} />
<SettingsStack.Screen name='PushTroubleshootView' component={PushTroubleshootView} />
<SettingsStack.Screen name='E2EEncryptionSecurityView' component={E2EEncryptionSecurityView} />
<SettingsStack.Screen name='LanguageView' component={LanguageView} />
<SettingsStack.Screen name='ThemeView' component={ThemeView} />

View File

@ -27,6 +27,7 @@ import MessagesView from '../../views/MessagesView';
import AutoTranslateView from '../../views/AutoTranslateView';
import DirectoryView from '../../views/DirectoryView';
import NotificationPrefView from '../../views/NotificationPreferencesView';
import PushTroubleshootView from '../../views/PushTroubleshootView';
import ForwardLivechatView from '../../views/ForwardLivechatView';
import ForwardMessageView from '../../views/ForwardMessageView';
import CloseLivechatView from '../../views/CloseLivechatView';
@ -187,6 +188,7 @@ const ModalStackNavigator = React.memo(({ navigation }: INavigation) => {
<ModalStack.Screen name='SecurityPrivacyView' component={SecurityPrivacyView} />
<ModalStack.Screen name='MediaAutoDownloadView' component={MediaAutoDownloadView} />
<ModalStack.Screen name='E2EEncryptionSecurityView' component={E2EEncryptionSecurityView} />
<ModalStack.Screen name='PushTroubleshootView' component={PushTroubleshootView} />
<ModalStack.Screen name='SupportedVersionsWarning' component={SupportedVersionsWarning} />
</ModalStack.Navigator>
</ModalContainer>

View File

@ -196,6 +196,7 @@ export type ModalStackParamList = {
SecurityPrivacyView: undefined;
MediaAutoDownloadView: undefined;
E2EEncryptionSecurityView: undefined;
PushTroubleshootView: undefined;
SupportedVersionsWarning: {
showCloseButton?: boolean;
};

View File

@ -122,6 +122,7 @@ export type ChatsStackParamList = {
rid: string;
room: TSubscriptionModel;
};
PushTroubleshootView: undefined;
CloseLivechatView: {
rid: string;
departmentId?: string;
@ -188,6 +189,7 @@ export type ProfileStackParamList = {
ProfileView: undefined;
UserPreferencesView: undefined;
UserNotificationPrefView: undefined;
PushTroubleshootView: undefined;
ChangeAvatarView: {
context: TChangeAvatarViewContext;
titleHeader?: string;
@ -207,6 +209,7 @@ export type SettingsStackParamList = {
ProfileView: undefined;
DisplayPrefsView: undefined;
MediaAutoDownloadView: undefined;
PushTroubleshootView: undefined;
};
export type AdminPanelStackParamList = {

View File

@ -1,6 +1,7 @@
import { RouteProp, useNavigation, useRoute } from '@react-navigation/core';
import React, { useEffect, useState } from 'react';
import { Switch, Text } from 'react-native';
import { StackNavigationProp } from '@react-navigation/stack';
import { TActionSheetOptionsItem, useActionSheet } from '../../containers/ActionSheet';
import { CustomIcon } from '../../containers/CustomIcon';
@ -91,8 +92,11 @@ const RenderSwitch = ({ preference, room, onChangeValue }: IBaseParams) => {
const NotificationPreferencesView = (): React.ReactElement => {
const route = useRoute<RouteProp<ChatsStackParamList, 'NotificationPrefView'>>();
const { rid, room } = route.params;
const navigation = useNavigation();
const serverVersion = useAppSelector(state => state.server.version);
const navigation = useNavigation<StackNavigationProp<ChatsStackParamList, 'NotificationPrefView'>>();
const { serverVersion, isMasterDetail } = useAppSelector(state => ({
serverVersion: state.server.version,
isMasterDetail: state.app.isMasterDetail
}));
const [hideUnreadStatus, setHideUnreadStatus] = useState(room.hideUnreadStatus);
useEffect(() => {
@ -108,6 +112,14 @@ const NotificationPreferencesView = (): React.ReactElement => {
});
}, []);
const navigateToPushTroubleshootView = () => {
if (isMasterDetail) {
navigation.navigate('ModalStackNavigator', { screen: 'PushTroubleshootView' });
} else {
navigation.navigate('PushTroubleshootView');
}
};
const saveNotificationSettings = async (key: TUnionOptionsRoomNotifications, params: IRoomNotifications, onError: Function) => {
try {
// @ts-ignore
@ -202,6 +214,13 @@ const NotificationPreferencesView = (): React.ReactElement => {
onChangeValue={saveNotificationSettings}
/>
<List.Separator />
<List.Item
title='Troubleshooting'
onPress={navigateToPushTroubleshootView}
testID='notification-preference-view-troubleshooting'
showActionIndicator
/>
<List.Separator />
<List.Info info='Push_Notifications_Alert_Info' />
</List.Section>
<List.Section title='Email'>

View File

@ -0,0 +1,56 @@
import React from 'react';
import { Alert, StyleSheet, Text } from 'react-native';
import * as List from '../../../containers/List';
import i18n from '../../../i18n';
import { useAppSelector } from '../../../lib/hooks';
import { useTheme } from '../../../theme';
import sharedStyles from '../../Styles';
const WARNING_MINIMUM_VALUE = 70;
const WARNING_MAXIMUM_VALUE = 90;
export default function CommunityEditionPushQuota(): React.ReactElement | null {
const { colors } = useTheme();
const { consumptionPercentage, isCommunityEdition } = useAppSelector(state => ({
isCommunityEdition: state.troubleshootingNotification.isCommunityEdition,
consumptionPercentage: state.troubleshootingNotification.consumptionPercentage
}));
if (!isCommunityEdition) return null;
const percentage = `${Math.floor(consumptionPercentage)}%`;
let percentageColor = colors.statusFontSuccess;
if (consumptionPercentage > WARNING_MINIMUM_VALUE && consumptionPercentage < WARNING_MAXIMUM_VALUE) {
percentageColor = colors.statusFontWarning;
}
if (consumptionPercentage >= WARNING_MAXIMUM_VALUE) {
percentageColor = colors.statusFontDanger;
}
const alertWorkspaceConsumption = () => {
Alert.alert(i18n.t('Push_consumption_alert_title'), i18n.t('Push_consumption_alert_description'));
};
return (
<List.Section title='Community_edition_push_quota'>
<List.Separator />
<List.Item
title='Workspace_consumption'
testID='push-troubleshoot-view-workspace-consumption'
onPress={alertWorkspaceConsumption}
right={() => <Text style={[styles.pickerText, { color: percentageColor }]}>{percentage}</Text>}
/>
<List.Separator />
<List.Info info='Workspace_consumption_description' />
</List.Section>
);
}
const styles = StyleSheet.create({
pickerText: {
...sharedStyles.textRegular,
fontSize: 16
}
});

View File

@ -0,0 +1,52 @@
import React from 'react';
import { StyleSheet, View } from 'react-native';
import { Header } from '../../../containers/List';
const styles = StyleSheet.create({
container: {
marginBottom: 16
},
headerContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center'
},
statusContainer: {
width: 10,
height: 10,
borderRadius: 5,
marginRight: 12
}
});
interface ICustomListSection {
children: (React.ReactElement | null)[] | React.ReactElement | null;
title: string;
translateTitle?: boolean;
statusColor?: string;
}
const CustomHeader = ({
title,
translateTitle,
statusColor
}: {
title: string;
translateTitle?: boolean;
statusColor?: string;
}) => (
<View style={styles.headerContainer}>
<Header {...{ title, translateTitle }} />
{statusColor ? <View style={[styles.statusContainer, { backgroundColor: statusColor }]} /> : null}
</View>
);
const CustomListSection = ({ children, title, translateTitle, statusColor }: ICustomListSection) => (
<View style={styles.container}>
{title ? <CustomHeader {...{ title, translateTitle, statusColor }} /> : null}
{children}
</View>
);
export default CustomListSection;

View File

@ -0,0 +1,50 @@
import notifee from '@notifee/react-native';
import React from 'react';
import { Linking } from 'react-native';
import * as List from '../../../containers/List';
import i18n from '../../../i18n';
import { useAppSelector } from '../../../lib/hooks';
import { isIOS, showErrorAlert } from '../../../lib/methods/helpers';
import { useTheme } from '../../../theme';
import CustomListSection from './CustomListSection';
export default function DeviceNotificationSettings(): React.ReactElement {
const { colors } = useTheme();
const { deviceNotificationEnabled } = useAppSelector(state => ({
deviceNotificationEnabled: state.troubleshootingNotification.deviceNotificationEnabled
}));
const goToNotificationSettings = () => {
if (isIOS) {
Linking.openURL('app-settings:');
} else {
notifee.openNotificationSettings();
}
};
const alertDeviceNotificationSettings = () => {
if (deviceNotificationEnabled) return;
showErrorAlert(
i18n.t('Device_notifications_alert_description'),
i18n.t('Device_notifications_alert_title'),
goToNotificationSettings
);
};
return (
<CustomListSection
title='Device_notification_settings'
statusColor={!deviceNotificationEnabled ? colors.userPresenceBusy : colors.userPresenceOnline}
>
<List.Separator />
<List.Item
title={!deviceNotificationEnabled ? 'Allow_push_notifications_for_rocket_chat' : 'No_further_action_is_needed'}
onPress={alertDeviceNotificationSettings}
testID='push-troubleshoot-view-allow-push-notifications'
disabled={deviceNotificationEnabled}
/>
<List.Separator />
</CustomListSection>
);
}

View File

@ -0,0 +1,25 @@
import React from 'react';
import { Linking } from 'react-native';
import * as List from '../../../containers/List';
import { useTheme } from '../../../theme';
export default function NotificationDelay(): React.ReactElement {
const { colors } = useTheme();
const openNotificationDocumentation = () => Linking.openURL('https://go.rocket.chat/i/push-notifications');
return (
<List.Section title='Notification_delay'>
<List.Separator />
<List.Item
title='Documentation'
onPress={openNotificationDocumentation}
right={() => <List.Icon size={32} name='new-window' color={colors.fontAnnotation} />}
testID='push-troubleshoot-view-notification-delay'
/>
<List.Separator />
<List.Info info='Notification_delay_description' />
</List.Section>
);
}

View File

@ -0,0 +1,65 @@
import React, { useState } from 'react';
import { Alert } from 'react-native';
import * as List from '../../../containers/List';
import i18n from '../../../i18n';
import { useAppSelector, usePermissions } from '../../../lib/hooks';
import { compareServerVersion, showErrorAlertWithEMessage } from '../../../lib/methods/helpers';
import { Services } from '../../../lib/services';
import { useTheme } from '../../../theme';
import CustomListSection from './CustomListSection';
export default function PushGatewayConnection(): React.ReactElement | null {
const [loading, setLoading] = useState(false);
const { colors } = useTheme();
const [testPushNotificationsPermission] = usePermissions(['test-push-notifications']);
const { defaultPushGateway, pushGatewayEnabled, serverVersion } = useAppSelector(state => ({
pushGatewayEnabled: state.troubleshootingNotification.pushGatewayEnabled,
defaultPushGateway: state.troubleshootingNotification.defaultPushGateway,
foreground: state.app.foreground,
serverVersion: state.server.version
}));
if (!compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '6.6.0')) return null;
const handleTestPushNotification = async () => {
setLoading(true);
try {
const result = await Services.pushTest();
if (result.success) {
Alert.alert(i18n.t('Test_push_notification'), i18n.t('Your_push_was_sent_to_s_devices', { s: result.tokensCount }));
}
} catch (error: any) {
showErrorAlertWithEMessage(error, i18n.t('Test_push_notification'));
}
setLoading(false);
};
let infoColor = 'Push_gateway_not_connected_description';
let statusColor = colors.userPresenceBusy;
if (pushGatewayEnabled) {
statusColor = colors.userPresenceOnline;
infoColor = 'Push_gateway_connected_description';
}
if (pushGatewayEnabled && !defaultPushGateway) {
statusColor = colors.badgeBackgroundLevel3;
infoColor = 'Custom_push_gateway_connected_description';
}
return (
<CustomListSection
title={!defaultPushGateway ? 'Custom_push_gateway_connection' : 'Push_gateway_connection'}
statusColor={statusColor}
>
<List.Separator />
<List.Item
title='Test_push_notification'
disabled={!pushGatewayEnabled || !testPushNotificationsPermission || loading}
onPress={handleTestPushNotification}
testID='push-troubleshoot-view-push-gateway-connection'
/>
<List.Separator />
<List.Info info={infoColor} />
</CustomListSection>
);
}

View File

@ -0,0 +1,49 @@
import { useFocusEffect } from '@react-navigation/native';
import { StackNavigationProp } from '@react-navigation/stack';
import React, { useCallback, useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { initTroubleshootingNotification } from '../../actions/troubleshootingNotification';
import * as List from '../../containers/List';
import SafeAreaView from '../../containers/SafeAreaView';
import StatusBar from '../../containers/StatusBar';
import I18n from '../../i18n';
import { SettingsStackParamList } from '../../stacks/types';
// import CommunityEditionPushQuota from './components/CommunityEditionPushQuota';
import DeviceNotificationSettings from './components/DeviceNotificationSettings';
import NotificationDelay from './components/NotificationDelay';
import PushGatewayConnection from './components/PushGatewayConnection';
interface IPushTroubleshootViewProps {
navigation: StackNavigationProp<SettingsStackParamList, 'PushTroubleshootView'>;
}
const PushTroubleshootView = ({ navigation }: IPushTroubleshootViewProps): JSX.Element => {
const dispatch = useDispatch();
useFocusEffect(
useCallback(() => {
dispatch(initTroubleshootingNotification());
}, [])
);
useEffect(() => {
navigation.setOptions({
title: I18n.t('Push_Troubleshooting')
});
}, [navigation]);
return (
<SafeAreaView testID='push-troubleshoot-view'>
<StatusBar />
<List.Container testID='push-troubleshoot-view-list'>
<DeviceNotificationSettings />
{/* <CommunityEditionPushQuota /> */}
<PushGatewayConnection />
<NotificationDelay />
</List.Container>
</SafeAreaView>
);
};
export default PushTroubleshootView;

View File

@ -21,6 +21,7 @@ import { getUserSelector } from '../../selectors/login';
import { TNavigation } from '../../stacks/stackType';
import { ChatsStackParamList } from '../../stacks/types';
import HeaderCallButton from './components/HeaderCallButton';
import { TColors, TSupportedThemes, withTheme } from '../../theme';
interface IRightButtonsProps extends Pick<ISubscription, 't'> {
userId?: string;
@ -43,6 +44,10 @@ interface IRightButtonsProps extends Pick<ISubscription, 't'> {
showActionSheet: Function;
departmentId?: string;
rid?: string;
theme?: TSupportedThemes;
colors?: TColors;
issuesWithNotifications: boolean;
notificationsDisabled?: boolean;
}
interface IRigthButtonsState {
@ -55,6 +60,7 @@ interface IRigthButtonsState {
class RightButtonsContainer extends Component<IRightButtonsProps, IRigthButtonsState> {
private threadSubscription?: Subscription;
private subSubscription?: Subscription;
private room?: TSubscriptionModel;
constructor(props: IRightButtonsProps) {
super(props);
@ -80,8 +86,8 @@ class RightButtonsContainer extends Component<IRightButtonsProps, IRigthButtonsS
if (rid) {
try {
const subCollection = db.get('subscriptions');
const subRecord = await subCollection.find(rid);
this.observeSubscription(subRecord);
this.room = await subCollection.find(rid);
this.observeSubscription(this.room);
} catch (e) {
console.log("Can't find subscription to observe.");
}
@ -90,7 +96,7 @@ class RightButtonsContainer extends Component<IRightButtonsProps, IRigthButtonsS
shouldComponentUpdate(nextProps: IRightButtonsProps, nextState: IRigthButtonsState) {
const { isFollowingThread, tunread, tunreadUser, tunreadGroup } = this.state;
const { teamId, status, joined, omnichannelPermissions } = this.props;
const { teamId, status, joined, omnichannelPermissions, theme, issuesWithNotifications, notificationsDisabled } = this.props;
if (nextProps.teamId !== teamId) {
return true;
}
@ -100,9 +106,18 @@ class RightButtonsContainer extends Component<IRightButtonsProps, IRigthButtonsS
if (nextProps.joined !== joined) {
return true;
}
if (nextProps.theme !== theme) {
return true;
}
if (nextState.isFollowingThread !== isFollowingThread) {
return true;
}
if (nextProps.issuesWithNotifications !== issuesWithNotifications) {
return true;
}
if (nextProps.notificationsDisabled !== notificationsDisabled) {
return true;
}
if (!dequal(nextProps.omnichannelPermissions, omnichannelPermissions)) {
return true;
}
@ -288,6 +303,31 @@ class RightButtonsContainer extends Component<IRightButtonsProps, IRigthButtonsS
showActionSheet({ options });
};
navigateToNotificationOrPushTroubleshoot = () => {
const { room } = this;
const { rid, navigation, isMasterDetail, issuesWithNotifications } = this.props;
if (!rid || !room) {
return;
}
if (!issuesWithNotifications && room) {
if (isMasterDetail) {
navigation.navigate('ModalStackNavigator', {
screen: 'NotificationPrefView',
params: { rid, room }
});
} else {
navigation.navigate('NotificationPrefView', { rid, room });
}
} else if (isMasterDetail) {
navigation.navigate('ModalStackNavigator', {
screen: 'PushTroubleshootView'
});
} else {
navigation.navigate('PushTroubleshootView');
}
};
goSearchView = () => {
logEvent(events.ROOM_GO_SEARCH);
const { rid, t, navigation, isMasterDetail, encrypted } = this.props;
@ -321,7 +361,7 @@ class RightButtonsContainer extends Component<IRightButtonsProps, IRigthButtonsS
render() {
const { isFollowingThread, tunread, tunreadUser, tunreadGroup } = this.state;
const { t, tmid, threadsEnabled, rid } = this.props;
const { t, tmid, threadsEnabled, rid, colors, issuesWithNotifications, notificationsDisabled } = this.props;
if (t === 'l') {
if (!this.isOmnichannelPreview()) {
@ -346,6 +386,14 @@ class RightButtonsContainer extends Component<IRightButtonsProps, IRigthButtonsS
}
return (
<HeaderButton.Container>
{issuesWithNotifications || notificationsDisabled ? (
<HeaderButton.Item
color={issuesWithNotifications ? colors!.fontDanger : colors!.headerTintColor}
iconName='notification-disabled'
onPress={this.navigateToNotificationOrPushTroubleshoot}
testID='room-view-push-troubleshoot'
/>
) : null}
{rid ? <HeaderCallButton rid={rid} /> : null}
{threadsEnabled ? (
<HeaderButton.Item
@ -365,7 +413,8 @@ const mapStateToProps = (state: IApplicationState) => ({
userId: getUserSelector(state).id,
threadsEnabled: state.settings.Threads_enabled as boolean,
isMasterDetail: state.app.isMasterDetail,
livechatRequestComment: state.settings.Livechat_request_comment_when_closing_conversation as boolean
livechatRequestComment: state.settings.Livechat_request_comment_when_closing_conversation as boolean,
issuesWithNotifications: state.troubleshootingNotification.issuesWithNotifications
});
export default connect(mapStateToProps)(RightButtonsContainer);
export default connect(mapStateToProps)(withTheme(RightButtonsContainer));

View File

@ -456,7 +456,7 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
const t = room?.t;
const teamMain = 'teamMain' in room ? room?.teamMain : false;
const omnichannelPermissions = { canForwardGuest, canReturnQueue, canPlaceLivechatOnHold };
const iSubRoom = room as ISubscription;
navigation.setOptions({
headerShown: true,
headerTitleAlign: 'left',
@ -513,6 +513,7 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
toggleFollowThread={this.toggleFollowThread}
showActionSheet={this.showActionSheet}
departmentId={departmentId}
notificationsDisabled={iSubRoom?.disableNotifications}
/>
)
});

View File

@ -92,6 +92,7 @@ interface IRoomsListViewProps {
createPrivateChannelPermission: [];
createDiscussionPermission: [];
serverVersion: string;
issuesWithNotifications: boolean;
}
interface IRoomsListViewState {
@ -146,6 +147,7 @@ const shouldUpdateProps = [
'createPublicChannelPermission',
'createPrivateChannelPermission',
'createDiscussionPermission',
'issuesWithNotifications',
'supportedVersionsStatus'
];
@ -200,7 +202,6 @@ class RoomsListView extends React.Component<IRoomsListViewProps, IRoomsListViewS
const { navigation, dispatch } = this.props;
this.handleHasPermission();
this.mounted = true;
this.unsubscribeFocus = navigation.addListener('focus', () => {
this.animated = true;
// Check if there were changes with sort preference, then call getSubscription to remount the list
@ -334,6 +335,7 @@ class RoomsListView extends React.Component<IRoomsListViewProps, IRoomsListViewS
createDiscussionPermission,
showAvatar,
displayMode,
issuesWithNotifications,
supportedVersionsStatus
} = this.props;
const { item } = this.state;
@ -358,6 +360,7 @@ class RoomsListView extends React.Component<IRoomsListViewProps, IRoomsListViewS
insets.left !== prevProps.insets.left ||
insets.right !== prevProps.insets.right ||
notificationPresenceCap !== prevProps.notificationPresenceCap ||
issuesWithNotifications !== prevProps.issuesWithNotifications ||
supportedVersionsStatus !== prevProps.supportedVersionsStatus
) {
this.setHeader();
@ -411,7 +414,8 @@ class RoomsListView extends React.Component<IRoomsListViewProps, IRoomsListViewS
getHeader = (): StackNavigationOptions => {
const { searching, canCreateRoom } = this.state;
const { navigation, isMasterDetail, notificationPresenceCap, supportedVersionsStatus, theme } = this.props;
const { navigation, isMasterDetail, notificationPresenceCap, issuesWithNotifications, supportedVersionsStatus, theme } =
this.props;
if (searching) {
return {
headerTitleAlign: 'left',
@ -458,6 +462,14 @@ class RoomsListView extends React.Component<IRoomsListViewProps, IRoomsListViewS
headerTitle: () => <RoomsListHeaderView />,
headerRight: () => (
<HeaderButton.Container>
{issuesWithNotifications ? (
<HeaderButton.Item
iconName='notification-disabled'
onPress={this.navigateToPushTroubleshootView}
testID='rooms-list-view-push-troubleshoot'
color={themes[theme].fontDanger}
/>
) : null}
{canCreateRoom ? (
<HeaderButton.Item
iconName='create'
@ -775,6 +787,15 @@ class RoomsListView extends React.Component<IRoomsListViewProps, IRoomsListViewS
}
};
navigateToPushTroubleshootView = () => {
const { navigation, isMasterDetail } = this.props;
if (isMasterDetail) {
navigation.navigate('ModalStackNavigator', { screen: 'PushTroubleshootView' });
} else {
navigation.navigate('PushTroubleshootView');
}
};
goQueue = () => {
logEvent(events.RL_GO_QUEUE);
const { navigation, isMasterDetail, inquiryEnabled } = this.props;
@ -1008,7 +1029,8 @@ const mapStateToProps = (state: IApplicationState) => ({
createPublicChannelPermission: state.permissions['create-c'],
createPrivateChannelPermission: state.permissions['create-p'],
createDiscussionPermission: state.permissions['start-discussion'],
serverVersion: state.server.version
serverVersion: state.server.version,
issuesWithNotifications: state.troubleshootingNotification.issuesWithNotifications
});
export default connect(mapStateToProps)(withDimensions(withTheme(withSafeAreaInsets(RoomsListView))));

View File

@ -1,7 +1,7 @@
import React, { useEffect, useLayoutEffect, useState } from 'react';
import { Switch } from 'react-native';
import { StackNavigationProp } from '@react-navigation/stack';
import { useNavigation } from '@react-navigation/native';
import { CompositeNavigationProp, useNavigation } from '@react-navigation/native';
import StatusBar from '../../containers/StatusBar';
import * as List from '../../containers/List';
@ -15,16 +15,25 @@ import { Services } from '../../lib/services';
import { useAppSelector } from '../../lib/hooks';
import ListPicker from './ListPicker';
import log from '../../lib/methods/helpers/log';
import { MasterDetailInsideStackParamList } from '../../stacks/MasterDetailStack/types';
import { useUserPreferences } from '../../lib/methods';
import { NOTIFICATION_IN_APP_VIBRATION, SWITCH_TRACK_COLOR } from '../../lib/constants';
type TNavigation = CompositeNavigationProp<
StackNavigationProp<ProfileStackParamList, 'UserNotificationPrefView'>,
StackNavigationProp<MasterDetailInsideStackParamList>
>;
const UserNotificationPreferencesView = () => {
const [inAppVibration, setInAppVibration] = useUserPreferences<boolean>(NOTIFICATION_IN_APP_VIBRATION, true);
const [preferences, setPreferences] = useState({} as INotificationPreferences);
const [loading, setLoading] = useState(true);
const navigation = useNavigation<StackNavigationProp<ProfileStackParamList, 'UserNotificationPrefView'>>();
const userId = useAppSelector(state => getUserSelector(state).id);
const navigation = useNavigation<TNavigation>();
const { userId, isMasterDetail } = useAppSelector(state => ({
userId: getUserSelector(state).id,
isMasterDetail: state.app.isMasterDetail
}));
useLayoutEffect(() => {
navigation.setOptions({
@ -62,6 +71,14 @@ const UserNotificationPreferencesView = () => {
}
};
const navigateToPushTroubleshootView = () => {
if (isMasterDetail) {
navigation.navigate('ModalStackNavigator', { screen: 'PushTroubleshootView' });
} else {
navigation.navigate('PushTroubleshootView');
}
};
const toggleInAppVibration = () => {
setInAppVibration(!inAppVibration);
};
@ -97,6 +114,13 @@ const UserNotificationPreferencesView = () => {
value={preferences.pushNotifications}
/>
<List.Separator />
<List.Item
title='Troubleshooting'
onPress={navigateToPushTroubleshootView}
testID='user-notification-preference-view-troubleshooting'
showActionIndicator
/>
<List.Separator />
<List.Info info='Push_Notifications_Alert_Info' />
</List.Section>

View File

@ -534,6 +534,11 @@ PODS:
- React-Core
- RNMathView (1.0.0):
- iosMath
- RNNotifee (7.8.0):
- React-Core
- RNNotifee/NotifeeCore (= 7.8.0)
- RNNotifee/NotifeeCore (7.8.0):
- React-Core
- RNReanimated (2.8.0):
- DoubleConversion
- FBLazyVector
@ -666,6 +671,7 @@ DEPENDENCIES:
- RNImageCropPicker (from `../node_modules/react-native-image-crop-picker`)
- RNLocalize (from `../node_modules/react-native-localize`)
- RNMathView (from `../node_modules/react-native-math-view/ios`)
- "RNNotifee (from `../node_modules/@notifee/react-native`)"
- RNReanimated (from `../node_modules/react-native-reanimated`)
- RNRootView (from `../node_modules/rn-root-view`)
- RNScreens (from `../node_modules/react-native-screens`)
@ -857,6 +863,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-localize"
RNMathView:
:path: "../node_modules/react-native-math-view/ios"
RNNotifee:
:path: "../node_modules/@notifee/react-native"
RNReanimated:
:path: "../node_modules/react-native-reanimated"
RNRootView:
@ -972,6 +980,7 @@ SPEC CHECKSUMS:
RNImageCropPicker: 97289cd94fb01ab79db4e5c92938be4d0d63415d
RNLocalize: 82a569022724d35461e2dc5b5d015a13c3ca995b
RNMathView: 4c8a3c081fa671ab3136c51fa0bdca7ffb708bd5
RNNotifee: f3c01b391dd8e98e67f539f9a35a9cbcd3bae744
RNReanimated: 64573e25e078ae6bec03b891586d50b9ec284393
RNRootView: 895a4813dedeaca82db2fa868ca1c333d790e494
RNScreens: fa9b582d85ae5d62c91c66003b5278458fed7aaa

View File

@ -19,11 +19,6 @@ module.exports = {
platforms: {
ios: null
}
},
'@notifee/react-native': {
platforms: {
ios: null
}
}
}
};