diff --git a/Makefile b/Makefile index f82ed51b77..e1bf0b5fce 100644 --- a/Makefile +++ b/Makefile @@ -154,7 +154,7 @@ ifneq ($(detected_OS),Windows) DOTHERSIDE := vendor/DOtherSide/build/lib/libDOtherSideStatic.a DOTHERSIDE_CMAKE_PARAMS := -DENABLE_DYNAMIC_LIBS=OFF -DENABLE_STATIC_LIBS=ON # order matters here, due to "-Wl,-as-needed" - NIM_PARAMS += --passL:"$(DOTHERSIDE)" --passL:"$(shell PKG_CONFIG_PATH="$(QT5_PCFILEDIR)" pkg-config --libs Qt5Core Qt5Qml Qt5Gui Qt5Quick Qt5QuickControls2 Qt5Widgets Qt5Svg)" + NIM_PARAMS += --passL:"$(DOTHERSIDE)" --passL:"$(shell PKG_CONFIG_PATH="$(QT5_PCFILEDIR)" pkg-config --libs Qt5Core Qt5Qml Qt5Gui Qt5Quick Qt5QuickControls2 Qt5Widgets Qt5Svg Qt5Multimedia)" else DOTHERSIDE := vendor/DOtherSide/build/lib/Release/DOtherSide.dll DOTHERSIDE_CMAKE_PARAMS := -T"v141" -A x64 -DENABLE_DYNAMIC_LIBS=ON -DENABLE_STATIC_LIBS=OFF diff --git a/src/app/boot/app_controller.nim b/src/app/boot/app_controller.nim index dca63ceea5..7ce2b35968 100644 --- a/src/app/boot/app_controller.nim +++ b/src/app/boot/app_controller.nim @@ -1,7 +1,6 @@ import NimQml import ../../app_service/service/general/service as general_service -import ../../app_service/service/os_notification/service as os_notification_service import ../../app_service/service/eth/service as eth_service import ../../app_service/service/keychain/service as keychain_service import ../../app_service/service/accounts/service as accounts_service @@ -35,7 +34,6 @@ import ../../app_service/service/ens/service as ens_service import ../modules/startup/module as startup_module import ../modules/main/module as main_module -import ../global/local_account_settings import ../global/global_singleton import ../core/[main] @@ -53,7 +51,6 @@ type # Services generalService: general_service.Service - osNotificationService: os_notification_service.Service keychainService: keychain_service.Service ethService: eth_service.Service accountsService: accounts_service.Service @@ -124,7 +121,6 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController = result.settingsService = settings_service.newService() result.nodeConfigurationService = node_configuration_service.newService(statusFoundation.fleetConfiguration, result.settingsService) - result.osNotificationService = os_notification_service.newService(statusFoundation.events) result.keychainService = keychain_service.newService(statusFoundation.events) result.ethService = eth_service.newService() result.accountsService = accounts_service.newService(statusFoundation.fleetConfiguration) @@ -226,7 +222,6 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController = proc delete*(self: AppController) = singletonInstance.delete - self.osNotificationService.delete self.keychainService.delete self.contactsService.delete self.bookmarkService.delete @@ -279,7 +274,6 @@ proc startupDidLoad*(self: AppController) = self.startupModule.startUpUIRaised() proc mainDidLoad*(self: AppController) = - self.statusFoundation.onLoggedIn() self.startupModule.moveToAppState() self.mainModule.checkForStoringPassword() @@ -347,8 +341,6 @@ proc userLoggedIn*(self: AppController) = if(importedAccount.isValid()): self.privacyService.removeMnemonic() - self.osNotificationService.userLoggedIn() - proc buildAndRegisterLocalAccountSensitiveSettings(self: AppController) = var pubKey = self.settingsService.getPublicKey() singletonInstance.localAccountSensitiveSettings.setFileName(pubKey) diff --git a/src/app/core/main.nim b/src/app/core/main.nim index 311593efaf..8f70f046ff 100644 --- a/src/app/core/main.nim +++ b/src/app/core/main.nim @@ -4,16 +4,18 @@ import ./fleets/fleet_configuration, ./tasks/marathon, ./tasks/threadpool, - ./signals/signals_manager + ./signals/signals_manager, + ./notifications/notifications_manager export eventemitter -export marathon, task_runner, signals_manager, fleet_configuration +export marathon, task_runner, signals_manager, fleet_configuration, notifications_manager type StatusFoundation* = ref object events*: EventEmitter fleetConfiguration*: FleetConfiguration threadpool*: ThreadPool signalsManager*: SignalsManager + notificationsManager*: NotificationsManager proc newStatusFoundation*(fleetConfig: string): StatusFoundation = result = StatusFoundation() @@ -21,11 +23,10 @@ proc newStatusFoundation*(fleetConfig: string): StatusFoundation = result.fleetConfiguration = newFleetConfiguration(fleetConfig) result.threadpool = newThreadPool() result.signalsManager = newSignalsManager(result.events) + result.notificationsManager = newNotificationsManager(result.events) proc delete*(self: StatusFoundation) = self.threadpool.teardown() self.fleetConfiguration.delete() self.signalsManager.delete() - -proc onLoggedIn*(self: StatusFoundation) = - discard + self.notificationsManager.delete() \ No newline at end of file diff --git a/src/app/core/notifications/details.nim b/src/app/core/notifications/details.nim new file mode 100644 index 0000000000..2a1429cc4c --- /dev/null +++ b/src/app/core/notifications/details.nim @@ -0,0 +1,39 @@ +{.used.} + +import json + +include ../../../app_service/common/json_utils + +type + NotificationType* {.pure.} = enum + NewContactRequest = 1, + AcceptedContactRequest, + JoinCommunityRequest, + MyRequestToJoinCommunityAccepted, + MyRequestToJoinCommunityRejected, + NewMessage, + NewMention + + NotificationDetails* = object + notificationType*: NotificationType + sectionId*: string + chatId*: string + messageId*: string + +proc toNotificationDetails*(jsonObj: JsonNode): NotificationDetails = + var notificationType: int + if (not (jsonObj.getProp("notificationType", notificationType) and + jsonObj.getProp("sectionId", result.sectionId) and + jsonObj.getProp("chatId", result.chatId) and + jsonObj.getProp("messageId", result.messageId))): + return NotificationDetails() + + result.notificationType = notificationType.NotificationType + +proc toJsonNode*(self: NotificationDetails): JsonNode = + result = %* { + "notificationType": self.notificationType.int, + "sectionId": self.sectionId, + "chatId": self.chatId, + "messageId": self.messageId + } diff --git a/src/app/core/notifications/notifications_manager.nim b/src/app/core/notifications/notifications_manager.nim new file mode 100644 index 0000000000..a531b79eb5 --- /dev/null +++ b/src/app/core/notifications/notifications_manager.nim @@ -0,0 +1,183 @@ +import NimQml, json, chronicles + +import ../../global/app_signals +import ../../global/global_singleton +import ../eventemitter +import details + +export details + +logScope: + topics = "notifications-manager" + +const NOTIFICATION_SOUND = "qrc:/imports/assets/audio/notification.wav" + +# Signals which may be emitted by this class: +const SIGNAL_DISPLAY_APP_NOTIFICATION* = "displayAppNotification" +const SIGNAL_OS_NOTIFICATION_CLICKED* = "osNotificationClicked" + +# Notification preferences +const NOTIFY_ABOUT_ALL_MESSAGES = 0 +const NOTIFY_JUST_ABOUT_MENTIONS = 1 +const NOTIFY_NOTHING_ABOUT = 2 + +# Anonymous preferences +const PREVIEW_ANONYMOUS = 0 +const PREVIEW_NAME_ONLY = 1 +const PREVIEW_NAME_AND_MESSAGE = 2 + +type + NotificationArgs* = ref object of Args + title*: string + message*: string + details*: NotificationDetails + + ClickedNotificationArgs* = ref object of Args + details*: NotificationDetails + +QtObject: + type NotificationsManager* = ref object of QObject + events: EventEmitter + osNotification: StatusOSNotification + soundManager: StatusSoundManager + + proc processNotification(self: NotificationsManager, title: string, message: string, details: NotificationDetails) + + proc setup(self: NotificationsManager, events: EventEmitter) = + self.QObject.setup + self.events = events + self.osNotification = newStatusOSNotification() + self.soundManager = newStatusSoundManager() + + signalConnect(self.osNotification, "notificationClicked(QString)", self, "onOSNotificationClicked(QString)", 2) + signalConnect(singletonInstance.globalEvents, "showNormalMessageNotification(QString, QString, QString, QString, QString)", + self, "onShowNormalMessageNotification(QString, QString, QString, QString, QString)", 2) + signalConnect(singletonInstance.globalEvents, "showMentionMessageNotification(QString, QString, QString, QString, QString)", + self, "onShowMentionMessageNotification(QString, QString, QString, QString, QString)", 2) + signalConnect(singletonInstance.globalEvents, "showNewContactRequestNotification(QString, QString, QString)", + self, "onShowNewContactRequestNotification(QString, QString, QString)", 2) + signalConnect(singletonInstance.globalEvents, "newCommunityMembershipRequestNotification(QString, QString, QString)", + self, "onNewCommunityMembershipRequestNotification(QString, QString, QString)", 2) + signalConnect(singletonInstance.globalEvents, "myRequestToJoinCommunityHasBeenAcccepted(QString, QString, QString)", + self, "onMyRequestToJoinCommunityHasBeenAcccepted(QString, QString, QString)", 2) + signalConnect(singletonInstance.globalEvents, "myRequestToJoinCommunityHasBeenRejected(QString, QString, QString)", + self, "onMyRequestToJoinCommunityHasBeenRejected(QString, QString, QString)", 2) + + proc delete*(self: NotificationsManager) = + self.osNotification.delete + self.QObject.delete + + proc newNotificationsManager*(events: EventEmitter): NotificationsManager = + new(result, delete) + result.setup(events) + + proc showOSNotification(self: NotificationsManager, title: string, message: string, identifier: string) = + ## This method will add new notification to the OS Notification center. Param + ## "identifier" is used to uniquely define notification bubble. + self.osNotification.showNotification(title, message, identifier) + + proc onOSNotificationClicked(self: NotificationsManager, identifier: string) {.slot.} = + ## This slot is called once user clicks OS notificaiton bubble, "identifier" + ## contains data which uniquely define that notification. + debug "OS notification clicked", identifier=identifier + + # Make the app the top most window. + app_makeItActive(singletonInstance.engine) + + let details = toNotificationDetails(parseJson(identifier)) + if(details.notificationType == NotificationType.NewMessage or + details.notificationType == NotificationType.NewMention): + let data = ActiveSectionChatArgs(sectionId: details.sectionId, chatId: details.chatId, messageId: details.messageId) + self.events.emit(SIGNAL_MAKE_SECTION_CHAT_ACTIVE, data) + else: + self.events.emit(SIGNAL_OS_NOTIFICATION_CLICKED, ClickedNotificationArgs(details: details)) + + proc onShowNormalMessageNotification(self: NotificationsManager, title: string, message: string, sectionId: string, + chatId: string, messageId: string) {.slot.} = + let details = NotificationDetails(notificationType: NotificationType.NewMessage, sectionId: sectionId, + chatId: chatId, messageId: messageId) + self.processNotification(title, message, details) + + proc onShowMentionMessageNotification(self: NotificationsManager, title: string, message: string, sectionId: string, + chatId: string, messageId: string) {.slot.} = + let details = NotificationDetails(notificationType: NotificationType.NewMention, sectionId: sectionId, + chatId: chatId, messageId: messageId) + self.processNotification(title, message, details) + + proc onShowNewContactRequestNotification*(self: NotificationsManager, title: string, message: string, + sectionId: string) {.slot.} = + let details = NotificationDetails(notificationType: NotificationType.NewContactRequest, sectionId: sectionId) + self.processNotification(title, message, details) + + proc onNewCommunityMembershipRequestNotification*(self: NotificationsManager, title: string, message: string, + sectionId: string) {.slot.} = + let details = NotificationDetails(notificationType: NotificationType.JoinCommunityRequest, sectionId: sectionId) + self.processNotification(title, message, details) + + proc onMyRequestToJoinCommunityHasBeenAcccepted*(self: NotificationsManager, title: string, message: string, + sectionId: string) {.slot.} = + let details = NotificationDetails(notificationType: NotificationType.MyRequestToJoinCommunityAccepted, + sectionId: sectionId) + self.processNotification(title, message, details) + + proc onMyRequestToJoinCommunityHasBeenRejected*(self: NotificationsManager, title: string, message: string, + sectionId: string) {.slot.} = + let details = NotificationDetails(notificationType: NotificationType.MyRequestToJoinCommunityRejected, + sectionId: sectionId) + self.processNotification(title, message, details) + + proc processNotification(self: NotificationsManager, title: string, message: string, details: NotificationDetails) = + ## This is the main method which need to be called to process an event according to the preferences set in the + ## notifications panel of the settings section. + ## + ## This method determines whether a notification need to be displayed or not, what notification to display, whether + ## to display App or OS notification, what level of anonymous to apply, whether to play sound or not and so... + + # I am not 100% sure about this. + # According to this: + # https://github.com/status-im/status-desktop/pull/4789#discussion_r805028513 + # The app should use OS notification only in case it is not the active app or it's minimized at the moment + # we're processing an event. + if(singletonInstance.localAccountSensitiveSettings.getUseOSNotifications() and + app_isActive(singletonInstance.engine)): + return + + var finalTitle = title + var finalMessage = message + + # Check if notification need to be displayed + if(singletonInstance.localAccountSensitiveSettings.getNotificationSetting() == NOTIFY_NOTHING_ABOUT and + (details.notificationType == NotificationType.NewMessage or + details.notificationType == NotificationType.NewMention)): + return + + if(singletonInstance.localAccountSensitiveSettings.getNotificationSetting() == NOTIFY_JUST_ABOUT_MENTIONS and + details.notificationType == NotificationType.NewMessage): + return + + if(not singletonInstance.localAccountSensitiveSettings.getNotifyOnNewRequests() and + details.notificationType == NotificationType.NewContactRequest): + return + + # Check anonymous level + if(singletonInstance.localAccountSensitiveSettings.getNotificationMessagePreviewSetting() == PREVIEW_ANONYMOUS): + finalTitle = "Status" + finalMessage = "You have a new message" + elif(singletonInstance.localAccountSensitiveSettings.getNotificationMessagePreviewSetting() == PREVIEW_NAME_ONLY): + finalMessage = "You have a new message" + + # Check whether to display APP or OS notification + if(singletonInstance.localAccountSensitiveSettings.getUseOSNotifications()): + let identifier = $(details.toJsonNode()) + debug "Add OS notification", title=finalTitle, message=finalMessage, identifier=identifier + self.showOSNotification(finalTitle, finalMessage, identifier) + else: + let data = NotificationArgs(title: finalTitle, message: finalMessage, details: details) + debug "Add APP notification", title=finalTitle, message=finalMessage + self.events.emit(SIGNAL_DISPLAY_APP_NOTIFICATION, data) + + # Check whether to play a sound + if(singletonInstance.localAccountSensitiveSettings.getNotificationSoundsEnabled()): + let currentVolume = singletonInstance.localAccountSensitiveSettings.getVolume() * 10 + self.soundManager.setPlayerVolume(currentVolume) + self.soundManager.playSound(NOTIFICATION_SOUND) \ No newline at end of file diff --git a/src/app/global/app_signals.nim b/src/app/global/app_signals.nim index 5fb74709c8..34ff6d02c9 100644 --- a/src/app/global/app_signals.nim +++ b/src/app/global/app_signals.nim @@ -9,3 +9,16 @@ type sectionType*: SectionType const TOGGLE_SECTION* = "toggleSection" +## Emmiting this signal will turn on section/s with passed `sectionType` if that section type is +## turned off, or turn it off in case that section type is turned on. + +type + ActiveSectionChatArgs* = ref object of Args + sectionId*: string + chatId*: string + messageId*: string + +const SIGNAL_MAKE_SECTION_CHAT_ACTIVE* = "makeSectionChatActive" +## Emmiting this signal will switch the app to passed `sectionId`, after that if `chatId` is set +## it will make that chat an active one and at the end if `messageId` is set it will point to +## that message. \ No newline at end of file diff --git a/src/app/global/global_events.nim b/src/app/global/global_events.nim new file mode 100644 index 0000000000..1f589c7efd --- /dev/null +++ b/src/app/global/global_events.nim @@ -0,0 +1,28 @@ +import NimQml + +QtObject: + type GlobalEvents* = ref object of QObject + + proc setup(self: GlobalEvents) = + self.QObject.setup + + proc delete*(self: GlobalEvents) = + self.QObject.delete + + proc newGlobalEvents*(): GlobalEvents = + new(result, delete) + result.setup + + proc showNormalMessageNotification*(self: GlobalEvents, title: string, message: string, sectionId: string, + chatId: string, messageId: string) {.signal.} + proc showMentionMessageNotification*(self: GlobalEvents, title: string, message: string, sectionId: string, + chatId: string, messageId: string) {.signal.} + proc showNewContactRequestNotification*(self: GlobalEvents, title: string, message: string, sectionId: string) + {.signal.} + proc newCommunityMembershipRequestNotification*(self: GlobalEvents, title: string, message: string, + sectionId: string) {.signal.} + proc myRequestToJoinCommunityHasBeenAcccepted*(self: GlobalEvents, title: string, message: string, + sectionId: string) {.signal.} + proc myRequestToJoinCommunityHasBeenRejected*(self: GlobalEvents, title: string, message: string, + sectionId: string) {.signal.} + \ No newline at end of file diff --git a/src/app/global/global_singleton.nim b/src/app/global/global_singleton.nim index 0432c52db5..ad53d8b6b5 100644 --- a/src/app/global/global_singleton.nim +++ b/src/app/global/global_singleton.nim @@ -5,12 +5,14 @@ import local_account_sensitive_settings import local_app_settings import user_profile import utils +import global_events export local_account_settings export local_account_sensitive_settings export local_app_settings export user_profile export utils +export global_events type GlobalSingleton = object @@ -23,44 +25,44 @@ proc engine*(self: GlobalSingleton): QQmlApplicationEngine = var qmlEngine {.global.}: QQmlApplicationEngine if (qmlEngine.isNil): qmlEngine = newQQmlApplicationEngine() - return qmlEngine proc localAccountSettings*(self: GlobalSingleton): LocalAccountSettings = var localAccountSettings {.global.}: LocalAccountSettings if (localAccountSettings.isNil): localAccountSettings = newLocalAccountSettings() - return localAccountSettings proc localAccountSensitiveSettings*(self: GlobalSingleton): LocalAccountSensitiveSettings = var localAccountSensitiveSettings {.global.}: LocalAccountSensitiveSettings if (localAccountSensitiveSettings.isNil): localAccountSensitiveSettings = newLocalAccountSensitiveSettings() - return localAccountSensitiveSettings proc localAppSettings*(self: GlobalSingleton): LocalAppSettings = var localAppSettings {.global.}: LocalAppSettings if (localAppSettings.isNil): localAppSettings = newLocalAppSettings("global") - return localAppSettings proc userProfile*(self: GlobalSingleton): UserProfile = var userProfile {.global.}: UserProfile if (userProfile.isNil): userProfile = newUserProfile() - return userProfile proc utils*(self: GlobalSingleton): Utils = var utils {.global.}: Utils if (utils.isNil): utils = newUtils() - return utils +proc globalEvents*(self: GlobalSingleton): GlobalEvents = + var globalEvents {.global.}: GlobalEvents + if (globalEvents.isNil): + globalEvents = newGlobalEvents() + return globalEvents + proc delete*(self: GlobalSingleton) = self.engine.delete() self.localAccountSettings.delete() diff --git a/src/app/global/local_account_sensitive_settings.nim b/src/app/global/local_account_sensitive_settings.nim index 5f34ee05e1..a92c74aefa 100644 --- a/src/app/global/local_account_sensitive_settings.nim +++ b/src/app/global/local_account_sensitive_settings.nim @@ -35,7 +35,7 @@ const DEFAULT_HIDDEN_COMMUNITY_WELCOME_BANNERS = "" const LSS_KEY_HIDDEN_COMMUNITY_BACKUP_BANNERS* = "hiddenCommunityBackUpBanners" const DEFAULT_HIDDEN_COMMUNITY_BACKUP_BANNERS = "" const LSS_KEY_VOLUME* = "volume" -const DEFAULT_VOLUME: float = 0.2 +const DEFAULT_VOLUME = 2 const LSS_KEY_NOTIFICATION_SETTING* = "notificationSetting" const DEFAULT_NOTIFICATION_SETTING = 1 #notifyJustMentions from qml const LSS_KEY_NOTIFICATION_SOUNDS_ENABLED* = "notificationSoundsEnabled" @@ -132,16 +132,16 @@ QtObject: # float type must be exposed through QVariant property. proc getSettingsPropQVariant(self: LocalAccountSensitiveSettings, prop: string, default: QVariant): QVariant = - result = if(self.settings.isNil): newQVariant() else: self.settings.value(prop, default) + result = if(self.settings.isNil): default else: self.settings.value(prop, default) proc getSettingsPropString(self: LocalAccountSensitiveSettings, prop: string, default: QVariant): string = - result = if(self.settings.isNil): "" else: self.settings.value(prop, default).stringVal + result = if(self.settings.isNil): default.stringVal else: self.settings.value(prop, default).stringVal proc getSettingsPropInt(self: LocalAccountSensitiveSettings, prop: string, default: QVariant): int = - result = if(self.settings.isNil): 0 else: self.settings.value(prop, default).intVal + result = if(self.settings.isNil): default.intVal else: self.settings.value(prop, default).intVal proc getSettingsPropBool(self: LocalAccountSensitiveSettings, prop: string, default: QVariant): bool = - result = if(self.settings.isNil): false else: self.settings.value(prop, default).boolVal + result = if(self.settings.isNil): default.boolVal else: self.settings.value(prop, default).boolVal template getSettingsProp[T](self: LocalAccountSensitiveSettings, prop: string, default: QVariant): untyped = # This doesn't work in case of QVariant, such properties will be handled in a common way. @@ -380,13 +380,13 @@ QtObject: proc volumeChanged*(self: LocalAccountSensitiveSettings) {.signal.} - proc getVolume*(self: LocalAccountSensitiveSettings): QVariant {.slot.} = - getSettingsPropQVariant(self, LSS_KEY_VOLUME, newQVariant(DEFAULT_VOLUME)) - proc setVolume*(self: LocalAccountSensitiveSettings, value: QVariant) {.slot.} = + proc getVolume*(self: LocalAccountSensitiveSettings): int {.slot.} = + getSettingsProp[int](self, LSS_KEY_VOLUME, newQVariant(DEFAULT_VOLUME)) + proc setVolume*(self: LocalAccountSensitiveSettings, value: int) {.slot.} = setSettingsProp(self, LSS_KEY_VOLUME, newQVariant(value)): self.volumeChanged() - QtProperty[QVariant] volume: + QtProperty[int] volume: read = getVolume write = setVolume notify = volumeChanged diff --git a/src/app/modules/main/activity_center/controller.nim b/src/app/modules/main/activity_center/controller.nim index 11d1dc0993..10abf3341b 100644 --- a/src/app/modules/main/activity_center/controller.nim +++ b/src/app/modules/main/activity_center/controller.nim @@ -2,6 +2,7 @@ import Tables, stint import ./controller_interface import ./io_interface +import ../../../global/app_signals import ../../../core/eventemitter import ../../../../app_service/service/activity_center/service as activity_center_service import ../../../../app_service/service/contacts/service as contacts_service @@ -104,4 +105,5 @@ method decodeContentHash*[T](self: Controller[T], hash: string): string = return eth_utils.decodeContentHash(hash) method switchTo*[T](self: Controller[T], sectionId, chatId, messageId: string) = - self.messageService.switchTo(sectionId, chatId, messageId) + let data = ActiveSectionChatArgs(sectionId: sectionId, chatId: chatId, messageId: messageId) + self.events.emit(SIGNAL_MAKE_SECTION_CHAT_ACTIVE, data) diff --git a/src/app/modules/main/app_search/controller.nim b/src/app/modules/main/app_search/controller.nim index f94565ebd4..1160a17413 100644 --- a/src/app/modules/main/app_search/controller.nim +++ b/src/app/modules/main/app_search/controller.nim @@ -1,6 +1,7 @@ import Tables, controller_interface, chronicles import io_interface +import ../../../global/app_signals import ../../../global/app_sections_config as conf import ../../../../app_service/service/contacts/service as contact_service import ../../../../app_service/service/chat/service as chat_service @@ -150,4 +151,7 @@ method resultItemClicked*(self: Controller, itemId: string) = info "important: we don't have stored details for a searched result item with id: ", itemId return - self.messageService.switchTo(itemDetails.sectionId, itemDetails.channelId, itemDetails.messageId) + let data = ActiveSectionChatArgs(sectionId: itemDetails.sectionId, + chatId: itemDetails.channelId, + messageId: itemDetails.messageId) + self.events.emit(SIGNAL_MAKE_SECTION_CHAT_ACTIVE, data) \ No newline at end of file diff --git a/src/app/modules/main/chat_section/chat_content/messages/controller.nim b/src/app/modules/main/chat_section/chat_content/messages/controller.nim index df31b721fd..d0225276f3 100644 --- a/src/app/modules/main/chat_section/chat_content/messages/controller.nim +++ b/src/app/modules/main/chat_section/chat_content/messages/controller.nim @@ -10,6 +10,7 @@ import ../../../../../../app_service/service/message/service as message_service import ../../../../../../app_service/service/mailservers/service as mailservers_service import ../../../../../../app_service/service/wallet_account/service as wallet_account_service import ../../../../../../app_service/service/eth/utils as eth_utils +import ../../../../../global/app_signals import ../../../../../core/signals/types import ../../../../../core/eventemitter diff --git a/src/app/modules/main/chat_section/controller.nim b/src/app/modules/main/chat_section/controller.nim index 4065e9edb2..c7648a289f 100644 --- a/src/app/modules/main/chat_section/controller.nim +++ b/src/app/modules/main/chat_section/controller.nim @@ -12,6 +12,7 @@ import ../../../../app_service/service/gif/service as gif_service import ../../../../app_service/service/mailservers/service as mailservers_service import ../../../core/signals/types +import ../../../global/app_signals import ../../../core/eventemitter export controller_interface @@ -255,16 +256,9 @@ method getCurrentFleet*(self: Controller): string = method getContacts*(self: Controller): seq[ContactsDto] = return self.contactService.getContacts() -method getContact*(self: Controller, id: string): ContactsDto = - return self.contactService.getContactById(id) - method getContactDetails*(self: Controller, id: string): ContactDetails = return self.contactService.getContactDetails(id) -method getContactNameAndImage*(self: Controller, contactId: string): - tuple[name: string, image: string, isIdenticon: bool] = - return self.contactService.getContactNameAndImage(contactId) - method addContact*(self: Controller, publicKey: string) = self.contactService.addContact(publicKey) @@ -374,3 +368,6 @@ method reorderCommunityCategories*(self: Controller, categoryId: string, positio method reorderCommunityChat*(self: Controller, categoryId: string, chatId: string, position: int): string = self.communityService.reorderCommunityChat(self.sectionId, categoryId, chatId, position) + +method getRenderedText*(self: Controller, parsedTextArray: seq[ParsedText]): string = + return self.messageService.getRenderedText(parsedTextArray) \ No newline at end of file diff --git a/src/app/modules/main/chat_section/controller_interface.nim b/src/app/modules/main/chat_section/controller_interface.nim index 2810d6f0ec..3587e78537 100644 --- a/src/app/modules/main/chat_section/controller_interface.nim +++ b/src/app/modules/main/chat_section/controller_interface.nim @@ -1,6 +1,7 @@ import ../../../../app_service/service/contacts/dto/[contacts, contact_details] import ../../../../app_service/service/chat/dto/[chat] import ../../../../app_service/service/community/dto/[community] +import ../../../../app_service/service/message/dto/[message] type AccessInterface* {.pure inheritable.} = ref object of RootObj @@ -76,16 +77,9 @@ method getCurrentFleet*(self: AccessInterface): string {.base.} = method getContacts*(self: AccessInterface): seq[ContactsDto] {.base.} = raise newException(ValueError, "No implementation available") -method getContact*(self: AccessInterface, id: string): ContactsDto {.base.} = - raise newException(ValueError, "No implementation available") - method getContactDetails*(self: AccessInterface, id: string): ContactDetails {.base.} = raise newException(ValueError, "No implementation available") -method getContactNameAndImage*(self: AccessInterface, contactId: string): - tuple[name: string, image: string, isIdenticon: bool] {.base.} = - raise newException(ValueError, "No implementation available") - method addContact*(self: AccessInterface, publicKey: string): void {.base.} = raise newException(ValueError, "No implementation available") @@ -152,8 +146,11 @@ method setCommunityMuted*(self: AccessInterface, muted: bool) {.base.} = method inviteUsersToCommunity*(self: AccessInterface, pubKeys: string): string {.base.} = raise newException(ValueError, "No implementation available") -method reorderCommunityCategories*(self: AccessInterface, categoryId: string, position: int) = +method reorderCommunityCategories*(self: AccessInterface, categoryId: string, position: int) {.base.} = raise newException(ValueError, "No implementation available") -method reorderCommunityChat*(self: AccessInterface, categoryId: string, chatId: string, position: int): string = +method reorderCommunityChat*(self: AccessInterface, categoryId: string, chatId: string, position: int): string {.base.} = raise newException(ValueError, "No implementation available") + +method getRenderedText*(self: AccessInterface, parsedTextArray: seq[ParsedText]): string {.base.} = + raise newException(ValueError, "No implementation available") \ No newline at end of file diff --git a/src/app/modules/main/chat_section/module.nim b/src/app/modules/main/chat_section/module.nim index 42a7991c4c..b80d41fae0 100644 --- a/src/app/modules/main/chat_section/module.nim +++ b/src/app/modules/main/chat_section/module.nim @@ -1,4 +1,5 @@ -import NimQml, Tables, chronicles, json, sequtils, strutils +import NimQml, Tables, chronicles, json, sequtils, strutils, strformat + import io_interface import ../io_interface as delegate_interface import view, controller, item, sub_item, sub_model, base_item @@ -8,6 +9,7 @@ import ../../shared_models/contacts_model as contacts_model import chat_content/module as chat_content_module +import ../../../global/app_sections_config as conf import ../../../global/global_singleton import ../../../core/eventemitter import ../../../../app_service/service/settings/service_interface as settings_service @@ -67,6 +69,9 @@ method delete*(self: Module) = method isCommunity*(self: Module): bool = return self.controller.isCommunity() +method getMySectionId*(self: Module): string = + return self.controller.getMySectionId() + proc amIMarkedAsAdminUser(self: Module, members: seq[ChatMember]): bool = for m in members: if (m.id == singletonInstance.userProfile.getPubKey() and m.admin): @@ -342,14 +347,14 @@ method getChatContentModule*(self: Module, chatId: string): QVariant = return self.chatContentModules[chatId].getModuleAsVariant() -proc updateParentNotifications(self: Module) = +proc updateParentBadgeNotifications(self: Module) = var (sectionHasUnreadMessages, sectionNotificationCount) = self.view.chatsModel().getAllNotifications() if(not self.controller.isCommunity()): sectionNotificationCount += self.view.contactRequestsModel().getCount() sectionHasUnreadMessages = sectionHasUnreadMessages or sectionNotificationCount > 0 self.delegate.onNotificationsUpdated(self.controller.getMySectionId(), sectionHasUnreadMessages, sectionNotificationCount) -proc updateNotifications(self: Module, chatId: string, unviewedMessagesCount: int, unviewedMentionsCount: int) = +proc updateBadgeNotifications(self: Module, chatId: string, unviewedMessagesCount: int, unviewedMentionsCount: int) = let hasUnreadMessages = unviewedMessagesCount > 0 # update model of this module (appropriate chat from the chats list (chats model)) self.view.chatsModel().updateNotificationsForItemOrSubItemById(chatId, hasUnreadMessages, unviewedMentionsCount) @@ -357,13 +362,13 @@ proc updateNotifications(self: Module, chatId: string, unviewedMessagesCount: in if (self.chatContentModules.contains(chatId)): self.chatContentModules[chatId].onNotificationsUpdated(hasUnreadMessages, unviewedMentionsCount) # update parent module - self.updateParentNotifications() + self.updateParentBadgeNotifications() method onActiveSectionChange*(self: Module, sectionId: string) = if(sectionId != self.controller.getMySectionId()): return - self.updateNotifications(self.controller.getActiveChatId(), unviewedMessagesCount=0, unviewedMentionsCount=0) + self.updateBadgeNotifications(self.controller.getActiveChatId(), unviewedMessagesCount=0, unviewedMentionsCount=0) self.delegate.onActiveChatChange(self.controller.getMySectionId(), self.controller.getActiveChatId()) method chatsModel*(self: Module): chats_model.Model = @@ -547,7 +552,7 @@ method onChatUnmuted*(self: Module, chatId: string) = self.view.chatsModel().muteUnmuteItemOrSubItemById(chatId, false) method onMarkAllMessagesRead*(self: Module, chatId: string) = - self.updateNotifications(chatId, unviewedMessagesCount=0, unviewedMentionsCount=0) + self.updateBadgeNotifications(chatId, unviewedMessagesCount=0, unviewedMentionsCount=0) method markAllMessagesRead*(self: Module, chatId: string) = self.controller.markAllMessagesRead(chatId) @@ -564,7 +569,7 @@ method acceptContactRequest*(self: Module, publicKey: string) = method onContactAccepted*(self: Module, publicKey: string) = self.view.contactRequestsModel().removeItemWithPubKey(publicKey) - self.updateParentNotifications() + self.updateParentBadgeNotifications() method acceptAllContactRequests*(self: Module) = let pubKeys = self.view.contactRequestsModel().getPublicKeys() @@ -576,7 +581,7 @@ method rejectContactRequest*(self: Module, publicKey: string) = method onContactRejected*(self: Module, publicKey: string) = self.view.contactRequestsModel().removeItemWithPubKey(publicKey) - self.updateParentNotifications() + self.updateParentBadgeNotifications() method rejectAllContactRequests*(self: Module) = let pubKeys = self.view.contactRequestsModel().getPublicKeys() @@ -594,16 +599,20 @@ method onContactUnblocked*(self: Module, publicKey: string) = self.view.chatsModel().blockUnblockItemOrSubItemById(publicKey, blocked=false) method onContactDetailsUpdated*(self: Module, publicKey: string) = - let contact = self.controller.getContact(publicKey) - if (contact.requestReceived() and - not contact.isContact()and - not contact.isBlocked() and + let contactDetails = self.controller.getContactDetails(publicKey) + if (contactDetails.details.requestReceived() and + not contactDetails.details.isContact()and + not contactDetails.details.isBlocked() and not self.view.contactRequestsModel().containsItemWithPubKey(publicKey)): let item = self.createItemFromPublicKey(publicKey) self.view.contactRequestsModel().addItem(item) - self.updateParentNotifications() + self.updateParentBadgeNotifications() + singletonInstance.globalEvents.showNewContactRequestNotification("New Contact Request", + fmt "{contactDetails.displayName} added you as contact", conf.CHAT_SECTION_ID) - let (chatName, chatImage, isIdenticon) = self.controller.getOneToOneChatNameAndImage(publicKey) + let chatName = contactDetails.displayName + let chatImage = contactDetails.icon + let isIdenticon = contactDetails.isIdenticon self.view.chatsModel().updateItemDetails(publicKey, chatName, chatImage, isIdenticon) method onNewMessagesReceived*(self: Module, chatId: string, unviewedMessagesCount: int, unviewedMentionsCount: int, @@ -611,7 +620,20 @@ method onNewMessagesReceived*(self: Module, chatId: string, unviewedMessagesCoun if(self.controller.getMySectionId() == self.delegate.getActiveSectionId() and self.controller.getActiveChatId() == chatId): return - self.updateNotifications(chatId, unviewedMessagesCount, unviewedMentionsCount) + self.updateBadgeNotifications(chatId, unviewedMessagesCount, unviewedMentionsCount) + + # Prepare bubble notification + let myPK = singletonInstance.userProfile.getPubKey() + for m in messages: + let contactDetails = self.controller.getContactDetails(m.`from`) + let renderedMessageText = self.controller.getRenderedText(m.parsedText) + let plainText = singletonInstance.utils.plainText(renderedMessageText) + if(m.isUserWithPkMentioned(myPK)): + singletonInstance.globalEvents.showMentionMessageNotification(contactDetails.displayName, plainText, + self.controller.getMySectionId(), chatId, m.id) + else: + singletonInstance.globalEvents.showNormalMessageNotification(contactDetails.displayName, plainText, + self.controller.getMySectionId(), chatId, m.id) method addGroupMembers*(self: Module, chatId: string, pubKeys: string) = self.controller.addGroupMembers(chatId, self.convertPubKeysToJson(pubKeys)) diff --git a/src/app/modules/main/chat_section/private_interfaces/module_view_delegate_interface.nim b/src/app/modules/main/chat_section/private_interfaces/module_view_delegate_interface.nim index 4da718fbd6..c33f01bff5 100644 --- a/src/app/modules/main/chat_section/private_interfaces/module_view_delegate_interface.nim +++ b/src/app/modules/main/chat_section/private_interfaces/module_view_delegate_interface.nim @@ -12,6 +12,9 @@ method getChatContentModule*(self: AccessInterface, chatId: string): QVariant {. method isCommunity*(self: AccessInterface): bool {.base.} = raise newException(ValueError, "No implementation available") +method getMySectionId*(self: AccessInterface): string {.base.} = + raise newException(ValueError, "No implementation available") + method createPublicChat*(self: AccessInterface, chatId: string) {.base.} = raise newException(ValueError, "No implementation available") diff --git a/src/app/modules/main/chat_section/view.nim b/src/app/modules/main/chat_section/view.nim index 5091aca817..048145d7df 100644 --- a/src/app/modules/main/chat_section/view.nim +++ b/src/app/modules/main/chat_section/view.nim @@ -56,6 +56,9 @@ QtObject: proc isCommunity(self: View): bool {.slot.} = return self.delegate.isCommunity() + method getMySectionId*(self: View): string {.slot.} = + return self.delegate.getMySectionId() + proc chatsModel*(self: View): chats_model.Model = return self.model diff --git a/src/app/modules/main/controller.nim b/src/app/modules/main/controller.nim index 962bc1d67c..50ef4146db 100644 --- a/src/app/modules/main/controller.nim +++ b/src/app/modules/main/controller.nim @@ -4,6 +4,7 @@ import ../../global/global_singleton import ../../global/app_signals import ../../core/signals/types import ../../core/eventemitter +import ../../core/notifications/notifications_manager import ../../../app_service/service/settings/service_interface as settings_service import ../../../app_service/service/keychain/service as keychain_service import ../../../app_service/service/accounts/service_interface as accounts_service @@ -168,6 +169,14 @@ method init*(self: Controller) = var args = ActiveSectionChatArgs(e) let sectionType = if args.sectionId == conf.CHAT_SECTION_ID: SectionType.Chat else: SectionType.Community self.setActiveSection(args.sectionId, sectionType) + + self.events.on(SIGNAL_OS_NOTIFICATION_CLICKED) do(e: Args): + var args = ClickedNotificationArgs(e) + self.delegate.osNotificationClicked(args.details) + + self.events.on(SIGNAL_NEW_REQUEST_TO_JOIN_COMMUNITY) do(e: Args): + var args = CommunityRequestArgs(e) + self.delegate.newCommunityMembershipRequestReceived(args.communityRequest) method getJoinedCommunities*(self: Controller): seq[CommunityDto] = return self.communityService.getJoinedCommunities() @@ -259,5 +268,9 @@ method resolveENS*(self: Controller, ensName: string, uuid: string = "") = method isMnemonicBackedUp*(self: Controller): bool = result = self.privacyService.isMnemonicBackedUp() -method switchTo*(self: Controller, sectionId, chatId: string) = - self.messageService.switchTo(sectionId, chatId, "") +method switchTo*(self: Controller, sectionId, chatId, messageId: string) = + let data = ActiveSectionChatArgs(sectionId: sectionId, chatId: chatId, messageId: messageId) + self.events.emit(SIGNAL_MAKE_SECTION_CHAT_ACTIVE, data) + +method getCommunityById*(self: Controller, communityId: string): CommunityDto = + return self.communityService.getCommunityById(communityId) \ No newline at end of file diff --git a/src/app/modules/main/controller_interface.nim b/src/app/modules/main/controller_interface.nim index 76e7eb9c35..140366bdf2 100644 --- a/src/app/modules/main/controller_interface.nim +++ b/src/app/modules/main/controller_interface.nim @@ -57,5 +57,8 @@ method resolveENS*(self: AccessInterface, ensName: string, uuid: string = "") {. method isMnemonicBackedUp*(self: AccessInterface): bool {.base.} = raise newException(ValueError, "No implementation available") -method switchTo*(self: AccessInterface, sectionId, chatId: string) {.base.} = +method switchTo*(self: AccessInterface, sectionId, chatId, messageId: string) {.base.} = raise newException(ValueError, "No implementation available") + +method getCommunityById*(self: AccessInterface, communityId: string): CommunityDto {.base.} = + raise newException(ValueError, "No implementation available") \ No newline at end of file diff --git a/src/app/modules/main/module.nim b/src/app/modules/main/module.nim index d46f869275..bad216e3d6 100644 --- a/src/app/modules/main/module.nim +++ b/src/app/modules/main/module.nim @@ -1,4 +1,4 @@ -import NimQml, tables, json, sugar, sequtils +import NimQml, tables, json, sugar, sequtils, strformat import io_interface, view, controller, chat_search_item, chat_search_model import ./communities/models/[pending_request_item, pending_request_model] @@ -50,7 +50,7 @@ import ../../../app_service/service/gif/service as gif_service import ../../../app_service/service/ens/service as ens_service import ../../../app_service/service/network/service as network_service - +import ../../core/notifications/details import ../../core/eventemitter export io_interface @@ -541,7 +541,7 @@ method rebuildChatSearchModel*[T](self: Module[T]) = self.view.chatSearchModel().setItems(items) method switchTo*[T](self: Module[T], sectionId, chatId: string) = - self.controller.switchTo(sectionId, chatId) + self.controller.switchTo(sectionId, chatId, "") method onActiveChatChange*[T](self: Module[T], sectionId: string, chatId: string) = self.appSearchModule.onActiveChatChange(sectionId, chatId) @@ -663,3 +663,21 @@ method mnemonicBackedUp*[T](self: Module[T]) = conf.SETTINGS_SECTION_ID, self.calculateProfileSectionHasNotification(), notificationsCount = 0) + +method osNotificationClicked*[T](self: Module[T], details: NotificationDetails) = + if(details.notificationType == NotificationType.NewContactRequest): + self.controller.switchTo(details.sectionId, "", "") + self.view.emitOpenContactRequestsPopupSignal() + elif(details.notificationType == NotificationType.JoinCommunityRequest): + self.controller.switchTo(details.sectionId, "", "") + self.view.emitOpenCommunityMembershipRequestsPopupSignal(details.sectionId) + elif(details.notificationType == NotificationType.MyRequestToJoinCommunityAccepted): + self.controller.switchTo(details.sectionId, "", "") + elif(details.notificationType == NotificationType.MyRequestToJoinCommunityRejected): + info "There is no particular action clicking on a notification informing you about rejection to join community" + +method newCommunityMembershipRequestReceived*[T](self: Module[T], membershipRequest: CommunityMembershipRequestDto) = + let (contactName, _, _) = self.controller.getContactNameAndImage(membershipRequest.publicKey) + let community = self.controller.getCommunityById(membershipRequest.communityId) + singletonInstance.globalEvents.newCommunityMembershipRequestNotification("New membership request", + fmt "{contactName} asks to join {community.name}", community.id) \ No newline at end of file diff --git a/src/app/modules/main/private_interfaces/module_controller_delegate_interface.nim b/src/app/modules/main/private_interfaces/module_controller_delegate_interface.nim index 6b55a33a96..e98d8c0f55 100644 --- a/src/app/modules/main/private_interfaces/module_controller_delegate_interface.nim +++ b/src/app/modules/main/private_interfaces/module_controller_delegate_interface.nim @@ -1,4 +1,5 @@ import ../../shared_models/section_item +import ../../../core/notifications/details method offerToStorePassword*(self: AccessInterface) {.base.} = raise newException(ValueError, "No implementation available") @@ -39,3 +40,10 @@ method contactUpdated*(self: AccessInterface, publicKey: string) {.base.} = method mnemonicBackedUp*(self: AccessInterface) {.base.} = raise newException(ValueError, "No implementation available") + +method osNotificationClicked*(self: AccessInterface, details: NotificationDetails) {.base.} = + raise newException(ValueError, "No implementation available") + +method newCommunityMembershipRequestReceived*(self: AccessInterface, membershipRequest: CommunityMembershipRequestDto) + {.base.} = + raise newException(ValueError, "No implementation available") \ No newline at end of file diff --git a/src/app/modules/main/view.nim b/src/app/modules/main/view.nim index a772184fb7..052eb14af0 100644 --- a/src/app/modules/main/view.nim +++ b/src/app/modules/main/view.nim @@ -157,3 +157,11 @@ QtObject: proc resolvedENS*(self: View, resolvedPubKey: string, resolvedAddress: string, uuid: string) {.signal.} proc emitResolvedENSSignal*(self: View, resolvedPubKey: string, resolvedAddress: string, uuid: string) = self.resolvedENS(resolvedPubKey, resolvedAddress, uuid) + + proc openContactRequestsPopup*(self: View) {.signal.} + proc emitOpenContactRequestsPopupSignal*(self: View) = + self.openContactRequestsPopup() + + proc openCommunityMembershipRequestsPopup*(self: View, sectionId: string) {.signal.} + proc emitOpenCommunityMembershipRequestsPopupSignal*(self: View, sectionId: string) = + self.openCommunityMembershipRequestsPopup(sectionId) \ No newline at end of file diff --git a/src/app_service/service/community/service.nim b/src/app_service/service/community/service.nim index a210f263a4..40fe2d799e 100644 --- a/src/app_service/service/community/service.nim +++ b/src/app_service/service/community/service.nim @@ -78,6 +78,7 @@ const SIGNAL_COMMUNITY_CATEGORY_CREATED* = "communityCategoryCreated" const SIGNAL_COMMUNITY_CATEGORY_EDITED* = "communityCategoryEdited" const SIGNAL_COMMUNITY_CATEGORY_DELETED* = "communityCategoryDeleted" const SIGNAL_COMMUNITY_MEMBER_APPROVED* = "communityMemberApproved" +const SIGNAL_NEW_REQUEST_TO_JOIN_COMMUNITY* = "newRequestToJoinCommunity" QtObject: type @@ -137,6 +138,8 @@ QtObject: self.joinedCommunities[membershipRequest.communityId] = community self.events.emit(SIGNAL_COMMUNITY_EDITED, CommunityArgs(community: community)) + self.events.emit(SIGNAL_NEW_REQUEST_TO_JOIN_COMMUNITY, CommunityRequestArgs(communityRequest: membershipRequest)) + proc mapChatToChatDto(chat: Chat, communityId: string): ChatDto = result = ChatDto() result.id = chat.id diff --git a/src/app_service/service/message/dto/message.nim b/src/app_service/service/message/dto/message.nim index c2b1c43b0c..6d23ff64fb 100644 --- a/src/app_service/service/message/dto/message.nim +++ b/src/app_service/service/message/dto/message.nim @@ -1,6 +1,6 @@ {.used.} -import json +import json, strutils include ../../../common/json_utils @@ -173,3 +173,10 @@ proc containsContactMentions*(self: MessageDto): bool = if (child.type == PARSED_TEXT_CHILD_TYPE_MENTION): return true return false + +proc isUserWithPkMentioned*(self: MessageDto, publicKey: string): bool = + for pText in self.parsedText: + for child in pText.children: + if (child.type == PARSED_TEXT_CHILD_TYPE_MENTION and child.literal.contains(publicKey)): + return true + return false \ No newline at end of file diff --git a/src/app_service/service/message/service.nim b/src/app_service/service/message/service.nim index b146ccffdf..4b4a5cce32 100644 --- a/src/app_service/service/message/service.nim +++ b/src/app_service/service/message/service.nim @@ -48,7 +48,6 @@ const SIGNAL_MESSAGE_REACTION_FROM_OTHERS* = "messageReactionFromOthers" const SIGNAL_MESSAGE_DELETION* = "messageDeleted" const SIGNAL_MESSAGE_EDITED* = "messageEdited" const SIGNAL_MESSAGE_LINK_PREVIEW_DATA_LOADED* = "messageLinkPreviewDataLoaded" -const SIGNAL_MAKE_SECTION_CHAT_ACTIVE* = "makeSectionChatActive" include async_tasks @@ -94,11 +93,6 @@ type LinkPreviewDataArgs* = ref object of Args response*: string - ActiveSectionChatArgs* = ref object of Args - sectionId*: string - chatId*: string - messageId*: string - QtObject: type Service* = ref object of QObject events: EventEmitter @@ -140,6 +134,10 @@ QtObject: # We included `chats` in this condition cause that's the form how `status-go` sends updates. # The first element from the `receivedData.chats` array contains details about the chat a messages received in # `receivedData.messages` refer to. + if(chats.len == 0): + error "error: received `chats` array for handling messages update is empty" + return + let chatId = chats[0].id let chatType = chats[0].chatType let unviewedMessagesCount = chats[0].unviewedMessagesCount @@ -704,14 +702,5 @@ proc editMessage*(self: Service, messageId: string, msg: string) = except Exception as e: error "error: ", methodName="editMessage", errName = e.name, errDesription = e.msg -proc switchTo*(self: Service, sectionId: string, chatId: string, messageId: string) = - ## Calling this proc the app will switch to passed `sectionId`, after that if `chatId` is set - ## it will make that chat an active one and at the end if `messageId` is set it will point to - ## that message. - ## We should use this proc (or just emit a signal bellow) when we want to switch to certain - ## section and/or chat and/or message - let data = ActiveSectionChatArgs(sectionId: sectionId, chatId: chatId, messageId: messageId) - self.events.emit(SIGNAL_MAKE_SECTION_CHAT_ACTIVE, data) - proc getWalletAccounts*(self: Service): seq[wallet_account_service.WalletAccountDto] = - return self.walletAccountService.getWalletAccounts() + return self.walletAccountService.getWalletAccounts() \ No newline at end of file diff --git a/src/app_service/service/os_notification/details.nim b/src/app_service/service/os_notification/details.nim deleted file mode 100644 index 596e7a095b..0000000000 --- a/src/app_service/service/os_notification/details.nim +++ /dev/null @@ -1,40 +0,0 @@ -{.used.} - -import json - -type - OsNotificationType* {.pure.} = enum - NewContactRequest = 1, - AcceptedContactRequest, - JoinCommunityRequest, - AcceptedIntoCommunity, - RejectedByCommunity, - NewMessage - - OsNotificationDetails* = object - notificationType*: OsNotificationType - communityId*: string - channelId*: string - messageId*: string - -proc toOsNotificationDetails*(json: JsonNode): OsNotificationDetails = - if (not (json.contains("notificationType") and - json.contains("communityId") and - json.contains("channelId") and - json.contains("messageId"))): - return OsNotificationDetails() - - return OsNotificationDetails( - notificationType: json{"notificationType"}.getInt.OsNotificationType, - communityId: json{"communityId"}.getStr, - channelId: json{"channelId"}.getStr, - messageId: json{"messageId"}.getStr - ) - -proc toJsonNode*(self: OsNotificationDetails): JsonNode = - result = %* { - "notificationType": self.notificationType.int, - "communityId": self.communityId, - "channelId": self.channelId, - "messageId": self.messageId - } diff --git a/src/app_service/service/os_notification/service.nim b/src/app_service/service/os_notification/service.nim deleted file mode 100644 index acbe6e2b8c..0000000000 --- a/src/app_service/service/os_notification/service.nim +++ /dev/null @@ -1,59 +0,0 @@ -import NimQml, json, chronicles -import details - -import ../../../app/core/eventemitter - -logScope: - topics = "os-notification-service" - -# Signals which may be emitted by this service: -const SIGNAL_OS_NOTIFICATION_CLICKED* = "osNotificationClicked" - -type - OsNotificationsArgs* = ref object of Args - details*: OsNotificationDetails - -QtObject: - type Service* = ref object of QObject - events: EventEmitter - notification: StatusOSNotification - notificationSetUp: bool - - proc setup(self: Service, events: EventEmitter) = - self.QObject.setup - self.events = events - - proc delete*(self: Service) = - if self.notificationSetUp: - self.notification.delete - self.QObject.delete - - proc newService*(events: EventEmitter): Service = - new(result, delete) - result.setup(events) - - proc showNotification*(self: Service, title: string, message: string, details: OsNotificationDetails, - useOSNotifications: bool) = - ## This method will add new notification to the Notification center. Param - ## "details" is used to uniquely define a notification bubble. - - # Whether we need to use OS notifications or not should be checked only here, - # but because we don't have settings class on the nim side yet, we're using - # useOSNotifications param sent from the qml side. Once we are able to check - # that here, we will remove useOSNotifications param from this method. - if(not useOSNotifications): - return - - let identifier = $(details.toJsonNode()) - self.notification.showNotification(title, message, identifier) - - proc onNotificationClicked*(self: Service, identifier: string) {.slot.} = - ## This slot is called once user clicks a notificaiton bubble, "identifier" - ## contains data which uniquely define that notification. - let details = toOsNotificationDetails(parseJson(identifier)) - self.events.emit(SIGNAL_OS_NOTIFICATION_CLICKED, OsNotificationsArgs(details: details)) - -proc userLoggedIn*(self: Service) = - self.notification = newStatusOSNotification() - signalConnect(self.notification, "notificationClicked(QString)", self, "onNotificationClicked(QString)", 2) - self.notificationSetUp = true diff --git a/ui/app/AppLayouts/Chat/stores/RootStore.qml b/ui/app/AppLayouts/Chat/stores/RootStore.qml index 75ce72c1a3..abc7ca9c6c 100644 --- a/ui/app/AppLayouts/Chat/stores/RootStore.qml +++ b/ui/app/AppLayouts/Chat/stores/RootStore.qml @@ -36,6 +36,10 @@ QtObject { communitiesModuleInst.setObservedCommunity(communityId); } + function getMySectionId() { + return chatCommunitySectionModule.getMySectionId() + } + function acceptContactRequest(pubKey) { chatCommunitySectionModule.acceptContactRequest(pubKey) } diff --git a/ui/app/AppLayouts/Chat/views/ChatColumnView.qml b/ui/app/AppLayouts/Chat/views/ChatColumnView.qml index 5cf8c81ff0..0b89aa2611 100644 --- a/ui/app/AppLayouts/Chat/views/ChatColumnView.qml +++ b/ui/app/AppLayouts/Chat/views/ChatColumnView.qml @@ -104,17 +104,6 @@ Item { tokenAddress) } - function clickOnNotification() { - // So far we're just showing this app as the top most window. Once we decide about the way - // how to notify the app what channle should be displayed within the app when user clicks - // notificaiton bubble this part should be updated accordingly. - // - // I removed part of this function which caused app crash. - Global.applicationWindow.show() - Global.applicationWindow.raise() - Global.applicationWindow.requestActivate() - } - Timer { interval: 60000; // 1 min running: true @@ -336,72 +325,6 @@ Item { messageContextMenu: contextmenu } - Connections { - target: systemTray - onMessageClicked: function () { - clickOnNotification() - } - } - - // Not Refactored Yet -// Connections { -// target: root.rootStore.chatsModelInst.messageView - -// onMessageNotificationPushed: function(messageId, communityId, chatId, msg, contentType, chatType, timestamp, identicon, username, hasMention, isAddedContact, channelName) { -// if (localAccountSensitiveSettings.notificationSetting == Constants.notifyAllMessages || -// (localAccountSensitiveSettings.notificationSetting == Constants.notifyJustMentions && hasMention)) { -// if (chatId === root.rootStore.chatsModelInst.channelView.activeChannel.id && applicationWindow.active === true) { -// // Do not show the notif if we are in the channel already and the window is active and focused -// return -// } - -// root.currentNotificationChatId = chatId -// root.currentNotificationCommunityId = null - -// let name; -// if (localAccountSensitiveSettings.notificationMessagePreviewSetting === Constants.notificationPreviewAnonymous) { -// name = "Status" -// } else if (chatType === Constants.chatType.publicChat) { -// name = chatId -// } else { -// name = chatType === Constants.chatType.privateGroupChat ? Utils.filterXSS(channelName) : Utils.removeStatusEns(username) -// } - -// let message; -// if (localAccountSensitiveSettings.notificationMessagePreviewSetting > Constants.notificationPreviewNameOnly) { -// switch(contentType){ -// //% "Image" -// case Constants.messageContentType.imageType: message = qsTrId("image"); break -// //% "Sticker" -// case Constants.messageContentType.stickerType: message = qsTrId("sticker"); break -// default: message = msg // don't parse emojis here as it emits HTML -// } -// } else { -// //% "You have a new message" -// message = qsTrId("you-have-a-new-message") -// } - -// currentlyHasANotification = true - -// if (Qt.platform.os === "linux") { -// // Linux Notifications are not implemented in Nim/C++ yet -// return systemTray.showMessage(name, message, systemTray.icon.source, 4000) -// } - -// // Note: -// // Show notification should be moved to the nim side. -// // Left here only cause we don't have a way to deal with translations on the nim side. -// root.rootStore.chatsModelInst.showOSNotification(name, -// message, -// Constants.osNotificationType.newMessage, -// communityId, -// chatId, -// messageId, -// localAccountSensitiveSettings.useOSNotifications) -// } -// } -// } - // Not Refactored Yet // Connections { // target: root.rootStore.chatsModelInst.stickers diff --git a/ui/app/AppLayouts/Chat/views/ChatMessagesView.qml b/ui/app/AppLayouts/Chat/views/ChatMessagesView.qml index 78d28cabe0..eaf05bd14a 100644 --- a/ui/app/AppLayouts/Chat/views/ChatMessagesView.qml +++ b/ui/app/AppLayouts/Chat/views/ChatMessagesView.qml @@ -221,63 +221,6 @@ Item { // onAppReady: { // chatLogView.scrollToBottom(true) // } -// } - -// Connections { - // Not Refactored Yet -// target: root.store.chatsModelInst.communities - -// // Note: -// // Whole this Connection object (both slots) should be moved to the nim side. -// // Left here only cause we don't have a way to deal with translations on the nim side. - -// onMembershipRequestChanged: function (communityId, communityName, accepted) { -// chatColumnLayout.currentNotificationChatId = null -// chatColumnLayout.currentNotificationCommunityId = communityId - -// const title = "Status" -// const message = //% "You have been accepted into the ‘%1’ community" -// accepted ? qsTrId("you-have-been-accepted-into-the---1--community").arg(communityName) : -// //% "Your request to join the ‘%1’ community was declined" -// qsTrId("your-request-to-join-the---1--community-was-declined").arg(communityName) - -// if (Qt.platform.os === "linux") { -// // Linux Notifications are not implemented in Nim/C++ yet -// return systemTray.showMessage(title, message, systemTray.icon.source, 4000) -// } - -// root.store.chatsModelInst.showOSNotification(title, -// message, -// accepted? Constants.osNotificationType.acceptedIntoCommunity : -// Constants.osNotificationType.rejectedByCommunity, -// communityId, -// "", -// "", -// localAccountSensitiveSettings.useOSNotifications) -// } - -// onMembershipRequestPushed: function (communityId, communityName, pubKey) { -// chatColumnLayout.currentNotificationChatId = null -// chatColumnLayout.currentNotificationCommunityId = communityId - -// //% "New membership request" -// const title = qsTrId("new-membership-request") -// //% "%1 asks to join ‘%2’" -// const message = qsTrId("-1-asks-to-join---2-").arg(Utils.getContactDetailsAsJson(pubKey).displayName).arg(communityName) - -// if (Qt.platform.os === "linux") { -// // Linux Notifications are not implemented in Nim/C++ yet -// return systemTray.showMessage(title, message, systemTray.icon.source, 4000) -// } - -// root.store.chatsModelInst.showOSNotification(title, -// message, -// Constants.osNotificationType.joinCommunityRequest, -// communityId, -// "", -// "", -// localAccountSensitiveSettings.useOSNotifications) -// } // } onContentYChanged: { diff --git a/ui/app/AppLayouts/Chat/views/CommunityColumnView.qml b/ui/app/AppLayouts/Chat/views/CommunityColumnView.qml index 6bfc20e9bf..80ed38de7c 100644 --- a/ui/app/AppLayouts/Chat/views/CommunityColumnView.qml +++ b/ui/app/AppLayouts/Chat/views/CommunityColumnView.qml @@ -476,4 +476,17 @@ Item { } } } + + Connections { + target: root.store.mainModuleInst + + onOpenCommunityMembershipRequestsPopup:{ + if(root.store.getMySectionId() != sectionId) + return + + Global.openPopup(membershipRequestPopup, { + communitySectionModule: root.communitySectionModule + }) + } + } } diff --git a/ui/app/AppLayouts/Chat/views/ContactsColumnView.qml b/ui/app/AppLayouts/Chat/views/ContactsColumnView.qml index 8e6bae16be..3f13b58927 100644 --- a/ui/app/AppLayouts/Chat/views/ContactsColumnView.qml +++ b/ui/app/AppLayouts/Chat/views/ContactsColumnView.qml @@ -461,4 +461,12 @@ Item { Global.toastMessage.open() } } + + Connections { + target: root.store.mainModuleInst + + onOpenContactRequestsPopup:{ + Global.openPopup(contactRequestsPopup) + } + } } diff --git a/ui/app/AppLayouts/Profile/views/SoundsView.qml b/ui/app/AppLayouts/Profile/views/SoundsView.qml index 6242c19561..b7ff72792b 100644 --- a/ui/app/AppLayouts/Profile/views/SoundsView.qml +++ b/ui/app/AppLayouts/Profile/views/SoundsView.qml @@ -45,11 +45,11 @@ Item { to: 1.0 stepSize: 0.1 onValueChanged: { - localAccountSensitiveSettings.volume = volume.value + localAccountSensitiveSettings.volume = volume.value * 10 } Component.onCompleted: { - value = localAccountSensitiveSettings.volume + value = localAccountSensitiveSettings.volume * 0.1 } } } diff --git a/ui/app/AppLayouts/stores/RootStore.qml b/ui/app/AppLayouts/stores/RootStore.qml index 046b409a1e..5bf87cc97f 100644 --- a/ui/app/AppLayouts/stores/RootStore.qml +++ b/ui/app/AppLayouts/stores/RootStore.qml @@ -47,7 +47,7 @@ QtObject { property var assets: walletSectionAccountTokens.model // property MessageStore messageStore: MessageStore { } - property real volume: !!localAccountSensitiveSettings ? localAccountSensitiveSettings.volume : 0.0 + property real volume: !!localAccountSensitiveSettings ? localAccountSensitiveSettings.volume * 0.1 : 0.2 property bool notificationSoundsEnabled: !!localAccountSensitiveSettings ? localAccountSensitiveSettings.notificationSoundsEnabled : false property var walletSectionTransactionsInst: walletSectionTransactions diff --git a/ui/app/AppMain.qml b/ui/app/AppMain.qml index d2216b39bd..6da1f5eeb3 100644 --- a/ui/app/AppMain.qml +++ b/ui/app/AppMain.qml @@ -573,71 +573,6 @@ Item { } } -// Connections { -// target: chatsModel -// onNotificationClicked: { -// Global.applicationWindow.makeStatusAppActive() - -// switch(notificationType){ -// case Constants.osNotificationType.newContactRequest: -// appView.currentIndex = Constants.appViewStackIndex.chat -// appMain.openContactsPopup() -// break -// case Constants.osNotificationType.acceptedContactRequest: -// appView.currentIndex = Constants.appViewStackIndex.chat -// break -// case Constants.osNotificationType.joinCommunityRequest: -// case Constants.osNotificationType.acceptedIntoCommunity: -// case Constants.osNotificationType.rejectedByCommunity: -// // Not Refactored - Need to check what community exactly we need to switch to. -//// appView.currentIndex = Utils.getAppSectionIndex(Constants.community) -// break -// case Constants.osNotificationType.newMessage: -// appView.currentIndex = Constants.appViewStackIndex.chat -// break -// } -// } -// } - - - // Not Refactored Yet - // This -// Connections { -// target: appMain.rootStore.contactsModuleInst.model -// onContactRequestAdded: { -// if (!localAccountSensitiveSettings.notifyOnNewRequests) { -// return -// } - -// const isContact = appMain.rootStore.contactsModuleInst.model.isAdded(address) - -// // Note: -// // Whole this Connection object should be moved to the nim side. -// // Left here only cause we don't have a way to deal with translations on the nim side. - -// const title = isContact ? qsTrId("contact-request-accepted") : -// //% "New contact request" -// qsTrId("new-contact-request") - -// const message = //% "You can now chat with %1" -// isContact ? qsTrId("you-can-now-chat-with--1").arg(Utils.removeStatusEns(name)) : -// //% "%1 requests to become contacts" -// qsTrId("-1-requests-to-become-contacts").arg(Utils.removeStatusEns(name)) - -// if (Qt.platform.os === "linux") { -// // Linux Notifications are not implemented in Nim/C++ yet -// return systemTray.showMessage(title, message, systemTray.icon.source, 4000) -// } - -// //% "Contact request accepted" -// profileModel.showOSNotification(title, -// message, -// isContact? Constants.osNotificationType.acceptedContactRequest : -// Constants.osNotificationType.newContactRequest, -// localAccountSensitiveSettings.useOSNotifications) -// } -// } - Component { id: chooseBrowserPopupComponent ChooseBrowserPopup { diff --git a/ui/imports/shared/stores/RootStore.qml b/ui/imports/shared/stores/RootStore.qml index bfb74feebe..8da246f9a2 100644 --- a/ui/imports/shared/stores/RootStore.qml +++ b/ui/imports/shared/stores/RootStore.qml @@ -14,7 +14,7 @@ QtObject { property var walletSectionInst: !!walletSection ? walletSection : null property var appSettings: !!localAppSettings ? localAppSettings : null property var accountSensitiveSettings: !!localAccountSensitiveSettings ? localAccountSensitiveSettings : null - property real volume: !!accountSensitiveSettings ? accountSensitiveSettings.volume : 0.0 + property real volume: !!accountSensitiveSettings ? accountSensitiveSettings.volume * 0.1 : 0.2 property bool isWalletEnabled: !!accountSensitiveSettings ? accountSensitiveSettings.isWalletEnabled : false property bool notificationSoundsEnabled: !!accountSensitiveSettings ? accountSensitiveSettings.notificationSoundsEnabled : false property bool neverAskAboutUnfurlingAgain: !!accountSensitiveSettings ? accountSensitiveSettings.neverAskAboutUnfurlingAgain : false diff --git a/ui/imports/utils/Constants.qml b/ui/imports/utils/Constants.qml index 0c1673d68b..b5e3baa5bd 100644 --- a/ui/imports/utils/Constants.qml +++ b/ui/imports/utils/Constants.qml @@ -27,15 +27,6 @@ QtObject { readonly property int node: 4 } - readonly property QtObject osNotificationType: QtObject{ - readonly property int newContactRequest: 1 - readonly property int acceptedContactRequest: 2 - readonly property int joinCommunityRequest: 3 - readonly property int acceptedIntoCommunity: 4 - readonly property int rejectedByCommunity: 5 - readonly property int newMessage: 6 - } - readonly property QtObject userStatus: QtObject{ readonly property int offline: 0 readonly property int online: 1 diff --git a/ui/main.qml b/ui/main.qml index 94545b8b6f..e829dc83e5 100644 --- a/ui/main.qml +++ b/ui/main.qml @@ -150,15 +150,6 @@ StatusWindow { } } } - - onActiveChanged: { - if (applicationWindow.active && currentlyHasANotification) { - currentlyHasANotification = false - // QML doesn't have a function to hide notifications, but this does the trick - systemTray.hide() - systemTray.show() - } - } } Connections { @@ -215,8 +206,6 @@ StatusWindow { signal navigateTo(string path) - property bool currentlyHasANotification: false - function makeStatusAppActive() { applicationWindow.show() applicationWindow.raise() diff --git a/vendor/DOtherSide b/vendor/DOtherSide index 379e803d94..5509d6f963 160000 --- a/vendor/DOtherSide +++ b/vendor/DOtherSide @@ -1 +1 @@ -Subproject commit 379e803d9467ddb460e3a32cae3455ccb2880d26 +Subproject commit 5509d6f9630d675075b070ba27044361be21315a diff --git a/vendor/nimqml b/vendor/nimqml index d3fdb6eed2..fea98f226c 160000 --- a/vendor/nimqml +++ b/vendor/nimqml @@ -1 +1 @@ -Subproject commit d3fdb6eed265b14cb902a16e06bf0ff6493ad6dd +Subproject commit fea98f226c58395347e987502a097c4386dac42a