Link preview generation

Signed-off-by: Volodymyr Kozieiev <vkjr.sp@gmail.com>
This commit is contained in:
Volodymyr Kozieiev 2020-11-02 15:05:33 +02:00
parent 27ad8f07c3
commit f99b2aa401
No known key found for this signature in database
GPG Key ID: 82B04968DF4C0535
23 changed files with 329 additions and 10 deletions

View File

@ -1349,7 +1349,6 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
StatusThreadPoolExecutor.getInstance().execute(r); StatusThreadPoolExecutor.getInstance().execute(r);
} }
@ReactMethod @ReactMethod
public void getNodesFromContract(final String rpcEndpoint, final String contractAddress, final Callback callback) { public void getNodesFromContract(final String rpcEndpoint, final String contractAddress, final Callback callback) {
Log.d(TAG, "getNodesFromContract"); Log.d(TAG, "getNodesFromContract");

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1,58 @@
(ns status-im.chat.models.link-preview
(:require [re-frame.core :as re-frame]
[status-im.utils.fx :as fx]
[status-im.multiaccounts.update.core :as multiaccounts.update]
[status-im.ethereum.json-rpc :as json-rpc]
[taoensso.timbre :as log]))
(fx/defn enable
{:events [::enable]}
[{{:keys [multiaccount]} :db :as cofx} site enabled?]
(fx/merge cofx
(multiaccounts.update/multiaccount-update
:link-previews-enabled-sites
(if enabled?
(conj (get multiaccount :link-previews-enabled-sites #{}) site)
(disj (get multiaccount :link-previews-enabled-sites #{}) site))
{})))
(fx/defn load-link-preview-data
{:events [::load-link-preview-data]}
[cofx link]
(fx/merge cofx
{::json-rpc/call [{:method (json-rpc/call-ext-method "getLinkPreviewData")
:params [link]
:on-success #(re-frame/dispatch [::cache-link-preview-data link %])
:on-error #(log/error "Can't get preview data for " link)}]}))
(fx/defn cache-link-preview-data
{:events [::cache-link-preview-data]}
[{{:keys [multiaccount]} :db :as cofx} site {:keys [error] :as data}]
(when-not error
(multiaccounts.update/optimistic
cofx
:link-previews-cache
(assoc (get multiaccount :link-previews-cache {}) site data))))
(fx/defn should-suggest-link-preview
{:events [::should-suggest-link-preview]}
[{:keys [db] :as cofx} enabled?]
(multiaccounts.update/multiaccount-update
cofx
:link-preview-request-enabled (boolean enabled?)
{}))
(fx/defn request-link-preview-whitelist
[_]
{::json-rpc/call [{:method (json-rpc/call-ext-method "getLinkPreviewWhitelist")
:params []
:on-success #(re-frame/dispatch [::link-preview-whitelist-received %])
:on-error #(log/error "Failed to get link preview whitelist")}]})
(fx/defn save-link-preview-whitelist
{:events [::link-preview-whitelist-received]}
[cofx whitelist]
(fx/merge cofx
(multiaccounts.update/multiaccount-update
:link-previews-whitelist whitelist {})))

View File

@ -124,7 +124,9 @@
:currency :usd :currency :usd
:appearance 0 :appearance 0
:log-level config/log-level :log-level config/log-level
:webview-allow-permission-requests? false}) :webview-allow-permission-requests? false
:link-previews-enabled-sites #{}
:link-preview-request-enabled true})
(defn default-visible-tokens [chain] (defn default-visible-tokens [chain]
(get-in default-multiaccount [:wallet/visible-tokens chain])) (get-in default-multiaccount [:wallet/visible-tokens chain]))

View File

@ -37,10 +37,11 @@
:ens-name (:ensName message) :ens-name (:ensName message)
:line-count (:lineCount message) :line-count (:lineCount message)
:parsed-text (:parsedText message) :parsed-text (:parsedText message)
:links (:links message)
:rtl? (:rtl message) :rtl? (:rtl message)
:response-to (:responseTo message)} :response-to (:responseTo message)}
:outgoing (boolean (:outgoingStatus message))) :outgoing (boolean (:outgoingStatus message)))
(dissoc :ensName :chatId :text :rtl :responseTo :image :sticker :lineCount :parsedText))) (dissoc :ensName :chatId :text :rtl :responseTo :image :sticker :lineCount :parsedText :links)))
(defn update-outgoing-status-rpc [message-id status] (defn update-outgoing-status-rpc [message-id status]
{::json-rpc/call [{:method (json-rpc/call-ext-method "updateMessageOutgoingStatus") {::json-rpc/call [{:method (json-rpc/call-ext-method "updateMessageOutgoingStatus")

View File

@ -17,7 +17,8 @@
:parsed-text "parsed-text" :parsed-text "parsed-text"
:rtl? false :rtl? false
:image nil :image nil
:response-to "a"} :response-to "a"
:links nil}
:whisper-timestamp 1 :whisper-timestamp 1
:outgoing-status :sending :outgoing-status :sending
:command-parameters nil :command-parameters nil

View File

@ -40,6 +40,7 @@
(update :pinned-mailservers rpc->pinned-mailservers) (update :pinned-mailservers rpc->pinned-mailservers)
(update :stickers/packs-installed rpc->stickers-packs) (update :stickers/packs-installed rpc->stickers-packs)
(update :stickers/packs-pending set) (update :stickers/packs-pending set)
(update :link-previews-enabled-sites set)
(update :custom-bootnodes rpc->custom-bootnodes) (update :custom-bootnodes rpc->custom-bootnodes)
(update :custom-bootnodes-enabled? rpc->custom-bootnodes) (update :custom-bootnodes-enabled? rpc->custom-bootnodes)
(update :currency keyword))) (update :currency keyword)))

View File

@ -81,6 +81,8 @@
"wakuext_sendEmojiReaction" {} "wakuext_sendEmojiReaction" {}
"wakuext_sendEmojiReactionRetraction" {} "wakuext_sendEmojiReactionRetraction" {}
"wakuext_emojiReactionsByChatID" {} "wakuext_emojiReactionsByChatID" {}
"wakuext_getLinkPreviewWhitelist" {}
"wakuext_getLinkPreviewData" {}
;;TODO not used anywhere? ;;TODO not used anywhere?
"wakuext_deleteChat" {} "wakuext_deleteChat" {}
"wakuext_saveContact" {} "wakuext_saveContact" {}

View File

@ -29,7 +29,8 @@
[status-im.wallet.prices :as prices] [status-im.wallet.prices :as prices]
[status-im.acquisition.core :as acquisition] [status-im.acquisition.core :as acquisition]
[taoensso.timbre :as log] [taoensso.timbre :as log]
[status-im.data-store.invitations :as data-store.invitations])) [status-im.data-store.invitations :as data-store.invitations]
[status-im.chat.models.link-preview :as link-preview]))
(re-frame/reg-fx (re-frame/reg-fx
::login ::login
@ -217,7 +218,8 @@
(mobile-network/on-network-status-change) (mobile-network/on-network-status-change)
(get-group-chat-invitations) (get-group-chat-invitations)
(logging/set-log-level (:log-level multiaccount)) (logging/set-log-level (:log-level multiaccount))
(multiaccounts/switch-preview-privacy-mode-flag)))) (multiaccounts/switch-preview-privacy-mode-flag)
(link-preview/request-link-preview-whitelist))))
(defn get-new-auth-method [auth-method save-password?] (defn get-new-auth-method [auth-method save-password?]
(when save-password? (when save-password?
@ -282,6 +284,7 @@
:mailserver-topics {} :mailserver-topics {}
:default-mailserver true}) :default-mailserver true})
(multiaccounts/switch-preview-privacy-mode-flag) (multiaccounts/switch-preview-privacy-mode-flag)
(link-preview/request-link-preview-whitelist)
(logging/set-log-level (:log-level multiaccount))))) (logging/set-log-level (:log-level multiaccount)))))
(defn- keycard-setup? [cofx] (defn- keycard-setup? [cofx]

View File

@ -34,6 +34,8 @@
:dapp-store (js/require "../resources/images/ui/dapp-store.png") :dapp-store (js/require "../resources/images/ui/dapp-store.png")
:ens-header (js/require "../resources/images/ui/ens-header.png") :ens-header (js/require "../resources/images/ui/ens-header.png")
:ens-header-dark (js/require "../resources/images/ui/ens-header-dark.png") :ens-header-dark (js/require "../resources/images/ui/ens-header-dark.png")
:unfurl (js/require "../resources/images/ui/unfurl.png")
:unfurl-dark (js/require "../resources/images/ui/unfurl-dark.png")
:new-chat-header (js/require "../resources/images/ui/new-chat-header.png") :new-chat-header (js/require "../resources/images/ui/new-chat-header.png")
:onboarding-phone (js/require "../resources/images/ui/onboarding-phone.png") :onboarding-phone (js/require "../resources/images/ui/onboarding-phone.png")
:theme-dark (js/require "../resources/images/ui/theme-dark.png") :theme-dark (js/require "../resources/images/ui/theme-dark.png")

View File

@ -2345,6 +2345,32 @@
(fn [manage] (fn [manage]
(not-any? :error (vals manage)))) (not-any? :error (vals manage))))
;; LINK PREVIEW ========================================================================================================
(re-frame/reg-sub
:link-preview/whitelist
:<- [:multiaccount]
(fn [multiaccount]
(get multiaccount :link-previews-whitelist)))
(re-frame/reg-sub
:link-preview/cache
:<- [:multiaccount]
(fn [multiaccount]
(get multiaccount :link-previews-cache)))
(re-frame/reg-sub
:link-preview/enabled-sites
:<- [:multiaccount]
(fn [multiaccount]
(get multiaccount :link-previews-enabled-sites)))
(re-frame/reg-sub
:link-preview/link-preview-request-enabled
:<- [:multiaccount]
(fn [multiaccount]
(get multiaccount :link-preview-request-enabled)))
;; NOTIFICATIONS ;; NOTIFICATIONS
(re-frame/reg-sub (re-frame/reg-sub

View File

@ -0,0 +1,97 @@
(ns status-im.ui.screens.chat.message.link-preview
(:require [re-frame.core :as re-frame]
[clojure.string :as string]
[status-im.ui.components.react :as react]
[quo.core :as quo]
[status-im.utils.security :as security]
[status-im.i18n :as i18n]
[status-im.ui.screens.chat.message.styles :as styles]
[status-im.react-native.resources :as resources]
[status-im.chat.models.link-preview :as link-preview])
(:require-macros [status-im.utils.views :refer [defview letsubs]]))
(defn link-belongs-to-domain [link domain]
(cond
(string/starts-with? link (str "https://" domain)) true
(string/starts-with? link (str "https://www." domain)) true
:else false))
(defn domain-info-if-whitelisted [link whitelist]
(first (filter
#(link-belongs-to-domain link (:address %))
whitelist)))
(defn link-extended-info [link whitelist enabled-list]
(let [domain-info (domain-info-if-whitelisted link whitelist)]
{:whitelisted (not (nil? domain-info))
:enabled (contains? enabled-list (:title domain-info))
:link link}))
(defn previewable-link [links whitelist enabled-list]
(->> links
(map #(link-extended-info % whitelist enabled-list))
(filter #(:whitelisted %))
(first)))
(defview link-preview-enable-request []
[react/view (styles/link-preview-request-wrapper)
[react/view {:margin 12}
[react/image {:source (resources/get-theme-image :unfurl)
:style styles/link-preview-request-image}]
[quo/text {:size :small
:align :center
:style {:margin-top 6}}
(i18n/label :t/enable-link-previews)]
[quo/text {:size :small
:color :secondary
:align :center
:style {:margin-top 2}}
(i18n/label :t/once-enabled-share-metadata)]]
[quo/separator]
[quo/button {:on-press #(re-frame/dispatch [:navigate-to :link-preview-settings])
:type :secondary}
(i18n/label :enable)]
[quo/separator]
[quo/button {:on-press #(re-frame/dispatch
[::link-preview/should-suggest-link-preview false])
:type :secondary}
(i18n/label :t/dont-ask)]])
(defview link-preview-loader [link outgoing]
(letsubs [cache [:link-preview/cache]]
(let [{:keys [site title thumbnailUrl] :as preview-data} (get cache link)]
(if (not preview-data)
(do
(re-frame/dispatch
[::link-preview/load-link-preview-data link])
nil)
[react/touchable-highlight
{:on-press #(when (and (security/safe-link? link))
(re-frame/dispatch
[:browser.ui/message-link-pressed link]))}
[react/view (styles/link-preview-wrapper outgoing)
[react/image {:source {:uri thumbnailUrl}
:style (styles/link-preview-image outgoing)
:accessibility-label :member-photo}]
[quo/text {:size :small
:style styles/link-preview-title}
title]
[quo/text {:size :small
:color :secondary
:style styles/link-preview-site}
site]]]))))
(defview link-preview-wrapper [links outgoing]
(letsubs
[ask-user? [:link-preview/link-preview-request-enabled]
whitelist [:link-preview/whitelist]
enabled-sites [:link-preview/enabled-sites]]
(let [link-info (previewable-link links whitelist enabled-sites)
{:keys [link whitelisted enabled]} link-info]
(when (and link whitelisted)
(if enabled
[link-preview-loader link outgoing]
(when ask-user?
[link-preview-enable-request]))))))

View File

@ -17,7 +17,8 @@
[status-im.ui.screens.chat.message.reactions :as reactions] [status-im.ui.screens.chat.message.reactions :as reactions]
[quo.core :as quo] [quo.core :as quo]
[reagent.core :as reagent] [reagent.core :as reagent]
[status-im.ui.screens.chat.components.reply :as components.reply]) [status-im.ui.screens.chat.components.reply :as components.reply]
[status-im.ui.screens.chat.message.link-preview :as link-preview])
(:require-macros [status-im.utils.views :refer [defview letsubs]])) (:require-macros [status-im.utils.views :refer [defview letsubs]]))
(defview mention-element [from] (defview mention-element [from]
@ -213,7 +214,8 @@
[message-author-name from modal]]) [message-author-name from modal]])
;;MESSAGE CONTENT ;;MESSAGE CONTENT
[react/view [react/view
content]]] content]
[link-preview/link-preview-wrapper (:links (:content message)) outgoing]]]
; delivery status ; delivery status
[react/view (style/delivery-status outgoing) [react/view (style/delivery-status outgoing)
[message-delivery-status message]]]) [message-delivery-status message]]])

