From 3685f6a500a95b6f44dc51a7044fe8f42223c94c Mon Sep 17 00:00:00 2001 From: Roman Volosovskyi Date: Mon, 18 Jan 2021 11:01:17 +0200 Subject: [PATCH] [#11117] Move PNs to status-go --- android/app/src/main/AndroidManifest.xml | 2 +- .../status/ethereum/module/StatusModule.java | 48 +----- .../ForegroundService.java | 3 +- .../NewMessageSignalHandler.java | 149 +++++++----------- .../pushnotifications/PushNotification.java | 28 +++- src/status_im/chat/models.cljs | 5 + src/status_im/native_module/core.cljs | 8 - src/status_im/notifications/android.cljs | 6 + src/status_im/notifications/core.cljs | 7 +- src/status_im/notifications/local.cljs | 134 ++++++++++++---- src/status_im/qr_scanner/core.cljs | 1 + src/status_im/router/core.cljs | 43 +++-- src/status_im/router/core_test.cljs | 4 +- .../ui/screens/add_new/new_chat/events.cljs | 1 + .../utils/universal_links/events.cljs | 2 +- status-go-version.json | 6 +- 16 files changed, 248 insertions(+), 199 deletions(-) rename modules/react-native-status/android/src/main/java/im/status/ethereum/{module => pushnotifications}/ForegroundService.java (97%) rename modules/react-native-status/android/src/main/java/im/status/ethereum/{module => pushnotifications}/NewMessageSignalHandler.java (75%) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index d722b7f703..d89194b6ce 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -70,7 +70,7 @@ - + = 21) { @@ -224,31 +237,13 @@ public class NewMessageSignalHandler { } } - void handleNewMessageSignal(JSONObject newMessageSignal) { - try { - JSONArray chatsNewMessagesData = newMessageSignal.getJSONObject("event").getJSONArray("chats"); - for (int i = 0; i < chatsNewMessagesData.length(); i++) { - try { - upsertChat(chatsNewMessagesData.getJSONObject(i)); - } catch (JSONException e) { - Log.e(TAG, "JSON conversion failed: " + e.getMessage()); - } - } - JSONArray messagesNewMessagesData = newMessageSignal.getJSONObject("event").getJSONArray("messages"); - for (int i = 0; i < messagesNewMessagesData.length(); i++) { - try { - upsertMessage(messagesNewMessagesData.getJSONObject(i)); - } catch (JSONException e) { - Log.e(TAG, "JSON conversion failed: " + e.getMessage()); - } - } + void handleNewMessage (Bundle data) { + upsertChat(data); + upsertMessage(data); - if(shouldRefreshNotifications) { - refreshNotifications(); - shouldRefreshNotifications = false; - } - } catch (JSONException e) { - Log.e(TAG, "JSON conversion failed: " + e.getMessage()); + if(shouldRefreshNotifications) { + refreshNotifications(); + shouldRefreshNotifications = false; } } @@ -268,69 +263,37 @@ public class NewMessageSignalHandler { return person; } - private void upsertChat(JSONObject chatData) { - try { - // NOTE: this is an exemple of chatData - // {"chatId":"contact-discovery-3622","filterId":"c0239d63f830e8b25f4bf7183c8d207f355a925b89514a17068cae4898e7f193", - // "symKeyId":"","oneToOne":true,"identity":"046599511623d7385b926ce709ac57d518dac10d451a81f75cd32c7fb4b1c...", - // "topic":"0xc446561b","discovery":false,"negotiated":false,"listen":true} - int oneToOne = chatData.getInt("chatType"); - // NOTE: for now we only notify one to one chats - // TODO: also notifiy on mentions, keywords and group chats - // TODO: one would have to pass the ens name and keywords to notify on when instanciating the class as well - // as have a method to add new ones after the handler is instanciated - if (oneToOne == 1) { - //JSONArray messagesData = chatNewMessagesData.getJSONArray("messages"); + private void upsertChat(Bundle data) { + String id = data.getString("chatId"); + int type = Integer.parseInt(data.getString("chatType")); + StatusChat chat = chats.get(id); - // there is no proper id for oneToOne chat in chatData so we peek into first message sig - // TODO: won't work for sync becaus it could be our own message - String id = chatData.getString("id"); - StatusChat chat = chats.get(id); + // if the chat was not already there, we create one + if (chat == null) { + chat = new StatusChat(id, type); + } + chats.put(id, chat); + } - // if the chat was not already there, we create one - if (chat == null) { - chat = new StatusChat(id, true); - } + private void upsertMessage(Bundle data) { + String chatId = data.getString("chatId"); + StatusChat chat = chats.get(chatId); + if (chat == null) { + return; + } - chats.put(id, chat); - } - } catch (JSONException e) { - Log.e(TAG, "JSON conversion failed: " + e.getMessage()); + StatusMessage message = createMessage(data); + if (message != null) { + chat.appendMessage(message); + chats.put(chatId, chat); + shouldRefreshNotifications = true; } } - - - private void upsertMessage(JSONObject messageData) { - try { - String chatId = messageData.getString("localChatId"); - StatusChat chat = chats.get(chatId); - if (chat == null) { - return; - } - - StatusMessage message = createMessage(messageData); - if (message != null) { - chat.appendMessage(message); - chats.put(chatId, chat); - shouldRefreshNotifications = true; - } - - } - catch (JSONException e) { - Log.e(TAG, "JSON conversion failed: " + e.getMessage()); - } - } - - private StatusMessage createMessage(JSONObject messageData) { - try { - Person author = getPerson(messageData.getString("from"), messageData.getString("identicon"), messageData.getString("alias")); - return new StatusMessage(author, messageData.getLong("whisperTimestamp"), messageData.getString("text")); - } catch (JSONException e) { - Log.e(TAG, "JSON conversion failed: " + e.getMessage()); - } - return null; + private StatusMessage createMessage(Bundle data) { + Person author = getPerson(data.getString("from"), data.getString("identicon"), data.getString("alias")); + return new StatusMessage(author, data.getLong("whisperTimestamp"), data.getString("text")); } } @@ -338,11 +301,11 @@ class StatusChat { private ArrayList messages; private String id; private String name; - private Boolean oneToOne; + private int type; - StatusChat(String id, Boolean oneToOne) { + StatusChat(String id, int type) { this.id = id; - this.oneToOne = oneToOne; + this.type = type; this.messages = new ArrayList(); this.name = name; } @@ -351,6 +314,10 @@ class StatusChat { return id; } + public int getType() { + return this.type; + } + public String getName() { //TODO this should be improved as it would rename the chat diff --git a/modules/react-native-status/android/src/main/java/im/status/ethereum/pushnotifications/PushNotification.java b/modules/react-native-status/android/src/main/java/im/status/ethereum/pushnotifications/PushNotification.java index fffca9330f..4ccd38f394 100644 --- a/modules/react-native-status/android/src/main/java/im/status/ethereum/pushnotifications/PushNotification.java +++ b/modules/react-native-status/android/src/main/java/im/status/ethereum/pushnotifications/PushNotification.java @@ -26,6 +26,7 @@ import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.WritableMap; +import android.util.Log; import im.status.ethereum.pushnotifications.PushNotificationJsDelivery; @@ -35,9 +36,12 @@ public class PushNotification extends ReactContextBaseJavaModule implements Acti private final SecureRandom mRandomNumberGenerator = new SecureRandom(); private PushNotificationHelper pushNotificationHelper; private PushNotificationJsDelivery delivery; + private ReactApplicationContext reactContext; + private NewMessageSignalHandler newMessageSignalHandler; public PushNotification(ReactApplicationContext reactContext) { super(reactContext); + this.reactContext = reactContext; reactContext.addActivityEventListener(this); Application applicationContext = (Application) reactContext.getApplicationContext(); @@ -105,6 +109,28 @@ public class PushNotification extends ReactContextBaseJavaModule implements Acti if (bundle.getString("id") == null) { bundle.putString("id", String.valueOf(mRandomNumberGenerator.nextInt())); } - pushNotificationHelper.sendToNotificationCentre(bundle); + + String type = bundle.getString("type"); + if (type != null && type.equals("message")) { + if (this.newMessageSignalHandler != null) { + newMessageSignalHandler.handleNewMessage(bundle); + } + } else { + pushNotificationHelper.sendToNotificationCentre(bundle); + } } + + @ReactMethod + public void enableNotifications() { + this.newMessageSignalHandler = new NewMessageSignalHandler(reactContext); + } + + @ReactMethod + public void disableNotifications() { + if (newMessageSignalHandler != null) { + newMessageSignalHandler.stop(); + newMessageSignalHandler = null; + } + } + } diff --git a/src/status_im/chat/models.cljs b/src/status_im/chat/models.cljs index 90aefa95cc..75e0d4e019 100644 --- a/src/status_im/chat/models.cljs +++ b/src/status_im/chat/models.cljs @@ -48,6 +48,11 @@ ([cofx chat-id] (active-chat? (get-chat cofx chat-id)))) +(defn foreground-chat? + [{{:keys [current-chat-id view-id]} :db} chat-id] + (and (= current-chat-id chat-id) + (= view-id :chat))) + (defn group-chat? ([chat] (and (multi-user-chat? chat) diff --git a/src/status_im/native_module/core.cljs b/src/status_im/native_module/core.cljs index 8765d25e7d..da297f84ef 100644 --- a/src/status_im/native_module/core.cljs +++ b/src/status_im/native_module/core.cljs @@ -35,14 +35,6 @@ config #(callback (types/json->clj %)))) -(defn enable-notifications [] - (log/debug "[native-module] enable-notifications") - (.enableNotifications ^js (status))) - -(defn disable-notifications [] - (log/debug "[native-module] disable-notifications") - (.disableNotifications ^js (status))) - (defn save-account-and-login "NOTE: beware, the password has to be sha3 hashed" [key-uid multiaccount-data hashed-password settings config accounts-data] diff --git a/src/status_im/notifications/android.cljs b/src/status_im/notifications/android.cljs index dfda12327d..0c52f63a2b 100644 --- a/src/status_im/notifications/android.cljs +++ b/src/status_im/notifications/android.cljs @@ -15,3 +15,9 @@ #js {:channelId channel-id :channelName channel-name} #(log/info "Notifications create channel:" %))) + +(defn enable-notifications [] + (.enableNotifications ^js (pn-android))) + +(defn disable-notifications [] + (.disableNotifications ^js (pn-android))) diff --git a/src/status_im/notifications/core.cljs b/src/status_im/notifications/core.cljs index 5021081888..dc30180102 100644 --- a/src/status_im/notifications/core.cljs +++ b/src/status_im/notifications/core.cljs @@ -5,7 +5,6 @@ [status-im.multiaccounts.update.core :as multiaccounts.update] ["@react-native-community/push-notification-ios" :default pn-ios] [status-im.notifications.android :as pn-android] - [status-im.native-module.core :as status] [status-im.notifications.local :as local] [quo.platform :as platform] [status-im.utils.config :as config] @@ -70,21 +69,21 @@ (do (pn-android/create-channel {:channel-id "status-im-notifications" :channel-name "Status push notifications"}) - (status/enable-notifications)) + (pn-android/enable-notifications)) (enable-ios-notifications)))) (re-frame/reg-fx ::disable (fn [_] (if platform/android? - (status/disable-notifications) + (pn-android/disable-notifications) (disable-ios-notifications)))) (re-frame/reg-fx ::logout-disable (fn [_] (if platform/android? - (status/disable-notifications) + (pn-android/disable-notifications) (.abandonPermissions ^js pn-ios)))) (fx/defn handle-enable-notifications-event diff --git a/src/status_im/notifications/local.cljs b/src/status_im/notifications/local.cljs index 640aa6c2f9..b3db981e5b 100644 --- a/src/status_im/notifications/local.cljs +++ b/src/status_im/notifications/local.cljs @@ -1,6 +1,5 @@ (ns status-im.notifications.local (:require [taoensso.timbre :as log] - [clojure.string :as cstr] [status-im.utils.fx :as fx] [status-im.ethereum.decode :as decode] ["@react-native-community/push-notification-ios" :default pn-ios] @@ -14,7 +13,11 @@ [quo.platform :as platform] [re-frame.core :as re-frame] [status-im.ui.components.react :as react] - [cljs-bean.core :as bean])) + [cljs-bean.core :as bean] + [status-im.ui.screens.chat.components.reply :as reply] + [clojure.string :as clojure.string] + [status-im.chat.models :as chat.models] + [status-im.constants :as constants])) (def default-erc20-token {:symbol :ERC20 @@ -24,23 +27,31 @@ (def notification-event-ios "localNotification") (def notification-event-android "remoteNotificationReceived") -(defn local-push-ios [{:keys [title message user-info]}] - (.presentLocalNotification pn-ios #js {:alertBody message - :alertTitle title - ;; NOTE: Use a special type to hide in Obj-C code other notifications - :userInfo (bean/->js (merge user-info - {:notificationType "local-notification"}))})) +(defn local-push-ios [{:keys [title message user-info body-type]}] + (when (not= body-type "message") + (.presentLocalNotification + pn-ios + #js {:alertBody message + :alertTitle title + ;; NOTE: Use a special type to hide in Obj-C code other notifications + :userInfo (bean/->js (merge user-info + {:notificationType "local-notification"}))}))) -(defn local-push-android [{:keys [title message icon user-info channel-id] - :or {channel-id "status-im-notifications"}}] - (pn-android/present-local-notification (merge {:channelId channel-id - :title title - :message message - :showBadge false} - (when user-info - {:userInfo (bean/->js user-info)}) - (when icon - {:largeIconUrl (:uri (react/resolve-asset-source icon))})))) +(defn local-push-android + [{:keys [title message icon user-info channel-id type] + :as notification + :or {channel-id "status-im-notifications"}}] + (pn-android/present-local-notification + (merge {:channelId channel-id + :title title + :message message + :showBadge false} + (when user-info + {:userInfo (bean/->js user-info)}) + (when icon + {:largeIconUrl (:uri (react/resolve-asset-source icon))}) + (when (= type "message") + notification)))) (defn handle-notification-press [{{deep-link :deepLink} :userInfo interaction :userInteraction}] @@ -61,13 +72,14 @@ (when (and data (.-dataJSON data)) (handle-notification-press (types/json->clj (.-dataJSON data)))))))) -(defn create-notification [{{:keys [state from to fromAccount toAccount value erc20 contract network]} - :body - :as notification}] +(defn create-transfer-notification + [{{:keys [state from to fromAccount toAccount value erc20 contract network]} + :body + :as notification}] (let [chain (ethereum/chain-id->chain-keyword network) token (if erc20 - (get-in tokens/all-tokens-normalized [(keyword chain) - (cstr/lower-case contract)] + (get-in tokens/all-tokens-normalized + [(keyword chain) (clojure.string/lower-case contract)] default-erc20-token) (tokens/native-currency (keyword chain))) amount (money/wei->ether (decode/uint value)) @@ -95,15 +107,83 @@ :user-info notification :message description})) +(defn show-message-pn? + [{{:keys [app-state multiaccount]} :db :as cofx} + {{:keys [message chat]} :body}] + (let [chat-id (get chat :id) + chat-type (get chat :chatType)] + (and + (or (= app-state "background") + (not (chat.models/foreground-chat? cofx chat-id))) + (or (contains? #{constants/one-to-one-chat-type + constants/private-group-chat-type} + chat-type) + (contains? (set (get message :mentions)) + (get multiaccount :public-key)))))) + +(defn create-message-notification + ([cofx notification] + (when (or (nil? cofx) + (show-message-pn? cofx notification)) + (create-message-notification notification))) + ([{{:keys [message contact chat]} :body}] + (let [chat-type (get chat :chatType) + chat-id (get chat :id) + contact-name @(re-frame/subscribe + [:contacts/contact-name-by-identity (get contact :id)]) + group-chat? (not= chat-type constants/one-to-one-chat-type) + title (clojure.string/join + " " + (cond-> [contact-name] + group-chat? + (conj + ;; TODO(rasom): to be translated + "in") + + group-chat? + (conj + (str (when (contains? #{constants/public-chat-type + constants/community-chat-type} + chat-type) + "#") + (get chat :name)))))] + {:type "message" + :chatType (str (get chat :chatType)) + :from title + :chatId chat-id + :alias title + :identicon (get contact :identicon) + :whisperTimestamp (get message :whisperTimestamp) + :text (reply/get-quoted-text-with-mentions (:parsedText message))}))) + +(defn create-notification + ([notification] + (create-notification nil notification)) + ([cofx {:keys [bodyType] :as notification}] + (assoc + (case bodyType + "message" (create-message-notification cofx notification) + "transaction" (create-transfer-notification notification) + nil) + :body-type bodyType))) + (re-frame/reg-fx ::local-push-ios (fn [evt] (-> evt create-notification local-push-ios))) +(fx/defn local-notification-android + {:events [::local-notification-android]} + [cofx event] + (some->> event + (create-notification cofx) + local-push-android)) + (fx/defn process - [_ evt] - (when platform/ios? - {::local-push-ios evt})) + [cofx evt] + (if platform/ios? + {::local-push-ios evt} + (local-notification-android cofx evt))) (defn handle [] (fn [^js message] @@ -112,7 +192,7 @@ (fn [on-success on-error] (try (when (= "local-notifications" (:type evt)) - (-> (:event evt) create-notification local-push-android)) + (re-frame/dispatch [::local-notification-android (:event evt)])) (on-success) (catch :default e (log/warn "failed to handle background notification" e) diff --git a/src/status_im/qr_scanner/core.cljs b/src/status_im/qr_scanner/core.cljs index 49f982ec38..69f11bfa77 100644 --- a/src/status_im/qr_scanner/core.cljs +++ b/src/status_im/qr_scanner/core.cljs @@ -95,5 +95,6 @@ {:events [::on-scan-success]} [{:keys [db]} uri] {::router/handle-uri {:chain (ethereum/chain-keyword db) + :chats (get db :chats) :uri uri :cb #(re-frame/dispatch [::match-scanned-value %])}}) diff --git a/src/status_im/router/core.cljs b/src/status_im/router/core.cljs index 717f2b8ab5..d053bce728 100644 --- a/src/status_im/router/core.cljs +++ b/src/status_im/router/core.cljs @@ -11,7 +11,8 @@ [cljs.spec.alpha :as spec] [status-im.ethereum.core :as ethereum] [status-im.utils.db :as utils.db] - [status-im.utils.http :as http])) + [status-im.utils.http :as http] + [status-im.chat.models :as chat.models])) (def ethereum-scheme "ethereum:") @@ -91,20 +92,30 @@ {:type :public-chat :error :invalid-topic})) -(defn match-group-chat [{:strs [a a1 a2]}] +(defn match-group-chat [chats {:strs [a a1 a2]}] (let [[admin-pk encoded-chat-name chat-id] [a a1 a2] chat-id-parts (when (not (string/blank? chat-id)) (string/split chat-id #"-")) chat-name (when (not (string/blank? encoded-chat-name)) (js/decodeURI encoded-chat-name))] - (if (and (not (string/blank? chat-id)) (not (string/blank? admin-pk)) (not (string/blank? chat-name)) - (> (count chat-id-parts) 1) - (not (string/blank? (first chat-id-parts))) - (utils.db/valid-public-key? admin-pk) - (utils.db/valid-public-key? (last chat-id-parts))) - {:type :group-chat - :chat-id chat-id - :invitation-admin admin-pk - :chat-name chat-name} - {:error :invalid-group-chat-data}))) + (cond (and (not (string/blank? chat-id)) (not (string/blank? admin-pk)) (not (string/blank? chat-name)) + (> (count chat-id-parts) 1) + (not (string/blank? (first chat-id-parts))) + (utils.db/valid-public-key? admin-pk) + (utils.db/valid-public-key? (last chat-id-parts))) + {:type :group-chat + :chat-id chat-id + :invitation-admin admin-pk + :chat-name chat-name} + + (and (not (string/blank? chat-id)) + (chat.models/group-chat? (get chats chat-id))) + (let [{:keys [chat-name invitation-admin]} (get chats chat-id)] + {:type :group-chat + :chat-id chat-id + :invitation-admin invitation-admin + :chat-name chat-name}) + + :else + {:error :invalid-group-chat-data}))) (defn match-private-chat-async [chain {:keys [chat-id]} cb] (match-contact-async chain @@ -165,7 +176,7 @@ {:type :wallet-account :account (when account (string/lower-case account))}) -(defn handle-uri [chain uri cb] +(defn handle-uri [chain chats uri cb] (let [{:keys [handler route-params query-params]} (match-uri uri)] (log/info "[router] uri " uri " matched " handler " with " route-params) (cond @@ -185,7 +196,7 @@ (match-private-chat-async chain route-params cb) (= handler :group-chat) - (cb (match-group-chat query-params)) + (cb (match-group-chat chats query-params)) (spec/valid? :global/public-key uri) (match-contact-async chain {:user-id uri} cb) @@ -205,5 +216,5 @@ (re-frame/reg-fx ::handle-uri - (fn [{:keys [chain uri cb]}] - (handle-uri chain uri cb))) + (fn [{:keys [chain chats uri cb]}] + (handle-uri chain chats uri cb))) diff --git a/src/status_im/router/core_test.cljs b/src/status_im/router/core_test.cljs index 5624367244..64894323b0 100644 --- a/src/status_im/router/core_test.cljs +++ b/src/status_im/router/core_test.cljs @@ -67,7 +67,7 @@ (def error {:error :invalid-group-chat-data}) (deftest match-group-chat-query - (are [query-params expected] (= (router/match-group-chat query-params) + (are [query-params expected] (= (router/match-group-chat {} query-params) expected) nil error {} error @@ -78,4 +78,4 @@ {"a" public-key "a1" chat-name "a2" chat-id} {:type :group-chat :chat-id chat-id :invitation-admin public-key - :chat-name chat-name})) \ No newline at end of file + :chat-name chat-name})) diff --git a/src/status_im/ui/screens/add_new/new_chat/events.cljs b/src/status_im/ui/screens/add_new/new_chat/events.cljs index 339d7cf546..81702c6807 100644 --- a/src/status_im/ui/screens/add_new/new_chat/events.cljs +++ b/src/status_im/ui/screens/add_new/new_chat/events.cljs @@ -97,5 +97,6 @@ {:events [:contact/qr-code-scanned]} [{:keys [db]} data opts] {::router/handle-uri {:chain (ethereum/chain-keyword db) + :chats (get db :chats) :uri data :cb #(re-frame/dispatch [::qr-code-handled % opts])}}) diff --git a/src/status_im/utils/universal_links/events.cljs b/src/status_im/utils/universal_links/events.cljs index 93e7be7e7d..4a404261e2 100644 --- a/src/status_im/utils/universal_links/events.cljs +++ b/src/status_im/utils/universal_links/events.cljs @@ -6,5 +6,5 @@ (handlers/register-handler-fx :handle-universal-link (fn [cofx [_ url]] - (log/debug "universal links: event received for " url) + (log/info "universal links: event received for " url) (universal-links/handle-url cofx url))) diff --git a/status-go-version.json b/status-go-version.json index cbbed34999..996b42cbf6 100644 --- a/status-go-version.json +++ b/status-go-version.json @@ -2,7 +2,7 @@ "_comment": "DO NOT EDIT THIS FILE BY HAND. USE 'scripts/update-status-go.sh ' instead", "owner": "status-im", "repo": "status-go", - "version": "v0.69.3", - "commit-sha1": "e18050b87f75b09cd8ad8451a7d7e0fa2e498654", - "src-sha256": "01s1s91afy2bwlx928b62fw4fc6mvsphz2py1sfc0fl9r7dw1lkz" + "version": "v0.70.0", + "commit-sha1": "d862b042ae78c8cdf38a33a7a824698ae5aa8089", + "src-sha256": "1g1kyn2nz2vjzh6qvs57k7yhy6zkw4s6jnhnxpvapzxndyg8q5h4" }