[#5038] desktop deep links

Add support for status-im://chat/public/status type of links

Signed-off-by: Dmitry Novotochinov <dmitry.novot@gmail.com>
This commit is contained in:
Dmitry Novotochinov 2018-10-16 16:23:29 +03:00
parent 299c44afef
commit 248e60e1d3
No known key found for this signature in database
GPG Key ID: 43D1DAF5AD39C927
16 changed files with 207 additions and 19 deletions

View File

@ -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"

View File

@ -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": [

View File

@ -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)

View File

@ -0,0 +1,72 @@
#include "desktoplinking.h"
#include "bridge.h"
#include "eventdispatcher.h"
#include <QCoreApplication>
#include <QDebug>
#include <QDesktopServices>
#include <QUrl>
#include <QFileOpenEvent>
namespace {
struct RegisterQMLMetaType {
RegisterQMLMetaType() { qRegisterMetaType<DesktopLinking *>(); }
} 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<ModuleMethod *> DesktopLinking::methodsToExport() {
return QList<ModuleMethod *>{};
}
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<QFileOpenEvent*>(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);
}
}

View File

@ -0,0 +1,38 @@
#ifndef DESKTOPLINKING_H
#define DESKTOPLINKING_H
#include "moduleinterface.h"
#include <QVariantMap>
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<ModuleMethod*> methodsToExport() override;
QVariantMap constantsToExport() override;
signals:
void urlOpened(QString path);
void fileOpened(QString path);
public slots:
void handleURL(const QString url);
private:
QScopedPointer<DesktopLinkingPrivate> d_ptr;
bool eventFilter(QObject* obj, QEvent* event) override;
};
#endif // DESKTOPLINKING_H

View File

@ -0,0 +1,4 @@
'use strict';
const NativeModules = require('NativeModules');
module.exports = NativeModules.DesktopLinking;

View File

@ -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": ""
}

View File

@ -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"}}})

View File

@ -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 [])})

View File

@ -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=( \

View File

@ -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)"

View File

@ -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))

View File

@ -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]))))))

View File

@ -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)))

View File

@ -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)

View File

@ -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 {}})