View File

@ -1,6 +1,7 @@
(ns status-im.ui.screens.chat.message.styles (ns status-im.ui.screens.chat.message.styles
(:require [quo.design-system.colors :as colors] (:require [quo.design-system.colors :as colors]
[status-im.ui.screens.chat.styles.photos :as photos])) [status-im.ui.screens.chat.styles.photos :as photos]
[status-im.ui.components.colors :as components.colors]))
(defn picker-wrapper-style [{:keys [display-photo? outgoing]}] (defn picker-wrapper-style [{:keys [display-photo? outgoing]}]
(merge {:flex-direction :row (merge {:flex-direction :row
@ -84,3 +85,43 @@
{:background-color (:interactive-02 @colors/theme) {:background-color (:interactive-02 @colors/theme)
;; FIXME: Use broder color here ;; FIXME: Use broder color here
:border-color "rgba(67, 96, 223, 0.2)"}))) :border-color "rgba(67, 96, 223, 0.2)"})))
(defn link-preview-request-wrapper []
{:border-radius 16
:border-width 1
:border-color components.colors/gray-lighter
:margin-vertical 4
:background-color (:ui-background @colors/theme)})
(def link-preview-request-image
{:width 132
:height 94
:align-self :center})
(defn link-preview-wrapper [outgoing]
{:overflow :hidden
:border-top-left-radius 16
:border-top-right-radius 16
:border-bottom-left-radius (if outgoing 16 4)
:border-bottom-right-radius (if outgoing 4 16)
:border-width 1
:border-color components.colors/gray-lighter
:margin-vertical 4
:background-color (:ui-background @colors/theme)})
(defn link-preview-image [outgoing]
{:height 170
:overflow :hidden
:border-top-left-radius 16
:border-top-right-radius 16
:border-bottom-left-radius (if outgoing 16 4)
:border-bottom-right-radius (if outgoing 4 16)})
(def link-preview-title
{:margin-horizontal 12
:margin-top 10})
(def link-preview-site
{:margin-horizontal 12
:margin-top 2
:margin-bottom 10})

