diff --git a/clj-rn.conf.edn b/clj-rn.conf.edn index e38fe090f6..cef16b4eda 100644 --- a/clj-rn.conf.edn +++ b/clj-rn.conf.edn @@ -72,6 +72,7 @@ "react-native/Libraries/vendor/emitter/EventEmitter" "react-native-fetch-polyfill" "react-native-desktop-linking" + "react-native-desktop-menu" "react-native-desktop-notification" "text-encoding" "js-sha3" diff --git a/desktop/main.cpp b/desktop/main.cpp index a0726be934..cafbf374e8 100644 --- a/desktop/main.cpp +++ b/desktop/main.cpp @@ -14,7 +14,7 @@ #include #include #include -#include +#include #include #include #include @@ -242,7 +242,7 @@ void renameRealmDirs() { int main(int argc, char **argv) { QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling); - QGuiApplication app(argc, argv); + QApplication app(argc, argv); QCoreApplication::setApplicationName("Status"); diff --git a/desktop_files/package.json.orig b/desktop_files/package.json.orig index 8281d31a1b..7db7add3cb 100644 --- a/desktop_files/package.json.orig +++ b/desktop_files/package.json.orig @@ -16,6 +16,7 @@ "node_modules/react-native-securerandom/desktop", "modules/react-native-status/desktop", "modules/react-native-desktop-linking/desktop", + "modules/react-native-desktop-menu/desktop", "modules/react-native-desktop-notification/desktop", "node_modules/google-breakpad" ], diff --git a/modules/react-native-desktop-menu/desktop/CMakeLists.txt b/modules/react-native-desktop-menu/desktop/CMakeLists.txt new file mode 100755 index 0000000000..f031325742 --- /dev/null +++ b/modules/react-native-desktop-menu/desktop/CMakeLists.txt @@ -0,0 +1,9 @@ +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(REACT_NATIVE_DESKTOP_EXTERNAL_MODULES_TYPE_NAMES ${REACT_NATIVE_DESKTOP_EXTERNAL_MODULES_TYPE_NAMES} + \"DesktopMenu\" PARENT_SCOPE) + +set(REACT_NATIVE_DESKTOP_EXTERNAL_MODULES_SRC ${REACT_NATIVE_DESKTOP_EXTERNAL_MODULES_SRC} + ${CMAKE_CURRENT_SOURCE_DIR}/desktopmenu.cpp PARENT_SCOPE) + +include(${CMAKE_ROOT}/Modules/ExternalProject.cmake) diff --git a/modules/react-native-desktop-menu/desktop/desktopmenu.cpp b/modules/react-native-desktop-menu/desktop/desktopmenu.cpp new file mode 100644 index 0000000000..e0c99c029d --- /dev/null +++ b/modules/react-native-desktop-menu/desktop/desktopmenu.cpp @@ -0,0 +1,64 @@ +#include "desktopmenu.h" +#include "bridge.h" + +#include +#include +#include +#include + +Q_LOGGING_CATEGORY(DESKTOPMENU, "DesktopMenu") + +namespace { +struct RegisterQMLMetaType { + RegisterQMLMetaType() { qRegisterMetaType(); } +} registerMetaType; +} // namespace + +class DesktopMenuPrivate { +public: + Bridge *bridge = nullptr; + void createMenu(const QStringList& items, double callback); +private: + void onTriggered(QAction* action); +}; + +void DesktopMenuPrivate::createMenu(const QStringList& items, double callback) { + QMenu* menu = new QMenu(); + for (const QString& name : items) { + menu->addAction(name); + } + QObject::connect(menu, &QMenu::triggered, [=](QAction* action) { + bridge->invokePromiseCallback(callback, QVariantList{action->text()}); + }); + QObject::connect(menu, &QMenu::triggered, menu, &QMenu::deleteLater); + menu->popup(QCursor::pos()); +} + +DesktopMenu::DesktopMenu(QObject *parent) + : QObject(parent), d_ptr(new DesktopMenuPrivate) { +} + +DesktopMenu::~DesktopMenu() { +} + +void DesktopMenu::setBridge(Bridge *bridge) { + Q_D(DesktopMenu); + + d->bridge = bridge; +} + +QString DesktopMenu::moduleName() { return "DesktopMenuManager"; } + +QList DesktopMenu::methodsToExport() { + return QList{}; +} + +QVariantMap DesktopMenu::constantsToExport() { return QVariantMap(); } + +void DesktopMenu::show(const QStringList& items, double callback) { + Q_D(DesktopMenu); + d_ptr->createMenu(items, callback); + +} + + diff --git a/modules/react-native-desktop-menu/desktop/desktopmenu.h b/modules/react-native-desktop-menu/desktop/desktopmenu.h new file mode 100644 index 0000000000..dff2fddbc6 --- /dev/null +++ b/modules/react-native-desktop-menu/desktop/desktopmenu.h @@ -0,0 +1,34 @@ +#ifndef DESKTOPMENU_H +#define DESKTOPMENU_H + +#include "moduleinterface.h" + +#include +#include + +Q_DECLARE_LOGGING_CATEGORY(MENU) + +class DesktopMenuPrivate; +class DesktopMenu : public QObject, public ModuleInterface { + Q_OBJECT + Q_INTERFACES(ModuleInterface) + + Q_DECLARE_PRIVATE(DesktopMenu) + +public: + Q_INVOKABLE DesktopMenu(QObject* parent = 0); + virtual ~DesktopMenu(); + + void setBridge(Bridge* bridge) override; + + QString moduleName() override; + QList methodsToExport() override; + QVariantMap constantsToExport() override; + + Q_INVOKABLE void show(const QStringList& items, double callback); + +private: + QScopedPointer d_ptr; +}; + +#endif // DESKTOPMENU_H diff --git a/modules/react-native-desktop-menu/index.js b/modules/react-native-desktop-menu/index.js new file mode 100644 index 0000000000..fbc69bea6b --- /dev/null +++ b/modules/react-native-desktop-menu/index.js @@ -0,0 +1,29 @@ +'use strict'; + +type MenuItems = Array<{ + text?: string, + onPress?: ?Function, +}>; + +const NativeModules = require('react-native').NativeModules; + +class DesktopMenu { + + static show( + menuItems?: MenuItems + ): void { + var itemNames = menuItems.map(i => i.text); + var itemMap = new Map(); + for (let i = 0; i < menuItems.length; ++i) { + itemMap.set(menuItems[i].text, menuItems[i].onPress); + } + NativeModules.DesktopMenuManager.show( + itemNames, + (name) => { + (itemMap.get(name))(); + } + ); + } +} + +module.exports = DesktopMenu; diff --git a/modules/react-native-desktop-menu/package.json b/modules/react-native-desktop-menu/package.json new file mode 100644 index 0000000000..7fe2fbee49 --- /dev/null +++ b/modules/react-native-desktop-menu/package.json @@ -0,0 +1,13 @@ +{ + "private": true, + "nativePackage": true, + "name": "react-native-desktop-menu", + "version": "1.0.0", + "description": "Native popup and context menus for Desktop", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "" +} diff --git a/react-native/src/desktop/status_im/react_native/js_dependencies.cljs b/react-native/src/desktop/status_im/react_native/js_dependencies.cljs index 1b43ff21d5..64d607ca79 100644 --- a/react-native/src/desktop/status_im/react_native/js_dependencies.cljs +++ b/react-native/src/desktop/status_im/react_native/js_dependencies.cljs @@ -13,6 +13,7 @@ (def fetch (.-default (js/require "react-native-fetch-polyfill"))) (def i18n (js/require "react-native-i18n")) (def desktop-linking (.-DesktopLinking (.-NativeModules react-native))) +(def desktop-menu (js/require "react-native-desktop-menu")) (def react-native-firebase #js {}) (def nfc-manager #js {}) diff --git a/react-native/src/mobile/status_im/react_native/js_dependencies.cljs b/react-native/src/mobile/status_im/react_native/js_dependencies.cljs index a01bb90b5f..d93ede7a9b 100644 --- a/react-native/src/mobile/status_im/react_native/js_dependencies.cljs +++ b/react-native/src/mobile/status_im/react_native/js_dependencies.cljs @@ -27,3 +27,4 @@ (def background-timer (.-default (js/require "react-native-background-timer"))) (def react-navigation (js/require "react-navigation")) (def desktop-linking #js {:addEventListener (fn [])}) +(def desktop-menu #js {:addEventListener (fn [])}) diff --git a/scripts/build-desktop.sh b/scripts/build-desktop.sh index 6cd5915fe1..c03b1fbb32 100755 --- a/scripts/build-desktop.sh +++ b/scripts/build-desktop.sh @@ -26,6 +26,7 @@ external_modules_dir=( \ 'modules/react-native-status/desktop' \ 'node_modules/google-breakpad' \ 'modules/react-native-desktop-linking/desktop' \ + 'modules/react-native-desktop-menu/desktop' \ 'modules/react-native-desktop-notification/desktop' \ ) diff --git a/src/status_im/ui/components/popup_menu/views.cljs b/src/status_im/ui/components/popup_menu/views.cljs new file mode 100644 index 0000000000..1f35fcfcec --- /dev/null +++ b/src/status_im/ui/components/popup_menu/views.cljs @@ -0,0 +1,28 @@ +(ns status-im.ui.components.popup-menu.views + (:require [status-im.ui.components.react :as react] + [status-im.react-native.js-dependencies :as rn-dependencies] + [status-im.utils.platform :as platform] + [status-im.i18n :as i18n] + [re-frame.core :as re-frame] + [taoensso.timbre :as log])) + +(defn show-desktop-menu [items] + (.show rn-dependencies/desktop-menu + (clj->js (mapv #(hash-map :text (:text %1) :onPress (:on-select %1)) items)))) + +(defn get-chat-menu-items [group-chat public? chat-id] + (->> [(when (and (not group-chat) (not public?)) + {:text (i18n/label :t/view-profile) + :on-select #(re-frame/dispatch [:show-profile-desktop chat-id])}) + (when (and group-chat (not public?)) + {:text (i18n/label :t/group-info) + :on-select #(re-frame/dispatch [:show-group-chat-profile])}) + {:text (i18n/label :t/clear-history) + :on-select #(re-frame/dispatch [:chat.ui/clear-history-pressed])} + {:text (i18n/label :t/delete-chat) + :on-select #(re-frame/dispatch [(if (and group-chat (not public?)) + :group-chats.ui/remove-chat-pressed + :chat.ui/remove-chat-pressed) + chat-id])}] + (remove nil?))) + diff --git a/src/status_im/ui/screens/desktop/main/chat/views.cljs b/src/status_im/ui/screens/desktop/main/chat/views.cljs index 79c48f0158..0033ccd1e8 100644 --- a/src/status_im/ui/screens/desktop/main/chat/views.cljs +++ b/src/status_im/ui/screens/desktop/main/chat/views.cljs @@ -21,6 +21,7 @@ [status-im.ui.components.icons.vector-icons :as vector-icons] [status-im.ui.screens.desktop.main.chat.styles :as styles] [status-im.contact.db :as contact.db] + [status-im.ui.components.popup-menu.views :as popup-menu] [status-im.i18n :as i18n] [status-im.ui.screens.desktop.main.chat.events :as chat.events] [status-im.ui.screens.chat.message.message :as chat.message])) @@ -49,24 +50,13 @@ public? [react/text {:style styles/public-chat-text} (i18n/label :t/public-chat)])]] - [react/view - (when (and (not group-chat) (not public?)) - [react/text {:style (styles/profile-actions-text colors/black) - :on-press #(re-frame/dispatch [:show-profile-desktop public-key])} - (i18n/label :t/view-profile)]) - (when (and group-chat (not public?)) - [react/text {:style (styles/profile-actions-text colors/black) - :on-press #(re-frame/dispatch [:show-group-chat-profile])} - (i18n/label :t/group-info)]) - [react/text {:style (styles/profile-actions-text colors/black) - :on-press #(re-frame/dispatch [:chat.ui/clear-history-pressed])} - (i18n/label :t/clear-history)] - [react/text {:style (styles/profile-actions-text colors/black) - :on-press #(re-frame/dispatch [(if (and group-chat (not public?)) - :group-chats.ui/remove-chat-pressed - :chat.ui/remove-chat-pressed) - chat-id])} - (i18n/label :t/delete-chat)]]])) + [react/touchable-highlight + {:on-press #(popup-menu/show-desktop-menu + (popup-menu/get-chat-menu-items group-chat public? chat-id))} + [vector-icons/icon :icons/dots-horizontal + {:style {:tint-color colors/black + :width 24 + :height 24}}]]])) (views/defview message-author-name [{:keys [from]}] (views/letsubs [incoming-name [:contacts/contact-name-by-identity from]] diff --git a/src/status_im/ui/screens/desktop/main/tabs/home/views.cljs b/src/status_im/ui/screens/desktop/main/tabs/home/views.cljs index ce06306cbc..3a672ed509 100644 --- a/src/status_im/ui/screens/desktop/main/tabs/home/views.cljs +++ b/src/status_im/ui/screens/desktop/main/tabs/home/views.cljs @@ -5,6 +5,7 @@ [status-im.i18n :as i18n] [status-im.ui.components.colors :as colors] [status-im.ui.screens.desktop.main.tabs.home.styles :as styles] + [status-im.ui.components.popup-menu.views :as popup-menu] [clojure.string :as string] [status-im.ui.screens.home.views.inner-item :as chat-item] [taoensso.timbre :as log] @@ -57,8 +58,15 @@ [react/view {:style styles/timestamp} [chat-item/message-timestamp (:timestamp last-message)]]]))) -(defn chat-list-item [[chat-id chat]] - [react/touchable-highlight {:on-press #(re-frame/dispatch [:chat.ui/navigate-to-chat chat-id])} +(defn chat-list-item [[chat-id + {:keys [group-chat public?] :as chat}]] + [react/touchable-highlight + {:on-press (fn [arg] + (let [right-click? (= "right" (.-button (.-nativeEvent arg)))] + (re-frame/dispatch [:chat.ui/navigate-to-chat chat-id]) + (when right-click? + (popup-menu/show-desktop-menu + (popup-menu/get-chat-menu-items group-chat public? chat-id)))))} [chat-list-item-inner-view (assoc chat :chat-id chat-id)]]) (defn tag-view [tag {:keys [on-press]}] diff --git a/test/cljs/status_im/react_native/js_dependencies.cljs b/test/cljs/status_im/react_native/js_dependencies.cljs index 3287796f7b..b6ce597ffe 100644 --- a/test/cljs/status_im/react_native/js_dependencies.cljs +++ b/test/cljs/status_im/react_native/js_dependencies.cljs @@ -14,7 +14,6 @@ (def linear-gradient #js {}) (def nfc #js {}) (def orientation #js {}) -(def popup-menu #js {}) (def qr-code #js {}) (def nfc-manager #js {:default #js {}}) (def react-native