From 248e60e1d3fe81a74d618b85ea97700ce040d9c7 Mon Sep 17 00:00:00 2001 From: Dmitry Novotochinov Date: Tue, 16 Oct 2018 16:23:29 +0300 Subject: [PATCH] [#5038] desktop deep links Add support for status-im://chat/public/status type of links Signed-off-by: Dmitry Novotochinov --- desktop_files/.re-natal | 1 + desktop_files/package.json | 1 + .../desktop/CMakeLists.txt | 9 +++ .../desktop/desktoplinking.cpp | 72 +++++++++++++++++++ .../desktop/desktoplinking.h | 38 ++++++++++ modules/react-native-desktop-linking/index.js | 4 ++ .../react-native-desktop-linking/package.json | 13 ++++ .../react_native/js_dependencies.cljs | 2 + .../react_native/js_dependencies.cljs | 1 + scripts/build-desktop.sh | 1 + src/status_im/chat/models.cljs | 8 ++- src/status_im/desktop/core.cljs | 5 +- src/status_im/desktop/deep_links.cljs | 14 ++++ .../ui/components/desktop/events.cljs | 47 ++++++++---- src/status_im/utils/universal_links/core.cljs | 8 ++- .../react_native/js_dependencies.cljs | 2 + 16 files changed, 207 insertions(+), 19 deletions(-) create mode 100755 modules/react-native-desktop-linking/desktop/CMakeLists.txt create mode 100644 modules/react-native-desktop-linking/desktop/desktoplinking.cpp create mode 100644 modules/react-native-desktop-linking/desktop/desktoplinking.h create mode 100644 modules/react-native-desktop-linking/index.js create mode 100644 modules/react-native-desktop-linking/package.json create mode 100644 src/status_im/desktop/deep_links.cljs diff --git a/desktop_files/.re-natal b/desktop_files/.re-natal index e538eb85b5..5a40b1d2b3 100644 --- a/desktop_files/.re-natal +++ b/desktop_files/.re-natal @@ -58,6 +58,7 @@ "rn-snoopy/stream/buffer", "react-native/Libraries/vendor/emitter/EventEmitter", "react-native-fetch-polyfill", + "react-native-desktop-linking", "text-encoding", "js-sha3", "web3-utils" diff --git a/desktop_files/package.json b/desktop_files/package.json index 07acbb6291..8946cd9fca 100644 --- a/desktop_files/package.json +++ b/desktop_files/package.json @@ -15,6 +15,7 @@ "node_modules/react-native-keychain/desktop", "node_modules/react-native-securerandom/desktop", "modules/react-native-status/desktop", + "modules/react-native-desktop-linking/desktop", "node_modules/google-breakpad" ], "desktopFonts": [ diff --git a/modules/react-native-desktop-linking/desktop/CMakeLists.txt b/modules/react-native-desktop-linking/desktop/CMakeLists.txt new file mode 100755 index 0000000000..1d917eb236 --- /dev/null +++ b/modules/react-native-desktop-linking/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} + \"DesktopLinking\" PARENT_SCOPE) + +set(REACT_NATIVE_DESKTOP_EXTERNAL_MODULES_SRC ${REACT_NATIVE_DESKTOP_EXTERNAL_MODULES_SRC} + ${CMAKE_CURRENT_SOURCE_DIR}/desktoplinking.cpp PARENT_SCOPE) + +include(${CMAKE_ROOT}/Modules/ExternalProject.cmake) \ No newline at end of file diff --git a/modules/react-native-desktop-linking/desktop/desktoplinking.cpp b/modules/react-native-desktop-linking/desktop/desktoplinking.cpp new file mode 100644 index 0000000000..cba96a8c87 --- /dev/null +++ b/modules/react-native-desktop-linking/desktop/desktoplinking.cpp @@ -0,0 +1,72 @@ +#include "desktoplinking.h" +#include "bridge.h" +#include "eventdispatcher.h" + +#include +#include +#include +#include +#include + +namespace { +struct RegisterQMLMetaType { + RegisterQMLMetaType() { qRegisterMetaType(); } +} registerMetaType; +} // namespace + +class DesktopLinkingPrivate { +public: + Bridge *bridge = nullptr; +}; + +DesktopLinking::DesktopLinking(QObject *parent) + : QObject(parent), d_ptr(new DesktopLinkingPrivate) { + + QCoreApplication::instance()->installEventFilter(this); + connect(this, &DesktopLinking::urlOpened, this, &DesktopLinking::handleURL); +} + +DesktopLinking::~DesktopLinking() { +} + +void DesktopLinking::setBridge(Bridge *bridge) { + Q_D(DesktopLinking); + d->bridge = bridge; +} + +QString DesktopLinking::moduleName() { return "DesktopLinking"; } + +QList DesktopLinking::methodsToExport() { + return QList{}; +} + +QVariantMap DesktopLinking::constantsToExport() { return QVariantMap(); } + +void DesktopLinking::handleURL(const QString url) { + Q_D(DesktopLinking); + qDebug() << "call of DesktopLinking::handleURL with param path: " << url; + d->bridge->eventDispatcher()->sendDeviceEvent("urlOpened", QVariantMap{{"url", url}}); +} + +bool DesktopLinking::eventFilter(QObject* obj, QEvent* event) { + if (event->type() == QEvent::FileOpen) + { + QFileOpenEvent* fileEvent = static_cast(event); + if (!fileEvent->url().isEmpty()) + { + auto m_lastUrl = fileEvent->url().toString(); + emit urlOpened(m_lastUrl); + } + else if (!fileEvent->file().isEmpty()) + { + emit fileOpened(fileEvent->file()); + } + + return false; + } + else + { + // standard event processing + return QObject::eventFilter(obj, event); + } +} \ No newline at end of file diff --git a/modules/react-native-desktop-linking/desktop/desktoplinking.h b/modules/react-native-desktop-linking/desktop/desktoplinking.h new file mode 100644 index 0000000000..e84078188a --- /dev/null +++ b/modules/react-native-desktop-linking/desktop/desktoplinking.h @@ -0,0 +1,38 @@ +#ifndef DESKTOPLINKING_H +#define DESKTOPLINKING_H + +#include "moduleinterface.h" + +#include + +class DesktopLinkingPrivate; +class DesktopLinking : public QObject, public ModuleInterface { + Q_OBJECT + Q_INTERFACES(ModuleInterface) + + Q_DECLARE_PRIVATE(DesktopLinking) + +public: + Q_INVOKABLE DesktopLinking(QObject* parent = 0); + ~DesktopLinking(); + + void setBridge(Bridge* bridge) override; + + QString moduleName() override; + QList methodsToExport() override; + QVariantMap constantsToExport() override; + +signals: + void urlOpened(QString path); + void fileOpened(QString path); + +public slots: + void handleURL(const QString url); + +private: + QScopedPointer d_ptr; + bool eventFilter(QObject* obj, QEvent* event) override; + +}; + +#endif // DESKTOPLINKING_H diff --git a/modules/react-native-desktop-linking/index.js b/modules/react-native-desktop-linking/index.js new file mode 100644 index 0000000000..6e2dd3735c --- /dev/null +++ b/modules/react-native-desktop-linking/index.js @@ -0,0 +1,4 @@ +'use strict'; + +const NativeModules = require('NativeModules'); +module.exports = NativeModules.DesktopLinking; \ No newline at end of file diff --git a/modules/react-native-desktop-linking/package.json b/modules/react-native-desktop-linking/package.json new file mode 100644 index 0000000000..dbc4fcdf72 --- /dev/null +++ b/modules/react-native-desktop-linking/package.json @@ -0,0 +1,13 @@ +{ + "private": true, + "nativePackage": true, + "name": "react-native-desktop-linking", + "version": "1.0.0", + "description": "Handle status-im:// links", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "" +} \ No newline at end of file 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 adf643c9b1..86fb88c005 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 @@ -12,6 +12,8 @@ (def EventEmmiter (js/require "react-native/Libraries/vendor/emitter/EventEmitter")) (def fetch (.-default (js/require "react-native-fetch-polyfill"))) (def i18n (js/require "react-native-i18n")) +(def desktop-linking (.-DesktopLinking (.-NativeModules react-native))) + (def react-native-firebase #js {}) (def nfc-manager #js {}) (def camera #js {:default #js {:constants {:Aspect "Portrait"}}}) 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 ec2b0d218c..ec2055e95e 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 snoopy-buffer (js/require "rn-snoopy/stream/buffer")) (def background-timer (.-default (js/require "react-native-background-timer"))) (def react-navigation (js/require "react-navigation")) +(def desktop-linking #js {:addEventListener (fn [])}) diff --git a/scripts/build-desktop.sh b/scripts/build-desktop.sh index 9eefd34563..61536611c1 100755 --- a/scripts/build-desktop.sh +++ b/scripts/build-desktop.sh @@ -21,6 +21,7 @@ external_modules_dir=( \ 'node_modules/react-native-securerandom/desktop' \ 'modules/react-native-status/desktop' \ 'node_modules/google-breakpad' \ + 'modules/react-native-desktop-linking/desktop' \ ) external_fonts=( \ diff --git a/src/status_im/chat/models.cljs b/src/status_im/chat/models.cljs index 47c7f6c6c0..87ec85f00f 100644 --- a/src/status_im/chat/models.cljs +++ b/src/status_im/chat/models.cljs @@ -8,11 +8,13 @@ [status-im.transport.message.protocol :as protocol] [status-im.transport.message.public-chat :as public-chat] [status-im.ui.components.colors :as colors] + [status-im.ui.components.desktop.events :as desktop.events] [status-im.ui.screens.navigation :as navigation] [status-im.utils.clocks :as utils.clocks] [status-im.utils.fx :as fx] [status-im.utils.gfycat.core :as gfycat] - [status-im.utils.utils :as utils])) + [status-im.utils.utils :as utils] + [status-im.utils.platform :as platform])) (defn multi-user-chat? [cofx chat-id] (get-in cofx [:db :chats chat-id :group-chat])) @@ -200,7 +202,9 @@ (add-public-chat topic) (navigate-to-chat topic {:modal? modal? :navigation-reset? true}) - (public-chat/join-public-chat topic))) + (public-chat/join-public-chat topic) + (when platform/desktop? + (desktop.events/change-tab :home)))) (fx/defn disable-chat-cooldown "Turns off chat cooldown (protection against message spamming)" diff --git a/src/status_im/desktop/core.cljs b/src/status_im/desktop/core.cljs index 1735be5840..28a7a487dc 100644 --- a/src/status_im/desktop/core.cljs +++ b/src/status_im/desktop/core.cljs @@ -7,11 +7,12 @@ status-im.data-store.core [status-im.ui.screens.desktop.views :as views] [status-im.core :as core] - [status-im.ui.components.react :as react])) + [status-im.desktop.deep-links :as deep-links])) (defn app-root [] (reagent/create-class - {:reagent-render views/main})) + {:component-did-mount deep-links/add-event-listener + :reagent-render views/main})) (defn init [] (core/init app-root)) diff --git a/src/status_im/desktop/deep_links.cljs b/src/status_im/desktop/deep_links.cljs new file mode 100644 index 0000000000..539ca7ecb0 --- /dev/null +++ b/src/status_im/desktop/deep_links.cljs @@ -0,0 +1,14 @@ +(ns status-im.desktop.deep-links + (:require [re-frame.core :as re-frame] + [status-im.react-native.js-dependencies :as js-dependencies] + [taoensso.timbre :as log])) + +(defn add-event-listener [] + (let [event-emitter (new (.-NativeEventEmitter js-dependencies/react-native) + js-dependencies/desktop-linking)] + (.addListener event-emitter + "urlOpened" + (fn [data] + (log/debug "urlOpened event with data:" data) + (let [url (get (js->clj data) "url")] + (re-frame/dispatch [:handle-universal-link url])))))) \ No newline at end of file diff --git a/src/status_im/ui/components/desktop/events.cljs b/src/status_im/ui/components/desktop/events.cljs index 31548f8983..69b2725451 100644 --- a/src/status_im/ui/components/desktop/events.cljs +++ b/src/status_im/ui/components/desktop/events.cljs @@ -1,19 +1,40 @@ (ns status-im.ui.components.desktop.events (:require [status-im.utils.handlers :as handlers] + [status-im.utils.fx :as fx] + [status-im.ui.screens.navigation :as navigation] [status-im.utils.platform :as platform])) +(fx/defn change-tab + [{:keys [db]} tab-name] + {:db (assoc-in db [:desktop/desktop :tab-view-id] tab-name)}) + +(fx/defn navigate-to + [{:keys [db] :as cofx} tab-name] + (navigation/navigate-to-cofx cofx + (if (and (= tab-name :home) (:current-chat-id db)) + :chat + :home) + nil)) + +(fx/defn fetch-desktop-version + [_ tab-name] + (when (and platform/isMacOs? + (= tab-name :profile)) + {:http-get + {:url + "https://raw.githubusercontent.com/status-im/status-im.github.io/develop/env.sh" + :success-event-creator + (fn [o] + [:fetch-desktop-version-success o])}})) + +(fx/defn show-desktop-tab + [cofx tab-name] + (fx/merge cofx + (change-tab tab-name) + (navigate-to tab-name) + (fetch-desktop-version tab-name))) + (handlers/register-handler-fx :show-desktop-tab - (fn [{:keys [db] :as cofx} [_ tab-name]] - (merge {:db (assoc-in db [:desktop/desktop :tab-view-id] tab-name) - :dispatch [:navigate-to (if (and (= tab-name :home) (:current-chat-id db)) - :chat - :home)]} - (when (and platform/isMacOs? - (= tab-name :profile)) - {:http-get - {:url - "https://raw.githubusercontent.com/status-im/status-im.github.io/develop/env.sh" - :success-event-creator - (fn [o] - [:fetch-desktop-version-success o])}})))) + (fn [cofx [_ tab-name]] + (show-desktop-tab cofx tab-name))) diff --git a/src/status_im/utils/universal_links/core.cljs b/src/status_im/utils/universal_links/core.cljs index f234325ad6..af488328ca 100644 --- a/src/status_im/utils/universal_links/core.cljs +++ b/src/status_im/utils/universal_links/core.cljs @@ -8,10 +8,12 @@ [status-im.ui.components.list-selection :as list-selection] [status-im.ui.components.react :as react] [status-im.ui.screens.add-new.new-chat.db :as new-chat.db] + [status-im.ui.screens.desktop.main.chat.events :as desktop.events] [status-im.ui.screens.navigation :as navigation] [status-im.utils.config :as config] [status-im.utils.fx :as fx] - [taoensso.timbre :as log])) + [taoensso.timbre :as log] + [status-im.utils.platform :as platform])) ;; TODO(yenda) investigate why `handle-universal-link` event is ;; dispatched 7 times for the same link @@ -63,7 +65,9 @@ (log/info "universal-links: handling view profile" profile-id) (if (new-chat.db/own-whisper-identity? db profile-id) (navigation/navigate-to-cofx cofx :my-profile nil) - (navigation/navigate-to-cofx (assoc-in cofx [:db :contacts/identity] profile-id) :profile nil))) + (if platform/desktop? + (desktop.events/show-profile-desktop profile-id cofx) + (navigation/navigate-to-cofx (assoc-in cofx [:db :contacts/identity] profile-id) :profile nil)))) (fx/defn handle-extension [cofx url] (log/info "universal-links: handling url profile" url) diff --git a/test/cljs/status_im/react_native/js_dependencies.cljs b/test/cljs/status_im/react_native/js_dependencies.cljs index b0578e65bc..1952b915b1 100644 --- a/test/cljs/status_im/react_native/js_dependencies.cljs +++ b/test/cljs/status_im/react_native/js_dependencies.cljs @@ -34,6 +34,8 @@ (defrecord Notification []) (def react-native-firebase #js {:default #js {:notifications #js {:Notification Notification}}}) +(def desktop-linking #js {:addEventListener (fn [])}) + (def snoopy #js {:default #js {}}) (def snoopy-filter #js {:default #js {}}) (def snoopy-bars #js {:default #js {}})