View File

@ -0,0 +1,13 @@
(ns status-im.ui.screens.link-previews-settings.styles)
(def link-preview-settings-image
{:height 100
:align-self :center
:margin-top 16})
(def whitelist-container
{:flex-direction :row
:justify-content :space-between})
(def enable-all
{:align-self :flex-end})

View File

@ -0,0 +1,50 @@
(ns status-im.ui.screens.link-previews-settings.views
(:require-macros [status-im.utils.views :as views])
(:require [re-frame.core :as re-frame]
[status-im.i18n :as i18n]
[status-im.ui.components.react :as react]
[status-im.ui.components.list.views :as list]
[quo.core :as quo]
[status-im.react-native.resources :as resources]
[status-im.ui.components.topbar :as topbar]
[status-im.ui.screens.link-previews-settings.styles :as styles]
[status-im.chat.models.link-preview :as link-preview]))
(defn prepare-urls-items-data [link-previews-enabled-sites]
(fn [{:keys [title address]}]
(let [enabled? (contains? link-previews-enabled-sites title)]
{:title title
:subtitle address
:size :small
:accessory :switch
:active (contains? link-previews-enabled-sites title)
:on-press #(re-frame/dispatch
[::link-preview/enable title ((complement boolean) enabled?)])})))
(views/defview link-previews-settings []
(views/letsubs [{:keys [link-previews-whitelist link-previews-enabled-sites]} [:multiaccount]]
[react/view {:flex 1}
[topbar/topbar {:title (i18n/label :t/chat-link-previews)}]
[react/image {:source (resources/get-theme-image :unfurl)
:style styles/link-preview-settings-image}]
[quo/text {:style {:margin 16}}
(i18n/label :t/you-can-choose-preview-websites)]
[quo/separator {:style {:margin-vertical 8}}]
[react/view styles/whitelist-container
[quo/list-header (i18n/label :t/websites)]
(when (> (count link-previews-whitelist) 1)
[quo/button {:on-press #(doseq [site (map :title link-previews-whitelist)]
(re-frame/dispatch
[::link-preview/enable site true]))
:type :secondary
:style styles/enable-all}
(i18n/label :t/enable-all)])]
[list/flat-list
{:data (vec (map (prepare-urls-items-data link-previews-enabled-sites) link-previews-whitelist))
:key-fn (fn [_ i] (str i))
:render-fn quo/list-item
:footer [quo/text {:color :secondary
:style {:margin 16}} (i18n/label :t/previewing-may-share-metadata)]}]]))

View File

@ -63,6 +63,11 @@
:on-press #(re-frame/dispatch :on-press #(re-frame/dispatch
[:multiaccounts.ui/preview-privacy-mode-switched [:multiaccounts.ui/preview-privacy-mode-switched
((complement boolean) preview-privacy?)])}] ((complement boolean) preview-privacy?)])}]
[quo/list-item {:size :small
:title (i18n/label :t/chat-link-previews)
:chevron true
:on-press #(re-frame/dispatch [:navigate-to :link-previews-settings])
:accessibility-label :chat-link-previews}]
(when platform/android? (when platform/android?
[quo/list-item {:size :small [quo/list-item {:size :small
:title (i18n/label :t/webview-camera-permission-requests) :title (i18n/label :t/webview-camera-permission-requests)

View File

@ -25,6 +25,7 @@
[status-im.ui.screens.profile.contact.views :as contact] [status-im.ui.screens.profile.contact.views :as contact]
[status-im.ui.screens.notifications-settings.views :as notifications-settings] [status-im.ui.screens.notifications-settings.views :as notifications-settings]
[status-im.ui.screens.wallet.send.views :as wallet] [status-im.ui.screens.wallet.send.views :as wallet]
[status-im.ui.screens.link-previews-settings.views :as link-previews]
[status-im.ui.screens.profile.my-status.views :as my-status])) [status-im.ui.screens.profile.my-status.views :as my-status]))
(defonce main-stack (navigation/create-stack)) (defonce main-stack (navigation/create-stack))
@ -79,6 +80,9 @@
:on-focus [::new-chat.events/new-chat-focus] :on-focus [::new-chat.events/new-chat-focus]
:transition :presentation-ios :transition :presentation-ios
:component new-chat/new-contact} :component new-chat/new-contact}
{:name :link-preview-settings
:transition :presentation-ios
:component link-previews/link-previews-settings}
{:name :new-public-chat {:name :new-public-chat
:transition :presentation-ios :transition :presentation-ios
:insets {:bottom true} :insets {:bottom true}

View File

@ -14,6 +14,7 @@
:as :as
offline-messaging-settings] offline-messaging-settings]
[status-im.ui.screens.dapps-permissions.views :as dapps-permissions] [status-im.ui.screens.dapps-permissions.views :as dapps-permissions]
[status-im.ui.screens.link-previews-settings.views :as link-previews-settings]
[status-im.ui.screens.privacy-and-security-settings.views :as privacy-and-security] [status-im.ui.screens.privacy-and-security-settings.views :as privacy-and-security]
[status-im.ui.screens.sync-settings.views :as sync-settings] [status-im.ui.screens.sync-settings.views :as sync-settings]
[status-im.ui.screens.advanced-settings.views :as advanced-settings] [status-im.ui.screens.advanced-settings.views :as advanced-settings]
@ -76,6 +77,8 @@
:component edit-mailserver/edit-mailserver} :component edit-mailserver/edit-mailserver}
{:name :dapps-permissions {:name :dapps-permissions
:component dapps-permissions/dapps-permissions} :component dapps-permissions/dapps-permissions}
{:name :link-previews-settings
:component link-previews-settings/link-previews-settings}
{:name :privacy-and-security {:name :privacy-and-security
:component privacy-and-security/privacy-and-security} :component privacy-and-security/privacy-and-security}
{:name :appearance {:name :appearance

View File

@ -1315,5 +1315,14 @@
"invalid-public-chat-topic": "Invalid public chat topic", "invalid-public-chat-topic": "Invalid public chat topic",
"now": "Now", "now": "Now",
"statuses-my-profile-descr": "Status updates are messages you publish to your profile. They can be viewed by anyone visiting your profile inside Status", "statuses-my-profile-descr": "Status updates are messages you publish to your profile. They can be viewed by anyone visiting your profile inside Status",
"new-status": "New status" "new-status": "New status",
"chat-link-previews": "Chat link previews",
"you-can-choose-preview-websites" : "You can choose which of the following websites can preview link of descriptions and pictures in chats",
"previewing-may-share-metadata" : "Previewing links from these websites may share your metadata with their owners",
"websites" : "Websites",
"enable-all" : "Enable all",
"warning-sending-to-contract-descr": "The address you entered is a smart contract, sending funds to this address may result in loss of funds. To interact with a DApp, open the DApp in the Status DApp Browser.",
"dont-ask": "Don't ask me again",
"enable-link-previews": "Enable link previews in chat?",
"once-enabled-share-metadata": "Once enabled, links posted in the chat may share your metadata with the site"
} }