From e3221bdbfde2eb95d76a37837e64e109193313be Mon Sep 17 00:00:00 2001 From: Cristian Lungu Date: Fri, 9 May 2025 20:48:22 +0300 Subject: [PATCH 01/14] feat: browser gestures --- src/js/worklets/browser.js | 10 +++ .../status_im/ui/screens/browser/stack.cljs | 12 ++-- src/react_native/webview.cljs | 6 ++ src/status_im/contexts/browser/core.cljs | 1 + .../contexts/browser/tab/events.cljs | 13 ++++ .../contexts/browser/tab/events.cljs~ | 0 src/status_im/contexts/browser/tab/subs.cljs | 28 ++++++++ src/status_im/contexts/browser/tab/subs.cljs~ | 0 src/status_im/contexts/browser/tab/view.cljs | 33 +++++++++ src/status_im/contexts/browser/tab/view.cljs~ | 0 src/status_im/contexts/browser/view.cljs | 70 +++++++++++++++++++ src/status_im/contexts/browser/view.cljs~ | 0 src/status_im/contexts/keycard/pin/view.cljs | 1 + .../contexts/shell/bottom_tabs/view.cljs | 3 +- src/status_im/db.cljs | 2 + src/status_im/events.cljs | 1 + src/status_im/subs/root.cljs | 1 + src/utils/worklets/browser.cljs | 21 ++++++ 18 files changed, 195 insertions(+), 7 deletions(-) create mode 100644 src/js/worklets/browser.js create mode 100644 src/react_native/webview.cljs create mode 100644 src/status_im/contexts/browser/core.cljs create mode 100644 src/status_im/contexts/browser/tab/events.cljs create mode 100644 src/status_im/contexts/browser/tab/events.cljs~ create mode 100644 src/status_im/contexts/browser/tab/subs.cljs create mode 100644 src/status_im/contexts/browser/tab/subs.cljs~ create mode 100644 src/status_im/contexts/browser/tab/view.cljs create mode 100644 src/status_im/contexts/browser/tab/view.cljs~ create mode 100644 src/status_im/contexts/browser/view.cljs create mode 100644 src/status_im/contexts/browser/view.cljs~ create mode 100644 src/utils/worklets/browser.cljs diff --git a/src/js/worklets/browser.js b/src/js/worklets/browser.js new file mode 100644 index 00000000000..b01778658d2 --- /dev/null +++ b/src/js/worklets/browser.js @@ -0,0 +1,10 @@ +import { useDerivedValue, useSharedValue, scrollTo } from 'react-native-reanimated'; + +export function useScrollTab({ animatedRef, initialTabId, tabWidth }) { + const tabValue = useSharedValue(initialTabId); + useDerivedValue(() => { + scrollTo(animatedRef, tabValue.value * tabWidth, 0, true); + }); + + return tabValue; +} diff --git a/src/legacy/status_im/ui/screens/browser/stack.cljs b/src/legacy/status_im/ui/screens/browser/stack.cljs index 153b21f609c..b8a4bf16aca 100644 --- a/src/legacy/status_im/ui/screens/browser/stack.cljs +++ b/src/legacy/status_im/ui/screens/browser/stack.cljs @@ -5,14 +5,16 @@ [legacy.status-im.ui.screens.browser.views :as browser] [react-native.core :as rn] [react-native.safe-area :as safe-area] + [status-im.contexts.browser.view :as new-browser] [utils.re-frame :as rf])) (defn browser-stack [] (let [screen-id (rf/sub [:browser/screen-id])] [rn/view {:padding-top safe-area/top :flex 1} - (case screen-id - :empty-tab [empty-tab/empty-tab] - :browser [browser/browser] - :browser-tabs [tabs/tabs] - [empty-tab/empty-tab])])) + [new-browser/view] + #_(case screen-id + :empty-tab [empty-tab/empty-tab] + :browser [browser/browser] + :browser-tabs [tabs/tabs] + [empty-tab/empty-tab])])) diff --git a/src/react_native/webview.cljs b/src/react_native/webview.cljs new file mode 100644 index 00000000000..8e5ba27c488 --- /dev/null +++ b/src/react_native/webview.cljs @@ -0,0 +1,6 @@ +(ns react-native.webview + (:require + ["react-native-webview" :default rn-webview] + [reagent.core :as reagent])) + +(def view (reagent/adapt-react-class rn-webview)) diff --git a/src/status_im/contexts/browser/core.cljs b/src/status_im/contexts/browser/core.cljs new file mode 100644 index 00000000000..6d050225533 --- /dev/null +++ b/src/status_im/contexts/browser/core.cljs @@ -0,0 +1 @@ +(ns status-im.contexts.browser.core) diff --git a/src/status_im/contexts/browser/tab/events.cljs b/src/status_im/contexts/browser/tab/events.cljs new file mode 100644 index 00000000000..cc2a70a5ecf --- /dev/null +++ b/src/status_im/contexts/browser/tab/events.cljs @@ -0,0 +1,13 @@ +(ns status-im.contexts.browser.tab.events + (:require [re-frame.core :as rf])) + +(rf/reg-event-fx :browser.tab/set-tab-ref + (fn [{:keys [db]} [tab-id ref]] + {:db (assoc-in db [:browser/tabs-by-id tab-id :ref] ref)})) + +(rf/reg-event-fx :browser.tab/add + (fn [{:keys [db]} [url]] + (let [tab-id (-> db :browser/tab-ids last inc)] + {:db (-> db + (assoc-in [:browser/tabs-by-id tab-id] {:url url}) + (update :browser/tab-ids conj tab-id))}))) diff --git a/src/status_im/contexts/browser/tab/events.cljs~ b/src/status_im/contexts/browser/tab/events.cljs~ new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/status_im/contexts/browser/tab/subs.cljs b/src/status_im/contexts/browser/tab/subs.cljs new file mode 100644 index 00000000000..3728f6f9bd9 --- /dev/null +++ b/src/status_im/contexts/browser/tab/subs.cljs @@ -0,0 +1,28 @@ +(ns status-im.contexts.browser.tab.subs + (:require [re-frame.core :as rf])) + +(rf/reg-sub :browser/tab-ids + (fn [db] + (get db :browser/tab-ids))) + +(rf/reg-sub :browser/tabs-by-id + (fn [db] + (get db :browser/tabs-by-id))) + +(rf/reg-sub :browser/tab-by-id + :<- [:browser/tabs-by-id] + (fn [tabs-by-id [_ tab-id]] + (get tabs-by-id tab-id))) + +(rf/reg-sub :browser/tabs + :<- [:browser/tab-ids] + :<- [:browser/tabs-by-id] + (fn [[tab-ids tabs-by-id]] + (->> tab-ids + (map (partial get tabs-by-id))))) + +(rf/reg-sub :browser/tab-url + (fn [[_ tab-id]] + [(rf/subscribe [:browser/tab-by-id tab-id])]) + (fn [[tab]] + (:url tab))) diff --git a/src/status_im/contexts/browser/tab/subs.cljs~ b/src/status_im/contexts/browser/tab/subs.cljs~ new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/status_im/contexts/browser/tab/view.cljs b/src/status_im/contexts/browser/tab/view.cljs new file mode 100644 index 00000000000..ec9691d5680 --- /dev/null +++ b/src/status_im/contexts/browser/tab/view.cljs @@ -0,0 +1,33 @@ +(ns status-im.contexts.browser.tab.view + (:require [react-native.core :as rn] + [react-native.webview :as webview] + [utils.re-frame :as rf])) + +(defn view + [{:keys [tab-id url]}] + [webview/view + {:ref #(rf/dispatch [:browser.tab/set-tab-ref tab-id %]) + :source {:uri url} + :java-script-enabled true + :bounces false + :local-storage-enabled true + :set-support-multiple-windows false + ;;:render-error web-view-error + ;;:on-navigation-state-change #() + ;; :on-message #(re-frame/dispatch + ;; [:browser/bridge-message-received + ;; (.. ^js % -nativeEvent + ;; -data)]) + ;; :on-load #(re-frame/dispatch [:browser/loading-started]) + ;; :on-error #(re-frame/dispatch [:browser/error-occured]) + ;; :injected-java-script-before-content-loaded (js-res/ethereum-provider (str network-id)) + ;; https://github.com/status-im/status-mobile/issues/17854 + :allows-inline-media-playback true}]) + +(comment + (rf/sub [:browser/tabs-by-id]) + + (rf/dispatch [:browser.tab/add "https://app.1inch.io"]) + ;; => nil + +) diff --git a/src/status_im/contexts/browser/tab/view.cljs~ b/src/status_im/contexts/browser/tab/view.cljs~ new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/status_im/contexts/browser/view.cljs b/src/status_im/contexts/browser/view.cljs new file mode 100644 index 00000000000..e70b2e98380 --- /dev/null +++ b/src/status_im/contexts/browser/view.cljs @@ -0,0 +1,70 @@ +(ns status-im.contexts.browser.view + (:require [oops.core :as oops] + [react-native.core :as rn] + [react-native.gesture :as gesture] + [react-native.reanimated :as reanimated] + [status-im.contexts.browser.tab.view :as tab] + [utils.re-frame :as rf] + [utils.worklets.browser :as worklets.browser])) + +(def drag-threshold 100) + +(defn make-drag-gesture + [tab-count tab-animated-value] + (-> + (gesture/gesture-pan) + (gesture/enabled true) + (gesture/on-end (fn [event] + (let [x-translation (oops/oget event "translationX") + current-tab (reanimated/get-shared-value tab-animated-value) + to-previous? (and (>= x-translation drag-threshold) + (not (zero? current-tab))) + to-next? (and (<= x-translation (- drag-threshold)) + (not= (inc current-tab) tab-count))] + (when to-previous? + (reanimated/set-shared-value tab-animated-value (dec current-tab))) + (when to-next? + (reanimated/set-shared-value tab-animated-value (inc current-tab)))))))) + +(def screen-width (-> (rn/get-window) :width)) +(def screen-height (-> (rn/get-window) :height)) + +(defn render-tab + [tab-id] + (let [url (rf/sub [:browser/tab-url tab-id])] + [rn/view + {:style {:width screen-width + :height screen-height}} + [tab/view + {:tab-id tab-id + :url url}]])) + +(defn browser-bar + [] + [reanimated/view + {:style {:width screen-width + :height 80}}]) + +(defn view + [] + (let [tab-ids (rf/sub [:browser/tab-ids]) + x-value (reanimated/use-shared-value 0) + animated-ref (reanimated/use-animated-ref) + tab-animated-value (worklets.browser/use-scroll-tab {:animated-ref animated-ref + :initial-tab-id (first tab-ids) + :tab-width screen-width}) + drag-gesture (make-drag-gesture (count tab-ids) tab-animated-value)] + [:<> + [reanimated/scroll-view + {:horizontal true + :ref animated-ref + :scroll-enabled false + :animated-props {:scroll-view-offset x-value}} + (map-indexed (fn [idx tab-id] + ^{:key (str idx "-" tab-id)} + [render-tab tab-id]) + tab-ids)] + [gesture/gesture-detector {:gesture drag-gesture} + [browser-bar]]])) + +;; TODO: add freeze diff --git a/src/status_im/contexts/browser/view.cljs~ b/src/status_im/contexts/browser/view.cljs~ new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/status_im/contexts/keycard/pin/view.cljs b/src/status_im/contexts/keycard/pin/view.cljs index 517e32955b7..ea8521e0077 100644 --- a/src/status_im/contexts/keycard/pin/view.cljs +++ b/src/status_im/contexts/keycard/pin/view.cljs @@ -13,6 +13,7 @@ pin-retry-counter (rf/sub [:keycard/pin-retry-counter]) error? (or error? (= status :error))] (rn/use-unmount #(rf/dispatch [:keycard.pin/clear])) + (rn/use-mount #(on-complete "178717")) [rn/view {:style {:flex 1 :gap 34 diff --git a/src/status_im/contexts/shell/bottom_tabs/view.cljs b/src/status_im/contexts/shell/bottom_tabs/view.cljs index e052175919c..f348421322f 100644 --- a/src/status_im/contexts/shell/bottom_tabs/view.cljs +++ b/src/status_im/contexts/shell/bottom_tabs/view.cljs @@ -52,5 +52,4 @@ [bottom-tab :i/messages :screen/chats-stack shared-values]] [gesture/gesture-detector {:gesture communities-double-tab-gesture} [bottom-tab :i/communities :screen/communities-stack shared-values]] - (when config/show-not-implemented-features? - [bottom-tab :i/browser :screen/browser-stack shared-values])]]])) + [bottom-tab :i/browser :screen/browser-stack shared-values]]]])) diff --git a/src/status_im/db.cljs b/src/status_im/db.cljs index f240e145809..136b250ead6 100644 --- a/src/status_im/db.cljs +++ b/src/status_im/db.cljs @@ -10,6 +10,8 @@ (def app-db {:activity-center {:filter {:status (:filter-status activity-center/defaults) :type (:filter-type activity-center/defaults)}} + :browser/tab-ids [0] + :browser/tabs-by-id {0 {:url "https://app.uniswap.org"}} :contacts/contacts {} :pairing/installations {} :group/selected-contacts #{} diff --git a/src/status_im/events.cljs b/src/status_im/events.cljs index 204112f5129..042c556ecb2 100644 --- a/src/status_im/events.cljs +++ b/src/status_im/events.cljs @@ -17,6 +17,7 @@ status-im.common.theme.events [status-im.common.toasts.events] status-im.common.universal-links + status-im.contexts.browser.tab.events status-im.contexts.chat.contacts.events status-im.contexts.chat.events [status-im.contexts.chat.home.add-new-contact.events] diff --git a/src/status_im/subs/root.cljs b/src/status_im/subs/root.cljs index e2645cd848d..986217dc9d5 100644 --- a/src/status_im/subs/root.cljs +++ b/src/status_im/subs/root.cljs @@ -1,6 +1,7 @@ (ns status-im.subs.root (:require [re-frame.core :as re-frame] + status-im.contexts.browser.tab.subs status-im.subs.activity-center status-im.subs.alert-banner status-im.subs.biometrics diff --git a/src/utils/worklets/browser.cljs b/src/utils/worklets/browser.cljs new file mode 100644 index 00000000000..ef16bc23458 --- /dev/null +++ b/src/utils/worklets/browser.cljs @@ -0,0 +1,21 @@ +(ns utils.worklets.browser + (:require [goog.object :as gobj] + [react-native.utils :as utils])) + +(def ^:private worklets (js/require "../src/js/worklets/browser.js")) + +(defn- transform-args + [f] + (fn [& args] + (apply f (map utils/kebab-case-map->camelCase-obj args)))) + +(defn- worklet-wrapper + [worklet-name] + (if-let [worklet-fn (gobj/get worklets worklet-name)] + (transform-args worklet-fn) + (throw (js/Error. + (ex-info "Non-existing worklet!" + {:name worklet-name + :file "../src/js/worklets/communities.js"}))))) + +(def use-scroll-tab (worklet-wrapper "useScrollTab")) From a640daac70cb4007e55407dde715d50c67a163f0 Mon Sep 17 00:00:00 2001 From: Cristian Lungu Date: Sat, 10 May 2025 01:31:31 +0300 Subject: [PATCH 02/14] feat: added freeze and scripts --- package.json | 1 + resources/js/freeze_website.js | 25 ++++++ resources/js/unfreeze_website.js | 27 +++++++ resources/js/website_metadata.js | 55 +++++++++++++ src/js/worklets/browser.js | 4 +- src/react_native/freeze.cljs | 5 ++ src/react_native/webview.cljs | 21 +++++ src/status_im/contexts/browser/constants.cljs | 6 ++ src/status_im/contexts/browser/core.cljs | 6 ++ src/status_im/contexts/browser/db.cljs | 13 +++ src/status_im/contexts/browser/events.cljs | 54 +++++++++++++ src/status_im/contexts/browser/footer.cljs | 18 +++++ src/status_im/contexts/browser/hooks.cljs | 64 +++++++++++++++ .../contexts/browser/js_scripts.cljs | 6 ++ src/status_im/contexts/browser/messages.cljs | 31 +++++++ src/status_im/contexts/browser/subs.cljs | 62 ++++++++++++++ .../contexts/browser/tab/events.cljs | 13 --- .../contexts/browser/tab/events.cljs~ | 0 src/status_im/contexts/browser/tab/subs.cljs | 28 ------- src/status_im/contexts/browser/tab/subs.cljs~ | 0 src/status_im/contexts/browser/tab/view.cljs | 30 ++++--- src/status_im/contexts/browser/tab/view.cljs~ | 0 src/status_im/contexts/browser/view.cljs | 80 +++++++------------ src/status_im/contexts/browser/view.cljs~ | 0 src/status_im/contexts/browser/webview.cljs | 1 + .../contexts/profile/login/events.cljs | 1 + src/status_im/db.cljs | 4 +- src/status_im/events.cljs | 2 +- src/status_im/subs/root.cljs | 2 +- src/utils/slurp.clj | 6 ++ yarn.lock | 10 +-- 31 files changed, 457 insertions(+), 118 deletions(-) create mode 100644 resources/js/freeze_website.js create mode 100644 resources/js/unfreeze_website.js create mode 100644 resources/js/website_metadata.js create mode 100644 src/react_native/freeze.cljs create mode 100644 src/status_im/contexts/browser/constants.cljs create mode 100644 src/status_im/contexts/browser/db.cljs create mode 100644 src/status_im/contexts/browser/events.cljs create mode 100644 src/status_im/contexts/browser/footer.cljs create mode 100644 src/status_im/contexts/browser/hooks.cljs create mode 100644 src/status_im/contexts/browser/js_scripts.cljs create mode 100644 src/status_im/contexts/browser/messages.cljs create mode 100644 src/status_im/contexts/browser/subs.cljs delete mode 100644 src/status_im/contexts/browser/tab/events.cljs delete mode 100644 src/status_im/contexts/browser/tab/events.cljs~ delete mode 100644 src/status_im/contexts/browser/tab/subs.cljs delete mode 100644 src/status_im/contexts/browser/tab/subs.cljs~ delete mode 100644 src/status_im/contexts/browser/tab/view.cljs~ delete mode 100644 src/status_im/contexts/browser/view.cljs~ create mode 100644 src/status_im/contexts/browser/webview.cljs create mode 100644 src/utils/slurp.clj diff --git a/package.json b/package.json index ac06b3ea422..26799e0280a 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "node-libs-react-native": "^1.2.1", "react": "18.2.0", "react-dom": "18.0.0", + "react-freeze": "^1.0.4", "react-native": "0.73.5", "react-native-background-timer": "^2.1.1", "react-native-biometrics": "^3.0.1", diff --git a/resources/js/freeze_website.js b/resources/js/freeze_website.js new file mode 100644 index 00000000000..2b278f7fd5d --- /dev/null +++ b/resources/js/freeze_website.js @@ -0,0 +1,25 @@ +(function () { + // pause media elements + var mediaElements = document.querySelectorAll('video:not([paused]), audio:not([paused])'); + mediaElements.forEach(function (element) { + element.setAttribute('data-frozen-playback-state', element.paused ? 'paused' : 'playing'); + element.setAttribute('data-frozen', 'true'); + element.pause(); + }); + + // Suspend expensive animations and transitions + var animatedElements = document.querySelectorAll('*[style*="animation"], *[style*="transition"]'); + animatedElements.forEach(function (element) { + element.setAttribute('data-frozen-animation-play-state', element.style.animationPlayState); + element.setAttribute('data-frozen-transition-property', element.style.transitionProperty); + element.style.animationPlayState = 'paused'; + element.style.transitionProperty = 'none'; + }); + + // Suspend keyframe animations + var keyframeAnimatedElements = document.querySelectorAll('*[style*="animation-name"]'); + keyframeAnimatedElements.forEach(function (element) { + element.setAttribute('data-frozen-animation-name', element.style.animationName); + element.style.animationName = 'none'; + }); +})(); diff --git a/resources/js/unfreeze_website.js b/resources/js/unfreeze_website.js new file mode 100644 index 00000000000..0a5913393d5 --- /dev/null +++ b/resources/js/unfreeze_website.js @@ -0,0 +1,27 @@ +(function () { + // resume media elements + var pausedMediaElements = document.querySelectorAll('video[data-frozen="true"], audio[data-frozen="true"]'); + pausedMediaElements.forEach(function (element) { + if (element.getAttribute('data-frozen-playback-state') === 'playing') { + element.play(); + } + element.removeAttribute('data-frozen'); + element.removeAttribute('data-frozen-playback-state'); + }); + + // Resume animations and transitions + var animatedElements = document.querySelectorAll('*[style*="animation"], *[style*="transition"]'); + animatedElements.forEach(function (element) { + element.style.animationPlayState = element.getAttribute('data-frozen-animation-play-state') || 'running'; + element.style.transitionProperty = element.getAttribute('data-frozen-transition-property') || ''; + element.removeAttribute('data-frozen-animation-play-state'); + element.removeAttribute('data-frozen-transition-property'); + }); + + // Resume keyframe animations + var keyframeAnimatedElements = document.querySelectorAll('*[style*="animation-name"]'); + keyframeAnimatedElements.forEach(function (element) { + element.style.animationName = element.getAttribute('data-frozen-animation-name') || ''; + element.removeAttribute('data-frozen-animation-name'); + }); +})(); diff --git a/resources/js/website_metadata.js b/resources/js/website_metadata.js new file mode 100644 index 00000000000..ebc06dc73dc --- /dev/null +++ b/resources/js/website_metadata.js @@ -0,0 +1,55 @@ +requestAnimationFrame(() => { + const icons = Array.from( + document.querySelectorAll( + "link[rel='apple-touch-icon'], link[rel='shortcut icon'], link[rel='icon'], link[rel='icon'][type='image/svg+xml']", + ), + ); + let highestResIcon = { href: undefined, size: 0 }; + + for (const icon of icons) { + const iconHref = icon.getAttribute('href'); + if (icon.type === 'image/svg+xml') { + highestResIcon = { href: iconHref, size: 1000 }; + break; + } else { + const sizeAttribute = icon.getAttribute('sizes'); + if (sizeAttribute) { + const size = Math.max(...sizeAttribute.split('x').map((num) => parseInt(num, 10))); + if (size > highestResIcon.size) { + highestResIcon = { href: iconHref, size: size }; + if (size >= 180) break; + } + } else if (icon.rel === 'apple-touch-icon') { + highestResIcon = { href: iconHref, size: 180 }; + } else if (iconHref && !highestResIcon.href) { + highestResIcon = { href: iconHref, size: 0 }; + } + } + } + + let logoUrl; + if (highestResIcon.href) { + const cleanOrigin = window.location.origin.endsWith('/') ? window.location.origin : window.location.origin + '/'; + let cleanHref = highestResIcon.href; + if (!(highestResIcon.href.startsWith('http:') || highestResIcon.href.startsWith('https:'))) { + cleanHref = cleanHref.startsWith('/') ? cleanHref.substring(1) : cleanHref; + logoUrl = cleanOrigin + cleanHref; + } else { + logoUrl = cleanHref; + } + } else { + logoUrl = undefined; + } + + const pageTitle = document.title || undefined; + + const websiteMetadata = { + topic: 'website-metadata', + payload: { + logoUrl: logoUrl, + pageTitle: pageTitle, + }, + }; + window.ReactNativeWebView.postMessage(JSON.stringify(websiteMetadata)); + true; +}); diff --git a/src/js/worklets/browser.js b/src/js/worklets/browser.js index b01778658d2..e26de9da6b8 100644 --- a/src/js/worklets/browser.js +++ b/src/js/worklets/browser.js @@ -1,7 +1,7 @@ import { useDerivedValue, useSharedValue, scrollTo } from 'react-native-reanimated'; -export function useScrollTab({ animatedRef, initialTabId, tabWidth }) { - const tabValue = useSharedValue(initialTabId); +export function useScrollTab({ animatedRef, initialTabIdx, tabWidth }) { + const tabValue = useSharedValue(initialTabIdx); useDerivedValue(() => { scrollTo(animatedRef, tabValue.value * tabWidth, 0, true); }); diff --git a/src/react_native/freeze.cljs b/src/react_native/freeze.cljs new file mode 100644 index 00000000000..9f0af7f0120 --- /dev/null +++ b/src/react_native/freeze.cljs @@ -0,0 +1,5 @@ +(ns react-native.freeze + (:require ["react-freeze" :refer [Freeze]] + [reagent.core :as reagent])) + +(def view (reagent/adapt-react-class Freeze)) diff --git a/src/react_native/webview.cljs b/src/react_native/webview.cljs index 8e5ba27c488..ac7fc808b8e 100644 --- a/src/react_native/webview.cljs +++ b/src/react_native/webview.cljs @@ -4,3 +4,24 @@ [reagent.core :as reagent])) (def view (reagent/adapt-react-class rn-webview)) + +;; TODO: doesn't exist pretty sure, remove +(defn set-active + [webview-ref active?] + (some-> ^js webview-ref + (.setActive active?))) + +(defn go-back + [webview-ref] + (some-> ^js webview-ref + (.goBack))) + +(defn go-forward + [webview-ref] + (some-> ^js webview-ref + (.goForward))) + +(defn inject-js + [webview-ref js-script] + (some-> ^js webview-ref + (.injectJavaScript js-script))) diff --git a/src/status_im/contexts/browser/constants.cljs b/src/status_im/contexts/browser/constants.cljs new file mode 100644 index 00000000000..231fe6c21eb --- /dev/null +++ b/src/status_im/contexts/browser/constants.cljs @@ -0,0 +1,6 @@ +(ns status-im.contexts.browser.constants + (:require [react-native.core :as rn] + [status-im.contexts.shell.utils :as shell.utils])) + +(def browser-width (-> (rn/get-window) :width)) +(def browser-height (-> (rn/get-window) :height (- (shell.utils/bottom-tabs-container-height) 80))) diff --git a/src/status_im/contexts/browser/core.cljs b/src/status_im/contexts/browser/core.cljs index 6d050225533..4cf84ccf707 100644 --- a/src/status_im/contexts/browser/core.cljs +++ b/src/status_im/contexts/browser/core.cljs @@ -1 +1,7 @@ (ns status-im.contexts.browser.core) + +(defn freeze-tab? + [tab-id focused-tab-id] + (-> #{(dec focused-tab-id) focused-tab-id (inc focused-tab-id)} + (contains? tab-id) + not)) diff --git a/src/status_im/contexts/browser/db.cljs b/src/status_im/contexts/browser/db.cljs new file mode 100644 index 00000000000..b8eb55fdcc3 --- /dev/null +++ b/src/status_im/contexts/browser/db.cljs @@ -0,0 +1,13 @@ +(ns status-im.contexts.browser.db) + +(defn tab-ids + [db] + (get db :browser/tab-ids)) + +(defn tabs-by-id + [db] + (get db :browser/tabs-by-id)) + +(defn tab-id-by-index + [db idx] + (-> db tab-ids (nth idx))) diff --git a/src/status_im/contexts/browser/events.cljs b/src/status_im/contexts/browser/events.cljs new file mode 100644 index 00000000000..20bf526e13c --- /dev/null +++ b/src/status_im/contexts/browser/events.cljs @@ -0,0 +1,54 @@ +(ns status-im.contexts.browser.events + (:require [cljs.pprint :as pprint] + [re-frame.core :as rf] + [status-im.contexts.browser.db :as browser.db] + [status-im.contexts.browser.messages :as messages])) + +(rf/reg-event-fx :browser/set-tab-ref + (fn [{:keys [db]} [tab-id ref]] + {:db (assoc-in db [:browser/tabs-by-id tab-id :ref] ref)})) + +(rf/reg-event-fx :browser/add-tab + (fn [{:keys [db]} [url]] + (let [tab-id (random-uuid)] + {:db (-> db + (assoc-in [:browser/tabs-by-id tab-id] + {:url url + :id tab-id}) + (update :browser/tab-ids conj tab-id))}))) + +(rf/reg-event-fx :browser/focus-tab-by-idx + (fn [{:keys [db]} [tab-idx]] + (let [tab-id (browser.db/tab-id-by-index db tab-idx)] + {:fx [[:dispatch [:browser/focus-tab tab-id]]]}))) + +(rf/reg-event-fx :browser/focus-tab + (fn [{:keys [db]} [tab-id]] + (let [tab-ids (get db :browser/tab-ids) + valid-tab-id? (-> tab-ids set (contains? tab-id))] + (when valid-tab-id? + {:db (assoc db :browser/focused-tab-id tab-id)})))) + +(rf/reg-event-fx :browser/init + (fn [{:keys [db]}] + {:db (-> db + (assoc :browser/tabs-by-id {} + :browser/tab-ids [])) + :fx [[:dispatch [:browser/add-tab "https://app.uniswap.org"]] + [:dispatch [:browser/add-tab "https://app.1inch.io"]] + [:dispatch [:browser/add-tab "https://vibor.it"]] + [:dispatch [:browser/focus-tab 0]]]})) + +(rf/reg-event-fx :browser/on-message + (fn [{:keys [_]} [tab-id js-event]] + (let [event (messages/parse-native-event js-event) + event-topic (messages/event-topic event)] + (pprint/pprint event) + {:fx [(condp = event-topic + "website-metadata" [:dispatch [:browser/on-website-metadata tab-id event]]) + ]}))) + +(rf/reg-event-fx :browser/on-website-metadata + (fn [{:keys [db]} [tab-id event]] + (let [metadata (messages/get-website-metadata event)] + {:db (assoc-in db [:browser/tabs-by-id tab-id :metadata] metadata)}))) diff --git a/src/status_im/contexts/browser/footer.cljs b/src/status_im/contexts/browser/footer.cljs new file mode 100644 index 00000000000..037ee25383b --- /dev/null +++ b/src/status_im/contexts/browser/footer.cljs @@ -0,0 +1,18 @@ +(ns status-im.contexts.browser.footer + (:require [react-native.fast-image :as fast-image] + [react-native.reanimated :as reanimated] + [status-im.contexts.browser.constants :as browser.constants] + [utils.re-frame :as rf])) + +(defn view + [] + (let [tab-id (rf/sub [:browser/focused-tab-id]) + metadata (rf/sub [:browser/tab-metadata tab-id])] + [reanimated/view + {:style {:width browser.constants/browser-width + :align-items :center + :justify-content :center + :height 80}} + [fast-image/fast-image + {:source (:logo-url metadata) + :style {:width 44 :height 44 :border-radius 12}}]])) diff --git a/src/status_im/contexts/browser/hooks.cljs b/src/status_im/contexts/browser/hooks.cljs new file mode 100644 index 00000000000..999a3db04eb --- /dev/null +++ b/src/status_im/contexts/browser/hooks.cljs @@ -0,0 +1,64 @@ +(ns status-im.contexts.browser.hooks + (:require [oops.core :as oops] + [react-native.core :as rn] + [react-native.gesture :as gesture] + [react-native.reanimated :as reanimated] + [react-native.webview :as webview] + [status-im.contexts.browser.constants :as browser.constants] + [status-im.contexts.browser.js-scripts :as js-scripts] + [utils.re-frame :as rf] + [utils.worklets.browser :as worklets.browser])) + +(defn use-deactivate-tab + [tab-id] + (let [focused-tab-id (-> (rf/sub [:browser/focused-tab]) :id) + tab (rf/sub [:browser/tabs-by-id tab-id]) + webview-ref (:ref tab) + active? (= focused-tab-id tab-id)] + (rn/use-effect (fn [] + (if active? + (webview/inject-js webview-ref js-scripts/unfreeze-website) + (webview/inject-js webview-ref js-scripts/freeze-website))) + [focused-tab-id]))) + +(def drag-threshold 100) +(defn- make-drag-gesture + [{:keys [on-swipe]}] + (-> + (gesture/gesture-pan) + (gesture/enabled true) + (gesture/on-end (fn [event] + (let [x-translation (oops/oget event "translationX")] + (when (>= x-translation drag-threshold) + (on-swipe :prev)) + (when (<= x-translation (- drag-threshold)) + (on-swipe :next))))))) + +(defn use-swipe-gesture + [scroll-ref] + (let [focused-tab-idx (rf/sub [:browser/focused-tab-idx]) + tab-idx-animated-value (worklets.browser/use-scroll-tab {:animated-ref scroll-ref + :initial-tab-idx focused-tab-idx + :tab-width + browser.constants/browser-width}) + ;; x-value (reanimated/use-shared-value 0) + can-focus-next? (rf/sub [:browser/can-focus-next?]) + can-focus-prev? (rf/sub [:browser/can-focus-prev?])] + + (rn/use-effect + (fn [] + (reanimated/set-shared-value tab-idx-animated-value focused-tab-idx)) + [focused-tab-idx]) + + (make-drag-gesture + {:on-swipe (fn [direction] + (when (or (and (= :next direction) can-focus-next?) + (and (= :prev direction) + can-focus-prev?)) + (let [new-tab-idx (if (= :next direction) + (inc focused-tab-idx) + (dec focused-tab-idx))] + (reanimated/set-shared-value + tab-idx-animated-value + new-tab-idx) + (rf/dispatch-sync [:browser/focus-tab-by-idx new-tab-idx]))))}))) diff --git a/src/status_im/contexts/browser/js_scripts.cljs b/src/status_im/contexts/browser/js_scripts.cljs new file mode 100644 index 00000000000..fe6523184a6 --- /dev/null +++ b/src/status_im/contexts/browser/js_scripts.cljs @@ -0,0 +1,6 @@ +(ns status-im.contexts.browser.js-scripts + (:require-macros [utils.slurp :refer [slurp]])) + +(def freeze-website (slurp "resources/js/freeze_website.js")) +(def unfreeze-website (slurp "resources/js/unfreeze_website.js")) +(def website-metadata (slurp "resources/js/website_metadata.js")) diff --git a/src/status_im/contexts/browser/messages.cljs b/src/status_im/contexts/browser/messages.cljs new file mode 100644 index 00000000000..1d3ca5cf538 --- /dev/null +++ b/src/status_im/contexts/browser/messages.cljs @@ -0,0 +1,31 @@ +(ns status-im.contexts.browser.messages + (:require [camel-snake-kebab.extras :as cske] + [oops.core :as oops] + [utils.transforms :as transforms])) + +(defn- parse-message-data + [data] + (->> data + transforms/json->clj + (cske/transform-keys transforms/->kebab-case-keyword))) + +(defn parse-native-event + [js-event] + (let [event (->> (oops/oget js-event "nativeEvent") + transforms/js->clj + (cske/transform-keys transforms/->kebab-case-keyword))] + (update event :data parse-message-data))) + +(defn event-topic + [event] + (-> event :data :topic)) + +(defn event-payload + [event] + (-> event :data :payload)) + +(defn get-website-metadata + [event] + (-> event + event-payload + (select-keys [:title :logo-url]))) diff --git a/src/status_im/contexts/browser/subs.cljs b/src/status_im/contexts/browser/subs.cljs new file mode 100644 index 00000000000..38cb93f4d6f --- /dev/null +++ b/src/status_im/contexts/browser/subs.cljs @@ -0,0 +1,62 @@ +(ns status-im.contexts.browser.subs + (:require [re-frame.core :as rf])) + +(rf/reg-sub :browser/tab-ids + (fn [db] + (get db :browser/tab-ids))) + +(rf/reg-sub :browser/tabs-by-id + (fn [db] + (get db :browser/tabs-by-id))) + +(rf/reg-sub :browser/focused-tab-id + (fn [db] + (let [default (-> db :browser/tab-ids first)] + (get db :browser/focused-tab-id default)))) + +(rf/reg-sub :browser/focused-tab + :<- [:browser/tabs-by-id] + :<- [:browser/focused-tab-id] + (fn [[tabs-by-id focused-tab-id]] + (get tabs-by-id focused-tab-id))) + +(rf/reg-sub :browser/focused-tab-idx + :<- [:browser/focused-tab-id] + :<- [:browser/tab-ids] + (fn [[focused-id tab-ids]] + (.indexOf tab-ids focused-id))) + +(rf/reg-sub :browser/can-focus-next? + :<- [:browser/focused-tab-idx] + :<- [:browser/tab-ids] + (fn [[focused-idx tab-ids]] + (not= (inc focused-idx) (count tab-ids)))) + +(rf/reg-sub :browser/can-focus-prev? + :<- [:browser/focused-tab-idx] + (fn [focused-idx] + (not (zero? focused-idx)))) + +(rf/reg-sub :browser/tab-by-id + :<- [:browser/tabs-by-id] + (fn [tabs-by-id [_ tab-id]] + (get tabs-by-id tab-id))) + +(rf/reg-sub :browser/tabs + :<- [:browser/tab-ids] + :<- [:browser/tabs-by-id] + (fn [[tab-ids tabs-by-id]] + (->> tab-ids + (map (partial get tabs-by-id))))) + +(rf/reg-sub :browser/tab-url + (fn [[_ tab-id]] + [(rf/subscribe [:browser/tab-by-id tab-id])]) + (fn [[tab-data]] + (:url tab-data))) + +(rf/reg-sub :browser/tab-metadata + (fn [[_ tab-id]] + [(rf/subscribe [:browser/tab-by-id tab-id])]) + (fn [[tab-data]] + (:metadata tab-data))) diff --git a/src/status_im/contexts/browser/tab/events.cljs b/src/status_im/contexts/browser/tab/events.cljs deleted file mode 100644 index cc2a70a5ecf..00000000000 --- a/src/status_im/contexts/browser/tab/events.cljs +++ /dev/null @@ -1,13 +0,0 @@ -(ns status-im.contexts.browser.tab.events - (:require [re-frame.core :as rf])) - -(rf/reg-event-fx :browser.tab/set-tab-ref - (fn [{:keys [db]} [tab-id ref]] - {:db (assoc-in db [:browser/tabs-by-id tab-id :ref] ref)})) - -(rf/reg-event-fx :browser.tab/add - (fn [{:keys [db]} [url]] - (let [tab-id (-> db :browser/tab-ids last inc)] - {:db (-> db - (assoc-in [:browser/tabs-by-id tab-id] {:url url}) - (update :browser/tab-ids conj tab-id))}))) diff --git a/src/status_im/contexts/browser/tab/events.cljs~ b/src/status_im/contexts/browser/tab/events.cljs~ deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/status_im/contexts/browser/tab/subs.cljs b/src/status_im/contexts/browser/tab/subs.cljs deleted file mode 100644 index 3728f6f9bd9..00000000000 --- a/src/status_im/contexts/browser/tab/subs.cljs +++ /dev/null @@ -1,28 +0,0 @@ -(ns status-im.contexts.browser.tab.subs - (:require [re-frame.core :as rf])) - -(rf/reg-sub :browser/tab-ids - (fn [db] - (get db :browser/tab-ids))) - -(rf/reg-sub :browser/tabs-by-id - (fn [db] - (get db :browser/tabs-by-id))) - -(rf/reg-sub :browser/tab-by-id - :<- [:browser/tabs-by-id] - (fn [tabs-by-id [_ tab-id]] - (get tabs-by-id tab-id))) - -(rf/reg-sub :browser/tabs - :<- [:browser/tab-ids] - :<- [:browser/tabs-by-id] - (fn [[tab-ids tabs-by-id]] - (->> tab-ids - (map (partial get tabs-by-id))))) - -(rf/reg-sub :browser/tab-url - (fn [[_ tab-id]] - [(rf/subscribe [:browser/tab-by-id tab-id])]) - (fn [[tab]] - (:url tab))) diff --git a/src/status_im/contexts/browser/tab/subs.cljs~ b/src/status_im/contexts/browser/tab/subs.cljs~ deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/status_im/contexts/browser/tab/view.cljs b/src/status_im/contexts/browser/tab/view.cljs index ec9691d5680..4c51e3f3d5a 100644 --- a/src/status_im/contexts/browser/tab/view.cljs +++ b/src/status_im/contexts/browser/tab/view.cljs @@ -1,17 +1,22 @@ (ns status-im.contexts.browser.tab.view - (:require [react-native.core :as rn] - [react-native.webview :as webview] + (:require [react-native.webview :as webview] + [status-im.contexts.browser.js-scripts :as js-scripts] [utils.re-frame :as rf])) (defn view [{:keys [tab-id url]}] [webview/view - {:ref #(rf/dispatch [:browser.tab/set-tab-ref tab-id %]) - :source {:uri url} - :java-script-enabled true - :bounces false - :local-storage-enabled true - :set-support-multiple-windows false + {:ref #(rf/dispatch [:browser/set-tab-ref tab-id %]) + :source {:uri url} + :java-script-enabled true + :bounces false + :cache-enabled true + :local-storage-enabled true + :set-support-multiple-windows false + :injected-java-script js-scripts/website-metadata + :on-message #(rf/dispatch [:browser/on-message tab-id %]) + :on-error #(println :on-error %) + :allowsBackForwardNavigationGestures true ;; iOS only ;;:render-error web-view-error ;;:on-navigation-state-change #() ;; :on-message #(re-frame/dispatch @@ -22,12 +27,11 @@ ;; :on-error #(re-frame/dispatch [:browser/error-occured]) ;; :injected-java-script-before-content-loaded (js-res/ethereum-provider (str network-id)) ;; https://github.com/status-im/status-mobile/issues/17854 - :allows-inline-media-playback true}]) + :allows-inline-media-playback true}]) (comment - (rf/sub [:browser/tabs-by-id]) - - (rf/dispatch [:browser.tab/add "https://app.1inch.io"]) - ;; => nil + (rf/sub [:browser/can-focus-next?]) + (rf/dispatch [:browser/add-tab "https://app.1inch.io"]) + (rf/dispatch [:browser/add-tab "https://vibor.it"]) ) diff --git a/src/status_im/contexts/browser/tab/view.cljs~ b/src/status_im/contexts/browser/tab/view.cljs~ deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/status_im/contexts/browser/view.cljs b/src/status_im/contexts/browser/view.cljs index e70b2e98380..8760e958feb 100644 --- a/src/status_im/contexts/browser/view.cljs +++ b/src/status_im/contexts/browser/view.cljs @@ -1,70 +1,44 @@ (ns status-im.contexts.browser.view - (:require [oops.core :as oops] - [react-native.core :as rn] + (:require [react-native.core :as rn] + [react-native.freeze :as freeze] [react-native.gesture :as gesture] [react-native.reanimated :as reanimated] + [status-im.contexts.browser.constants :as browser.constants] + [status-im.contexts.browser.footer :as footer] + [status-im.contexts.browser.hooks :as hooks] [status-im.contexts.browser.tab.view :as tab] - [utils.re-frame :as rf] - [utils.worklets.browser :as worklets.browser])) - -(def drag-threshold 100) - -(defn make-drag-gesture - [tab-count tab-animated-value] - (-> - (gesture/gesture-pan) - (gesture/enabled true) - (gesture/on-end (fn [event] - (let [x-translation (oops/oget event "translationX") - current-tab (reanimated/get-shared-value tab-animated-value) - to-previous? (and (>= x-translation drag-threshold) - (not (zero? current-tab))) - to-next? (and (<= x-translation (- drag-threshold)) - (not= (inc current-tab) tab-count))] - (when to-previous? - (reanimated/set-shared-value tab-animated-value (dec current-tab))) - (when to-next? - (reanimated/set-shared-value tab-animated-value (inc current-tab)))))))) - -(def screen-width (-> (rn/get-window) :width)) -(def screen-height (-> (rn/get-window) :height)) + [utils.re-frame :as rf])) (defn render-tab [tab-id] - (let [url (rf/sub [:browser/tab-url tab-id])] + (let [url (rf/sub [:browser/tab-url tab-id]) + focused-tab-id (-> (rf/sub [:browser/focused-tab]) :id) + ;;freeze-tab? (browser/freeze-tab? tab-id focused-tab-id) + freeze-tab? (not= tab-id focused-tab-id)] + (hooks/use-deactivate-tab tab-id) [rn/view - {:style {:width screen-width - :height screen-height}} - [tab/view - {:tab-id tab-id - :url url}]])) - -(defn browser-bar - [] - [reanimated/view - {:style {:width screen-width - :height 80}}]) + {:style {:width browser.constants/browser-width + :height browser.constants/browser-height}} + [freeze/view {:freeze freeze-tab?} + [tab/view + {:tab-id tab-id + :url url}]]])) (defn view [] - (let [tab-ids (rf/sub [:browser/tab-ids]) - x-value (reanimated/use-shared-value 0) - animated-ref (reanimated/use-animated-ref) - tab-animated-value (worklets.browser/use-scroll-tab {:animated-ref animated-ref - :initial-tab-id (first tab-ids) - :tab-width screen-width}) - drag-gesture (make-drag-gesture (count tab-ids) tab-animated-value)] + (let [tab-ids (rf/sub [:browser/tab-ids]) + scroll-ref (reanimated/use-animated-ref) + gesture (hooks/use-swipe-gesture scroll-ref)] [:<> [reanimated/scroll-view - {:horizontal true - :ref animated-ref - :scroll-enabled false - :animated-props {:scroll-view-offset x-value}} + {:horizontal true + :ref scroll-ref + :shows-horizontal-scroll-indicator false + :shows-vertical-scroll-indicator false + :scroll-enabled false} (map-indexed (fn [idx tab-id] ^{:key (str idx "-" tab-id)} [render-tab tab-id]) tab-ids)] - [gesture/gesture-detector {:gesture drag-gesture} - [browser-bar]]])) - -;; TODO: add freeze + [gesture/gesture-detector {:gesture gesture} + [footer/view]]])) diff --git a/src/status_im/contexts/browser/view.cljs~ b/src/status_im/contexts/browser/view.cljs~ deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/status_im/contexts/browser/webview.cljs b/src/status_im/contexts/browser/webview.cljs new file mode 100644 index 00000000000..cb46e57b54d --- /dev/null +++ b/src/status_im/contexts/browser/webview.cljs @@ -0,0 +1 @@ +(ns status-im.contexts.browser.webview) diff --git a/src/status_im/contexts/profile/login/events.cljs b/src/status_im/contexts/profile/login/events.cljs index 6802e49fca7..606baa247c6 100644 --- a/src/status_im/contexts/profile/login/events.cljs +++ b/src/status_im/contexts/profile/login/events.cljs @@ -131,6 +131,7 @@ (fn [{:keys [db]}] (let [{:keys [preview-privacy?]} (:profile/profile db)] {:fx [[:browser/initialize-browser] + [:dispatch [:browser/init]] [:logging/initialize-web3-client-version] [:group-chats/get-group-chat-invitations] [:profile.settings/blank-preview-flag-changed preview-privacy?] diff --git a/src/status_im/db.cljs b/src/status_im/db.cljs index 136b250ead6..ece01cad9c2 100644 --- a/src/status_im/db.cljs +++ b/src/status_im/db.cljs @@ -10,8 +10,8 @@ (def app-db {:activity-center {:filter {:status (:filter-status activity-center/defaults) :type (:filter-type activity-center/defaults)}} - :browser/tab-ids [0] - :browser/tabs-by-id {0 {:url "https://app.uniswap.org"}} + :browser/tab-ids [] + :browser/tabs-by-id {} :contacts/contacts {} :pairing/installations {} :group/selected-contacts #{} diff --git a/src/status_im/events.cljs b/src/status_im/events.cljs index 042c556ecb2..2ed4b7fc349 100644 --- a/src/status_im/events.cljs +++ b/src/status_im/events.cljs @@ -17,7 +17,7 @@ status-im.common.theme.events [status-im.common.toasts.events] status-im.common.universal-links - status-im.contexts.browser.tab.events + status-im.contexts.browser.events status-im.contexts.chat.contacts.events status-im.contexts.chat.events [status-im.contexts.chat.home.add-new-contact.events] diff --git a/src/status_im/subs/root.cljs b/src/status_im/subs/root.cljs index 986217dc9d5..be1abc74972 100644 --- a/src/status_im/subs/root.cljs +++ b/src/status_im/subs/root.cljs @@ -1,7 +1,7 @@ (ns status-im.subs.root (:require [re-frame.core :as re-frame] - status-im.contexts.browser.tab.subs + status-im.contexts.browser.subs status-im.subs.activity-center status-im.subs.alert-banner status-im.subs.biometrics diff --git a/src/utils/slurp.clj b/src/utils/slurp.clj new file mode 100644 index 00000000000..66a06b192e7 --- /dev/null +++ b/src/utils/slurp.clj @@ -0,0 +1,6 @@ +(ns utils.slurp + (:refer-clojure :exclude [slurp])) + +(defmacro slurp + [file] + (clojure.core/slurp file)) diff --git a/yarn.lock b/yarn.lock index 714506024fa..4fc14855f07 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9807,6 +9807,11 @@ react-dom@18.0.0: loose-envify "^1.1.0" scheduler "^0.21.0" +react-freeze@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/react-freeze/-/react-freeze-1.0.4.tgz#cbbea2762b0368b05cbe407ddc9d518c57c6f3ad" + integrity sha512-r4F0Sec0BLxWicc7HEyo2x3/2icUTrRmDjaaRyzzn+7aDyFZliszMDOgLVwSnQnYENOlL1o569Ze2HZefk8clA== + react-is@^16.12.0, react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" @@ -10055,11 +10060,6 @@ react-native-svg@13.10.0: css-select "^5.1.0" css-tree "^1.1.3" -react-native-system-navigation-bar@^2.6.4: - version "2.6.4" - resolved "https://registry.yarnpkg.com/react-native-system-navigation-bar/-/react-native-system-navigation-bar-2.6.4.tgz#34edee7051dea01531ff2be95dc14f9fa8a540b7" - integrity sha512-4pysgADW53PiuHv+2glzNLJnHSxqDszZvLoitLFI5os4D+gCDfxmR36VSET4EnXkzSf8X9mbeFkHYDypDHJyZA== - react-native-url-polyfill@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/react-native-url-polyfill/-/react-native-url-polyfill-2.0.0.tgz#db714520a2985cff1d50ab2e66279b9f91ffd589" From 2ccf61464cafa5e3f11b5174e956517347e9547c Mon Sep 17 00:00:00 2001 From: Cristian Lungu Date: Mon, 12 May 2025 17:53:17 +0300 Subject: [PATCH 03/14] feat: scroll animation --- src/js/worklets/browser.js | 7 +- src/status_im/contexts/browser/core.cljs | 7 +- src/status_im/contexts/browser/events.cljs | 3 +- src/status_im/contexts/browser/footer.cljs | 38 +++++++-- src/status_im/contexts/browser/hooks.cljs | 84 ++++++++++++-------- src/status_im/contexts/browser/messages.cljs | 7 +- src/status_im/contexts/browser/subs.cljs | 8 ++ src/status_im/contexts/browser/tab/view.cljs | 8 +- src/status_im/contexts/browser/view.cljs | 13 +-- src/utils/worklets/browser.cljs | 2 +- 10 files changed, 119 insertions(+), 58 deletions(-) diff --git a/src/js/worklets/browser.js b/src/js/worklets/browser.js index e26de9da6b8..cbc9f83913b 100644 --- a/src/js/worklets/browser.js +++ b/src/js/worklets/browser.js @@ -1,10 +1,7 @@ import { useDerivedValue, useSharedValue, scrollTo } from 'react-native-reanimated'; -export function useScrollTab({ animatedRef, initialTabIdx, tabWidth }) { - const tabValue = useSharedValue(initialTabIdx); +export function useScrollTab({ animatedRef, xTranslation }) { useDerivedValue(() => { - scrollTo(animatedRef, tabValue.value * tabWidth, 0, true); + scrollTo(animatedRef, xTranslation.value, 0, true); }); - - return tabValue; } diff --git a/src/status_im/contexts/browser/core.cljs b/src/status_im/contexts/browser/core.cljs index 4cf84ccf707..79363d27dcd 100644 --- a/src/status_im/contexts/browser/core.cljs +++ b/src/status_im/contexts/browser/core.cljs @@ -1,7 +1,12 @@ -(ns status-im.contexts.browser.core) +(ns status-im.contexts.browser.core + (:require [status-im.contexts.browser.constants :as browser.constants])) (defn freeze-tab? [tab-id focused-tab-id] (-> #{(dec focused-tab-id) focused-tab-id (inc focused-tab-id)} (contains? tab-id) not)) + +(defn tab-position + [tab-idx] + (* tab-idx browser.constants/browser-width)) diff --git a/src/status_im/contexts/browser/events.cljs b/src/status_im/contexts/browser/events.cljs index 20bf526e13c..ff55deb46d3 100644 --- a/src/status_im/contexts/browser/events.cljs +++ b/src/status_im/contexts/browser/events.cljs @@ -43,7 +43,6 @@ (fn [{:keys [_]} [tab-id js-event]] (let [event (messages/parse-native-event js-event) event-topic (messages/event-topic event)] - (pprint/pprint event) {:fx [(condp = event-topic "website-metadata" [:dispatch [:browser/on-website-metadata tab-id event]]) ]}))) @@ -51,4 +50,4 @@ (rf/reg-event-fx :browser/on-website-metadata (fn [{:keys [db]} [tab-id event]] (let [metadata (messages/get-website-metadata event)] - {:db (assoc-in db [:browser/tabs-by-id tab-id :metadata] metadata)}))) + {:db (update-in db [:browser/tabs-by-id tab-id :metadata] merge metadata)}))) diff --git a/src/status_im/contexts/browser/footer.cljs b/src/status_im/contexts/browser/footer.cljs index 037ee25383b..6bcbd57fa06 100644 --- a/src/status_im/contexts/browser/footer.cljs +++ b/src/status_im/contexts/browser/footer.cljs @@ -1,5 +1,8 @@ (ns status-im.contexts.browser.footer - (:require [react-native.fast-image :as fast-image] + (:require [quo.core :as quo] + [quo.foundations.colors :as colors] + [react-native.core :as rn] + [react-native.fast-image :as fast-image] [react-native.reanimated :as reanimated] [status-im.contexts.browser.constants :as browser.constants] [utils.re-frame :as rf])) @@ -7,12 +10,35 @@ (defn view [] (let [tab-id (rf/sub [:browser/focused-tab-id]) + title (rf/sub [:browser/tab-title tab-id]) metadata (rf/sub [:browser/tab-metadata tab-id])] [reanimated/view - {:style {:width browser.constants/browser-width - :align-items :center - :justify-content :center - :height 80}} + {:style {:width browser.constants/browser-width + :background-color colors/neutral-100 + :align-items :center + :justify-content :space-between + :flex-direction :row + :padding-horizontal 20 + :height 80}} [fast-image/fast-image {:source (:logo-url metadata) - :style {:width 44 :height 44 :border-radius 12}}]])) + :style {:width 44 :height 44 :border-radius 12}}] + [rn/view + {:style {:padding 8 + :background-color colors/neutral-30 + :border-radius 12 + :height 44 + :margin-horizontal 12 + :padding-horizontal 20 + :flex 1 + :align-items :center + :justify-content :center}} + [quo/text + {:size :paragraph-1 + :number-of-lines 1 + :weight :bold + :style {:color colors/neutral-100}} title]] + [rn/view + {:style {:width 44 + :height 44 + :border-radius 12}}]])) diff --git a/src/status_im/contexts/browser/hooks.cljs b/src/status_im/contexts/browser/hooks.cljs index 999a3db04eb..443ecc09b00 100644 --- a/src/status_im/contexts/browser/hooks.cljs +++ b/src/status_im/contexts/browser/hooks.cljs @@ -4,7 +4,7 @@ [react-native.gesture :as gesture] [react-native.reanimated :as reanimated] [react-native.webview :as webview] - [status-im.contexts.browser.constants :as browser.constants] + [status-im.contexts.browser.core :as browser] [status-im.contexts.browser.js-scripts :as js-scripts] [utils.re-frame :as rf] [utils.worklets.browser :as worklets.browser])) @@ -21,44 +21,58 @@ (webview/inject-js webview-ref js-scripts/freeze-website))) [focused-tab-id]))) +(defn animate-with-spring + [animation-val to-val] + (reanimated/animate-shared-value-with-spring animation-val + to-val + {:mass 1 + :damping 100 + :stiffness 400})) + (def drag-threshold 100) (defn- make-drag-gesture - [{:keys [on-swipe]}] - (-> - (gesture/gesture-pan) - (gesture/enabled true) - (gesture/on-end (fn [event] - (let [x-translation (oops/oget event "translationX")] - (when (>= x-translation drag-threshold) - (on-swipe :prev)) - (when (<= x-translation (- drag-threshold)) - (on-swipe :next))))))) + [{:keys [can-move-left? can-move-right? x-translation-value focused-tab-idx on-tab-change]}] + (let [initial-x-val (atom 0)] + (-> + (gesture/gesture-pan) + (gesture/enabled true) + (gesture/on-begin (fn [_] + (reset! initial-x-val (browser/tab-position focused-tab-idx)))) + (gesture/on-update (fn [event] + (let [x-translation (oops/oget event "translationX") + new-val (- @initial-x-val x-translation)] + (reanimated/set-shared-value x-translation-value new-val)))) + (gesture/on-end + (fn [event] + (let [x-translation (oops/oget event "translationX") + drag-right? (>= x-translation drag-threshold) + drag-left? (<= x-translation (- drag-threshold))] + (cond + (and drag-right? can-move-left?) + (let [tab-idx (dec focused-tab-idx)] + (animate-with-spring x-translation-value (browser/tab-position tab-idx)) + (on-tab-change tab-idx)) + + (and drag-left? can-move-right?) + (let [tab-idx (inc focused-tab-idx)] + (animate-with-spring x-translation-value (browser/tab-position tab-idx)) + (on-tab-change tab-idx)) + + :else + (animate-with-spring x-translation-value @initial-x-val)))))))) (defn use-swipe-gesture [scroll-ref] - (let [focused-tab-idx (rf/sub [:browser/focused-tab-idx]) - tab-idx-animated-value (worklets.browser/use-scroll-tab {:animated-ref scroll-ref - :initial-tab-idx focused-tab-idx - :tab-width - browser.constants/browser-width}) - ;; x-value (reanimated/use-shared-value 0) - can-focus-next? (rf/sub [:browser/can-focus-next?]) - can-focus-prev? (rf/sub [:browser/can-focus-prev?])] - - (rn/use-effect - (fn [] - (reanimated/set-shared-value tab-idx-animated-value focused-tab-idx)) - [focused-tab-idx]) + (let [focused-tab-idx (rf/sub [:browser/focused-tab-idx]) + x-translation-value (reanimated/use-shared-value (browser/tab-position focused-tab-idx)) + can-focus-next? (rf/sub [:browser/can-focus-next?]) + can-focus-prev? (rf/sub [:browser/can-focus-prev?])] + (worklets.browser/use-scroll-tab {:animated-ref scroll-ref + :x-translation x-translation-value}) (make-drag-gesture - {:on-swipe (fn [direction] - (when (or (and (= :next direction) can-focus-next?) - (and (= :prev direction) - can-focus-prev?)) - (let [new-tab-idx (if (= :next direction) - (inc focused-tab-idx) - (dec focused-tab-idx))] - (reanimated/set-shared-value - tab-idx-animated-value - new-tab-idx) - (rf/dispatch-sync [:browser/focus-tab-by-idx new-tab-idx]))))}))) + {:x-translation-value x-translation-value + :can-move-left? can-focus-prev? + :can-move-right? can-focus-next? + :focused-tab-idx focused-tab-idx + :on-tab-change #(rf/dispatch-sync [:browser/focus-tab-by-idx %])}))) diff --git a/src/status_im/contexts/browser/messages.cljs b/src/status_im/contexts/browser/messages.cljs index 1d3ca5cf538..080eb8f23c5 100644 --- a/src/status_im/contexts/browser/messages.cljs +++ b/src/status_im/contexts/browser/messages.cljs @@ -24,8 +24,13 @@ [event] (-> event :data :payload)) +(defn get-webview-event-data + [event] + (-> event + (select-keys [:url :can-go-forward :can-go-back]))) + (defn get-website-metadata [event] (-> event event-payload - (select-keys [:title :logo-url]))) + (select-keys [:page-title :logo-url]))) diff --git a/src/status_im/contexts/browser/subs.cljs b/src/status_im/contexts/browser/subs.cljs index 38cb93f4d6f..f5ace678e25 100644 --- a/src/status_im/contexts/browser/subs.cljs +++ b/src/status_im/contexts/browser/subs.cljs @@ -60,3 +60,11 @@ [(rf/subscribe [:browser/tab-by-id tab-id])]) (fn [[tab-data]] (:metadata tab-data))) + +(rf/reg-sub :browser/tab-title + (fn [[_ tab-id]] + [(rf/subscribe [:browser/tab-by-id tab-id])]) + (fn [[tab-data]] + (let [title (-> tab-data :metadata :page-title) + url (-> tab-data :url)] + (or title url)))) diff --git a/src/status_im/contexts/browser/tab/view.cljs b/src/status_im/contexts/browser/tab/view.cljs index 4c51e3f3d5a..4744c0eae5f 100644 --- a/src/status_im/contexts/browser/tab/view.cljs +++ b/src/status_im/contexts/browser/tab/view.cljs @@ -30,7 +30,13 @@ :allows-inline-media-playback true}]) (comment - (rf/sub [:browser/can-focus-next?]) + (rf/sub [:browser/tab-ids]) + ;; => [#uuid "b780d00a-7740-43e4-a5db-0ed3cc2b5ab7" + ;; #uuid "c4401b67-35a9-474e-a2c9-f4017017281d" + ;; #uuid "ceaa3002-2b59-4e8f-847c-5ecce16be809"] + + + (rf/sub [:browser/tab-by-id (uuid "c4401b67-35a9-474e-a2c9-f4017017281d")]) (rf/dispatch [:browser/add-tab "https://app.1inch.io"]) (rf/dispatch [:browser/add-tab "https://vibor.it"]) diff --git a/src/status_im/contexts/browser/view.cljs b/src/status_im/contexts/browser/view.cljs index 8760e958feb..e2dc0a3205d 100644 --- a/src/status_im/contexts/browser/view.cljs +++ b/src/status_im/contexts/browser/view.cljs @@ -4,17 +4,17 @@ [react-native.gesture :as gesture] [react-native.reanimated :as reanimated] [status-im.contexts.browser.constants :as browser.constants] + [status-im.contexts.browser.core :as browser] [status-im.contexts.browser.footer :as footer] [status-im.contexts.browser.hooks :as hooks] [status-im.contexts.browser.tab.view :as tab] [utils.re-frame :as rf])) (defn render-tab - [tab-id] - (let [url (rf/sub [:browser/tab-url tab-id]) - focused-tab-id (-> (rf/sub [:browser/focused-tab]) :id) - ;;freeze-tab? (browser/freeze-tab? tab-id focused-tab-id) - freeze-tab? (not= tab-id focused-tab-id)] + [tab-id idx] + (let [url (rf/sub [:browser/tab-url tab-id]) + focused-tab-idx (rf/sub [:browser/focused-tab-idx]) + freeze-tab? (browser/freeze-tab? idx focused-tab-idx)] (hooks/use-deactivate-tab tab-id) [rn/view {:style {:width browser.constants/browser-width @@ -38,7 +38,8 @@ :scroll-enabled false} (map-indexed (fn [idx tab-id] ^{:key (str idx "-" tab-id)} - [render-tab tab-id]) + [render-tab tab-id idx]) tab-ids)] + [gesture/gesture-detector {:gesture gesture} [footer/view]]])) diff --git a/src/utils/worklets/browser.cljs b/src/utils/worklets/browser.cljs index ef16bc23458..2444d36d60d 100644 --- a/src/utils/worklets/browser.cljs +++ b/src/utils/worklets/browser.cljs @@ -16,6 +16,6 @@ (throw (js/Error. (ex-info "Non-existing worklet!" {:name worklet-name - :file "../src/js/worklets/communities.js"}))))) + :file "../src/js/worklets/browser.js"}))))) (def use-scroll-tab (worklet-wrapper "useScrollTab")) From d919f56c7f4b5306b502191508e165aebcf72ed3 Mon Sep 17 00:00:00 2001 From: Cristian Lungu Date: Tue, 13 May 2025 18:40:18 +0300 Subject: [PATCH 04/14] feat: added rpc handlers --- resources/js/web3_provider.js | 98 +++++++++++++++++++ resources/js/website_metadata.js | 1 + src/react_native/webview.cljs | 18 ++-- src/status_im/contexts/browser/constants.cljs | 5 +- src/status_im/contexts/browser/db.cljs | 33 ++++++- src/status_im/contexts/browser/effects.cljs | 11 +++ src/status_im/contexts/browser/events.cljs | 33 +++++-- .../contexts/browser/js_scripts.cljs | 15 ++- src/status_im/contexts/browser/messages.cljs | 2 +- .../contexts/browser/rpc_errors.cljs | 14 +++ .../contexts/browser/rpc_events.cljs | 91 +++++++++++++++++ .../contexts/browser/rpc_params.cljs | 6 ++ src/status_im/contexts/browser/tab/view.cljs | 52 +++++++--- src/status_im/contexts/browser/view.cljs | 3 +- .../contexts/browser/web3_provider.js | 97 ++++++++++++++++++ src/utils/hex.cljs | 10 ++ 16 files changed, 453 insertions(+), 36 deletions(-) create mode 100644 resources/js/web3_provider.js create mode 100644 src/status_im/contexts/browser/effects.cljs create mode 100644 src/status_im/contexts/browser/rpc_errors.cljs create mode 100644 src/status_im/contexts/browser/rpc_events.cljs create mode 100644 src/status_im/contexts/browser/rpc_params.cljs create mode 100644 src/status_im/contexts/browser/web3_provider.js diff --git a/resources/js/web3_provider.js b/resources/js/web3_provider.js new file mode 100644 index 00000000000..890b8ddc4a4 --- /dev/null +++ b/resources/js/web3_provider.js @@ -0,0 +1,98 @@ +console.log('TESTING INJECTION test'); + +(function () { + // Internal storage for pending calls + const _callbacks = {}; + let _nextId = 1; + + // Minimal EventEmitter for provider.on(...) + const _listeners = {}; + + // 1️⃣ Define our fake provider + const fakeProvider = { + // EIP-1193 request() + request({ method, params }) { + return new Promise((resolve, reject) => { + const id = _nextId++; + _callbacks[id] = { resolve, reject }; + // send to RN side + ReactNativeWebView.postMessage(JSON.stringify({ topic: 'rpc', id, method, params }), '*'); + }); + }, + + // EIP-1193 event subscription + on(eventName, handler) { + _listeners[eventName] = _listeners[eventName] || []; + _listeners[eventName].push(handler); + }, + }; + + ReactNativeWebView.onMessage = function (ev) { + console.log('MESSAGE INCOMING'); + ReactNativeWebView.postMessage(JSON.stringify({ topic: 'fuck-you' }), '*'); + let msg; + try { + msg = JSON.parse(ev); + } catch (e) { + alert('ERROR parsing:', e, ev); + return; + } + + alert('message', msg); + // RPC response + if (msg.type === 'rpcResponse' && _callbacks[msg.id]) { + const { resolve, reject } = _callbacks[msg.id]; + delete _callbacks[msg.id]; + msg.error ? reject(msg.error) : resolve(msg.result); + } + + // Emitted event from RN + else if (msg.type === 'emitEvent' && _listeners[msg.event]) { + _listeners[msg.event].forEach((fn) => fn(msg.data)); + } + }; + + // 2️⃣ Listen for RN → WebView messages + // window.addEventListener('message', function (ev) { + // console.log('onMessage RECEIVED'); + + // ReactNativeWebView.postMessage(JSON.stringify({ topic: 'fuck-you' }), '*'); + // let msg; + // try { + // msg = JSON.parse(ev); + // } catch (e) { + // alert('ERROR parsing:', e, ev); + // return; + // } + + // alert('message', msg); + // // RPC response + // if (msg.type === 'rpcResponse' && _callbacks[msg.id]) { + // const { resolve, reject } = _callbacks[msg.id]; + // delete _callbacks[msg.id]; + // msg.error ? reject(msg.error) : resolve(msg.result); + // } + + // // Emitted event from RN + // else if (msg.type === 'emitEvent' && _listeners[msg.event]) { + // _listeners[msg.event].forEach((fn) => fn(msg.data)); + // } + // }); + + // 3️⃣ Expose to the page + window.__RN_WALLET_PROVIDER__ = fakeProvider; + window.ethereum = fakeProvider; // if Dapp expects `window.ethereum` + + // 4️⃣ (Optional) announce via EIP-6963 discovery + const info = { + uuid: window.__WALLET_UUID__, + name: window.__WALLET_NAME__, + icon: window.__WALLET_ICON__, + rdns: window.__WALLET_RDNS__, + }; + const detail = { info, provider: fakeProvider }; + window.dispatchEvent(new CustomEvent('eip6963:announceProvider', { detail })); + window.addEventListener('eip6963:requestProvider', () => + window.dispatchEvent(new CustomEvent('eip6963:announceProvider', { detail })), + ); +})(); diff --git a/resources/js/website_metadata.js b/resources/js/website_metadata.js index ebc06dc73dc..67ff9958198 100644 --- a/resources/js/website_metadata.js +++ b/resources/js/website_metadata.js @@ -46,6 +46,7 @@ requestAnimationFrame(() => { const websiteMetadata = { topic: 'website-metadata', payload: { + originUrl: window.location.origin, logoUrl: logoUrl, pageTitle: pageTitle, }, diff --git a/src/react_native/webview.cljs b/src/react_native/webview.cljs index ac7fc808b8e..60dd1fd3689 100644 --- a/src/react_native/webview.cljs +++ b/src/react_native/webview.cljs @@ -1,27 +1,33 @@ (ns react-native.webview (:require ["react-native-webview" :default rn-webview] - [reagent.core :as reagent])) + [reagent.core :as reagent] + [utils.transforms :as transforms])) (def view (reagent/adapt-react-class rn-webview)) ;; TODO: doesn't exist pretty sure, remove (defn set-active - [webview-ref active?] + [^js webview-ref active?] (some-> ^js webview-ref (.setActive active?))) (defn go-back - [webview-ref] + [^js webview-ref] (some-> ^js webview-ref (.goBack))) (defn go-forward - [webview-ref] + [^js webview-ref] (some-> ^js webview-ref (.goForward))) (defn inject-js - [webview-ref js-script] - (some-> ^js webview-ref + [^js webview-ref js-script] + (some-> webview-ref (.injectJavaScript js-script))) + +(defn post-message + [^js webview-ref message] + (some-> webview-ref + (.postMessage (-> message transforms/clj->json)))) diff --git a/src/status_im/contexts/browser/constants.cljs b/src/status_im/contexts/browser/constants.cljs index 231fe6c21eb..5c6537ed311 100644 --- a/src/status_im/contexts/browser/constants.cljs +++ b/src/status_im/contexts/browser/constants.cljs @@ -1,6 +1,5 @@ (ns status-im.contexts.browser.constants - (:require [react-native.core :as rn] - [status-im.contexts.shell.utils :as shell.utils])) + (:require [react-native.core :as rn])) (def browser-width (-> (rn/get-window) :width)) -(def browser-height (-> (rn/get-window) :height (- (shell.utils/bottom-tabs-container-height) 80))) +(def browser-height "100%") diff --git a/src/status_im/contexts/browser/db.cljs b/src/status_im/contexts/browser/db.cljs index b8eb55fdcc3..31eab848e2e 100644 --- a/src/status_im/contexts/browser/db.cljs +++ b/src/status_im/contexts/browser/db.cljs @@ -1,13 +1,38 @@ (ns status-im.contexts.browser.db) -(defn tab-ids +(defn get-tab-ids [db] (get db :browser/tab-ids)) -(defn tabs-by-id +(defn get-tabs-by-id [db] (get db :browser/tabs-by-id)) -(defn tab-id-by-index +(defn get-dapps + [db] + (get db :browser/dapps)) + +(defn get-dapp-by-id + [db dapp-id] + (-> db get-dapps (get dapp-id))) + +(defn get-tab-id-by-index [db idx] - (-> db tab-ids (nth idx))) + (-> db get-tab-ids (nth idx))) + +(defn get-tab + [db tab-id] + (-> db get-tabs-by-id (get tab-id))) + +(defn get-tab-url + [db tab-id] + (-> db (get-tab tab-id) :url)) + +(defn get-dapp-id + [db tab-id] + (-> db (get-tab tab-id) :dapp-id)) + +(defn get-tab-dapp + [db tab-id] + (->> (get-dapp-id db tab-id) + (get-dapp-by-id db))) diff --git a/src/status_im/contexts/browser/effects.cljs b/src/status_im/contexts/browser/effects.cljs new file mode 100644 index 00000000000..27ca70e1e97 --- /dev/null +++ b/src/status_im/contexts/browser/effects.cljs @@ -0,0 +1,11 @@ +(ns status-im.contexts.browser.effects + (:require [cljs.pprint :as pprint] + [re-frame.core :as rf] + [react-native.webview :as webview])) + +(rf/reg-fx :fx.browser/send-message + (fn [[webview-ref message]] + (println :send-response) + (pprint/pprint message) + (webview/post-message webview-ref message))) + diff --git a/src/status_im/contexts/browser/events.cljs b/src/status_im/contexts/browser/events.cljs index ff55deb46d3..c65d94e0d13 100644 --- a/src/status_im/contexts/browser/events.cljs +++ b/src/status_im/contexts/browser/events.cljs @@ -2,7 +2,9 @@ (:require [cljs.pprint :as pprint] [re-frame.core :as rf] [status-im.contexts.browser.db :as browser.db] - [status-im.contexts.browser.messages :as messages])) + status-im.contexts.browser.effects + [status-im.contexts.browser.messages :as messages] + status-im.contexts.browser.rpc-events)) (rf/reg-event-fx :browser/set-tab-ref (fn [{:keys [db]} [tab-id ref]] @@ -19,7 +21,7 @@ (rf/reg-event-fx :browser/focus-tab-by-idx (fn [{:keys [db]} [tab-idx]] - (let [tab-id (browser.db/tab-id-by-index db tab-idx)] + (let [tab-id (browser.db/get-tab-id-by-index db tab-idx)] {:fx [[:dispatch [:browser/focus-tab tab-id]]]}))) (rf/reg-event-fx :browser/focus-tab @@ -44,10 +46,29 @@ (let [event (messages/parse-native-event js-event) event-topic (messages/event-topic event)] {:fx [(condp = event-topic - "website-metadata" [:dispatch [:browser/on-website-metadata tab-id event]]) - ]}))) + "website-metadata" [:dispatch [:browser/on-website-metadata tab-id event]] + "rpc" [:dispatch [:browser.rpc/on tab-id event]] + (do (println :unhandled-event-topic event-topic) + (pprint/pprint event)))]}))) (rf/reg-event-fx :browser/on-website-metadata (fn [{:keys [db]} [tab-id event]] - (let [metadata (messages/get-website-metadata event)] - {:db (update-in db [:browser/tabs-by-id tab-id :metadata] merge metadata)}))) + (let [{:keys [origin-url logo-url page-title]} (messages/get-website-metadata event) + dapp-metadata {:logo-url logo-url + :page-title page-title}] + {:db (update-in db + [:browser/tabs-by-id tab-id] + assoc + :dapp-id origin-url + :metadata dapp-metadata) + :fx [[:dispatch [:browser/save-dapp origin-url dapp-metadata]]]}))) + +(rf/reg-event-fx :browser/save-dapp + (fn [{:keys [db]} [dapp-id dapp-metadata]] + (when-not (contains? (:browser/dapps db) dapp-id) + ;;TODO: persist dapps + {:db (assoc-in db [:browser/dapps dapp-id] dapp-metadata)}))) + +(rf/reg-event-fx :browser/update-dapp-chain-id + (fn [{:keys [db]} [dapp-id chain-id]] + {:db (assoc-in db [:browser/dapps dapp-id :chain-id] chain-id)})) diff --git a/src/status_im/contexts/browser/js_scripts.cljs b/src/status_im/contexts/browser/js_scripts.cljs index fe6523184a6..8748b9534a2 100644 --- a/src/status_im/contexts/browser/js_scripts.cljs +++ b/src/status_im/contexts/browser/js_scripts.cljs @@ -1,6 +1,19 @@ (ns status-im.contexts.browser.js-scripts - (:require-macros [utils.slurp :refer [slurp]])) + (:require-macros [utils.slurp :refer [slurp]]) + (:require [shadow.resource :as rc] + [utils.transforms :as transforms])) +(defn add-global-var + [script var-name value] + (-> script + (str "window.__" var-name "__ = " (transforms/clj->json value) ";"))) + +(defn add-script + [script more-script] + (-> script + (str more-script))) + +(def web3-provider (rc/inline "./web3_provider.js")) (def freeze-website (slurp "resources/js/freeze_website.js")) (def unfreeze-website (slurp "resources/js/unfreeze_website.js")) (def website-metadata (slurp "resources/js/website_metadata.js")) diff --git a/src/status_im/contexts/browser/messages.cljs b/src/status_im/contexts/browser/messages.cljs index 080eb8f23c5..6d5068b2cc2 100644 --- a/src/status_im/contexts/browser/messages.cljs +++ b/src/status_im/contexts/browser/messages.cljs @@ -33,4 +33,4 @@ [event] (-> event event-payload - (select-keys [:page-title :logo-url]))) + (select-keys [:page-title :logo-url :origin-url]))) diff --git a/src/status_im/contexts/browser/rpc_errors.cljs b/src/status_im/contexts/browser/rpc_errors.cljs new file mode 100644 index 00000000000..252164202b3 --- /dev/null +++ b/src/status_im/contexts/browser/rpc_errors.cljs @@ -0,0 +1,14 @@ +(ns status-im.contexts.browser.rpc-errors) + +(defn get-error + ([error-key] + (get-error error-key {})) + ([error-key params] + (condp = error-key + :rpc.error/user-rejected + {:code 4001 + :message "User rejected the request."} + + :rpc.error/unrecognized-chain-id + {:code -32602 + :message (str "Unrecognized chain ID " (:chain-id params))}))) diff --git a/src/status_im/contexts/browser/rpc_events.cljs b/src/status_im/contexts/browser/rpc_events.cljs new file mode 100644 index 00000000000..812931d2a65 --- /dev/null +++ b/src/status_im/contexts/browser/rpc_events.cljs @@ -0,0 +1,91 @@ +(ns status-im.contexts.browser.rpc-events + (:require [cljs.pprint :as pprint] + [re-frame.core :as rf] + [status-im.contexts.browser.db :as browser.db] + [status-im.contexts.browser.rpc-errors :as rpc-errors] + [status-im.contexts.browser.rpc-params :as rpc-params] + [status-im.contexts.wallet.account.db :as account.db] + [status-im.contexts.wallet.networks.db :as networks.db] + [taoensso.timbre :as log] + [utils.hex :as hex])) + +(rf/reg-event-fx :browser.rpc/on + (fn [{:keys [db]} [tab-id event]] + (let [rpc (:data event) + rpc-method (:method rpc)] + (pprint/pprint rpc) + {;;:db (update-in db [:browser/tabs-by-id tab-id :rpc-events] conj rpc) + :fx [(condp = rpc-method + "eth_requestAccounts" + [:dispatch [:browser.rpc/accounts tab-id rpc]] + + "eth_accounts" + [:dispatch [:browser.rpc/accounts tab-id rpc]] + + "wallet_requestPermissions" + [:dispatch [:browser.rpc/request-permissions tab-id rpc]] + + "wallet_switchEthereumChain" + [:dispatch [:browser.rpc/switch-eth-chain tab-id rpc]] + + "eth_chainId" + [:dispatch [:browser.rpc/chain-id tab-id rpc]] + + (log/error "Unhandled rpc method" rpc-method))]}))) + +(rf/reg-event-fx :browser.rpc/send + (fn [{:keys [db]} [tab-id rpc-event message]] + (let [rpc-id (:id rpc-event) + webview-ref (get-in db [:browser/tabs-by-id tab-id :ref])] + {:fx [[:fx.browser/send-message + [webview-ref + {:id rpc-id + :jsonrpc "2.0" + :type "rpcResponse" + :result message}]]]}))) + +(rf/reg-event-fx :browser.rpc/send-error + (fn [{:keys [db]} [tab-id rpc-event error]] + (let [rpc-id (:id rpc-event) + webview-ref (get-in db [:browser/tabs-by-id tab-id :ref])] + {:fx [[:fx.browser/send-message + [webview-ref + {:id rpc-id + :jsonrpc "2.0" + :type "rpcResponse" + :error (rpc-errors/get-error error)}]]]}))) + +(rf/reg-event-fx :browser.rpc/accounts + (fn [{:keys [db]} [tab-id rpc-event]] + (let [accounts (account.db/get-accounts-addresses db)] + {:fx [[:dispatch [:browser.rpc/send tab-id rpc-event accounts]]]}))) + +(rf/reg-event-fx :browser.rpc/switch-eth-chain + (fn [{:keys [db]} [tab-id rpc-event]] + (let [chain-ids (networks.db/get-chain-ids db) + requested-chain-id (rpc-params/switch-ethereum-chain rpc-event) + chain-supported? (contains? chain-ids requested-chain-id) + dapp-id (browser.db/get-dapp-id db tab-id)] + (if chain-supported? + {:fx [[:dispatch [:browser/update-dapp-chain-id dapp-id requested-chain-id]] + [:dispatch [:browser.rpc/send tab-id rpc-event nil]]]} + {:fx [[:dispatch + [:browser.rpc/send-error tab-id rpc-event :rpc.error/unrecognized-chain-id]]]})))) + +;; TODO: make a scheduler for rpc events +(rf/reg-event-fx :browser.rpc/chain-id + (fn [{:keys [db]} [tab-id rpc-event]] + (let [dapp (browser.db/get-tab-dapp db tab-id) + chain-id (-> dapp :chain-id hex/number-to-hex)] + {:fx [[:dispatch [:browser.rpc/send tab-id rpc-event chain-id]]]}))) + +(rf/reg-event-fx :browser.rpc/request-permissions + (fn [{:keys [db]} [tab-id rpc-event]] + (let [permissions (-> rpc-event :params first) + tab-url (browser.db/get-tab-url db tab-id)] + {:fx [[:dispatch + [:browser.rpc/send tab-id rpc-event + [{:parentCapability "eth_accounts" + :caveats [{:type "restrictReturnedAccounts" + :value (account.db/get-accounts-addresses db)}] + :invoker tab-url}]]]]}))) diff --git a/src/status_im/contexts/browser/rpc_params.cljs b/src/status_im/contexts/browser/rpc_params.cljs new file mode 100644 index 00000000000..1ff71ef3fb9 --- /dev/null +++ b/src/status_im/contexts/browser/rpc_params.cljs @@ -0,0 +1,6 @@ +(ns status-im.contexts.browser.rpc-params + (:require [utils.hex :as hex])) + +(defn switch-ethereum-chain + [event] + (-> event :params first :chain-id hex/normalize-hex hex/hex-to-number)) diff --git a/src/status_im/contexts/browser/tab/view.cljs b/src/status_im/contexts/browser/tab/view.cljs index 4744c0eae5f..4cb09a3747f 100644 --- a/src/status_im/contexts/browser/tab/view.cljs +++ b/src/status_im/contexts/browser/tab/view.cljs @@ -3,20 +3,42 @@ [status-im.contexts.browser.js-scripts :as js-scripts] [utils.re-frame :as rf])) +(defn make-injected-script + [tab-id] + (-> + "" + (js-scripts/add-global-var "WALLET_UUID" tab-id) + (js-scripts/add-global-var "WALLET_NAME" "Status") + (js-scripts/add-global-var + "WALLET_ICON" + "https://play-lh.googleusercontent.com/VZTM4ybMq2LtICfiF_nYOlId_TfgVo1rgACYrPSUqcuY4MplG-CvWw-1dN4XPKTNixmc=w240-h480-rw") + (js-scripts/add-global-var "WALLET_RDNS" "im.status.ethereum") + (js-scripts/add-script js-scripts/website-metadata) + (js-scripts/add-script js-scripts/web3-provider))) + (defn view [{:keys [tab-id url]}] [webview/view - {:ref #(rf/dispatch [:browser/set-tab-ref tab-id %]) - :source {:uri url} - :java-script-enabled true - :bounces false - :cache-enabled true - :local-storage-enabled true - :set-support-multiple-windows false - :injected-java-script js-scripts/website-metadata - :on-message #(rf/dispatch [:browser/on-message tab-id %]) - :on-error #(println :on-error %) - :allowsBackForwardNavigationGestures true ;; iOS only + {:ref #(rf/dispatch [:browser/set-tab-ref tab-id %]) + :source {:uri url} + :java-script-enabled true + :bounces false + :cache-enabled true + :local-storage-enabled true + :set-support-multiple-windows false + ;;:injected-java-script js-scripts/website-metadata + :injected-java-script-before-content-loaded (make-injected-script tab-id) + :on-message #(rf/dispatch [:browser/on-message tab-id %]) + :on-error #(println :on-error %) + :allows-back-forward-navigation-gestures true ;; iOS only + :pull-to-refresh-enabled true + :webview-debugging-enabled true + ;; https://github.com/status-im/status-mobile/issues/17854 + :allows-inline-media-playback true + :mixed-content-mode :always + :origin-white-list ["*"] + :user-agent + "Mozilla/5.0 (Linux; Android 14) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.103 Mobile Safari/537.36" ;;:render-error web-view-error ;;:on-navigation-state-change #() ;; :on-message #(re-frame/dispatch @@ -27,7 +49,7 @@ ;; :on-error #(re-frame/dispatch [:browser/error-occured]) ;; :injected-java-script-before-content-loaded (js-res/ethereum-provider (str network-id)) ;; https://github.com/status-im/status-mobile/issues/17854 - :allows-inline-media-playback true}]) + }]) (comment (rf/sub [:browser/tab-ids]) @@ -35,9 +57,11 @@ ;; #uuid "c4401b67-35a9-474e-a2c9-f4017017281d" ;; #uuid "ceaa3002-2b59-4e8f-847c-5ecce16be809"] - - (rf/sub [:browser/tab-by-id (uuid "c4401b67-35a9-474e-a2c9-f4017017281d")]) + (rf/sub [:browser/tabs-by-id]) (rf/dispatch [:browser/add-tab "https://app.1inch.io"]) (rf/dispatch [:browser/add-tab "https://vibor.it"]) + (rf/dispatch [:browser/add-tab "https://pancakeswap.finance/swap"]) + ;; => nil + ) diff --git a/src/status_im/contexts/browser/view.cljs b/src/status_im/contexts/browser/view.cljs index e2dc0a3205d..48753f237e7 100644 --- a/src/status_im/contexts/browser/view.cljs +++ b/src/status_im/contexts/browser/view.cljs @@ -14,7 +14,8 @@ [tab-id idx] (let [url (rf/sub [:browser/tab-url tab-id]) focused-tab-idx (rf/sub [:browser/focused-tab-idx]) - freeze-tab? (browser/freeze-tab? idx focused-tab-idx)] + ;;freeze-tab? (browser/freeze-tab? idx focused-tab-idx) + freeze-tab? (not= idx focused-tab-idx)] (hooks/use-deactivate-tab tab-id) [rn/view {:style {:width browser.constants/browser-width diff --git a/src/status_im/contexts/browser/web3_provider.js b/src/status_im/contexts/browser/web3_provider.js new file mode 100644 index 00000000000..36b0cf1a656 --- /dev/null +++ b/src/status_im/contexts/browser/web3_provider.js @@ -0,0 +1,97 @@ +console.log('TESTING INJECTION'); + +(function () { + // Internal storage for pending calls + const _callbacks = {}; + let _nextId = 1; + + // Minimal EventEmitter for provider.on(...) + const _listeners = {}; + + // 1️⃣ Define our fake provider + const fakeProvider = { + // EIP-1193 request() + request({ method, params }) { + return new Promise((resolve, reject) => { + const id = _nextId++; + _callbacks[id] = { resolve, reject }; + // send to RN side + window.ReactNativeWebView.postMessage(JSON.stringify({ topic: 'rpc', id, method, params })); + }); + }, + + // EIP-1193 event subscription + on(eventName, handler) { + _listeners[eventName] = _listeners[eventName] || []; + _listeners[eventName].push(handler); + }, + }; + + // window.ReactNativeWebView.onMessage = function (ev) { + // console.log('MESSAGE INCOMING', ev); + // let msg; + // try { + // msg = JSON.parse(ev); + // } catch (e) { + // alert('ERROR parsing:', e, ev); + // return; + // } + + // console.log('message', msg); + // // RPC response + // if (msg.type === 'rpcResponse' && _callbacks[msg.id]) { + // const { resolve, reject } = _callbacks[msg.id]; + + // console.log('resolving rcpResponse', msg.id, _callbacks[msg.id]); + // delete _callbacks[msg.id]; + // msg.error ? reject(msg.error) : resolve(msg.result); + // } + + // // Emitted event from RN + // else if (msg.type === 'emitEvent' && _listeners[msg.event]) { + // _listeners[msg.event].forEach((fn) => fn(msg.data)); + // } + // }; + + //2️⃣ Listen for RN → WebView messages + document.addEventListener('message', (ev) => { + console.log('Message received from RN', ev.data); + + let msg; + try { + msg = JSON.parse(ev.data); + } catch (e) { + console.error('ERROR parsing:', e, ev); + return; + } + + // RPC response + if (msg.type === 'rpcResponse' && _callbacks[msg.id]) { + const { resolve, reject } = _callbacks[msg.id]; + delete _callbacks[msg.id]; + msg.error ? reject(msg.error) : resolve(msg.result); + } + + // Emitted event from RN + else if (msg.type === 'emitEvent' && _listeners[msg.event]) { + _listeners[msg.event].forEach((fn) => fn(msg.data)); + } + }); + + // 3️⃣ Expose to the page + window.__RN_WALLET_PROVIDER__ = fakeProvider; + window.ethereum = fakeProvider; // if Dapp expects `window.ethereum` + + // 4️⃣ (Optional) announce via EIP-6963 discovery + const info = { + uuid: window.__WALLET_UUID__, + name: window.__WALLET_NAME__, + icon: window.__WALLET_ICON__, + rdns: window.__WALLET_RDNS__, + }; + const detail = { info, provider: fakeProvider }; + window.dispatchEvent(new CustomEvent('eip6963:announceProvider', { detail })); + window.addEventListener('eip6963:requestProvider', () => + window.dispatchEvent(new CustomEvent('eip6963:announceProvider', { detail })), + ); +})(); diff --git a/src/utils/hex.cljs b/src/utils/hex.cljs index 649ca121e9f..ad89fa1382e 100644 --- a/src/utils/hex.cljs +++ b/src/utils/hex.cljs @@ -37,3 +37,13 @@ [:=> [:cat [:or :string :int]] :string]) + +(defn hex-to-number + [value] + (->> value + native-module/hex-to-number)) + +(schema/=> hex-to-number + [:=> + [:cat [:or :string :int]] + :int]) From 1bec94d1d649297acc5a3e296a6f61ba87b8fbfa Mon Sep 17 00:00:00 2001 From: Cristian Lungu Date: Wed, 14 May 2025 17:59:07 +0300 Subject: [PATCH 05/14] feat: added rpc event queue --- .../browser/approve_bottom_sheet.cljs | 36 ++++++++++ src/status_im/contexts/browser/events.cljs | 2 +- .../contexts/browser/rpc_errors.cljs | 4 ++ .../contexts/browser/rpc_events.cljs | 72 ++++++++++++++----- 4 files changed, 95 insertions(+), 19 deletions(-) create mode 100644 src/status_im/contexts/browser/approve_bottom_sheet.cljs diff --git a/src/status_im/contexts/browser/approve_bottom_sheet.cljs b/src/status_im/contexts/browser/approve_bottom_sheet.cljs new file mode 100644 index 00000000000..17f03232d91 --- /dev/null +++ b/src/status_im/contexts/browser/approve_bottom_sheet.cljs @@ -0,0 +1,36 @@ +(ns status-im.contexts.browser.approve-bottom-sheet + (:require [quo.core :as quo] + [react-native.core :as rn] + [status-im.common.raw-data-block.view :as data-block] + [utils.re-frame :as rf] + [utils.transforms :as transforms])) + +(defn on-approve + [tab-id event] + (rf/dispatch [:browser.rpc/handle-rpc tab-id event]) + (rf/dispatch [:hide-bottom-sheet])) + +(defn on-reject + [tab-id event] + (rf/dispatch [:browser.rpc/send-error tab-id event + :rpc.error/user-rejected]) + (rf/dispatch [:hide-bottom-sheet])) + +(defn view + [{:keys [tab-id event]}] + (let [tab-title (rf/sub [:browser/tab-title tab-id])] + [rn/view {:style {:padding-top 20}} + [rn/view + {:style {:padding-horizontal 20 + :margin-bottom 12}} + [quo/text + {:style {:margin-bottom 12} + :weight :bold + :size :heading-2} (str tab-title " requests:")] + [data-block/view (transforms/clj->pretty-json event 2)]] + [quo/bottom-actions + {:actions :two-actions + :button-one-label "Approve" + :button-one-props {:on-press #(on-approve tab-id event)} + :button-two-label "Reject" + :button-two-props {:on-press #(on-reject tab-id event)}}]])) diff --git a/src/status_im/contexts/browser/events.cljs b/src/status_im/contexts/browser/events.cljs index c65d94e0d13..c3f08ad23e7 100644 --- a/src/status_im/contexts/browser/events.cljs +++ b/src/status_im/contexts/browser/events.cljs @@ -47,7 +47,7 @@ event-topic (messages/event-topic event)] {:fx [(condp = event-topic "website-metadata" [:dispatch [:browser/on-website-metadata tab-id event]] - "rpc" [:dispatch [:browser.rpc/on tab-id event]] + "rpc" [:dispatch [:browser.rpc/on-event tab-id event]] (do (println :unhandled-event-topic event-topic) (pprint/pprint event)))]}))) diff --git a/src/status_im/contexts/browser/rpc_errors.cljs b/src/status_im/contexts/browser/rpc_errors.cljs index 252164202b3..f4564477950 100644 --- a/src/status_im/contexts/browser/rpc_errors.cljs +++ b/src/status_im/contexts/browser/rpc_errors.cljs @@ -5,6 +5,10 @@ (get-error error-key {})) ([error-key params] (condp = error-key + :rpc.error/method-not-available + {:code -32601 + :message (str "The method" (:method params) "does not exist/is not available")} + :rpc.error/user-rejected {:code 4001 :message "User rejected the request."} diff --git a/src/status_im/contexts/browser/rpc_events.cljs b/src/status_im/contexts/browser/rpc_events.cljs index 812931d2a65..67ab55edb70 100644 --- a/src/status_im/contexts/browser/rpc_events.cljs +++ b/src/status_im/contexts/browser/rpc_events.cljs @@ -1,6 +1,6 @@ (ns status-im.contexts.browser.rpc-events - (:require [cljs.pprint :as pprint] - [re-frame.core :as rf] + (:require [re-frame.core :as rf] + [status-im.contexts.browser.approve-bottom-sheet :as approve-bottom-sheet] [status-im.contexts.browser.db :as browser.db] [status-im.contexts.browser.rpc-errors :as rpc-errors] [status-im.contexts.browser.rpc-params :as rpc-params] @@ -9,29 +9,63 @@ [taoensso.timbre :as log] [utils.hex :as hex])) -(rf/reg-event-fx :browser.rpc/on +(rf/reg-event-fx :browser.rpc/show-approve-modal + (fn [_ [tab-id event]] + {:fx [[:dispatch + [:show-bottom-sheet + {:hide-handle? true + :drag-content? false + :hide-on-background-press? false + :content (fn [] + [approve-bottom-sheet/view + {:tab-id tab-id + :event event}])}]]]})) + +(rf/reg-event-fx :browser.rpc/on-event (fn [{:keys [db]} [tab-id event]] - (let [rpc (:data event) - rpc-method (:method rpc)] - (pprint/pprint rpc) - {;;:db (update-in db [:browser/tabs-by-id tab-id :rpc-events] conj rpc) - :fx [(condp = rpc-method + (let [current-queue (get db :browser/rpc-queue []) + rpc-event (:data event) + new-queue (conj current-queue + {:tab-id tab-id + :event rpc-event})] + (log/info (str "Browser RPC " (-> rpc-event :method) " for " (browser.db/get-tab-url db tab-id))) + (log/info (str "Queue size: " (count current-queue))) + {:db (assoc db :browser/rpc-queue new-queue) + :fx [(when (empty? current-queue) + [:dispatch [:browser.rpc/show-approve-modal tab-id rpc-event]])]}))) + +(rf/reg-event-fx :browser.rpc/call-next-in-queue + (fn [{:keys [db]}] + (let [current-queue (get db :browser/rpc-queue []) + remaining-queue (-> current-queue rest vec) + {:keys [tab-id event]} (first remaining-queue)] + {:db (assoc db :browser/rpc-queue remaining-queue) + :fx [(when event + [:dispatch [:browser.rpc/show-approve-modal tab-id event]])]}))) + +(rf/reg-event-fx :browser.rpc/handle-rpc + (fn [_ [tab-id rpc-event]] + (let [rpc-method (:method rpc-event)] + {:fx [(condp = rpc-method "eth_requestAccounts" - [:dispatch [:browser.rpc/accounts tab-id rpc]] + [:dispatch [:browser.rpc/accounts tab-id rpc-event]] "eth_accounts" - [:dispatch [:browser.rpc/accounts tab-id rpc]] + [:dispatch [:browser.rpc/accounts tab-id rpc-event]] "wallet_requestPermissions" - [:dispatch [:browser.rpc/request-permissions tab-id rpc]] + [:dispatch [:browser.rpc/request-permissions tab-id rpc-event]] "wallet_switchEthereumChain" - [:dispatch [:browser.rpc/switch-eth-chain tab-id rpc]] + [:dispatch [:browser.rpc/switch-eth-chain tab-id rpc-event]] "eth_chainId" - [:dispatch [:browser.rpc/chain-id tab-id rpc]] + [:dispatch [:browser.rpc/chain-id tab-id rpc-event]] - (log/error "Unhandled rpc method" rpc-method))]}))) + (do + (log/error "Unhandled rpc method" rpc-method) + [:dispatch + [:browser.rpc/send-error tab-id rpc-event :rpc.error/method-not-available]]))]}))) (rf/reg-event-fx :browser.rpc/send (fn [{:keys [db]} [tab-id rpc-event message]] @@ -42,7 +76,8 @@ {:id rpc-id :jsonrpc "2.0" :type "rpcResponse" - :result message}]]]}))) + :result message}]] + [:dispatch [:browser.rpc/call-next-in-queue]]]}))) (rf/reg-event-fx :browser.rpc/send-error (fn [{:keys [db]} [tab-id rpc-event error]] @@ -53,7 +88,8 @@ {:id rpc-id :jsonrpc "2.0" :type "rpcResponse" - :error (rpc-errors/get-error error)}]]]}))) + :error (rpc-errors/get-error error)}]] + [:dispatch [:browser.rpc/call-next-in-queue]]]}))) (rf/reg-event-fx :browser.rpc/accounts (fn [{:keys [db]} [tab-id rpc-event]] @@ -81,8 +117,8 @@ (rf/reg-event-fx :browser.rpc/request-permissions (fn [{:keys [db]} [tab-id rpc-event]] - (let [permissions (-> rpc-event :params first) - tab-url (browser.db/get-tab-url db tab-id)] + (let [;;permissions (-> rpc-event :params first) + tab-url (browser.db/get-tab-url db tab-id)] {:fx [[:dispatch [:browser.rpc/send tab-id rpc-event [{:parentCapability "eth_accounts" From 8c566f390795734c5ce30ccb4488df72fe7ae8c5 Mon Sep 17 00:00:00 2001 From: Cristian Lungu Date: Wed, 14 May 2025 18:24:37 +0300 Subject: [PATCH 06/14] feat: restructuring --- resources/js/web3_provider.js | 98 ------------------- .../approve_bottom_sheet.cljs | 2 +- .../browser/{ => components}/footer.cljs | 2 +- .../view.cljs => components/webview_tab.cljs} | 4 +- .../browser/{events.cljs => events/core.cljs} | 8 +- .../browser/{ => events}/effects.cljs | 2 +- .../{rpc_events.cljs => events/rpc.cljs} | 4 +- src/status_im/contexts/browser/hooks.cljs | 2 +- .../contexts/browser/js_scripts.cljs | 19 ---- .../contexts/browser/js_scripts/core.cljs | 21 ++++ .../browser/js_scripts}/freeze_website.js | 0 .../browser/js_scripts}/unfreeze_website.js | 0 .../browser/{ => js_scripts}/web3_provider.js | 0 .../browser/js_scripts}/website_metadata.js | 0 .../browser/{subs.cljs => subs/core.cljs} | 2 +- src/status_im/contexts/browser/view.cljs | 7 +- src/status_im/contexts/browser/webview.cljs | 1 - src/status_im/events.cljs | 2 +- src/status_im/subs/root.cljs | 2 +- 19 files changed, 39 insertions(+), 137 deletions(-) delete mode 100644 resources/js/web3_provider.js rename src/status_im/contexts/browser/{ => components}/approve_bottom_sheet.cljs (95%) rename src/status_im/contexts/browser/{ => components}/footer.cljs (97%) rename src/status_im/contexts/browser/{tab/view.cljs => components/webview_tab.cljs} (95%) rename src/status_im/contexts/browser/{events.cljs => events/core.cljs} (93%) rename src/status_im/contexts/browser/{ => events}/effects.cljs (86%) rename src/status_im/contexts/browser/{rpc_events.cljs => events/rpc.cljs} (97%) delete mode 100644 src/status_im/contexts/browser/js_scripts.cljs create mode 100644 src/status_im/contexts/browser/js_scripts/core.cljs rename {resources/js => src/status_im/contexts/browser/js_scripts}/freeze_website.js (100%) rename {resources/js => src/status_im/contexts/browser/js_scripts}/unfreeze_website.js (100%) rename src/status_im/contexts/browser/{ => js_scripts}/web3_provider.js (100%) rename {resources/js => src/status_im/contexts/browser/js_scripts}/website_metadata.js (100%) rename src/status_im/contexts/browser/{subs.cljs => subs/core.cljs} (97%) delete mode 100644 src/status_im/contexts/browser/webview.cljs diff --git a/resources/js/web3_provider.js b/resources/js/web3_provider.js deleted file mode 100644 index 890b8ddc4a4..00000000000 --- a/resources/js/web3_provider.js +++ /dev/null @@ -1,98 +0,0 @@ -console.log('TESTING INJECTION test'); - -(function () { - // Internal storage for pending calls - const _callbacks = {}; - let _nextId = 1; - - // Minimal EventEmitter for provider.on(...) - const _listeners = {}; - - // 1️⃣ Define our fake provider - const fakeProvider = { - // EIP-1193 request() - request({ method, params }) { - return new Promise((resolve, reject) => { - const id = _nextId++; - _callbacks[id] = { resolve, reject }; - // send to RN side - ReactNativeWebView.postMessage(JSON.stringify({ topic: 'rpc', id, method, params }), '*'); - }); - }, - - // EIP-1193 event subscription - on(eventName, handler) { - _listeners[eventName] = _listeners[eventName] || []; - _listeners[eventName].push(handler); - }, - }; - - ReactNativeWebView.onMessage = function (ev) { - console.log('MESSAGE INCOMING'); - ReactNativeWebView.postMessage(JSON.stringify({ topic: 'fuck-you' }), '*'); - let msg; - try { - msg = JSON.parse(ev); - } catch (e) { - alert('ERROR parsing:', e, ev); - return; - } - - alert('message', msg); - // RPC response - if (msg.type === 'rpcResponse' && _callbacks[msg.id]) { - const { resolve, reject } = _callbacks[msg.id]; - delete _callbacks[msg.id]; - msg.error ? reject(msg.error) : resolve(msg.result); - } - - // Emitted event from RN - else if (msg.type === 'emitEvent' && _listeners[msg.event]) { - _listeners[msg.event].forEach((fn) => fn(msg.data)); - } - }; - - // 2️⃣ Listen for RN → WebView messages - // window.addEventListener('message', function (ev) { - // console.log('onMessage RECEIVED'); - - // ReactNativeWebView.postMessage(JSON.stringify({ topic: 'fuck-you' }), '*'); - // let msg; - // try { - // msg = JSON.parse(ev); - // } catch (e) { - // alert('ERROR parsing:', e, ev); - // return; - // } - - // alert('message', msg); - // // RPC response - // if (msg.type === 'rpcResponse' && _callbacks[msg.id]) { - // const { resolve, reject } = _callbacks[msg.id]; - // delete _callbacks[msg.id]; - // msg.error ? reject(msg.error) : resolve(msg.result); - // } - - // // Emitted event from RN - // else if (msg.type === 'emitEvent' && _listeners[msg.event]) { - // _listeners[msg.event].forEach((fn) => fn(msg.data)); - // } - // }); - - // 3️⃣ Expose to the page - window.__RN_WALLET_PROVIDER__ = fakeProvider; - window.ethereum = fakeProvider; // if Dapp expects `window.ethereum` - - // 4️⃣ (Optional) announce via EIP-6963 discovery - const info = { - uuid: window.__WALLET_UUID__, - name: window.__WALLET_NAME__, - icon: window.__WALLET_ICON__, - rdns: window.__WALLET_RDNS__, - }; - const detail = { info, provider: fakeProvider }; - window.dispatchEvent(new CustomEvent('eip6963:announceProvider', { detail })); - window.addEventListener('eip6963:requestProvider', () => - window.dispatchEvent(new CustomEvent('eip6963:announceProvider', { detail })), - ); -})(); diff --git a/src/status_im/contexts/browser/approve_bottom_sheet.cljs b/src/status_im/contexts/browser/components/approve_bottom_sheet.cljs similarity index 95% rename from src/status_im/contexts/browser/approve_bottom_sheet.cljs rename to src/status_im/contexts/browser/components/approve_bottom_sheet.cljs index 17f03232d91..4708ffa9c3c 100644 --- a/src/status_im/contexts/browser/approve_bottom_sheet.cljs +++ b/src/status_im/contexts/browser/components/approve_bottom_sheet.cljs @@ -1,4 +1,4 @@ -(ns status-im.contexts.browser.approve-bottom-sheet +(ns status-im.contexts.browser.components.approve-bottom-sheet (:require [quo.core :as quo] [react-native.core :as rn] [status-im.common.raw-data-block.view :as data-block] diff --git a/src/status_im/contexts/browser/footer.cljs b/src/status_im/contexts/browser/components/footer.cljs similarity index 97% rename from src/status_im/contexts/browser/footer.cljs rename to src/status_im/contexts/browser/components/footer.cljs index 6bcbd57fa06..29cb16e3b33 100644 --- a/src/status_im/contexts/browser/footer.cljs +++ b/src/status_im/contexts/browser/components/footer.cljs @@ -1,4 +1,4 @@ -(ns status-im.contexts.browser.footer +(ns status-im.contexts.browser.components.footer (:require [quo.core :as quo] [quo.foundations.colors :as colors] [react-native.core :as rn] diff --git a/src/status_im/contexts/browser/tab/view.cljs b/src/status_im/contexts/browser/components/webview_tab.cljs similarity index 95% rename from src/status_im/contexts/browser/tab/view.cljs rename to src/status_im/contexts/browser/components/webview_tab.cljs index 4cb09a3747f..704aedca8df 100644 --- a/src/status_im/contexts/browser/tab/view.cljs +++ b/src/status_im/contexts/browser/components/webview_tab.cljs @@ -1,6 +1,6 @@ -(ns status-im.contexts.browser.tab.view +(ns status-im.contexts.browser.components.webview-tab (:require [react-native.webview :as webview] - [status-im.contexts.browser.js-scripts :as js-scripts] + [status-im.contexts.browser.js-scripts.core :as js-scripts] [utils.re-frame :as rf])) (defn make-injected-script diff --git a/src/status_im/contexts/browser/events.cljs b/src/status_im/contexts/browser/events/core.cljs similarity index 93% rename from src/status_im/contexts/browser/events.cljs rename to src/status_im/contexts/browser/events/core.cljs index c3f08ad23e7..389c8567445 100644 --- a/src/status_im/contexts/browser/events.cljs +++ b/src/status_im/contexts/browser/events/core.cljs @@ -1,10 +1,10 @@ -(ns status-im.contexts.browser.events +(ns status-im.contexts.browser.events.core (:require [cljs.pprint :as pprint] [re-frame.core :as rf] [status-im.contexts.browser.db :as browser.db] - status-im.contexts.browser.effects - [status-im.contexts.browser.messages :as messages] - status-im.contexts.browser.rpc-events)) + status-im.contexts.browser.events.effects + status-im.contexts.browser.events.rpc + [status-im.contexts.browser.messages :as messages])) (rf/reg-event-fx :browser/set-tab-ref (fn [{:keys [db]} [tab-id ref]] diff --git a/src/status_im/contexts/browser/effects.cljs b/src/status_im/contexts/browser/events/effects.cljs similarity index 86% rename from src/status_im/contexts/browser/effects.cljs rename to src/status_im/contexts/browser/events/effects.cljs index 27ca70e1e97..53de63563f6 100644 --- a/src/status_im/contexts/browser/effects.cljs +++ b/src/status_im/contexts/browser/events/effects.cljs @@ -1,4 +1,4 @@ -(ns status-im.contexts.browser.effects +(ns status-im.contexts.browser.events.effects (:require [cljs.pprint :as pprint] [re-frame.core :as rf] [react-native.webview :as webview])) diff --git a/src/status_im/contexts/browser/rpc_events.cljs b/src/status_im/contexts/browser/events/rpc.cljs similarity index 97% rename from src/status_im/contexts/browser/rpc_events.cljs rename to src/status_im/contexts/browser/events/rpc.cljs index 67ab55edb70..961d20ecbec 100644 --- a/src/status_im/contexts/browser/rpc_events.cljs +++ b/src/status_im/contexts/browser/events/rpc.cljs @@ -1,6 +1,6 @@ -(ns status-im.contexts.browser.rpc-events +(ns status-im.contexts.browser.events.rpc (:require [re-frame.core :as rf] - [status-im.contexts.browser.approve-bottom-sheet :as approve-bottom-sheet] + [status-im.contexts.browser.components.approve-bottom-sheet :as approve-bottom-sheet] [status-im.contexts.browser.db :as browser.db] [status-im.contexts.browser.rpc-errors :as rpc-errors] [status-im.contexts.browser.rpc-params :as rpc-params] diff --git a/src/status_im/contexts/browser/hooks.cljs b/src/status_im/contexts/browser/hooks.cljs index 443ecc09b00..d6508674857 100644 --- a/src/status_im/contexts/browser/hooks.cljs +++ b/src/status_im/contexts/browser/hooks.cljs @@ -5,7 +5,7 @@ [react-native.reanimated :as reanimated] [react-native.webview :as webview] [status-im.contexts.browser.core :as browser] - [status-im.contexts.browser.js-scripts :as js-scripts] + [status-im.contexts.browser.js-scripts.core :as js-scripts] [utils.re-frame :as rf] [utils.worklets.browser :as worklets.browser])) diff --git a/src/status_im/contexts/browser/js_scripts.cljs b/src/status_im/contexts/browser/js_scripts.cljs deleted file mode 100644 index 8748b9534a2..00000000000 --- a/src/status_im/contexts/browser/js_scripts.cljs +++ /dev/null @@ -1,19 +0,0 @@ -(ns status-im.contexts.browser.js-scripts - (:require-macros [utils.slurp :refer [slurp]]) - (:require [shadow.resource :as rc] - [utils.transforms :as transforms])) - -(defn add-global-var - [script var-name value] - (-> script - (str "window.__" var-name "__ = " (transforms/clj->json value) ";"))) - -(defn add-script - [script more-script] - (-> script - (str more-script))) - -(def web3-provider (rc/inline "./web3_provider.js")) -(def freeze-website (slurp "resources/js/freeze_website.js")) -(def unfreeze-website (slurp "resources/js/unfreeze_website.js")) -(def website-metadata (slurp "resources/js/website_metadata.js")) diff --git a/src/status_im/contexts/browser/js_scripts/core.cljs b/src/status_im/contexts/browser/js_scripts/core.cljs new file mode 100644 index 00000000000..8f4cc07ae20 --- /dev/null +++ b/src/status_im/contexts/browser/js_scripts/core.cljs @@ -0,0 +1,21 @@ +(ns status-im.contexts.browser.js-scripts.core + (:require [shadow.resource :as rc] + [utils.transforms :as transforms])) + +(defn add-global-var + [script var-name value] + (-> script + (str "window.__" var-name "__ = " (transforms/clj->json value) ";"))) + +(defn add-script + [script more-script] + (-> script + (str more-script))) + +;; NOTE: using `rc/inline` instead of `slurp`, so that changes to the js files trigger a recompilation +;; and are included in the bundle. With `slurp`, you'd have to either re-run `shadow-cljs` or +;; re-evaluate the slurp in the REPL. +(def web3-provider (rc/inline "./web3_provider.js")) +(def freeze-website (rc/inline "./freeze_website.js")) +(def unfreeze-website (rc/inline "./unfreeze_website.js")) +(def website-metadata (rc/inline "./website_metadata.js")) diff --git a/resources/js/freeze_website.js b/src/status_im/contexts/browser/js_scripts/freeze_website.js similarity index 100% rename from resources/js/freeze_website.js rename to src/status_im/contexts/browser/js_scripts/freeze_website.js diff --git a/resources/js/unfreeze_website.js b/src/status_im/contexts/browser/js_scripts/unfreeze_website.js similarity index 100% rename from resources/js/unfreeze_website.js rename to src/status_im/contexts/browser/js_scripts/unfreeze_website.js diff --git a/src/status_im/contexts/browser/web3_provider.js b/src/status_im/contexts/browser/js_scripts/web3_provider.js similarity index 100% rename from src/status_im/contexts/browser/web3_provider.js rename to src/status_im/contexts/browser/js_scripts/web3_provider.js diff --git a/resources/js/website_metadata.js b/src/status_im/contexts/browser/js_scripts/website_metadata.js similarity index 100% rename from resources/js/website_metadata.js rename to src/status_im/contexts/browser/js_scripts/website_metadata.js diff --git a/src/status_im/contexts/browser/subs.cljs b/src/status_im/contexts/browser/subs/core.cljs similarity index 97% rename from src/status_im/contexts/browser/subs.cljs rename to src/status_im/contexts/browser/subs/core.cljs index f5ace678e25..f3d687b4c70 100644 --- a/src/status_im/contexts/browser/subs.cljs +++ b/src/status_im/contexts/browser/subs/core.cljs @@ -1,4 +1,4 @@ -(ns status-im.contexts.browser.subs +(ns status-im.contexts.browser.subs.core (:require [re-frame.core :as rf])) (rf/reg-sub :browser/tab-ids diff --git a/src/status_im/contexts/browser/view.cljs b/src/status_im/contexts/browser/view.cljs index 48753f237e7..b8d75320cdb 100644 --- a/src/status_im/contexts/browser/view.cljs +++ b/src/status_im/contexts/browser/view.cljs @@ -3,11 +3,10 @@ [react-native.freeze :as freeze] [react-native.gesture :as gesture] [react-native.reanimated :as reanimated] + [status-im.contexts.browser.components.footer :as footer] + [status-im.contexts.browser.components.webview-tab :as webview-tab] [status-im.contexts.browser.constants :as browser.constants] - [status-im.contexts.browser.core :as browser] - [status-im.contexts.browser.footer :as footer] [status-im.contexts.browser.hooks :as hooks] - [status-im.contexts.browser.tab.view :as tab] [utils.re-frame :as rf])) (defn render-tab @@ -21,7 +20,7 @@ {:style {:width browser.constants/browser-width :height browser.constants/browser-height}} [freeze/view {:freeze freeze-tab?} - [tab/view + [webview-tab/view {:tab-id tab-id :url url}]]])) diff --git a/src/status_im/contexts/browser/webview.cljs b/src/status_im/contexts/browser/webview.cljs deleted file mode 100644 index cb46e57b54d..00000000000 --- a/src/status_im/contexts/browser/webview.cljs +++ /dev/null @@ -1 +0,0 @@ -(ns status-im.contexts.browser.webview) diff --git a/src/status_im/events.cljs b/src/status_im/events.cljs index 2ed4b7fc349..47d17ec6033 100644 --- a/src/status_im/events.cljs +++ b/src/status_im/events.cljs @@ -17,7 +17,7 @@ status-im.common.theme.events [status-im.common.toasts.events] status-im.common.universal-links - status-im.contexts.browser.events + status-im.contexts.browser.events.core status-im.contexts.chat.contacts.events status-im.contexts.chat.events [status-im.contexts.chat.home.add-new-contact.events] diff --git a/src/status_im/subs/root.cljs b/src/status_im/subs/root.cljs index be1abc74972..0ec085c9d5e 100644 --- a/src/status_im/subs/root.cljs +++ b/src/status_im/subs/root.cljs @@ -1,7 +1,7 @@ (ns status-im.subs.root (:require [re-frame.core :as re-frame] - status-im.contexts.browser.subs + status-im.contexts.browser.subs.core status-im.subs.activity-center status-im.subs.alert-banner status-im.subs.biometrics From ca5b110e22a0041031b4268ff19bbe00a319e391 Mon Sep 17 00:00:00 2001 From: Cristian Lungu Date: Thu, 15 May 2025 00:12:16 +0300 Subject: [PATCH 07/14] feat: integrated status-go connector rpc approvals --- src/status_im/common/signals/events.cljs | 24 +++++-- src/status_im/contexts/browser/api.cljs | 54 +++++++++++++++ .../components/approve_bottom_sheet.cljs | 29 ++++---- .../contexts/browser/components/footer.cljs | 14 ++-- .../browser/components/webview_tab.cljs | 15 +--- src/status_im/contexts/browser/constants.cljs | 2 + src/status_im/contexts/browser/db.cljs | 14 +++- .../contexts/browser/events/core.cljs | 15 ++++ .../contexts/browser/events/effects.cljs | 25 ++++++- .../contexts/browser/events/rpc.cljs | 69 +++++++++++++++---- src/status_im/contexts/browser/subs/core.cljs | 4 ++ src/status_im/contexts/profile/config.cljs | 3 +- 12 files changed, 214 insertions(+), 54 deletions(-) create mode 100644 src/status_im/contexts/browser/api.cljs diff --git a/src/status_im/common/signals/events.cljs b/src/status_im/common/signals/events.cljs index 9a6d8eb4144..e05a74f668b 100644 --- a/src/status_im/common/signals/events.cljs +++ b/src/status_im/common/signals/events.cljs @@ -1,5 +1,6 @@ (ns status-im.common.signals.events (:require + [camel-snake-kebab.extras :as cske] [legacy.status-im.chat.models.message :as models.message] [legacy.status-im.mailserver.core :as mailserver] [legacy.status-im.visibility-status-updates.core :as visibility-status-updates] @@ -45,11 +46,10 @@ "wallet.router.transactions-sent" {:fx [[:dispatch [:wallet/transactions-sent-signal-received (transforms/js->clj event-js)]]]} - "envelope.sent" - (messages.transport/update-envelopes-status - cofx - (:ids (transforms/js->clj event-js)) - :sent) + "envelope.sent" (messages.transport/update-envelopes-status + cofx + (:ids (transforms/js->clj event-js)) + :sent) "envelope.expired" (messages.transport/update-envelopes-status @@ -110,4 +110,18 @@ "backup.performed" {:db (assoc-in db [:profile/profile :last-backup] (oops/oget event-js :lastBackup))} + "connector.sendRequestAccounts" + {:fx [[:dispatch + [:browser.rpc/on-request-accounts-signal + (->> event-js + transforms/js->clj + (cske/transform-keys transforms/->kebab-case-keyword))]]]} + + "connector.dAppPermissionGranted" + {:fx [[:dispatch + [:browser.rpc/on-permission-granted + (->> event-js + transforms/js->clj + (cske/transform-keys transforms/->kebab-case-keyword))]]]} + (log/debug "Event " type " not handled")))) diff --git a/src/status_im/contexts/browser/api.cljs b/src/status_im/contexts/browser/api.cljs new file mode 100644 index 00000000000..91afe803205 --- /dev/null +++ b/src/status_im/contexts/browser/api.cljs @@ -0,0 +1,54 @@ +(ns status-im.contexts.browser.api + (:require [camel-snake-kebab.extras :as cske] + [cljs.pprint :as pprint] + [promesa.core :as promesa] + [status-im.common.json-rpc.events :as rpc] + [utils.transforms :as transforms])) + +(defn- process-dapp-permissions + [permissions] + (->> permissions + (cske/transform-keys transforms/->kebab-case-keyword) + (into {} (map (juxt :url identity))))) + +(defn get-dapp-permissions + [] + (-> (rpc/call-async :connector_getPermittedDAppsList false) + (promesa/then process-dapp-permissions))) + +(defn call-connector-rpc + [json-rpc dapp] + (let [arg (-> {:params [] + :jsonrpc "2.0"} + (merge json-rpc) + (merge dapp) + (dissoc :topic))] + (pprint/pprint arg) + (rpc/call-async :connector_callRPC false (transforms/clj->json arg)))) + +(defn approve-accounts-request + [response] + (->> response + clj->js + (rpc/call-async :connector_requestAccountsAccepted false))) + +(defn reject-accounts-request + [response] + (->> response + clj->js + (rpc/call-async :connector_requestAccountsRejected false))) + +(defn get-dapp-browsers + [] + (-> (rpc/call-async :wakuext_getBrowsers false) + (promesa/then process-dapp-permissions))) + +(defn get-dapp-bookmarks + [] + (-> (rpc/call-async :wakuext_getBookmarks false) + (promesa/then process-dapp-permissions))) + +(defn save-browser + [browser] + (-> (rpc/call-async :wakuext_addBrowser browser false) + (promesa/then process-dapp-permissions))) diff --git a/src/status_im/contexts/browser/components/approve_bottom_sheet.cljs b/src/status_im/contexts/browser/components/approve_bottom_sheet.cljs index 4708ffa9c3c..966dfdf628d 100644 --- a/src/status_im/contexts/browser/components/approve_bottom_sheet.cljs +++ b/src/status_im/contexts/browser/components/approve_bottom_sheet.cljs @@ -1,36 +1,37 @@ (ns status-im.contexts.browser.components.approve-bottom-sheet (:require [quo.core :as quo] [react-native.core :as rn] - [status-im.common.raw-data-block.view :as data-block] - [utils.re-frame :as rf] - [utils.transforms :as transforms])) + [react-native.fast-image :as fast-image] + [utils.re-frame :as rf])) (defn on-approve - [tab-id event] - (rf/dispatch [:browser.rpc/handle-rpc tab-id event]) + [request] + (rf/dispatch [:browser.rpc/approve-request-accounts request]) (rf/dispatch [:hide-bottom-sheet])) (defn on-reject - [tab-id event] - (rf/dispatch [:browser.rpc/send-error tab-id event - :rpc.error/user-rejected]) + [request] + (rf/dispatch [:browser.rpc/reject-request-accounts request]) (rf/dispatch [:hide-bottom-sheet])) (defn view - [{:keys [tab-id event]}] - (let [tab-title (rf/sub [:browser/tab-title tab-id])] + [{:keys [request]}] + (let [{:keys [icon-url name]} request] [rn/view {:style {:padding-top 20}} [rn/view {:style {:padding-horizontal 20 + :flex-direction :row :margin-bottom 12}} + [fast-image/fast-image + {:source icon-url + :style {:width 20 :height 20 :border-radius 8 :margin-right 8}}] [quo/text {:style {:margin-bottom 12} :weight :bold - :size :heading-2} (str tab-title " requests:")] - [data-block/view (transforms/clj->pretty-json event 2)]] + :size :heading-2} (str name " requests accounts permission")]] [quo/bottom-actions {:actions :two-actions :button-one-label "Approve" - :button-one-props {:on-press #(on-approve tab-id event)} + :button-one-props {:on-press #(on-approve request)} :button-two-label "Reject" - :button-two-props {:on-press #(on-reject tab-id event)}}]])) + :button-two-props {:on-press #(on-reject request)}}]])) diff --git a/src/status_im/contexts/browser/components/footer.cljs b/src/status_im/contexts/browser/components/footer.cljs index 29cb16e3b33..de68286e78a 100644 --- a/src/status_im/contexts/browser/components/footer.cljs +++ b/src/status_im/contexts/browser/components/footer.cljs @@ -20,12 +20,14 @@ :flex-direction :row :padding-horizontal 20 :height 80}} - [fast-image/fast-image - {:source (:logo-url metadata) - :style {:width 44 :height 44 :border-radius 12}}] + [rn/view + {:style {:width 44 + :height 44 + :border-radius 12}}] [rn/view {:style {:padding 8 :background-color colors/neutral-30 + :flex-direction :row :border-radius 12 :height 44 :margin-horizontal 12 @@ -33,11 +35,15 @@ :flex 1 :align-items :center :justify-content :center}} + [fast-image/fast-image + {:source (:logo-url metadata) + :style {:width 24 :height 24 :border-radius 8}}] [quo/text {:size :paragraph-1 :number-of-lines 1 :weight :bold - :style {:color colors/neutral-100}} title]] + :style {:color colors/neutral-100 + :margin-left 12}} title]] [rn/view {:style {:width 44 :height 44 diff --git a/src/status_im/contexts/browser/components/webview_tab.cljs b/src/status_im/contexts/browser/components/webview_tab.cljs index 704aedca8df..1ebb2dfd4a2 100644 --- a/src/status_im/contexts/browser/components/webview_tab.cljs +++ b/src/status_im/contexts/browser/components/webview_tab.cljs @@ -50,18 +50,5 @@ ;; :injected-java-script-before-content-loaded (js-res/ethereum-provider (str network-id)) ;; https://github.com/status-im/status-mobile/issues/17854 }]) - -(comment - (rf/sub [:browser/tab-ids]) - ;; => [#uuid "b780d00a-7740-43e4-a5db-0ed3cc2b5ab7" - ;; #uuid "c4401b67-35a9-474e-a2c9-f4017017281d" - ;; #uuid "ceaa3002-2b59-4e8f-847c-5ecce16be809"] - - (rf/sub [:browser/tabs-by-id]) - - (rf/dispatch [:browser/add-tab "https://app.1inch.io"]) - (rf/dispatch [:browser/add-tab "https://vibor.it"]) - (rf/dispatch [:browser/add-tab "https://pancakeswap.finance/swap"]) - ;; => nil - +( ) diff --git a/src/status_im/contexts/browser/constants.cljs b/src/status_im/contexts/browser/constants.cljs index 5c6537ed311..5a21d20b7e4 100644 --- a/src/status_im/contexts/browser/constants.cljs +++ b/src/status_im/contexts/browser/constants.cljs @@ -3,3 +3,5 @@ (def browser-width (-> (rn/get-window) :width)) (def browser-height "100%") + +(def default-chain-id 1) diff --git a/src/status_im/contexts/browser/db.cljs b/src/status_im/contexts/browser/db.cljs index 31eab848e2e..1c66c79ea4a 100644 --- a/src/status_im/contexts/browser/db.cljs +++ b/src/status_im/contexts/browser/db.cljs @@ -1,4 +1,5 @@ -(ns status-im.contexts.browser.db) +(ns status-im.contexts.browser.db + (:require [status-im.contexts.browser.constants :as browser.constants])) (defn get-tab-ids [db] @@ -36,3 +37,14 @@ [db tab-id] (->> (get-dapp-id db tab-id) (get-dapp-by-id db))) + +(defn get-dapp-permissions + [db] + (get db :browser/permissions)) + +(defn get-dapp-chain-id + [db dapp-url] + (-> db + get-dapp-permissions + (get dapp-url) + (get :chain-id browser.constants/default-chain-id))) diff --git a/src/status_im/contexts/browser/events/core.cljs b/src/status_im/contexts/browser/events/core.cljs index 389c8567445..3d46f75738c 100644 --- a/src/status_im/contexts/browser/events/core.cljs +++ b/src/status_im/contexts/browser/events/core.cljs @@ -32,6 +32,21 @@ {:db (assoc db :browser/focused-tab-id tab-id)})))) (rf/reg-event-fx :browser/init + (fn [{:keys [db]}] + {:db (-> db + (assoc :browser/tabs-by-id {} + :browser/tab-ids [])) + :fx [[:fx.browser/get-dapp-permissions + {:on-error (fn [error] (println :error error)) + :on-success (fn [permissions] + (rf/dispatch [:browser/on-init-done permissions]))}]]})) + +(rf/reg-event-fx :browser/on-init-done + (fn [{:keys [db]} [permissions]] + {:db (assoc db :browser/permissions permissions) + :fx [[:dispatch [:browser/init-tabs]]]})) + +(rf/reg-event-fx :browser/init-tabs (fn [{:keys [db]}] {:db (-> db (assoc :browser/tabs-by-id {} diff --git a/src/status_im/contexts/browser/events/effects.cljs b/src/status_im/contexts/browser/events/effects.cljs index 53de63563f6..bd8a5154e74 100644 --- a/src/status_im/contexts/browser/events/effects.cljs +++ b/src/status_im/contexts/browser/events/effects.cljs @@ -1,7 +1,9 @@ (ns status-im.contexts.browser.events.effects (:require [cljs.pprint :as pprint] + [promesa.core :as promesa] [re-frame.core :as rf] - [react-native.webview :as webview])) + [react-native.webview :as webview] + [status-im.contexts.browser.api :as browser.api])) (rf/reg-fx :fx.browser/send-message (fn [[webview-ref message]] @@ -9,3 +11,24 @@ (pprint/pprint message) (webview/post-message webview-ref message))) +(rf/reg-fx :fx.browser/get-dapp-permissions + (fn [{:keys [on-success on-error]}] + (-> (browser.api/get-dapp-permissions) + (promesa/then on-success) + (promesa/catch on-error)))) + +(rf/reg-fx :fx.browser/call-connector-rpc + (fn [{:keys [rpc-event dapp on-success on-error]}] + (-> (browser.api/call-connector-rpc rpc-event dapp) + (promesa/then on-success) + (promesa/catch on-error)))) + +(rf/reg-fx :fx.browser/approve-request-accounts + (fn [{:keys [response on-error]}] + (-> (browser.api/approve-accounts-request response) + (promesa/catch on-error)))) + +(rf/reg-fx :fx.browser/reject-request-accounts + (fn [{:keys [response on-error]}] + (-> (browser.api/reject-accounts-request response) + (promesa/catch on-error)))) diff --git a/src/status_im/contexts/browser/events/rpc.cljs b/src/status_im/contexts/browser/events/rpc.cljs index 961d20ecbec..8a74e444faa 100644 --- a/src/status_im/contexts/browser/events/rpc.cljs +++ b/src/status_im/contexts/browser/events/rpc.cljs @@ -1,5 +1,6 @@ (ns status-im.contexts.browser.events.rpc - (:require [re-frame.core :as rf] + (:require [cljs.pprint :as pprint] + [re-frame.core :as rf] [status-im.contexts.browser.components.approve-bottom-sheet :as approve-bottom-sheet] [status-im.contexts.browser.db :as browser.db] [status-im.contexts.browser.rpc-errors :as rpc-errors] @@ -9,17 +10,20 @@ [taoensso.timbre :as log] [utils.hex :as hex])) -(rf/reg-event-fx :browser.rpc/show-approve-modal - (fn [_ [tab-id event]] - {:fx [[:dispatch - [:show-bottom-sheet - {:hide-handle? true - :drag-content? false - :hide-on-background-press? false - :content (fn [] - [approve-bottom-sheet/view - {:tab-id tab-id - :event event}])}]]]})) +(rf/reg-event-fx :browser.rpc/process-rpc + (fn [{:keys [db]} [tab-id rpc-event]] + (let [tab (-> db (browser.db/get-tab tab-id)) + connector-dapp {:url (:dapp-id tab) + :name (-> tab :metadata :page-title (or (:dapp-id tab))) + :iconUrl (-> tab :metadata :logo-url) + :chainId (browser.db/get-dapp-chain-id db (:dapp-id tab))}] + (pprint/pprint rpc-event) + {:fx [[:fx.browser/call-connector-rpc + {:rpc-event rpc-event + :dapp connector-dapp + :on-error #(rf/dispatch [:browser.rpc/send-error tab-id rpc-event + :rpc.error/user-rejected]) + :on-success #(rf/dispatch [:browser.rpc/send tab-id rpc-event %])}]]}))) (rf/reg-event-fx :browser.rpc/on-event (fn [{:keys [db]} [tab-id event]] @@ -32,7 +36,7 @@ (log/info (str "Queue size: " (count current-queue))) {:db (assoc db :browser/rpc-queue new-queue) :fx [(when (empty? current-queue) - [:dispatch [:browser.rpc/show-approve-modal tab-id rpc-event]])]}))) + [:dispatch [:browser.rpc/process-rpc tab-id rpc-event]])]}))) (rf/reg-event-fx :browser.rpc/call-next-in-queue (fn [{:keys [db]}] @@ -41,7 +45,43 @@ {:keys [tab-id event]} (first remaining-queue)] {:db (assoc db :browser/rpc-queue remaining-queue) :fx [(when event - [:dispatch [:browser.rpc/show-approve-modal tab-id event]])]}))) + [:dispatch [:browser.rpc/process-rpc tab-id event]])]}))) + +(rf/reg-event-fx :browser.rpc/on-request-accounts-signal + (fn [_ [request]] + {:fx [[:dispatch + [:show-bottom-sheet + {:hide-handle? true + :drag-content? false + :hide-on-background-press? false + :content (fn [] + [approve-bottom-sheet/view {:request request}])}]]]})) + +(rf/reg-event-fx :browser.rpc/approve-request-accounts + (fn [{:keys [db]} [request]] + (let [addresses (account.db/get-accounts-addresses db) + response {:requestId (:request-id request) + :chainId 1 + :account (first addresses)}] + {:fx [[:fx.browser/approve-request-accounts + {:response response + :on-error #(log/error "Failed to approve" + {:error % + :response response})}]]}))) + +(rf/reg-event-fx :browser.rpc/reject-request-accounts + (fn [_ [request]] + (let [response {:requestId (:request-id request)}] + {:fx [[:fx.browser/reject-request-accounts + {:response response + :on-error #(log/error "Failed to reject" + {:error % + :response response})}]]}))) + +(rf/reg-event-fx :browser.rpc/on-permission-granted + (fn [_ [dapp]] + (log/info "dApp permission granted") + (pprint/pprint dapp))) (rf/reg-event-fx :browser.rpc/handle-rpc (fn [_ [tab-id rpc-event]] @@ -71,6 +111,7 @@ (fn [{:keys [db]} [tab-id rpc-event message]] (let [rpc-id (:id rpc-event) webview-ref (get-in db [:browser/tabs-by-id tab-id :ref])] + (println :browser.rpc/send message) {:fx [[:fx.browser/send-message [webview-ref {:id rpc-id diff --git a/src/status_im/contexts/browser/subs/core.cljs b/src/status_im/contexts/browser/subs/core.cljs index f3d687b4c70..9c6c21fdb92 100644 --- a/src/status_im/contexts/browser/subs/core.cljs +++ b/src/status_im/contexts/browser/subs/core.cljs @@ -9,6 +9,10 @@ (fn [db] (get db :browser/tabs-by-id))) +(rf/reg-sub :browser/permissions + (fn [db] + (get db :browser/permissions))) + (rf/reg-sub :browser/focused-tab-id (fn [db] (let [default (-> db :browser/tab-ids first)] diff --git a/src/status_im/contexts/profile/config.cljs b/src/status_im/contexts/profile/config.cljs index 36dedee9424..2b635aec508 100644 --- a/src/status_im/contexts/profile/config.cljs +++ b/src/status_im/contexts/profile/config.cljs @@ -32,7 +32,8 @@ :alchemyArbitrumMainnetToken config/ALCHEMY_ARBITRUM_MAINNET_TOKEN :alchemyArbitrumSepoliaToken config/ALCHEMY_ARBITRUM_SEPOLIA_TOKEN :alchemyBaseMainnetToken config/ALCHEMY_BASE_MAINNET_TOKEN - :alchemyBaseSepoliaToken config/ALCHEMY_BASE_SEPOLIA_TOKEN}) + :alchemyBaseSepoliaToken config/ALCHEMY_BASE_SEPOLIA_TOKEN + :apiConfig {:connectorEnabled true}}) (defn- common-config [] From c94cd75702bc71cd059aa668de3fc591b6939fba Mon Sep 17 00:00:00 2001 From: Cristian Lungu Date: Thu, 15 May 2025 13:55:33 +0300 Subject: [PATCH 08/14] feat: transactions init --- src/react_native/webview.cljs | 6 - src/status_im/common/signals/events.cljs | 23 +- src/status_im/contexts/browser/api.cljs | 13 +- .../components/approve_bottom_sheet.cljs | 37 --- .../components/request_accounts_sheet.cljs | 63 +++++ .../components/request_transaction.cljs | 54 ++++ .../contexts/browser/events/core.cljs | 25 +- .../contexts/browser/events/effects.cljs | 31 +-- .../contexts/browser/events/rpc.cljs | 247 ++++++++++-------- .../contexts/browser/rpc_errors.cljs | 18 -- .../collectible/tabs/overview/view.cljs | 2 +- .../modals/session_proposal/view.cljs | 2 +- .../modals/session_proposal/view.cljs~ | 163 ++++++++++++ src/status_im/effects.cljs | 12 + src/status_im/events.cljs | 1 + src/status_im/navigation/events.cljs | 17 +- src/status_im/subs/wallet/dapps/core.cljs | 2 +- src/utils/hex.cljs | 1 + 18 files changed, 488 insertions(+), 229 deletions(-) delete mode 100644 src/status_im/contexts/browser/components/approve_bottom_sheet.cljs create mode 100644 src/status_im/contexts/browser/components/request_accounts_sheet.cljs create mode 100644 src/status_im/contexts/browser/components/request_transaction.cljs create mode 100644 src/status_im/contexts/wallet/wallet_connect/modals/session_proposal/view.cljs~ create mode 100644 src/status_im/effects.cljs diff --git a/src/react_native/webview.cljs b/src/react_native/webview.cljs index 60dd1fd3689..4934dd4a564 100644 --- a/src/react_native/webview.cljs +++ b/src/react_native/webview.cljs @@ -6,12 +6,6 @@ (def view (reagent/adapt-react-class rn-webview)) -;; TODO: doesn't exist pretty sure, remove -(defn set-active - [^js webview-ref active?] - (some-> ^js webview-ref - (.setActive active?))) - (defn go-back [^js webview-ref] (some-> ^js webview-ref diff --git a/src/status_im/common/signals/events.cljs b/src/status_im/common/signals/events.cljs index e05a74f668b..58b0fc77ec4 100644 --- a/src/status_im/common/signals/events.cljs +++ b/src/status_im/common/signals/events.cljs @@ -119,7 +119,28 @@ "connector.dAppPermissionGranted" {:fx [[:dispatch - [:browser.rpc/on-permission-granted + [:browser.rpc/on-permission-granted-signal + (->> event-js + transforms/js->clj + (cske/transform-keys transforms/->kebab-case-keyword))]]]} + + "connector.dAppChainIdSwitched" + {:fx [[:dispatch + [:browser.rpc/on-chain-switched-signal + (->> event-js + transforms/js->clj + (cske/transform-keys transforms/->kebab-case-keyword))]]]} + + "connector.dAppPermissionRevoked" + {:fx [[:dispatch + [:browser.rpc/on-permission-revoked-signal + (->> event-js + transforms/js->clj + (cske/transform-keys transforms/->kebab-case-keyword))]]]} + + "connector.sendTransaction" + {:fx [[:dispatch + [:browser.rpc/on-transaction-signal (->> event-js transforms/js->clj (cske/transform-keys transforms/->kebab-case-keyword))]]]} diff --git a/src/status_im/contexts/browser/api.cljs b/src/status_im/contexts/browser/api.cljs index 91afe803205..cddcda6b567 100644 --- a/src/status_im/contexts/browser/api.cljs +++ b/src/status_im/contexts/browser/api.cljs @@ -23,7 +23,6 @@ (merge json-rpc) (merge dapp) (dissoc :topic))] - (pprint/pprint arg) (rpc/call-async :connector_callRPC false (transforms/clj->json arg)))) (defn approve-accounts-request @@ -38,6 +37,18 @@ clj->js (rpc/call-async :connector_requestAccountsRejected false))) +(defn approve-transaction + [response] + (->> response + clj->js + (rpc/call-async :connector_sendTransactionAccepted false))) + +(defn reject-transaction + [response] + (->> response + clj->js + (rpc/call-async :connector_sendTransactionRejected false))) + (defn get-dapp-browsers [] (-> (rpc/call-async :wakuext_getBrowsers false) diff --git a/src/status_im/contexts/browser/components/approve_bottom_sheet.cljs b/src/status_im/contexts/browser/components/approve_bottom_sheet.cljs deleted file mode 100644 index 966dfdf628d..00000000000 --- a/src/status_im/contexts/browser/components/approve_bottom_sheet.cljs +++ /dev/null @@ -1,37 +0,0 @@ -(ns status-im.contexts.browser.components.approve-bottom-sheet - (:require [quo.core :as quo] - [react-native.core :as rn] - [react-native.fast-image :as fast-image] - [utils.re-frame :as rf])) - -(defn on-approve - [request] - (rf/dispatch [:browser.rpc/approve-request-accounts request]) - (rf/dispatch [:hide-bottom-sheet])) - -(defn on-reject - [request] - (rf/dispatch [:browser.rpc/reject-request-accounts request]) - (rf/dispatch [:hide-bottom-sheet])) - -(defn view - [{:keys [request]}] - (let [{:keys [icon-url name]} request] - [rn/view {:style {:padding-top 20}} - [rn/view - {:style {:padding-horizontal 20 - :flex-direction :row - :margin-bottom 12}} - [fast-image/fast-image - {:source icon-url - :style {:width 20 :height 20 :border-radius 8 :margin-right 8}}] - [quo/text - {:style {:margin-bottom 12} - :weight :bold - :size :heading-2} (str name " requests accounts permission")]] - [quo/bottom-actions - {:actions :two-actions - :button-one-label "Approve" - :button-one-props {:on-press #(on-approve request)} - :button-two-label "Reject" - :button-two-props {:on-press #(on-reject request)}}]])) diff --git a/src/status_im/contexts/browser/components/request_accounts_sheet.cljs b/src/status_im/contexts/browser/components/request_accounts_sheet.cljs new file mode 100644 index 00000000000..d9c2ffba8dd --- /dev/null +++ b/src/status_im/contexts/browser/components/request_accounts_sheet.cljs @@ -0,0 +1,63 @@ +(ns status-im.contexts.browser.components.request-accounts-sheet + (:require [quo.core :as quo] + [react-native.core :as rn] + [utils.i18n :as i18n] + [utils.re-frame :as rf])) + +(defn on-approve + [request address] + (rf/dispatch [:browser.rpc/approve-request-accounts request address]) + (rf/dispatch [:hide-bottom-sheet])) + +(defn on-reject + [request] + (rf/dispatch [:browser.rpc/reject-request-accounts request]) + (rf/dispatch [:hide-bottom-sheet])) + +(defn view + [{:keys [request]}] + (let [{:keys [icon-url name url]} request + accounts (rf/sub [:wallet/operable-accounts]) + addresses (map :address accounts) + [selected-address set-selected-address] (rn/use-state (first addresses))] + (println :selected selected-address) + [rn/view {:style {:padding-top 20}} + [rn/view {:style {:margin-bottom 20}} + [rn/view + {:style {:margin-bottom 12 + :padding-horizontal 20}} + [quo/user-avatar + {:profile-picture icon-url + :size :big + :full-name name}]] + [quo/page-top + {:title name + :description :context-tag + :context-tag {:type :icon + :size 32 + :icon :i/link + :context url}}]] + [rn/view {:style {:padding-horizontal 20}} + [quo/text + {:size :heading-2 + :weight :semi-bold + :accessibility-label "select-account-title"} + (i18n/label :t/select-account)] + [rn/view {:style {:margin-top 12 :margin-bottom 20}} + (for [{:keys [address] :as account} accounts] + ^{:key (str address)} + [quo/account-item + {:type :default + :state (if (= address selected-address) + :selected + :default) + :account-props account + :on-press (fn [] + (set-selected-address (:address account)))}])]] + [quo/bottom-actions + {:actions :two-actions + :button-one-label "Approve" + :button-one-props {:on-press #(on-approve request selected-address)} + :button-two-label "Reject" + :button-two-props {:type :outline + :on-press #(on-reject request)}}]])) diff --git a/src/status_im/contexts/browser/components/request_transaction.cljs b/src/status_im/contexts/browser/components/request_transaction.cljs new file mode 100644 index 00000000000..6efda3c16f7 --- /dev/null +++ b/src/status_im/contexts/browser/components/request_transaction.cljs @@ -0,0 +1,54 @@ +(ns status-im.contexts.browser.components.request-transaction + (:require [quo.core :as quo] + [react-native.core :as rn] + [status-im.common.raw-data-block.view :as raw-data-block] + [utils.re-frame :as rf] + [utils.transforms :as transforms])) + +(defn on-approve + [{:keys [tx-args] :as request}] + ;;(rf/dispatch [:browser.rpc/approve-transaction request tx-hash]) + (rf/dispatch [:browser.rpc/reject-transaction request]) + (rf/dispatch [:hide-bottom-sheet])) + +(defn on-reject + [request] + (rf/dispatch [:browser.rpc/reject-transaction request]) + (rf/dispatch [:hide-bottom-sheet])) + +(defn view + [{:keys [request]}] + (let [{:keys [icon-url name url tx-args]} request] + [rn/view {:style {:padding-top 20}} + [rn/view {:style {:margin-bottom 20}} + [rn/view + {:style {:margin-bottom 12 + :padding-horizontal 20}} + [quo/user-avatar + {:profile-picture icon-url + :size :big + :full-name name}]] + [quo/page-top + {:title name + :description :context-tag + :context-tag {:type :icon + :size 32 + :icon :i/link + :context url}}]] + [rn/view {:style {:padding-horizontal 20}} + [quo/text + {:size :heading-2 + :weight :semi-bold + :accessibility-label "select-account-title"} + "Sign transaction"] + [raw-data-block/view + (-> tx-args + transforms/json->clj + (transforms/clj->pretty-json 2))]] + [quo/bottom-actions + {:actions :two-actions + :button-one-label "Approve" + :button-one-props {:on-press #(on-approve request)} + :button-two-label "Reject" + :button-two-props {:type :outline + :on-press #(on-reject request)}}]])) diff --git a/src/status_im/contexts/browser/events/core.cljs b/src/status_im/contexts/browser/events/core.cljs index 3d46f75738c..284195dca83 100644 --- a/src/status_im/contexts/browser/events/core.cljs +++ b/src/status_im/contexts/browser/events/core.cljs @@ -1,10 +1,12 @@ (ns status-im.contexts.browser.events.core (:require [cljs.pprint :as pprint] [re-frame.core :as rf] + [status-im.contexts.browser.api :as browser.api] [status-im.contexts.browser.db :as browser.db] status-im.contexts.browser.events.effects status-im.contexts.browser.events.rpc - [status-im.contexts.browser.messages :as messages])) + [status-im.contexts.browser.messages :as messages] + [taoensso.timbre :as log])) (rf/reg-event-fx :browser/set-tab-ref (fn [{:keys [db]} [tab-id ref]] @@ -32,28 +34,17 @@ {:db (assoc db :browser/focused-tab-id tab-id)})))) (rf/reg-event-fx :browser/init - (fn [{:keys [db]}] - {:db (-> db - (assoc :browser/tabs-by-id {} - :browser/tab-ids [])) - :fx [[:fx.browser/get-dapp-permissions - {:on-error (fn [error] (println :error error)) - :on-success (fn [permissions] - (rf/dispatch [:browser/on-init-done permissions]))}]]})) - -(rf/reg-event-fx :browser/on-init-done - (fn [{:keys [db]} [permissions]] - {:db (assoc db :browser/permissions permissions) - :fx [[:dispatch [:browser/init-tabs]]]})) + (fn [_] + {:fx [[:dispatch [:browser/init-tabs]] + [:dispatch [:browser.rpc/get-permissions]]]})) (rf/reg-event-fx :browser/init-tabs (fn [{:keys [db]}] {:db (-> db (assoc :browser/tabs-by-id {} :browser/tab-ids [])) - :fx [[:dispatch [:browser/add-tab "https://app.uniswap.org"]] - [:dispatch [:browser/add-tab "https://app.1inch.io"]] - [:dispatch [:browser/add-tab "https://vibor.it"]] + :fx [[:dispatch [:browser/add-tab "https://pancakeswap.finance/swap"]] + [:dispatch [:browser/add-tab "https://app.uniswap.org"]] [:dispatch [:browser/focus-tab 0]]]})) (rf/reg-event-fx :browser/on-message diff --git a/src/status_im/contexts/browser/events/effects.cljs b/src/status_im/contexts/browser/events/effects.cljs index bd8a5154e74..4c73a03f910 100644 --- a/src/status_im/contexts/browser/events/effects.cljs +++ b/src/status_im/contexts/browser/events/effects.cljs @@ -1,34 +1,7 @@ (ns status-im.contexts.browser.events.effects - (:require [cljs.pprint :as pprint] - [promesa.core :as promesa] - [re-frame.core :as rf] - [react-native.webview :as webview] - [status-im.contexts.browser.api :as browser.api])) + (:require [re-frame.core :as rf] + [react-native.webview :as webview])) (rf/reg-fx :fx.browser/send-message (fn [[webview-ref message]] - (println :send-response) - (pprint/pprint message) (webview/post-message webview-ref message))) - -(rf/reg-fx :fx.browser/get-dapp-permissions - (fn [{:keys [on-success on-error]}] - (-> (browser.api/get-dapp-permissions) - (promesa/then on-success) - (promesa/catch on-error)))) - -(rf/reg-fx :fx.browser/call-connector-rpc - (fn [{:keys [rpc-event dapp on-success on-error]}] - (-> (browser.api/call-connector-rpc rpc-event dapp) - (promesa/then on-success) - (promesa/catch on-error)))) - -(rf/reg-fx :fx.browser/approve-request-accounts - (fn [{:keys [response on-error]}] - (-> (browser.api/approve-accounts-request response) - (promesa/catch on-error)))) - -(rf/reg-fx :fx.browser/reject-request-accounts - (fn [{:keys [response on-error]}] - (-> (browser.api/reject-accounts-request response) - (promesa/catch on-error)))) diff --git a/src/status_im/contexts/browser/events/rpc.cljs b/src/status_im/contexts/browser/events/rpc.cljs index 8a74e444faa..1a3013c1841 100644 --- a/src/status_im/contexts/browser/events/rpc.cljs +++ b/src/status_im/contexts/browser/events/rpc.cljs @@ -1,15 +1,24 @@ (ns status-im.contexts.browser.events.rpc (:require [cljs.pprint :as pprint] [re-frame.core :as rf] - [status-im.contexts.browser.components.approve-bottom-sheet :as approve-bottom-sheet] + [status-im.contexts.browser.api :as browser.api] + [status-im.contexts.browser.components.request-accounts-sheet :as request-accounts-sheet] [status-im.contexts.browser.db :as browser.db] - [status-im.contexts.browser.rpc-errors :as rpc-errors] - [status-im.contexts.browser.rpc-params :as rpc-params] - [status-im.contexts.wallet.account.db :as account.db] [status-im.contexts.wallet.networks.db :as networks.db] [taoensso.timbre :as log] [utils.hex :as hex])) +(rf/reg-event-fx :browser.rpc/get-permissions + (fn [_] + {:fx [[:fx.promise + {:promise browser.api/get-dapp-permissions + :on-success [:browser/store-permissions] + :on-error #(log/error "Failed to get dapp permissions" {:error %})}]]})) + +(rf/reg-event-fx :browser/store-permissions + (fn [{:keys [db]} [permissions]] + {:db (assoc db :browser/permissions permissions)})) + (rf/reg-event-fx :browser.rpc/process-rpc (fn [{:keys [db]} [tab-id rpc-event]] (let [tab (-> db (browser.db/get-tab tab-id)) @@ -17,13 +26,34 @@ :name (-> tab :metadata :page-title (or (:dapp-id tab))) :iconUrl (-> tab :metadata :logo-url) :chainId (browser.db/get-dapp-chain-id db (:dapp-id tab))}] - (pprint/pprint rpc-event) - {:fx [[:fx.browser/call-connector-rpc - {:rpc-event rpc-event - :dapp connector-dapp - :on-error #(rf/dispatch [:browser.rpc/send-error tab-id rpc-event - :rpc.error/user-rejected]) - :on-success #(rf/dispatch [:browser.rpc/send tab-id rpc-event %])}]]}))) + {:fx [[:fx.promise + {:promise #(browser.api/call-connector-rpc rpc-event connector-dapp) + :on-success [:browser.rpc/send tab-id rpc-event] + :on-error [:browser.rpc/send-error tab-id rpc-event]}]]}))) + +(rf/reg-event-fx :browser.rpc/send + (fn [{:keys [db]} [tab-id rpc-event message]] + (let [rpc-id (:id rpc-event) + webview-ref (get-in db [:browser/tabs-by-id tab-id :ref])] + {:fx [[:fx.browser/send-message + [webview-ref + {:id rpc-id + :jsonrpc "2.0" + :type "rpcResponse" + :result message}]] + [:dispatch [:browser.rpc/call-next-in-queue]]]}))) + +(rf/reg-event-fx :browser.rpc/send-error + (fn [{:keys [db]} [tab-id rpc-event error]] + (let [rpc-id (:id rpc-event) + webview-ref (get-in db [:browser/tabs-by-id tab-id :ref])] + {:fx [[:fx.browser/send-message + [webview-ref + {:id rpc-id + :jsonrpc "2.0" + :type "rpcResponse" + :error error}]] + [:dispatch [:browser.rpc/call-next-in-queue]]]}))) (rf/reg-event-fx :browser.rpc/on-event (fn [{:keys [db]} [tab-id event]] @@ -32,8 +62,6 @@ new-queue (conj current-queue {:tab-id tab-id :event rpc-event})] - (log/info (str "Browser RPC " (-> rpc-event :method) " for " (browser.db/get-tab-url db tab-id))) - (log/info (str "Queue size: " (count current-queue))) {:db (assoc db :browser/rpc-queue new-queue) :fx [(when (empty? current-queue) [:dispatch [:browser.rpc/process-rpc tab-id rpc-event]])]}))) @@ -47,122 +75,111 @@ :fx [(when event [:dispatch [:browser.rpc/process-rpc tab-id event]])]}))) -(rf/reg-event-fx :browser.rpc/on-request-accounts-signal +(rf/reg-event-fx :browser.rpc/approve-request-accounts + (fn [{:keys [db]} [request address]] + (let [response {:requestId (:request-id request) + :chainId (browser.db/get-dapp-chain-id db (:url request)) + :account address}] + {:fx [[:fx.promise + {:promise #(browser.api/approve-accounts-request response) + :on-error #(log/error "Failed to approve" + {:error % + :response response})}]]}))) + +(rf/reg-event-fx :browser.rpc/reject-request-accounts (fn [_ [request]] + (let [response {:requestId (:request-id request)}] + {:fx [[:fx.promise + {:promise #(browser.api/reject-accounts-request response) + :on-error #(log/error "Failed to reject" + {:error % + :response response})}]]}))) + +(rf/reg-event-fx :browser.rpc/on-permission-granted-signal + (fn [{:keys [db]} [connector-dapp]] + (let [{:keys [url name] :as dapp-permission} (-> connector-dapp + (assoc :chain-id (-> connector-dapp :chains first)) + (dissoc :chains))] + (log/info "dApp permission granted") + {:db (assoc-in db [:browser/permissions url] dapp-permission) + :fx [[:dispatch + [:toasts/upsert + {:type :positive + :text (str "Connected to " name)}]]]}))) + +(rf/reg-event-fx :browser.rpc/on-permission-revoked-signal + (fn [{:keys [db]} [{:keys [url name]}]] + (log/info "dApp permission revoked") + {:db (update-in db [:browser/permissions] dissoc url) + :fx [[:dispatch + [:toasts/upsert + {:type :positive + :text (str "Disconnected from " name)}]]]})) + +(rf/reg-event-fx :browser.rpc/show-approval-sheet + (fn [_ [{:keys [content]}]] {:fx [[:dispatch [:show-bottom-sheet {:hide-handle? true :drag-content? false :hide-on-background-press? false - :content (fn [] - [approve-bottom-sheet/view {:request request}])}]]]})) + :content content}]]]})) -(rf/reg-event-fx :browser.rpc/approve-request-accounts - (fn [{:keys [db]} [request]] - (let [addresses (account.db/get-accounts-addresses db) - response {:requestId (:request-id request) - :chainId 1 - :account (first addresses)}] - {:fx [[:fx.browser/approve-request-accounts - {:response response +(rf/reg-event-fx :browser.rpc/on-request-accounts-signal + (fn [_ [request]] + {:fx [[:dispatch + [:browser.rpc/show-approval-sheet + {:content (fn [] + [request-accounts-sheet/view {:request request}])}]]]})) + +(rf/reg-event-fx :browser.rpc/on-transaction-signal + (fn [_ [request]] + {:fx [[:dispatch + [:browser.rpc/show-approval-sheet + {:content (fn [] + [request-accounts-sheet/view {:request request}])}]]]})) + +;; (rf/reg-event-fx :browser.rpc/sign-transaction +;; (fn [{:keys [db]} [request]] +;; {:fx [[:dispatch +;; [:standard-auth/authorize-and-sign +;; {:sign-payload (:tx-args request) +;; :theme :dark +;; :blur? false +;; :on-sign-success (fn [signatures] +;; (let [ (-> signatures +;; first +;; :signature +;; hex/prefix-hex)])) +;; :on-sign-error identity +;; :auth-button-label "Sign transaction"}]]]})) + +(rf/reg-event-fx :browser.rpc/approve-transaction + (fn [_ [request tx-hash]] + (let [response {:requestId (:request-id request) + :hash tx-hash}] + {:fx [[:fx.promise + {:promise #(browser.api/approve-accounts-request response) :on-error #(log/error "Failed to approve" {:error % :response response})}]]}))) -(rf/reg-event-fx :browser.rpc/reject-request-accounts +(rf/reg-event-fx :browser.rpc/reject-transaction (fn [_ [request]] (let [response {:requestId (:request-id request)}] - {:fx [[:fx.browser/reject-request-accounts - {:response response + {:fx [[:fx.promise + {:promise #(browser.api/reject-accounts-request response) :on-error #(log/error "Failed to reject" {:error % :response response})}]]}))) -(rf/reg-event-fx :browser.rpc/on-permission-granted - (fn [_ [dapp]] - (log/info "dApp permission granted") - (pprint/pprint dapp))) - -(rf/reg-event-fx :browser.rpc/handle-rpc - (fn [_ [tab-id rpc-event]] - (let [rpc-method (:method rpc-event)] - {:fx [(condp = rpc-method - "eth_requestAccounts" - [:dispatch [:browser.rpc/accounts tab-id rpc-event]] - - "eth_accounts" - [:dispatch [:browser.rpc/accounts tab-id rpc-event]] - - "wallet_requestPermissions" - [:dispatch [:browser.rpc/request-permissions tab-id rpc-event]] - - "wallet_switchEthereumChain" - [:dispatch [:browser.rpc/switch-eth-chain tab-id rpc-event]] - - "eth_chainId" - [:dispatch [:browser.rpc/chain-id tab-id rpc-event]] - - (do - (log/error "Unhandled rpc method" rpc-method) - [:dispatch - [:browser.rpc/send-error tab-id rpc-event :rpc.error/method-not-available]]))]}))) - -(rf/reg-event-fx :browser.rpc/send - (fn [{:keys [db]} [tab-id rpc-event message]] - (let [rpc-id (:id rpc-event) - webview-ref (get-in db [:browser/tabs-by-id tab-id :ref])] - (println :browser.rpc/send message) - {:fx [[:fx.browser/send-message - [webview-ref - {:id rpc-id - :jsonrpc "2.0" - :type "rpcResponse" - :result message}]] - [:dispatch [:browser.rpc/call-next-in-queue]]]}))) - -(rf/reg-event-fx :browser.rpc/send-error - (fn [{:keys [db]} [tab-id rpc-event error]] - (let [rpc-id (:id rpc-event) - webview-ref (get-in db [:browser/tabs-by-id tab-id :ref])] - {:fx [[:fx.browser/send-message - [webview-ref - {:id rpc-id - :jsonrpc "2.0" - :type "rpcResponse" - :error (rpc-errors/get-error error)}]] - [:dispatch [:browser.rpc/call-next-in-queue]]]}))) - -(rf/reg-event-fx :browser.rpc/accounts - (fn [{:keys [db]} [tab-id rpc-event]] - (let [accounts (account.db/get-accounts-addresses db)] - {:fx [[:dispatch [:browser.rpc/send tab-id rpc-event accounts]]]}))) - -(rf/reg-event-fx :browser.rpc/switch-eth-chain - (fn [{:keys [db]} [tab-id rpc-event]] - (let [chain-ids (networks.db/get-chain-ids db) - requested-chain-id (rpc-params/switch-ethereum-chain rpc-event) - chain-supported? (contains? chain-ids requested-chain-id) - dapp-id (browser.db/get-dapp-id db tab-id)] - (if chain-supported? - {:fx [[:dispatch [:browser/update-dapp-chain-id dapp-id requested-chain-id]] - [:dispatch [:browser.rpc/send tab-id rpc-event nil]]]} - {:fx [[:dispatch - [:browser.rpc/send-error tab-id rpc-event :rpc.error/unrecognized-chain-id]]]})))) - -;; TODO: make a scheduler for rpc events -(rf/reg-event-fx :browser.rpc/chain-id - (fn [{:keys [db]} [tab-id rpc-event]] - (let [dapp (browser.db/get-tab-dapp db tab-id) - chain-id (-> dapp :chain-id hex/number-to-hex)] - {:fx [[:dispatch [:browser.rpc/send tab-id rpc-event chain-id]]]}))) - -(rf/reg-event-fx :browser.rpc/request-permissions - (fn [{:keys [db]} [tab-id rpc-event]] - (let [;;permissions (-> rpc-event :params first) - tab-url (browser.db/get-tab-url db tab-id)] - {:fx [[:dispatch - [:browser.rpc/send tab-id rpc-event - [{:parentCapability "eth_accounts" - :caveats [{:type "restrictReturnedAccounts" - :value (account.db/get-accounts-addresses db)}] - :invoker tab-url}]]]]}))) +(rf/reg-event-fx :browser.rpc/on-chain-switched-signal + (fn [{:keys [db]} [{:keys [chain-id url]}]] + (let [{:keys [page-title]} (browser.db/get-dapp-by-id db url) + new-chain-id (hex/hex-to-number chain-id) + network-name (networks.db/get-network-name db new-chain-id)] + {:db (assoc-in db [:browser/permissions url :chain-id] new-chain-id) + :fx [[:dispatch + [:toasts/upsert + {:type :positive + :text (str page-title " switched the network to " network-name)}]]]}))) diff --git a/src/status_im/contexts/browser/rpc_errors.cljs b/src/status_im/contexts/browser/rpc_errors.cljs index f4564477950..e69de29bb2d 100644 --- a/src/status_im/contexts/browser/rpc_errors.cljs +++ b/src/status_im/contexts/browser/rpc_errors.cljs @@ -1,18 +0,0 @@ -(ns status-im.contexts.browser.rpc-errors) - -(defn get-error - ([error-key] - (get-error error-key {})) - ([error-key params] - (condp = error-key - :rpc.error/method-not-available - {:code -32601 - :message (str "The method" (:method params) "does not exist/is not available")} - - :rpc.error/user-rejected - {:code 4001 - :message "User rejected the request."} - - :rpc.error/unrecognized-chain-id - {:code -32602 - :message (str "Unrecognized chain ID " (:chain-id params))}))) diff --git a/src/status_im/contexts/wallet/collectible/tabs/overview/view.cljs b/src/status_im/contexts/wallet/collectible/tabs/overview/view.cljs index 8dc2a42ca8a..12f9bb428b7 100644 --- a/src/status_im/contexts/wallet/collectible/tabs/overview/view.cljs +++ b/src/status_im/contexts/wallet/collectible/tabs/overview/view.cljs @@ -61,7 +61,7 @@ [collectible] (let [owner-address (or (rf/sub [:wallet/current-viewing-account-address]) (-> collectible :ownership first :address)) - owner-account (rf/sub [:wallet-connect/account-details-by-address owner-address]) + owner-account (rf/sub [:dapps/account-details-by-address owner-address]) traits (-> collectible :collectible-data :traits)] [:<> [info diff --git a/src/status_im/contexts/wallet/wallet_connect/modals/session_proposal/view.cljs b/src/status_im/contexts/wallet/wallet_connect/modals/session_proposal/view.cljs index 245066be89c..d419ebce586 100644 --- a/src/status_im/contexts/wallet/wallet_connect/modals/session_proposal/view.cljs +++ b/src/status_im/contexts/wallet/wallet_connect/modals/session_proposal/view.cljs @@ -88,7 +88,7 @@ all-networks-in-session?]} (rf/sub [:wallet-connect/session-proposal-network-details]) address (rf/sub [:wallet-connect/current-proposal-address]) - {:keys [name customization-color emoji]} (rf/sub [:wallet-connect/account-details-by-address + {:keys [name customization-color emoji]} (rf/sub [:dapps/account-details-by-address address]) network-names (->> session-networks (map format-network-name) diff --git a/src/status_im/contexts/wallet/wallet_connect/modals/session_proposal/view.cljs~ b/src/status_im/contexts/wallet/wallet_connect/modals/session_proposal/view.cljs~ new file mode 100644 index 00000000000..245066be89c --- /dev/null +++ b/src/status_im/contexts/wallet/wallet_connect/modals/session_proposal/view.cljs~ @@ -0,0 +1,163 @@ +(ns status-im.contexts.wallet.wallet-connect.modals.session-proposal.view + (:require + [clojure.string :as string] + [quo.context] + [quo.core :as quo] + [react-native.core :as rn] + [status-im.common.floating-button-page.view :as floating-button-page] + [status-im.contexts.wallet.wallet-connect.modals.common.list-info-box.view :as list-info-box] + [status-im.contexts.wallet.wallet-connect.modals.session-proposal.style :as style] + [status-im.contexts.wallet.wallet-connect.utils.data-store :as data-store] + [utils.i18n :as i18n] + [utils.re-frame :as rf] + [utils.string])) + +(defn- dapp-metadata + [] + (let [proposer (rf/sub [:wallet-connect/session-proposer]) + {:keys [icons name url]} (:metadata proposer) + first-icon (first icons) + dapp-name (data-store/compute-dapp-name name url) + profile-picture (data-store/compute-dapp-icon-path first-icon url)] + [:<> + [rn/view {:style style/dapp-avatar} + [quo/user-avatar + {:profile-picture profile-picture + :size :big + :full-name dapp-name}]] + [quo/page-top + {:title dapp-name + :description :context-tag + :context-tag {:type :icon + :size 32 + :icon :i/link + :context url}}]])) + +(defn- approval-note + [] + (let [dapp-name (rf/sub [:wallet-connect/session-proposer-name])] + [list-info-box/view + {:dapp-name dapp-name + :container-style {:margin-horizontal 20}}])) + +(defn- format-network-name + [network] + (-> network :network-name name string/capitalize)) + +(defn- set-current-proposal-address + [acc] + (fn [] + (rf/dispatch [:wallet-connect/set-current-proposal-address (:address acc)]) + (rf/dispatch [:hide-bottom-sheet]))) + +(defn- accounts-list + [] + (let [accounts (rf/sub [:wallet/operable-accounts]) + selected-address (rf/sub [:wallet-connect/current-proposal-address])] + [rn/view {:style style/account-switcher-list} + (for [{:keys [address] :as account} accounts] + ^{:key (str address)} + [quo/account-item + {:type :default + :state (if (= address selected-address) + :selected + :default) + :account-props account + :on-press (set-current-proposal-address account)}])])) + +(defn- account-switcher-sheet + [] + [:<> + [rn/view {:style style/account-switcher-title} + [quo/text + {:size :heading-2 + :weight :semi-bold + :accessibility-label "select-account-title"} + (i18n/label :t/select-account)]] + [accounts-list]]) + +(defn- show-account-switcher-bottom-sheet + [] + (rf/dispatch + [:show-bottom-sheet + {:content account-switcher-sheet}])) + +(defn- connection-category + [] + (let [{:keys [session-networks + all-networks-in-session?]} (rf/sub + [:wallet-connect/session-proposal-network-details]) + address (rf/sub [:wallet-connect/current-proposal-address]) + {:keys [name customization-color emoji]} (rf/sub [:wallet-connect/account-details-by-address + address]) + network-names (->> session-networks + (map format-network-name) + (string/join ", ")) + network-images (mapv :source session-networks) + data-item-common-props {:blur? false + :card? false + :status :default + :size :large} + account-data-item-props (assoc data-item-common-props + :right-content {:type :accounts + :size :size-32 + :data [{:emoji emoji + :customization-color + customization-color}]} + :on-press show-account-switcher-bottom-sheet + :title (i18n/label :t/account-title) + :subtitle name + :right-icon :i/chevron-right) + networks-data-item-props (assoc data-item-common-props + :right-content {:type :network + :data network-images} + :title (i18n/label :t/networks) + :subtitle (if all-networks-in-session? + (i18n/label :t/all-networks) + network-names))] + [quo/category + {:blur? false + :list-type :data-item + :data [account-data-item-props + networks-data-item-props]}])) + +(defn- footer + [] + (let [customization-color (rf/sub [:profile/customization-color])] + [quo/bottom-actions + {:actions :two-actions + :buttons-container-style style/footer-buttons-container + :button-two-label (i18n/label :t/decline) + :button-two-props {:type :grey + :accessibility-label :wc-deny-connection + :on-press #(rf/dispatch + [:dismiss-modal + :screen/wallet.wallet-connect-session-proposal])} + :button-one-label (i18n/label :t/connect) + :button-one-props {:customization-color customization-color + :type :primary + :accessibility-label :wc-connect + :on-press #(rf/dispatch + [:wallet-connect/approve-session])}}])) + +(defn- header + [] + [quo/page-nav + {:type :no-title + :background :blur + :icon-name :i/close + :on-press (rn/use-callback + #(rf/dispatch [:dismiss-modal :screen/wallet.wallet-connect-session-proposal])) + :accessibility-label :wc-session-proposal-top-bar}]) + +(defn view + [] + (rn/use-unmount #(rf/dispatch [:wallet-connect/reject-session-proposal])) + [floating-button-page/view + {:footer-container-padding 0 + :header [header] + :footer [footer]} + [rn/view + [dapp-metadata] + [connection-category] + [approval-note]]]) diff --git a/src/status_im/effects.cljs b/src/status_im/effects.cljs new file mode 100644 index 00000000000..00c8d6e8fa0 --- /dev/null +++ b/src/status_im/effects.cljs @@ -0,0 +1,12 @@ +(ns status-im.effects + (:require [promesa.core :as promesa] + [utils.re-frame :as rf])) + +(rf/reg-fx :fx.promise + (fn [{:keys [promise args on-success on-error] + :or {args [] + on-success identity + on-error identity}}] + (-> (apply promise args) + (promesa/then (partial rf/call-continuation on-success)) + (promesa/catch (partial rf/call-continuation on-error))))) diff --git a/src/status_im/events.cljs b/src/status_im/events.cljs index 47d17ec6033..a5141421f7b 100644 --- a/src/status_im/events.cljs +++ b/src/status_im/events.cljs @@ -58,6 +58,7 @@ status-im.contexts.wallet.swap.events status-im.contexts.wallet.wallet-connect.events.core [status-im.db :as db] + status-im.effects status-im.navigation.effects status-im.navigation.events [utils.re-frame :as rf])) diff --git a/src/status_im/navigation/events.cljs b/src/status_im/navigation/events.cljs index 721338b842c..0704349acdd 100644 --- a/src/status_im/navigation/events.cljs +++ b/src/status_im/navigation/events.cljs @@ -115,16 +115,29 @@ {:events [:show-bottom-sheet]} [{:keys [db] :as cofx} content] (let [theme (or (:theme content) (:theme db)) + ;; NOTE: if true, allows to return the bottom-sheet that was being shown after the stacked + ;; one is hidden + {:keys [stacked?]} content {:keys [sheets hide?]} (:bottom-sheet db)] (rf/merge cofx - {:db (update-in db [:bottom-sheet :sheets] conj content) + {:db (if stacked? + (update-in db [:bottom-sheet :sheets] concat [content]) + (update-in db [:bottom-sheet :sheets] conj content)) :dismiss-keyboard nil} (fn [new-cofx] (when-not hide? - (if (seq sheets) + (if (and (not stacked?) (seq sheets)) (hide-bottom-sheet new-cofx) {:show-bottom-sheet {:theme theme}})))))) +;; (rf/defn show-nested-bottom-sheet +;; {:events [:show-nested-bottom-sheet]} +;; [{:keys [db] :as cofx} content] +;; (let [theme (or (:theme content) (:theme db)) +;; {:keys [sheets hide?]} (:bottom-sheet db)] +;; (rf/merge cofx +;; {:db (update-in db [:bottom-sheet :sheets] conj content)}))) + (rf/defn set-view-id {:events [:set-view-id]} [{:keys [db]} view-id] diff --git a/src/status_im/subs/wallet/dapps/core.cljs b/src/status_im/subs/wallet/dapps/core.cljs index 252a3393bdf..1114d1282a5 100644 --- a/src/status_im/subs/wallet/dapps/core.cljs +++ b/src/status_im/subs/wallet/dapps/core.cljs @@ -8,7 +8,7 @@ [utils.string])) (rf/reg-sub - :wallet-connect/account-details-by-address + :dapps/account-details-by-address :<- [:wallet/accounts-without-watched-accounts] (fn [accounts [_ address]] (let [{:keys [customization-color name emoji]} (wallet-utils/get-account-by-address accounts address)] diff --git a/src/utils/hex.cljs b/src/utils/hex.cljs index ad89fa1382e..3e34d24b1f0 100644 --- a/src/utils/hex.cljs +++ b/src/utils/hex.cljs @@ -41,6 +41,7 @@ (defn hex-to-number [value] (->> value + normalize-hex native-module/hex-to-number)) (schema/=> hex-to-number From 901756d9e9b9acf8f82be452bb1695b87c8f420f Mon Sep 17 00:00:00 2001 From: Cristian Lungu Date: Mon, 19 May 2025 13:39:48 +0300 Subject: [PATCH 09/14] feat: native dapps and removed bottom tabs --- .../contexts/browser/components/footer.cljs | 18 ++++++-- .../browser/components/webview_tab.cljs | 4 +- .../contexts/browser/events/core.cljs | 44 ++++++++++++------- .../contexts/browser/events/rpc.cljs | 10 ++--- src/status_im/contexts/browser/hooks.cljs | 12 ----- .../contexts/browser/native_dapps.cljs | 34 ++++++++++++++ src/status_im/contexts/browser/subs/core.cljs | 44 ++++++++++++++----- src/status_im/contexts/browser/view.cljs | 35 ++++++++++----- .../contexts/shell/bottom_tabs/view.cljs | 1 - .../contexts/shell/home_stack/view.cljs | 14 +++--- src/status_im/contexts/shell/view.cljs | 6 +-- 11 files changed, 152 insertions(+), 70 deletions(-) create mode 100644 src/status_im/contexts/browser/native_dapps.cljs diff --git a/src/status_im/contexts/browser/components/footer.cljs b/src/status_im/contexts/browser/components/footer.cljs index de68286e78a..d0891defa6f 100644 --- a/src/status_im/contexts/browser/components/footer.cljs +++ b/src/status_im/contexts/browser/components/footer.cljs @@ -10,8 +10,9 @@ (defn view [] (let [tab-id (rf/sub [:browser/focused-tab-id]) + tab-type (rf/sub [:browser/tab-type tab-id]) title (rf/sub [:browser/tab-title tab-id]) - metadata (rf/sub [:browser/tab-metadata tab-id])] + dapp (rf/sub [:browser/dapp-for-tab tab-id])] [reanimated/view {:style {:width browser.constants/browser-width :background-color colors/neutral-100 @@ -35,9 +36,18 @@ :flex 1 :align-items :center :justify-content :center}} - [fast-image/fast-image - {:source (:logo-url metadata) - :style {:width 24 :height 24 :border-radius 8}}] + (if (= :tab/native tab-type) + ;; TODO: remove `when` + (when (:icon dapp) + [rn/view + {:style {:background-color colors/neutral-80 + :padding 2 + :border-radius 8}} + [quo/icon (:icon dapp) + {:size 20}]]) + [fast-image/fast-image + {:source (:logo-url dapp) + :style {:width 24 :height 24 :border-radius 8}}]) [quo/text {:size :paragraph-1 :number-of-lines 1 diff --git a/src/status_im/contexts/browser/components/webview_tab.cljs b/src/status_im/contexts/browser/components/webview_tab.cljs index 1ebb2dfd4a2..4051b5aafb9 100644 --- a/src/status_im/contexts/browser/components/webview_tab.cljs +++ b/src/status_im/contexts/browser/components/webview_tab.cljs @@ -1,5 +1,6 @@ (ns status-im.contexts.browser.components.webview-tab - (:require [react-native.webview :as webview] + (:require [react-native.safe-area :as safe-area] + [react-native.webview :as webview] [status-im.contexts.browser.js-scripts.core :as js-scripts] [utils.re-frame :as rf])) @@ -20,6 +21,7 @@ [{:keys [tab-id url]}] [webview/view {:ref #(rf/dispatch [:browser/set-tab-ref tab-id %]) + :container-style {:padding-top safe-area/top} :source {:uri url} :java-script-enabled true :bounces false diff --git a/src/status_im/contexts/browser/events/core.cljs b/src/status_im/contexts/browser/events/core.cljs index 284195dca83..b94cb46ddf3 100644 --- a/src/status_im/contexts/browser/events/core.cljs +++ b/src/status_im/contexts/browser/events/core.cljs @@ -1,24 +1,25 @@ (ns status-im.contexts.browser.events.core (:require [cljs.pprint :as pprint] [re-frame.core :as rf] - [status-im.contexts.browser.api :as browser.api] [status-im.contexts.browser.db :as browser.db] status-im.contexts.browser.events.effects status-im.contexts.browser.events.rpc [status-im.contexts.browser.messages :as messages] - [taoensso.timbre :as log])) + [status-im.contexts.browser.native-dapps :as native-dapps])) (rf/reg-event-fx :browser/set-tab-ref (fn [{:keys [db]} [tab-id ref]] {:db (assoc-in db [:browser/tabs-by-id tab-id :ref] ref)})) (rf/reg-event-fx :browser/add-tab - (fn [{:keys [db]} [url]] + (fn [{:keys [db]} [url tab-type]] (let [tab-id (random-uuid)] {:db (-> db (assoc-in [:browser/tabs-by-id tab-id] - {:url url - :id tab-id}) + {:type tab-type + :url url + :dapp-id url + :id tab-id}) (update :browser/tab-ids conj tab-id))}))) (rf/reg-event-fx :browser/focus-tab-by-idx @@ -33,9 +34,23 @@ (when valid-tab-id? {:db (assoc db :browser/focused-tab-id tab-id)})))) +(rf/reg-event-fx :browser/save-native-dapp + (fn [_ [dapp-id]] + {:fx [[:dispatch [:browser/save-dapp dapp-id (get native-dapps/metadata dapp-id)]]]})) + +(rf/reg-event-fx :browser/init-native-dapps + (fn [_] + {:fx [[:dispatch [:browser/save-native-dapp "wallet.status"]] + [:dispatch [:browser/save-native-dapp "messages.status"]] + [:dispatch [:browser/save-native-dapp "communities.status"]] + [:dispatch [:browser/add-tab "wallet.status" :tab/native]] + [:dispatch [:browser/add-tab "messages.status" :tab/native]] + [:dispatch [:browser/add-tab "communities.status" :tab/native]]]})) + (rf/reg-event-fx :browser/init (fn [_] - {:fx [[:dispatch [:browser/init-tabs]] + {:fx [[:dispatch [:browser/init-native-dapps]] + [:dispatch [:browser/init-tabs]] [:dispatch [:browser.rpc/get-permissions]]]})) (rf/reg-event-fx :browser/init-tabs @@ -43,9 +58,9 @@ {:db (-> db (assoc :browser/tabs-by-id {} :browser/tab-ids [])) - :fx [[:dispatch [:browser/add-tab "https://pancakeswap.finance/swap"]] - [:dispatch [:browser/add-tab "https://app.uniswap.org"]] - [:dispatch [:browser/focus-tab 0]]]})) + :fx [[:dispatch [:browser/add-tab "https://pancakeswap.finance/swap" :tab/web]] + [:dispatch [:browser/add-tab "https://app.uniswap.org" :tab/web]] + [:dispatch [:browser/focus-tab-by-idx 0]]]})) (rf/reg-event-fx :browser/on-message (fn [{:keys [_]} [tab-id js-event]] @@ -61,12 +76,13 @@ (fn [{:keys [db]} [tab-id event]] (let [{:keys [origin-url logo-url page-title]} (messages/get-website-metadata event) dapp-metadata {:logo-url logo-url - :page-title page-title}] + :origin-url origin-url + :title page-title}] {:db (update-in db [:browser/tabs-by-id tab-id] assoc - :dapp-id origin-url - :metadata dapp-metadata) + :dapp-id + origin-url) :fx [[:dispatch [:browser/save-dapp origin-url dapp-metadata]]]}))) (rf/reg-event-fx :browser/save-dapp @@ -74,7 +90,3 @@ (when-not (contains? (:browser/dapps db) dapp-id) ;;TODO: persist dapps {:db (assoc-in db [:browser/dapps dapp-id] dapp-metadata)}))) - -(rf/reg-event-fx :browser/update-dapp-chain-id - (fn [{:keys [db]} [dapp-id chain-id]] - {:db (assoc-in db [:browser/dapps dapp-id :chain-id] chain-id)})) diff --git a/src/status_im/contexts/browser/events/rpc.cljs b/src/status_im/contexts/browser/events/rpc.cljs index 1a3013c1841..4f7ab6e04c7 100644 --- a/src/status_im/contexts/browser/events/rpc.cljs +++ b/src/status_im/contexts/browser/events/rpc.cljs @@ -23,7 +23,7 @@ (fn [{:keys [db]} [tab-id rpc-event]] (let [tab (-> db (browser.db/get-tab tab-id)) connector-dapp {:url (:dapp-id tab) - :name (-> tab :metadata :page-title (or (:dapp-id tab))) + :name (-> tab :metadata :title (or (:dapp-id tab))) :iconUrl (-> tab :metadata :logo-url) :chainId (browser.db/get-dapp-chain-id db (:dapp-id tab))}] {:fx [[:fx.promise @@ -175,11 +175,11 @@ (rf/reg-event-fx :browser.rpc/on-chain-switched-signal (fn [{:keys [db]} [{:keys [chain-id url]}]] - (let [{:keys [page-title]} (browser.db/get-dapp-by-id db url) - new-chain-id (hex/hex-to-number chain-id) - network-name (networks.db/get-network-name db new-chain-id)] + (let [{:keys [title]} (browser.db/get-dapp-by-id db url) + new-chain-id (hex/hex-to-number chain-id) + network-name (networks.db/get-network-name db new-chain-id)] {:db (assoc-in db [:browser/permissions url :chain-id] new-chain-id) :fx [[:dispatch [:toasts/upsert {:type :positive - :text (str page-title " switched the network to " network-name)}]]]}))) + :text (str title " switched the network to " network-name)}]]]}))) diff --git a/src/status_im/contexts/browser/hooks.cljs b/src/status_im/contexts/browser/hooks.cljs index d6508674857..5ec7d824885 100644 --- a/src/status_im/contexts/browser/hooks.cljs +++ b/src/status_im/contexts/browser/hooks.cljs @@ -9,18 +9,6 @@ [utils.re-frame :as rf] [utils.worklets.browser :as worklets.browser])) -(defn use-deactivate-tab - [tab-id] - (let [focused-tab-id (-> (rf/sub [:browser/focused-tab]) :id) - tab (rf/sub [:browser/tabs-by-id tab-id]) - webview-ref (:ref tab) - active? (= focused-tab-id tab-id)] - (rn/use-effect (fn [] - (if active? - (webview/inject-js webview-ref js-scripts/unfreeze-website) - (webview/inject-js webview-ref js-scripts/freeze-website))) - [focused-tab-id]))) - (defn animate-with-spring [animation-val to-val] (reanimated/animate-shared-value-with-spring animation-val diff --git a/src/status_im/contexts/browser/native_dapps.cljs b/src/status_im/contexts/browser/native_dapps.cljs new file mode 100644 index 00000000000..b1cb5d790b8 --- /dev/null +++ b/src/status_im/contexts/browser/native_dapps.cljs @@ -0,0 +1,34 @@ +(ns status-im.contexts.browser.native-dapps + (:require + [quo.context :as quo.context] + [status-im.contexts.chat.home.view :as chat] + [status-im.contexts.communities.home.view :as communities] + [status-im.contexts.wallet.home.view :as wallet])) + +(defn- stack + [stack-id & children] + (let [theme (quo.context/use-theme)] + (into [quo.context/provider {:theme theme :screen-id stack-id}] + children))) + +(def views + {"communities.status" [stack + :screen/communities-stack + [communities/view]] + "messages.status" [stack + :screen/chats-stack + [chat/view]] + "wallet.status" [stack :screen/wallet-stack + [wallet/view]]}) + + +(def metadata + {"communities.status" {:title "Communities" + :origin-url "communities.status" + :icon :i/communities} + "messages.status" {:title "Messages" + :origin-url "messages.status" + :icon :i/messages} + "wallet.status" {:title "Wallet" + :origin-url "wallet.status" + :icon :i/wallet}}) diff --git a/src/status_im/contexts/browser/subs/core.cljs b/src/status_im/contexts/browser/subs/core.cljs index 9c6c21fdb92..bca2432d642 100644 --- a/src/status_im/contexts/browser/subs/core.cljs +++ b/src/status_im/contexts/browser/subs/core.cljs @@ -13,6 +13,10 @@ (fn [db] (get db :browser/permissions))) +(rf/reg-sub :browser/dapps + (fn [db] + (get db :browser/dapps))) + (rf/reg-sub :browser/focused-tab-id (fn [db] (let [default (-> db :browser/tab-ids first)] @@ -46,6 +50,10 @@ (fn [tabs-by-id [_ tab-id]] (get tabs-by-id tab-id))) +(defn- tab-by-id-query + [[_ tab-id]] + [(rf/subscribe [:browser/tab-by-id tab-id])]) + (rf/reg-sub :browser/tabs :<- [:browser/tab-ids] :<- [:browser/tabs-by-id] @@ -54,21 +62,35 @@ (map (partial get tabs-by-id))))) (rf/reg-sub :browser/tab-url - (fn [[_ tab-id]] - [(rf/subscribe [:browser/tab-by-id tab-id])]) + tab-by-id-query (fn [[tab-data]] (:url tab-data))) -(rf/reg-sub :browser/tab-metadata - (fn [[_ tab-id]] - [(rf/subscribe [:browser/tab-by-id tab-id])]) +(rf/reg-sub :browser/tab-type + tab-by-id-query (fn [[tab-data]] - (:metadata tab-data))) + (:type tab-data))) -(rf/reg-sub :browser/tab-title - (fn [[_ tab-id]] - [(rf/subscribe [:browser/tab-by-id tab-id])]) +(rf/reg-sub :browser/tab-type + tab-by-id-query (fn [[tab-data]] - (let [title (-> tab-data :metadata :page-title) - url (-> tab-data :url)] + (:type tab-data))) + +(rf/reg-sub :browser/dapp-for-tab + :<- [:browser/tabs-by-id] + :<- [:browser/dapps] + (fn [[tabs dapps] [_ tab-id]] + (let [dapp-id (get-in tabs [tab-id :dapp-id]) + dapp (get dapps dapp-id)] + dapp))) + +(defn- dapp-by-tab-id-query + [[_ tab-id]] + [(rf/subscribe [:browser/dapp-for-tab tab-id])]) + +(rf/reg-sub :browser/tab-title + dapp-by-tab-id-query + (fn [[dapp]] + (let [title (-> dapp :metadata :title) + url (-> dapp :origin-url)] (or title url)))) diff --git a/src/status_im/contexts/browser/view.cljs b/src/status_im/contexts/browser/view.cljs index b8d75320cdb..9d707c99b14 100644 --- a/src/status_im/contexts/browser/view.cljs +++ b/src/status_im/contexts/browser/view.cljs @@ -1,28 +1,39 @@ (ns status-im.contexts.browser.view - (:require [react-native.core :as rn] + (:require [quo.context :as quo.context] + [quo.foundations.colors :as colors] + [react-native.core :as rn] [react-native.freeze :as freeze] [react-native.gesture :as gesture] [react-native.reanimated :as reanimated] [status-im.contexts.browser.components.footer :as footer] [status-im.contexts.browser.components.webview-tab :as webview-tab] [status-im.contexts.browser.constants :as browser.constants] + [status-im.contexts.browser.core :as browser] [status-im.contexts.browser.hooks :as hooks] + [status-im.contexts.browser.native-dapps :as native-dapps] [utils.re-frame :as rf])) (defn render-tab [tab-id idx] - (let [url (rf/sub [:browser/tab-url tab-id]) + (let [theme (quo.context/use-theme) + url (rf/sub [:browser/tab-url tab-id]) + tab-type (rf/sub [:browser/tab-type tab-id]) focused-tab-idx (rf/sub [:browser/focused-tab-idx]) - ;;freeze-tab? (browser/freeze-tab? idx focused-tab-idx) - freeze-tab? (not= idx focused-tab-idx)] - (hooks/use-deactivate-tab tab-id) + freeze-tab? (browser/freeze-tab? idx focused-tab-idx) + ;;freeze-tab? (not= idx focused-tab-idx) + ] [rn/view - {:style {:width browser.constants/browser-width - :height browser.constants/browser-height}} + {:style {:width browser.constants/browser-width + :border-bottom-left-radius 20 + :border-bottom-right-radius 20 + :background-color (colors/theme-colors colors/white colors/neutral-95 theme) + :height browser.constants/browser-height}} [freeze/view {:freeze freeze-tab?} - [webview-tab/view - {:tab-id tab-id - :url url}]]])) + (if (= :tab/native tab-type) + (get native-dapps/views url) + [webview-tab/view + {:tab-id tab-id + :url url}])]])) (defn view [] @@ -33,6 +44,7 @@ [reanimated/scroll-view {:horizontal true :ref scroll-ref + :content-container-style {:background-color colors/neutral-100} :shows-horizontal-scroll-indicator false :shows-vertical-scroll-indicator false :scroll-enabled false} @@ -43,3 +55,6 @@ [gesture/gesture-detector {:gesture gesture} [footer/view]]])) + +(comment + (rf/dispatch [:browser/add-tab "wallet.status" :tab/native])) diff --git a/src/status_im/contexts/shell/bottom_tabs/view.cljs b/src/status_im/contexts/shell/bottom_tabs/view.cljs index f348421322f..5c8262ac363 100644 --- a/src/status_im/contexts/shell/bottom_tabs/view.cljs +++ b/src/status_im/contexts/shell/bottom_tabs/view.cljs @@ -5,7 +5,6 @@ [react-native.core :as rn] [react-native.gesture :as gesture] [react-native.reanimated :as reanimated] - [status-im.config :as config] [status-im.contexts.shell.bottom-tabs.style :as style] [status-im.contexts.shell.constants :as shell.constants] [status-im.feature-flags :as ff] diff --git a/src/status_im/contexts/shell/home_stack/view.cljs b/src/status_im/contexts/shell/home_stack/view.cljs index 902b679a22a..66baca85a6b 100644 --- a/src/status_im/contexts/shell/home_stack/view.cljs +++ b/src/status_im/contexts/shell/home_stack/view.cljs @@ -1,9 +1,9 @@ (ns status-im.contexts.shell.home-stack.view (:require - [legacy.status-im.ui.screens.browser.stack :as browser.stack] [quo.context] [react-native.core :as rn] [react-native.reanimated :as reanimated] + [status-im.contexts.browser.view :as new-browser] [status-im.contexts.chat.home.view :as chat] [status-im.contexts.communities.home.view :as communities] [status-im.contexts.market.view :as market] @@ -35,21 +35,21 @@ :screen/chats-stack [chat/view] :screen/wallet-stack [wallet/view] :screen/market-stack [market/view] - :screen/browser-stack [browser.stack/browser-stack] + :screen/browser-stack [new-browser/view] [:<>])]) (defn lazy-screen [stack-id shared-values theme] (when (load-stack? stack-id) - [quo.context/provider {:theme theme :screen-id stack-id} + [quo.context/provider {:theme theme} [f-stack-view stack-id shared-values]])) (defn view [shared-values] (let [theme (quo.context/use-theme)] [rn/view {:style (style/home-stack theme)} - [lazy-screen :screen/communities-stack shared-values theme] - [lazy-screen :screen/chats-stack shared-values theme] + #_[lazy-screen :screen/communities-stack shared-values theme] + #_[lazy-screen :screen/chats-stack shared-values theme] [lazy-screen :screen/browser-stack shared-values theme] - [lazy-screen :screen/wallet-stack shared-values theme] - [lazy-screen :screen/market-stack shared-values theme]])) + #_[lazy-screen :screen/wallet-stack shared-values theme] + #_[lazy-screen :screen/market-stack shared-values theme]])) diff --git a/src/status_im/contexts/shell/view.cljs b/src/status_im/contexts/shell/view.cljs index 63d1a8a437e..432021a5368 100644 --- a/src/status_im/contexts/shell/view.cljs +++ b/src/status_im/contexts/shell/view.cljs @@ -13,8 +13,8 @@ [] (when (or (seq @navigation.state/modals) (> (count (navigation.state/get-navigation-state)) 1)) - (rf/dispatch [:navigate-back]) - true)) + (rf/dispatch [:navigate-back])) + true) ;; A hidden view-id-tracker view, required for e2e testing (defn- view-id-tracker @@ -37,4 +37,4 @@ [home-stack/view shared-values] (when config/enable-view-id-tracker? [view-id-tracker]) - [bottom-tabs/view shared-values]])) + #_[bottom-tabs/view shared-values]])) From dc8df1f5c11e389d5a3157b874fd6357f0bec48f Mon Sep 17 00:00:00 2001 From: Cristian Lungu Date: Mon, 19 May 2025 15:51:08 +0300 Subject: [PATCH 10/14] feat: tabs transition and bottom bar --- .../components/navigation/top_nav/view.cljs | 3 +- .../contexts/browser/components/footer.cljs | 125 +++++++++++++----- .../browser/components/webview_tab.cljs | 5 +- src/status_im/contexts/browser/hooks.cljs | 13 +- src/status_im/contexts/browser/view.cljs | 61 ++++++--- 5 files changed, 148 insertions(+), 59 deletions(-) diff --git a/src/quo/components/navigation/top_nav/view.cljs b/src/quo/components/navigation/top_nav/view.cljs index 7b133582162..9b58c0269f7 100644 --- a/src/quo/components/navigation/top_nav/view.cljs +++ b/src/quo/components/navigation/top_nav/view.cljs @@ -128,8 +128,7 @@ :blur? true/false :theme :light/:dark :notification :mention/:seen/:notification (TODO :mention-seen temporarily used while resolving https://github.com/status-im/status-mobile/issues/17102 ) - :avatar-props qu2/user-avatar props - :avatar-on-press callback + :avatar-props qu2/user-avatar props :avatar-on-press callback :scan-on-press callback :activity-center-on-press callback :qr-code-on-press callback diff --git a/src/status_im/contexts/browser/components/footer.cljs b/src/status_im/contexts/browser/components/footer.cljs index d0891defa6f..f469ff2952e 100644 --- a/src/status_im/contexts/browser/components/footer.cljs +++ b/src/status_im/contexts/browser/components/footer.cljs @@ -5,56 +5,115 @@ [react-native.fast-image :as fast-image] [react-native.reanimated :as reanimated] [status-im.contexts.browser.constants :as browser.constants] + [status-im.contexts.profile.utils :as profile.utils] [utils.re-frame :as rf])) +(def icon-size 32) -(defn view +(defn dapp-bar [] (let [tab-id (rf/sub [:browser/focused-tab-id]) tab-type (rf/sub [:browser/tab-type tab-id]) - title (rf/sub [:browser/tab-title tab-id]) dapp (rf/sub [:browser/dapp-for-tab tab-id])] - [reanimated/view - {:style {:width browser.constants/browser-width - :background-color colors/neutral-100 - :align-items :center - :justify-content :space-between + [rn/view + {:style {:padding 8 + :background-color colors/neutral-30 :flex-direction :row - :padding-horizontal 20 - :height 80}} - [rn/view - {:style {:width 44 - :height 44 - :border-radius 12}}] + :border-radius 20 + :height 44 + :margin-horizontal 12 + :padding-horizontal 8 + :flex 1 + :align-items :center + :justify-content :space-between}} [rn/view - {:style {:padding 8 - :background-color colors/neutral-30 - :flex-direction :row - :border-radius 12 - :height 44 - :margin-horizontal 12 - :padding-horizontal 20 - :flex 1 - :align-items :center - :justify-content :center}} (if (= :tab/native tab-type) ;; TODO: remove `when` (when (:icon dapp) [rn/view {:style {:background-color colors/neutral-80 - :padding 2 - :border-radius 8}} - [quo/icon (:icon dapp) - {:size 20}]]) + :padding 4 + :border-radius 20 + :overflow :hidden}} + [rn/view {:style {:transform [{:scale 0.8}]}} + [quo/icon (:icon dapp) + {:size 24}]]]) [fast-image/fast-image {:source (:logo-url dapp) - :style {:width 24 :height 24 :border-radius 8}}]) + :style {:width icon-size :height icon-size :border-radius 20}}])] + [rn/view + {:style {:padding-horizontal 2 + :flex 1 + :align-items :center + :justify-content :center}} [quo/text {:size :paragraph-1 :number-of-lines 1 :weight :bold - :style {:color colors/neutral-100 - :margin-left 12}} title]] + :style {:color colors/neutral-100}} + (:title dapp)]] [rn/view - {:style {:width 44 - :height 44 - :border-radius 12}}]])) + {:style + {:width 24 + :height 24 + :align-items :center + :justify-content :center}} + [rn/view {:style {:transform [{:scale 1}]}} + [quo/icon :i/info + {:size 20 :color colors/neutral-80}]]]])) + +(defn profile-btn + [] + (let [{:keys [public-key] :as profile} (rf/sub [:profile/profile-with-image]) + online? (rf/sub [:visibility-status-updates/online? + public-key]) + customization-color (rf/sub [:profile/customization-color]) + avatar-props {:online? online? + :full-name (profile.utils/displayed-name profile) + :profile-picture (profile.utils/photo profile)} + on-press #(rf/dispatch [:open-modal :screen/settings])] + [rn/pressable + {:on-press on-press + :style {:transform [{:scale 1.3}] + :padding 2 + :background-color colors/neutral-80 + :border-radius 40} + :accessibility-label :open-profile} + [quo/user-avatar + (merge {:status-indicator? true + :customization-color customization-color + :size :small} + avatar-props)]])) + +(defn tabs-btn + [] + [rn/pressable + {:style {:flex 1 + :align-items :center + :justify-content :center} + :on-press identity} + [rn/view {:style {:transform [{:scale 1.2}]}} + [quo/icon :i/tabs {:size 20 :color colors/white}]]]) + +(defn view + [] + [reanimated/view + {:style {:width browser.constants/browser-width + :background-color colors/neutral-100 + :align-items :center + :justify-content :space-between + :flex-direction :row + :padding-horizontal 20 + :height 80}} + [rn/view + {:style {:align-items :center + :justify-content :center + :margin-right 12}} + [profile-btn]] + [dapp-bar] + [rn/view + {:style {:width 44 + :height 44 + :margin-left 8 + :background-color colors/neutral-80 + :border-radius 12}} + [tabs-btn]]]) diff --git a/src/status_im/contexts/browser/components/webview_tab.cljs b/src/status_im/contexts/browser/components/webview_tab.cljs index 4051b5aafb9..61c8fdb290e 100644 --- a/src/status_im/contexts/browser/components/webview_tab.cljs +++ b/src/status_im/contexts/browser/components/webview_tab.cljs @@ -1,6 +1,5 @@ (ns status-im.contexts.browser.components.webview-tab - (:require [react-native.safe-area :as safe-area] - [react-native.webview :as webview] + (:require [react-native.webview :as webview] [status-im.contexts.browser.js-scripts.core :as js-scripts] [utils.re-frame :as rf])) @@ -21,7 +20,7 @@ [{:keys [tab-id url]}] [webview/view {:ref #(rf/dispatch [:browser/set-tab-ref tab-id %]) - :container-style {:padding-top safe-area/top} + :container-style {:border-radius 20} :source {:uri url} :java-script-enabled true :bounces false diff --git a/src/status_im/contexts/browser/hooks.cljs b/src/status_im/contexts/browser/hooks.cljs index 5ec7d824885..ecba1153ae0 100644 --- a/src/status_im/contexts/browser/hooks.cljs +++ b/src/status_im/contexts/browser/hooks.cljs @@ -58,9 +58,10 @@ (worklets.browser/use-scroll-tab {:animated-ref scroll-ref :x-translation x-translation-value}) - (make-drag-gesture - {:x-translation-value x-translation-value - :can-move-left? can-focus-prev? - :can-move-right? can-focus-next? - :focused-tab-idx focused-tab-idx - :on-tab-change #(rf/dispatch-sync [:browser/focus-tab-by-idx %])}))) + {:x-translation-value x-translation-value + :gesture (make-drag-gesture + {:x-translation-value x-translation-value + :can-move-left? can-focus-prev? + :can-move-right? can-focus-next? + :focused-tab-idx focused-tab-idx + :on-tab-change #(rf/dispatch-sync [:browser/focus-tab-by-idx %])})})) diff --git a/src/status_im/contexts/browser/view.cljs b/src/status_im/contexts/browser/view.cljs index 9d707c99b14..1170b3ec335 100644 --- a/src/status_im/contexts/browser/view.cljs +++ b/src/status_im/contexts/browser/view.cljs @@ -5,6 +5,7 @@ [react-native.freeze :as freeze] [react-native.gesture :as gesture] [react-native.reanimated :as reanimated] + [react-native.safe-area :as safe-area] [status-im.contexts.browser.components.footer :as footer] [status-im.contexts.browser.components.webview-tab :as webview-tab] [status-im.contexts.browser.constants :as browser.constants] @@ -13,44 +14,74 @@ [status-im.contexts.browser.native-dapps :as native-dapps] [utils.re-frame :as rf])) +(defn interpolate-x-position + [x-value tab-idx output-values] + (let [x-tab (browser/tab-position tab-idx) + x-tab-prev (browser/tab-position (dec tab-idx)) + x-tab-next (browser/tab-position (inc tab-idx))] + (reanimated/interpolate x-value + [x-tab-prev x-tab x-tab-next] + output-values + {:extrapolateLeft "clamp" + :extrapolateRight "clamp"}))) + +(defn tab-container + [{:keys [idx x-translation-value]} & children] + (let [theme (quo.context/use-theme) + scale-down-amount 0.9] + (into [reanimated/view + {:style + [{:transform [{:scale (interpolate-x-position x-translation-value + idx + [scale-down-amount 1 + scale-down-amount])}]} + {:width browser.constants/browser-width + :transform-origin :bottom + :border-radius 20 + :overflow :hidden + :background-color (colors/theme-colors colors/white colors/neutral-95 theme) + :height browser.constants/browser-height}]}] + children))) + (defn render-tab - [tab-id idx] - (let [theme (quo.context/use-theme) - url (rf/sub [:browser/tab-url tab-id]) + [tab-id idx x-translation-value] + (let [url (rf/sub [:browser/tab-url tab-id]) tab-type (rf/sub [:browser/tab-type tab-id]) focused-tab-idx (rf/sub [:browser/focused-tab-idx]) freeze-tab? (browser/freeze-tab? idx focused-tab-idx) ;;freeze-tab? (not= idx focused-tab-idx) ] - [rn/view - {:style {:width browser.constants/browser-width - :border-bottom-left-radius 20 - :border-bottom-right-radius 20 - :background-color (colors/theme-colors colors/white colors/neutral-95 theme) - :height browser.constants/browser-height}} + [tab-container + {:idx idx + :x-translation-value x-translation-value} [freeze/view {:freeze freeze-tab?} (if (= :tab/native tab-type) - (get native-dapps/views url) + [rn/view + {:style {:margin-top (- 10 safe-area/top) + :z-index 5 + :flex 1}} + (get native-dapps/views url)] [webview-tab/view {:tab-id tab-id :url url}])]])) (defn view [] - (let [tab-ids (rf/sub [:browser/tab-ids]) - scroll-ref (reanimated/use-animated-ref) - gesture (hooks/use-swipe-gesture scroll-ref)] + (let [tab-ids (rf/sub [:browser/tab-ids]) + scroll-ref (reanimated/use-animated-ref) + {:keys [gesture x-translation-value]} (hooks/use-swipe-gesture scroll-ref)] [:<> [reanimated/scroll-view {:horizontal true :ref scroll-ref - :content-container-style {:background-color colors/neutral-100} + :content-container-style {:background-color colors/neutral-100 + :padding-top safe-area/top} :shows-horizontal-scroll-indicator false :shows-vertical-scroll-indicator false :scroll-enabled false} (map-indexed (fn [idx tab-id] ^{:key (str idx "-" tab-id)} - [render-tab tab-id idx]) + [render-tab tab-id idx x-translation-value]) tab-ids)] [gesture/gesture-detector {:gesture gesture} From f69ce5f73ee6b6d34557d3501341f7b2a6fff847 Mon Sep 17 00:00:00 2001 From: Cristian Lungu Date: Mon, 19 May 2025 19:00:58 +0300 Subject: [PATCH 11/14] feat: added all tabs view --- src/js/worklets/browser.js | 4 +- .../contexts/browser/components/footer.cljs | 33 +++-- src/status_im/contexts/browser/constants.cljs | 1 + src/status_im/contexts/browser/db.cljs | 2 +- .../contexts/browser/events/core.cljs | 16 ++- src/status_im/contexts/browser/hooks.cljs | 29 ++-- src/status_im/contexts/browser/subs/core.cljs | 7 +- src/status_im/contexts/browser/view.cljs | 130 ++++++++++++++---- 8 files changed, 174 insertions(+), 48 deletions(-) diff --git a/src/js/worklets/browser.js b/src/js/worklets/browser.js index cbc9f83913b..d8457e0dfd6 100644 --- a/src/js/worklets/browser.js +++ b/src/js/worklets/browser.js @@ -1,7 +1,7 @@ import { useDerivedValue, useSharedValue, scrollTo } from 'react-native-reanimated'; -export function useScrollTab({ animatedRef, xTranslation }) { +export function useScrollTab({ animatedRef, xTranslation, animate }) { useDerivedValue(() => { - scrollTo(animatedRef, xTranslation.value, 0, true); + scrollTo(animatedRef, xTranslation.value, 0, animate); }); } diff --git a/src/status_im/contexts/browser/components/footer.cljs b/src/status_im/contexts/browser/components/footer.cljs index f469ff2952e..6a865e9c308 100644 --- a/src/status_im/contexts/browser/components/footer.cljs +++ b/src/status_im/contexts/browser/components/footer.cljs @@ -7,6 +7,7 @@ [status-im.contexts.browser.constants :as browser.constants] [status-im.contexts.profile.utils :as profile.utils] [utils.re-frame :as rf])) + (def icon-size 32) (defn dapp-bar @@ -84,15 +85,31 @@ :size :small} avatar-props)]])) +(defn on-tabs-press + [browser-mode] + (condp = browser-mode + :browser-mode/browser (rf/dispatch [:browser/show-tabs]) + :browser-mode/tabs (rf/dispatch [:browser/show-browser]) + nil)) + +(defn tabs-btn-icon + [browser-mode] + (condp = browser-mode + :browser-mode/tabs :i/browser + :browser-mode/browser :i/tabs + :i/tabs)) + (defn tabs-btn [] - [rn/pressable - {:style {:flex 1 - :align-items :center - :justify-content :center} - :on-press identity} - [rn/view {:style {:transform [{:scale 1.2}]}} - [quo/icon :i/tabs {:size 20 :color colors/white}]]]) + (let [tab-id (rf/sub [:browser/focused-tab-id]) + browser-mode (rf/sub [:browser/mode tab-id])] + [rn/pressable + {:style {:flex 1 + :align-items :center + :justify-content :center} + :on-press #(on-tabs-press browser-mode)} + [rn/view {:style {:transform [{:scale 1.2}]}} + [quo/icon (tabs-btn-icon browser-mode) {:size 20 :color colors/white}]]])) (defn view [] @@ -103,7 +120,7 @@ :justify-content :space-between :flex-direction :row :padding-horizontal 20 - :height 80}} + :height browser.constants/footer-height}} [rn/view {:style {:align-items :center :justify-content :center diff --git a/src/status_im/contexts/browser/constants.cljs b/src/status_im/contexts/browser/constants.cljs index 5a21d20b7e4..0f7f2155c06 100644 --- a/src/status_im/contexts/browser/constants.cljs +++ b/src/status_im/contexts/browser/constants.cljs @@ -1,6 +1,7 @@ (ns status-im.contexts.browser.constants (:require [react-native.core :as rn])) +(def footer-height 80) (def browser-width (-> (rn/get-window) :width)) (def browser-height "100%") diff --git a/src/status_im/contexts/browser/db.cljs b/src/status_im/contexts/browser/db.cljs index 1c66c79ea4a..22920a9dac3 100644 --- a/src/status_im/contexts/browser/db.cljs +++ b/src/status_im/contexts/browser/db.cljs @@ -19,7 +19,7 @@ (defn get-tab-id-by-index [db idx] - (-> db get-tab-ids (nth idx))) + (-> db get-tab-ids (nth idx 0))) (defn get-tab [db tab-id] diff --git a/src/status_im/contexts/browser/events/core.cljs b/src/status_im/contexts/browser/events/core.cljs index b94cb46ddf3..1eb2fa2eb6c 100644 --- a/src/status_im/contexts/browser/events/core.cljs +++ b/src/status_im/contexts/browser/events/core.cljs @@ -48,8 +48,9 @@ [:dispatch [:browser/add-tab "communities.status" :tab/native]]]})) (rf/reg-event-fx :browser/init - (fn [_] - {:fx [[:dispatch [:browser/init-native-dapps]] + (fn [{:keys [db]}] + {:db (assoc db :browser/mode :browser-mode/browser) + :fx [[:dispatch [:browser/init-native-dapps]] [:dispatch [:browser/init-tabs]] [:dispatch [:browser.rpc/get-permissions]]]})) @@ -59,8 +60,7 @@ (assoc :browser/tabs-by-id {} :browser/tab-ids [])) :fx [[:dispatch [:browser/add-tab "https://pancakeswap.finance/swap" :tab/web]] - [:dispatch [:browser/add-tab "https://app.uniswap.org" :tab/web]] - [:dispatch [:browser/focus-tab-by-idx 0]]]})) + [:dispatch [:browser/add-tab "https://app.uniswap.org" :tab/web]]]})) (rf/reg-event-fx :browser/on-message (fn [{:keys [_]} [tab-id js-event]] @@ -90,3 +90,11 @@ (when-not (contains? (:browser/dapps db) dapp-id) ;;TODO: persist dapps {:db (assoc-in db [:browser/dapps dapp-id] dapp-metadata)}))) + +(rf/reg-event-fx :browser/show-tabs + (fn [{:keys [db]}] + {:db (assoc db :browser/mode :browser-mode/tabs)})) + +(rf/reg-event-fx :browser/show-browser + (fn [{:keys [db]}] + {:db (assoc db :browser/mode :browser-mode/browser)})) diff --git a/src/status_im/contexts/browser/hooks.cljs b/src/status_im/contexts/browser/hooks.cljs index ecba1153ae0..71722940875 100644 --- a/src/status_im/contexts/browser/hooks.cljs +++ b/src/status_im/contexts/browser/hooks.cljs @@ -3,9 +3,7 @@ [react-native.core :as rn] [react-native.gesture :as gesture] [react-native.reanimated :as reanimated] - [react-native.webview :as webview] [status-im.contexts.browser.core :as browser] - [status-im.contexts.browser.js-scripts.core :as js-scripts] [utils.re-frame :as rf] [utils.worklets.browser :as worklets.browser])) @@ -19,11 +17,11 @@ (def drag-threshold 100) (defn- make-drag-gesture - [{:keys [can-move-left? can-move-right? x-translation-value focused-tab-idx on-tab-change]}] + [{:keys [enabled? can-move-left? can-move-right? x-translation-value focused-tab-idx on-tab-change]}] (let [initial-x-val (atom 0)] (-> (gesture/gesture-pan) - (gesture/enabled true) + (gesture/enabled enabled?) (gesture/on-begin (fn [_] (reset! initial-x-val (browser/tab-position focused-tab-idx)))) (gesture/on-update (fn [event] @@ -52,16 +50,29 @@ (defn use-swipe-gesture [scroll-ref] (let [focused-tab-idx (rf/sub [:browser/focused-tab-idx]) - x-translation-value (reanimated/use-shared-value (browser/tab-position focused-tab-idx)) + browser-mode (rf/sub [:browser/mode]) + gestures-enabled? (= browser-mode :browser-mode/browser) + x-translation-value (reanimated/use-shared-value (or (browser/tab-position focused-tab-idx) 0)) can-focus-next? (rf/sub [:browser/can-focus-next?]) can-focus-prev? (rf/sub [:browser/can-focus-prev?])] + (rn/use-effect + (fn [] + (reanimated/set-shared-value x-translation-value (browser/tab-position focused-tab-idx))) + [focused-tab-idx]) + (worklets.browser/use-scroll-tab {:animated-ref scroll-ref - :x-translation x-translation-value}) + :x-translation x-translation-value + :animate true}) + {:x-translation-value x-translation-value - :gesture (make-drag-gesture - {:x-translation-value x-translation-value + :gesture-prop (make-drag-gesture + {:enabled? gestures-enabled? + :x-translation-value x-translation-value :can-move-left? can-focus-prev? :can-move-right? can-focus-next? :focused-tab-idx focused-tab-idx - :on-tab-change #(rf/dispatch-sync [:browser/focus-tab-by-idx %])})})) + :on-tab-change (fn [tab-idx] + (js/setTimeout #(rf/dispatch-sync + [:browser/focus-tab-by-idx tab-idx]) + 200))})})) diff --git a/src/status_im/contexts/browser/subs/core.cljs b/src/status_im/contexts/browser/subs/core.cljs index bca2432d642..8c9db5d1235 100644 --- a/src/status_im/contexts/browser/subs/core.cljs +++ b/src/status_im/contexts/browser/subs/core.cljs @@ -17,6 +17,10 @@ (fn [db] (get db :browser/dapps))) +(rf/reg-sub :browser/mode + (fn [db] + (get db :browser/mode))) + (rf/reg-sub :browser/focused-tab-id (fn [db] (let [default (-> db :browser/tab-ids first)] @@ -32,7 +36,8 @@ :<- [:browser/focused-tab-id] :<- [:browser/tab-ids] (fn [[focused-id tab-ids]] - (.indexOf tab-ids focused-id))) + (let [idx (.indexOf tab-ids focused-id)] + (when (not= -1 idx) idx)))) (rf/reg-sub :browser/can-focus-next? :<- [:browser/focused-tab-idx] diff --git a/src/status_im/contexts/browser/view.cljs b/src/status_im/contexts/browser/view.cljs index 1170b3ec335..bf1908a1627 100644 --- a/src/status_im/contexts/browser/view.cljs +++ b/src/status_im/contexts/browser/view.cljs @@ -1,7 +1,9 @@ (ns status-im.contexts.browser.view (:require [quo.context :as quo.context] + [quo.core :as quo] [quo.foundations.colors :as colors] [react-native.core :as rn] + [react-native.fast-image :as fast-image] [react-native.freeze :as freeze] [react-native.gesture :as gesture] [react-native.reanimated :as reanimated] @@ -28,13 +30,12 @@ (defn tab-container [{:keys [idx x-translation-value]} & children] (let [theme (quo.context/use-theme) - scale-down-amount 0.9] + scale-down-amount 0.95] (into [reanimated/view {:style [{:transform [{:scale (interpolate-x-position x-translation-value idx - [scale-down-amount 1 - scale-down-amount])}]} + [scale-down-amount 1 scale-down-amount])}]} {:width browser.constants/browser-width :transform-origin :bottom :border-radius 20 @@ -65,27 +66,110 @@ {:tab-id tab-id :url url}])]])) +(def preview-width + (/ (- browser.constants/browser-width (* 3 20)) 2)) + +(def preview-height (* preview-width 1.2)) + +(defn on-press-preview + [tab-id] + (rf/dispatch [:browser/show-browser]) + (rf/dispatch [:browser/focus-tab tab-id])) + +(defn tab-preview + [tab-id idx] + (let [tab (rf/sub [:browser/tab-by-id tab-id]) + dapp (rf/sub [:browser/dapp-for-tab tab-id])] + [rn/pressable + {:on-press #(on-press-preview tab-id) + :style {:align-items :center + :justify-content :center + :background-color colors/neutral-80 + :border-radius 12 + :width preview-width + :height preview-height}} + [rn/view + (if (= :tab/native (:type tab)) + ;; TODO: remove `when` + (when (:icon dapp) + [rn/view + {:style {:background-color colors/neutral-20 + :padding 4 + :border-radius 20 + :overflow :hidden}} + [rn/view {:style {:transform [{:scale 0.8}]}} + [quo/icon (:icon dapp) + {:size 24 :color colors/neutral-80}]]]) + [fast-image/fast-image + {:source (:logo-url dapp) + :style {:width 32 :height 32 :border-radius 20}}])]])) + +(defn tabs-mode + [] + (let [tab-ids (rf/sub [:browser/tab-ids])] + [rn/scroll-view + {:shows-horizontal-scroll-indicator false + :shows-vertical-scroll-indicator false + :content-container-style {:padding-horizontal 20 + :padding-vertical 20 + :flex-direction :row + :flex-wrap :wrap + :gap 20}} + (map-indexed (fn [idx tab-id] + ^{:key (str idx "-" tab-id)} + [tab-preview tab-id idx]) + tab-ids)])) + +(defn use-hide + [show?] + (let [opacity-value (reanimated/use-shared-value (if show? 1 0)) + scale-value (reanimated/use-shared-value (if show? 1 0.8))] + (rn/use-effect + (fn [] + (if show? + (do (reanimated/animate-shared-value-with-timing opacity-value 1 200 :easing2) + (reanimated/animate-shared-value-with-timing scale-value 1 200 :easing2)) + (do (reanimated/animate-shared-value-with-timing opacity-value 0 200 :easing2) + (reanimated/animate-shared-value-with-timing scale-value 0.8 200 :easing2)))) + [show?]) + {:opacity opacity-value + :transform-origin :top + :transform [{:scale scale-value}]})) + (defn view [] - (let [tab-ids (rf/sub [:browser/tab-ids]) - scroll-ref (reanimated/use-animated-ref) - {:keys [gesture x-translation-value]} (hooks/use-swipe-gesture scroll-ref)] - [:<> - [reanimated/scroll-view - {:horizontal true - :ref scroll-ref - :content-container-style {:background-color colors/neutral-100 - :padding-top safe-area/top} - :shows-horizontal-scroll-indicator false - :shows-vertical-scroll-indicator false - :scroll-enabled false} - (map-indexed (fn [idx tab-id] - ^{:key (str idx "-" tab-id)} - [render-tab tab-id idx x-translation-value]) - tab-ids)] + (let [tab-ids (rf/sub [:browser/tab-ids]) + browser-mode (rf/sub [:browser/mode]) + browser-style (use-hide (= browser-mode :browser-mode/browser)) + tabs-style (use-hide (= browser-mode :browser-mode/tabs)) + scroll-ref (reanimated/use-animated-ref) + {:keys [gesture-prop x-translation-value]} (hooks/use-swipe-gesture scroll-ref)] + [rn/view + {:background-color colors/neutral-100 + :padding-top safe-area/top + :flex 1} + [reanimated/view + {:style [browser-style + {:flex 1}]} + [reanimated/scroll-view + {:horizontal true + :ref scroll-ref + :shows-horizontal-scroll-indicator false + :shows-vertical-scroll-indicator false + :scroll-enabled false} + (map-indexed (fn [idx tab-id] + ^{:key (str idx "-" tab-id)} + [render-tab tab-id idx x-translation-value]) + tab-ids)]] - [gesture/gesture-detector {:gesture gesture} + (when (= :browser-mode/tabs browser-mode) + [reanimated/view + {:style [tabs-style + {:position :absolute + :top safe-area/top + :left 0 + :right 0 + :bottom (+ browser.constants/footer-height safe-area/bottom)}]} + [tabs-mode]]) + [gesture/gesture-detector {:gesture gesture-prop} [footer/view]]])) - -(comment - (rf/dispatch [:browser/add-tab "wallet.status" :tab/native])) From 149d34037c9d9321ba35c8340759776d82a934c1 Mon Sep 17 00:00:00 2001 From: Cristian Lungu Date: Tue, 20 May 2025 00:38:55 +0300 Subject: [PATCH 12/14] feat: added ugly screenshots --- nix/deps/gradle/proj.list | 1 + package.json | 1 + src/react_native/view_shot.cljs | 11 ++ .../contexts/browser/components/footer.cljs | 2 +- .../contexts/browser/events/core.cljs | 4 + src/status_im/contexts/browser/hooks.cljs | 3 +- src/status_im/contexts/browser/subs/core.cljs | 9 + src/status_im/contexts/browser/view.cljs | 160 +++++++++++++----- yarn.lock | 41 +++++ 9 files changed, 192 insertions(+), 40 deletions(-) create mode 100644 src/react_native/view_shot.cljs diff --git a/nix/deps/gradle/proj.list b/nix/deps/gradle/proj.list index 39e5af6b067..17a6822c8f7 100644 --- a/nix/deps/gradle/proj.list +++ b/nix/deps/gradle/proj.list @@ -38,5 +38,6 @@ react-native-share react-native-status react-native-status-keycard react-native-svg +react-native-view-shot react-native-webview walletconnect_react-native-compat diff --git a/package.json b/package.json index 26799e0280a..210b189eafd 100644 --- a/package.json +++ b/package.json @@ -72,6 +72,7 @@ "react-native-share": "10.0.2", "react-native-status-keycard": "git+https://github.com/status-im/react-native-status-keycard.git#refs/tags/v2.6.6", "react-native-svg": "13.10.0", + "react-native-view-shot": "^3.8.0", "react-native-webview": "13.6.3", "react-syntax-highlighter": "^15.5.0" }, diff --git a/src/react_native/view_shot.cljs b/src/react_native/view_shot.cljs new file mode 100644 index 00000000000..0db7e12b2b6 --- /dev/null +++ b/src/react_native/view_shot.cljs @@ -0,0 +1,11 @@ +(ns react-native.view-shot + (:require ["react-native-view-shot" :as view-shot] + [reagent.core :as reagent])) + +(def view (reagent/adapt-react-class view-shot/default)) + +(defn capture + [^js ref] + (some-> ^js ref + (.capture))) + diff --git a/src/status_im/contexts/browser/components/footer.cljs b/src/status_im/contexts/browser/components/footer.cljs index 6a865e9c308..4131526b7bf 100644 --- a/src/status_im/contexts/browser/components/footer.cljs +++ b/src/status_im/contexts/browser/components/footer.cljs @@ -102,7 +102,7 @@ (defn tabs-btn [] (let [tab-id (rf/sub [:browser/focused-tab-id]) - browser-mode (rf/sub [:browser/mode tab-id])] + browser-mode (rf/sub [:browser/mode])] [rn/pressable {:style {:flex 1 :align-items :center diff --git a/src/status_im/contexts/browser/events/core.cljs b/src/status_im/contexts/browser/events/core.cljs index 1eb2fa2eb6c..a8f5e01612d 100644 --- a/src/status_im/contexts/browser/events/core.cljs +++ b/src/status_im/contexts/browser/events/core.cljs @@ -98,3 +98,7 @@ (rf/reg-event-fx :browser/show-browser (fn [{:keys [db]}] {:db (assoc db :browser/mode :browser-mode/browser)})) + +(rf/reg-event-fx :browser/add-tab-preview + (fn [{:keys [db]} [tab-id url]] + {:db (assoc-in db [:browser/tab-previews tab-id] url)})) diff --git a/src/status_im/contexts/browser/hooks.cljs b/src/status_im/contexts/browser/hooks.cljs index 71722940875..2c15bda8a5b 100644 --- a/src/status_im/contexts/browser/hooks.cljs +++ b/src/status_im/contexts/browser/hooks.cljs @@ -52,7 +52,8 @@ (let [focused-tab-idx (rf/sub [:browser/focused-tab-idx]) browser-mode (rf/sub [:browser/mode]) gestures-enabled? (= browser-mode :browser-mode/browser) - x-translation-value (reanimated/use-shared-value (or (browser/tab-position focused-tab-idx) 0)) + x-translation-value (reanimated/use-shared-value + (or (browser/tab-position focused-tab-idx) 0)) can-focus-next? (rf/sub [:browser/can-focus-next?]) can-focus-prev? (rf/sub [:browser/can-focus-prev?])] diff --git a/src/status_im/contexts/browser/subs/core.cljs b/src/status_im/contexts/browser/subs/core.cljs index 8c9db5d1235..3e7cd9c8eba 100644 --- a/src/status_im/contexts/browser/subs/core.cljs +++ b/src/status_im/contexts/browser/subs/core.cljs @@ -99,3 +99,12 @@ (let [title (-> dapp :metadata :title) url (-> dapp :origin-url)] (or title url)))) + +(rf/reg-sub :browser/tab-previews + (fn [db] + (get db :browser/tab-previews))) + +(rf/reg-sub :browser/tab-preview + :<- [:browser/tab-previews] + (fn [tab-previews [_ tab-id]] + (get tab-previews tab-id))) diff --git a/src/status_im/contexts/browser/view.cljs b/src/status_im/contexts/browser/view.cljs index bf1908a1627..13824c3a2a4 100644 --- a/src/status_im/contexts/browser/view.cljs +++ b/src/status_im/contexts/browser/view.cljs @@ -8,6 +8,8 @@ [react-native.gesture :as gesture] [react-native.reanimated :as reanimated] [react-native.safe-area :as safe-area] + [react-native.view-shot :as view-shot] + [reagent.core :as reagent] [status-im.contexts.browser.components.footer :as footer] [status-im.contexts.browser.components.webview-tab :as webview-tab] [status-im.contexts.browser.constants :as browser.constants] @@ -37,34 +39,98 @@ idx [scale-down-amount 1 scale-down-amount])}]} {:width browser.constants/browser-width - :transform-origin :bottom + :height browser.constants/browser-height :border-radius 20 + :transform-origin :bottom :overflow :hidden - :background-color (colors/theme-colors colors/white colors/neutral-95 theme) - :height browser.constants/browser-height}]}] + :background-color (colors/theme-colors colors/white colors/neutral-95 theme)}]}] children))) +(defn use-screenshot-tab + [tab-id] + (let [ref (rn/use-ref) + capture (fn [] + (println "capture" tab-id) + (some-> (.-current ^js ref) + (view-shot/capture) + (.then #(rf/dispatch [:browser/add-tab-preview tab-id %])) + (.catch #(println :capture-error %)))) + options {:fileName (str "tab-screenshot-" tab-id) + :format :jpg + :quality 0.9} + screenshot (rf/sub [:browser/tab-preview tab-id])] + {:capture capture + :options options + :ref ref + :screenshot screenshot})) + +(defn placeholder + [{:keys [url]}] + [rn/view + {:style + {:width browser.constants/browser-width + :height browser.constants/browser-height + :border-radius 20}} + [fast-image/fast-image + {:source url + :style {:flex 1}}]]) + (defn render-tab [tab-id idx x-translation-value] - (let [url (rf/sub [:browser/tab-url tab-id]) - tab-type (rf/sub [:browser/tab-type tab-id]) - focused-tab-idx (rf/sub [:browser/focused-tab-idx]) - freeze-tab? (browser/freeze-tab? idx focused-tab-idx) - ;;freeze-tab? (not= idx focused-tab-idx) - ] - [tab-container - {:idx idx - :x-translation-value x-translation-value} - [freeze/view {:freeze freeze-tab?} - (if (= :tab/native tab-type) - [rn/view - {:style {:margin-top (- 10 safe-area/top) - :z-index 5 - :flex 1}} - (get native-dapps/views url)] - [webview-tab/view - {:tab-id tab-id - :url url}])]])) + (let [url (rf/sub [:browser/tab-url tab-id]) + tab-type (rf/sub [:browser/tab-type tab-id]) + browser-mode (rf/sub [:browser/mode]) + focused-tab-idx (rf/sub [:browser/focused-tab-idx]) + ;;freeze-tab? (browser/freeze-tab? idx focused-tab-idx) + freeze-tab? (not= idx focused-tab-idx) + {:keys [ref options capture screenshot]} (use-screenshot-tab tab-id) + screenshot-interval (rn/use-ref-atom nil) + [freeze? set-freeze] (rn/use-state freeze-tab?) + placeholder-opacity-value (reanimated/use-shared-value (if freeze-tab? 1 0))] + (rn/use-effect (fn [] + (if freeze-tab? + (when @screenshot-interval + (js/clearInterval @screenshot-interval)) + (let [interval-id (js/setInterval capture 4000)] + (js/setTimeout capture 800) + (reset! screenshot-interval interval-id)))) + [freeze-tab?]) + (rn/use-unmount (fn [] (js/clearInterval @screenshot-interval))) + (rn/use-effect (fn [] + (reanimated/animate-shared-value-with-delay placeholder-opacity-value + (if freeze-tab? 1 0) + 400 + :easing2 + 400) + (js/setTimeout #(set-freeze freeze-tab?) 800)) + [freeze-tab?]) + [view-shot/view + {:ref ref + :style {:flex 1} + :options options} + [tab-container + {:idx idx + :x-translation-value x-translation-value} + [freeze/view + {:freeze freeze-tab? + :placeholder (reagent/as-element + [placeholder {:url screenshot :opacity-value placeholder-opacity-value}]) + } + (if (= :tab/native tab-type) + [rn/view + {:style {:margin-top (- 10 safe-area/top) + :z-index 5 + :flex 1}} + (get native-dapps/views url)] + [webview-tab/view + {:tab-id tab-id + :url url}])] + (when freeze? + [reanimated/view + {:pointer-events :none + :style [{:opacity placeholder-opacity-value} + {:position :absolute :left 0 :right 0 :bottom 0 :top 0}]} + [placeholder {:url screenshot}]])]])) (def preview-width (/ (- browser.constants/browser-width (* 3 20)) 2)) @@ -78,8 +144,9 @@ (defn tab-preview [tab-id idx] - (let [tab (rf/sub [:browser/tab-by-id tab-id]) - dapp (rf/sub [:browser/dapp-for-tab tab-id])] + (let [tab (rf/sub [:browser/tab-by-id tab-id]) + dapp (rf/sub [:browser/dapp-for-tab tab-id]) + preview-url (rf/sub [:browser/tab-preview tab-id])] [rn/pressable {:on-press #(on-press-preview tab-id) :style {:align-items :center @@ -88,21 +155,38 @@ :border-radius 12 :width preview-width :height preview-height}} + [fast-image/fast-image + {:source preview-url + :resize-mode :cover + :style {:border-radius 12 + :width preview-width + :height preview-height}}] [rn/view - (if (= :tab/native (:type tab)) - ;; TODO: remove `when` - (when (:icon dapp) - [rn/view - {:style {:background-color colors/neutral-20 - :padding 4 - :border-radius 20 - :overflow :hidden}} - [rn/view {:style {:transform [{:scale 0.8}]}} - [quo/icon (:icon dapp) - {:size 24 :color colors/neutral-80}]]]) - [fast-image/fast-image - {:source (:logo-url dapp) - :style {:width 32 :height 32 :border-radius 20}}])]])) + {:style {:position :absolute + :bottom -8 + :left 0 + :right 0 + :justify-content :center + :align-items :center}} + [rn/view + {:style {:width 32 + :height 32 + :background-color colors/neutral-20 + :border-radius 20 + :justify-content :center + :align-items :center}} + (if (= :tab/native (:type tab)) + ;; TODO: remove `when` + (when (:icon dapp) + [rn/view + {:style {:padding 4 + :overflow :hidden}} + [rn/view {:style {:transform [{:scale 0.8}]}} + [quo/icon (:icon dapp) + {:size 24 :color colors/neutral-80}]]]) + [fast-image/fast-image + {:source (:logo-url dapp) + :style {:width 32 :height 32 :border-radius 20}}])]]])) (defn tabs-mode [] diff --git a/yarn.lock b/yarn.lock index 4fc14855f07..4584ea607bd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4870,6 +4870,11 @@ base-64@^1.0.0: resolved "https://registry.yarnpkg.com/base-64/-/base-64-1.0.0.tgz#09d0f2084e32a3fd08c2475b973788eee6ae8f4a" integrity sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg== +base64-arraybuffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz#1c37589a7c4b0746e34bd1feb951da2df01c1bdc" + integrity sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ== + base64-js@^1.0.2: version "1.3.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" @@ -5726,6 +5731,13 @@ crypto-random-string@^2.0.0: resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== +css-line-break@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/css-line-break/-/css-line-break-2.1.0.tgz#bfef660dfa6f5397ea54116bb3cb4873edbc4fa0" + integrity sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w== + dependencies: + utrie "^1.0.2" + css-select@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6" @@ -6971,6 +6983,14 @@ html-escaper@^2.0.0: resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== +html2canvas@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/html2canvas/-/html2canvas-1.4.1.tgz#7cef1888311b5011d507794a066041b14669a543" + integrity sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA== + dependencies: + css-line-break "^2.1.0" + text-segmentation "^1.0.3" + http-cache-semantics@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" @@ -10067,6 +10087,13 @@ react-native-url-polyfill@2.0.0: dependencies: whatwg-url-without-unicode "8.0.0-3" +react-native-view-shot@^3.8.0: + version "3.8.0" + resolved "https://registry.yarnpkg.com/react-native-view-shot/-/react-native-view-shot-3.8.0.tgz#1aa1905f0e79428ca32bf80c16fd4abc719c600b" + integrity sha512-4cU8SOhMn3YQIrskh+5Q8VvVRxQOu8/s1M9NAL4z5BY1Rm0HXMWkQJ4N0XsZ42+Yca+y86ISF3LC5qdLPvPuiA== + dependencies: + html2canvas "^1.4.1" + react-native-webview@13.6.3: version "13.6.3" resolved "https://registry.yarnpkg.com/react-native-webview/-/react-native-webview-13.6.3.tgz#f3d26e942ef5cc5a07547f2e47903aa81a68e25e" @@ -11122,6 +11149,13 @@ test-exclude@^6.0.0: glob "^7.1.4" minimatch "^3.0.4" +text-segmentation@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/text-segmentation/-/text-segmentation-1.0.3.tgz#52a388159efffe746b24a63ba311b6ac9f2d7943" + integrity sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw== + dependencies: + utrie "^1.0.2" + thread-stream@^0.15.1: version "0.15.2" resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-0.15.2.tgz#fb95ad87d2f1e28f07116eb23d85aba3bc0425f4" @@ -11499,6 +11533,13 @@ utils-merge@1.0.1: resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== +utrie@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/utrie/-/utrie-1.0.2.tgz#d42fe44de9bc0119c25de7f564a6ed1b2c87a645" + integrity sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw== + dependencies: + base64-arraybuffer "^1.0.2" + uuid@^3.3.2: version "3.4.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" From 47eb82beae72fbb8f1ae66f009138760b3d68d97 Mon Sep 17 00:00:00 2001 From: Cristian Lungu Date: Tue, 20 May 2025 13:22:36 +0300 Subject: [PATCH 13/14] restructuring --- .../contexts/browser/components/dapp_bar.cljs | 46 +++ .../browser/components/dapp_icon.cljs | 27 ++ .../footer.cljs => footer/view.cljs} | 72 +---- src/status_im/contexts/browser/hooks.cljs | 19 ++ .../contexts/browser/tabs/tab_window.cljs | 110 ++++++++ .../contexts/browser/tabs/tabs_preview.cljs | 93 +++++++ src/status_im/contexts/browser/tabs/view.cljs | 39 +++ .../webview_tab.cljs => tabs/webview.cljs} | 2 +- src/status_im/contexts/browser/view.cljs | 263 ++---------------- 9 files changed, 366 insertions(+), 305 deletions(-) create mode 100644 src/status_im/contexts/browser/components/dapp_bar.cljs create mode 100644 src/status_im/contexts/browser/components/dapp_icon.cljs rename src/status_im/contexts/browser/{components/footer.cljs => footer/view.cljs} (56%) create mode 100644 src/status_im/contexts/browser/tabs/tab_window.cljs create mode 100644 src/status_im/contexts/browser/tabs/tabs_preview.cljs create mode 100644 src/status_im/contexts/browser/tabs/view.cljs rename src/status_im/contexts/browser/{components/webview_tab.cljs => tabs/webview.cljs} (97%) diff --git a/src/status_im/contexts/browser/components/dapp_bar.cljs b/src/status_im/contexts/browser/components/dapp_bar.cljs new file mode 100644 index 00000000000..a20a4c77a72 --- /dev/null +++ b/src/status_im/contexts/browser/components/dapp_bar.cljs @@ -0,0 +1,46 @@ +(ns status-im.contexts.browser.components.dapp-bar + (:require [quo.core :as quo] + [quo.foundations.colors :as colors] + [react-native.core :as rn])) + +(defn container + [& children] + (into [rn/view + {:style {:padding 8 + :background-color colors/neutral-80 + :flex-direction :row + :border-radius 20 + :height 44 + :margin-horizontal 12 + :padding-horizontal 8 + :flex 1 + :align-items :center + :justify-content :space-between}}] + children)) + +(defn title + [{:keys [text container-style]}] + [rn/view + {:style (merge {:padding-horizontal 2 + :flex 1 + :align-items :center + :justify-content :center} + container-style)} + [quo/text + {:size :paragraph-1 + :number-of-lines 1 + :weight :bold + :style {:color colors/white-opa-80}} + text]]) + +(defn info-button + [] + [rn/view + {:style + {:width 24 + :height 24 + :align-items :center + :justify-content :center}} + [quo/icon :i/info + {:size 20 + :color colors/white-opa-80}]]) diff --git a/src/status_im/contexts/browser/components/dapp_icon.cljs b/src/status_im/contexts/browser/components/dapp_icon.cljs new file mode 100644 index 00000000000..d46ff4cdba7 --- /dev/null +++ b/src/status_im/contexts/browser/components/dapp_icon.cljs @@ -0,0 +1,27 @@ +(ns status-im.contexts.browser.components.dapp-icon + (:require [quo.core :as quo] + [quo.foundations.colors :as colors] + [react-native.core :as rn] + [react-native.fast-image :as fast-image])) + +(defn view + [{:keys [dapp size background-color]}] + (let [icon (:icon dapp) + image (:logo-url dapp)] + (when (or icon image) + [rn/view + {:padding 4 + :border-radius 20 + :background-color background-color + :overflow :hidden} + (cond + icon + [rn/view {:style {:transform [{:scale 0.8}]}} + [quo/icon (:icon dapp) + {:size size + :color colors/white-opa-80}]] + + image + [fast-image/fast-image + {:source (:logo-url dapp) + :style {:width size :height size :border-radius 20}}])]))) diff --git a/src/status_im/contexts/browser/components/footer.cljs b/src/status_im/contexts/browser/footer/view.cljs similarity index 56% rename from src/status_im/contexts/browser/components/footer.cljs rename to src/status_im/contexts/browser/footer/view.cljs index 4131526b7bf..85e04dbe6c1 100644 --- a/src/status_im/contexts/browser/components/footer.cljs +++ b/src/status_im/contexts/browser/footer/view.cljs @@ -1,66 +1,24 @@ -(ns status-im.contexts.browser.components.footer +(ns status-im.contexts.browser.footer.view (:require [quo.core :as quo] [quo.foundations.colors :as colors] [react-native.core :as rn] - [react-native.fast-image :as fast-image] [react-native.reanimated :as reanimated] + [status-im.contexts.browser.components.dapp-bar :as dapp-bar] + [status-im.contexts.browser.components.dapp-icon :as dapp-icon] [status-im.contexts.browser.constants :as browser.constants] [status-im.contexts.profile.utils :as profile.utils] [utils.re-frame :as rf])) -(def icon-size 32) - (defn dapp-bar [] - (let [tab-id (rf/sub [:browser/focused-tab-id]) - tab-type (rf/sub [:browser/tab-type tab-id]) - dapp (rf/sub [:browser/dapp-for-tab tab-id])] - [rn/view - {:style {:padding 8 - :background-color colors/neutral-30 - :flex-direction :row - :border-radius 20 - :height 44 - :margin-horizontal 12 - :padding-horizontal 8 - :flex 1 - :align-items :center - :justify-content :space-between}} - [rn/view - (if (= :tab/native tab-type) - ;; TODO: remove `when` - (when (:icon dapp) - [rn/view - {:style {:background-color colors/neutral-80 - :padding 4 - :border-radius 20 - :overflow :hidden}} - [rn/view {:style {:transform [{:scale 0.8}]}} - [quo/icon (:icon dapp) - {:size 24}]]]) - [fast-image/fast-image - {:source (:logo-url dapp) - :style {:width icon-size :height icon-size :border-radius 20}}])] - [rn/view - {:style {:padding-horizontal 2 - :flex 1 - :align-items :center - :justify-content :center}} - [quo/text - {:size :paragraph-1 - :number-of-lines 1 - :weight :bold - :style {:color colors/neutral-100}} - (:title dapp)]] - [rn/view - {:style - {:width 24 - :height 24 - :align-items :center - :justify-content :center}} - [rn/view {:style {:transform [{:scale 1}]}} - [quo/icon :i/info - {:size 20 :color colors/neutral-80}]]]])) + (let [tab-id (rf/sub [:browser/focused-tab-id]) + dapp (rf/sub [:browser/dapp-for-tab tab-id])] + [dapp-bar/container + [dapp-icon/view + {:dapp dapp + :size 24}] + [dapp-bar/title {:text (:title dapp)}] + [dapp-bar/info-button]])) (defn profile-btn [] @@ -74,8 +32,7 @@ on-press #(rf/dispatch [:open-modal :screen/settings])] [rn/pressable {:on-press on-press - :style {:transform [{:scale 1.3}] - :padding 2 + :style {:padding 4 :background-color colors/neutral-80 :border-radius 40} :accessibility-label :open-profile} @@ -101,8 +58,7 @@ (defn tabs-btn [] - (let [tab-id (rf/sub [:browser/focused-tab-id]) - browser-mode (rf/sub [:browser/mode])] + (let [browser-mode (rf/sub [:browser/mode])] [rn/pressable {:style {:flex 1 :align-items :center @@ -124,7 +80,7 @@ [rn/view {:style {:align-items :center :justify-content :center - :margin-right 12}} + :margin-right 8}} [profile-btn]] [dapp-bar] [rn/view diff --git a/src/status_im/contexts/browser/hooks.cljs b/src/status_im/contexts/browser/hooks.cljs index 2c15bda8a5b..feabe22f437 100644 --- a/src/status_im/contexts/browser/hooks.cljs +++ b/src/status_im/contexts/browser/hooks.cljs @@ -3,6 +3,7 @@ [react-native.core :as rn] [react-native.gesture :as gesture] [react-native.reanimated :as reanimated] + [react-native.view-shot :as view-shot] [status-im.contexts.browser.core :as browser] [utils.re-frame :as rf] [utils.worklets.browser :as worklets.browser])) @@ -77,3 +78,21 @@ (js/setTimeout #(rf/dispatch-sync [:browser/focus-tab-by-idx tab-idx]) 200))})})) + +(defn use-screenshot-tab + [tab-id] + (let [ref (rn/use-ref) + capture (fn [] + (println "capture" tab-id) + (some-> (.-current ^js ref) + (view-shot/capture) + (.then #(rf/dispatch [:browser/add-tab-preview tab-id %])) + (.catch #(println :capture-error %)))) + options {:fileName (str "tab-screenshot-" tab-id) + :format :jpg + :quality 0.9} + screenshot (rf/sub [:browser/tab-preview tab-id])] + {:capture capture + :options options + :ref ref + :screenshot screenshot})) diff --git a/src/status_im/contexts/browser/tabs/tab_window.cljs b/src/status_im/contexts/browser/tabs/tab_window.cljs new file mode 100644 index 00000000000..35f3150f561 --- /dev/null +++ b/src/status_im/contexts/browser/tabs/tab_window.cljs @@ -0,0 +1,110 @@ +(ns status-im.contexts.browser.tabs.tab-window + (:require [quo.context :as quo.context] + [quo.foundations.colors :as colors] + [react-native.core :as rn] + [react-native.fast-image :as fast-image] + [react-native.freeze :as freeze] + [react-native.reanimated :as reanimated] + [react-native.safe-area :as safe-area] + [react-native.view-shot :as view-shot] + [reagent.core :as reagent] + [status-im.contexts.browser.constants :as browser.constants] + [status-im.contexts.browser.core :as browser] + [status-im.contexts.browser.hooks :as hooks] + [status-im.contexts.browser.native-dapps :as native-dapps] + [status-im.contexts.browser.tabs.webview :as webview] + [utils.re-frame :as rf])) + +(defn interpolate-x-position + [x-value tab-idx output-values] + (let [x-tab (browser/tab-position tab-idx) + x-tab-prev (browser/tab-position (dec tab-idx)) + x-tab-next (browser/tab-position (inc tab-idx))] + (reanimated/interpolate x-value + [x-tab-prev x-tab x-tab-next] + output-values + {:extrapolateLeft "clamp" + :extrapolateRight "clamp"}))) + +(defn tab-container + [{:keys [idx x-translation-value]} & children] + (let [theme (quo.context/use-theme) + scale-down-amount 0.95] + (into [reanimated/view + {:style + [{:transform [{:scale (interpolate-x-position x-translation-value + idx + [scale-down-amount 1 scale-down-amount])}]} + {:width browser.constants/browser-width + :height browser.constants/browser-height + :border-radius 20 + :transform-origin :bottom + :overflow :hidden + :background-color (colors/theme-colors colors/white colors/neutral-95 theme)}]}] + children))) + +(defn freeze-placeholder + [{:keys [url]}] + [rn/view + {:style + {:width browser.constants/browser-width + :height browser.constants/browser-height + :border-radius 20}} + [fast-image/fast-image + {:source url + :style {:flex 1}}]]) + +(defn view + [tab-id idx x-translation-value] + (let [url (rf/sub [:browser/tab-url tab-id]) + tab-type (rf/sub [:browser/tab-type tab-id]) + focused-tab-idx (rf/sub [:browser/focused-tab-idx]) + ;;freeze-tab? (browser/freeze-tab? idx focused-tab-idx) + freeze-tab? (not= idx focused-tab-idx) + {:keys [ref options capture screenshot]} (hooks/use-screenshot-tab tab-id) + screenshot-interval (rn/use-ref-atom nil) + [freeze? set-freeze] (rn/use-state freeze-tab?) + placeholder-opacity-value (reanimated/use-shared-value (if freeze-tab? 1 0))] + (rn/use-effect (fn [] + (if freeze-tab? + (when @screenshot-interval + (js/clearInterval @screenshot-interval)) + (let [interval-id (js/setInterval capture 4000)] + (js/setTimeout capture 800) + (reset! screenshot-interval interval-id)))) + [freeze-tab?]) + (rn/use-unmount (fn [] (js/clearInterval @screenshot-interval))) + (rn/use-effect (fn [] + (reanimated/animate-shared-value-with-delay placeholder-opacity-value + (if freeze-tab? 1 0) + 400 + :easing2 + 400) + (js/setTimeout #(set-freeze freeze-tab?) 800)) + [freeze-tab?]) + [view-shot/view + {:ref ref + :style {:flex 1} + :options options} + [tab-container + {:idx idx + :x-translation-value x-translation-value} + [freeze/view + {:freeze freeze-tab? + :placeholder (reagent/as-element + [freeze-placeholder {:url screenshot :opacity-value placeholder-opacity-value}])} + (if (= :tab/native tab-type) + [rn/view + {:style {:margin-top (- 10 safe-area/top) + :z-index 5 + :flex 1}} + (get native-dapps/views url)] + [webview/view + {:tab-id tab-id + :url url}])] + (when freeze? + [reanimated/view + {:pointer-events :none + :style [{:opacity placeholder-opacity-value} + {:position :absolute :left 0 :right 0 :bottom 0 :top 0}]} + [freeze-placeholder {:url screenshot}]])]])) diff --git a/src/status_im/contexts/browser/tabs/tabs_preview.cljs b/src/status_im/contexts/browser/tabs/tabs_preview.cljs new file mode 100644 index 00000000000..85d953ede4f --- /dev/null +++ b/src/status_im/contexts/browser/tabs/tabs_preview.cljs @@ -0,0 +1,93 @@ +(ns status-im.contexts.browser.tabs.tabs-preview + (:require [quo.foundations.colors :as colors] + [react-native.core :as rn] + [react-native.fast-image :as fast-image] + [react-native.reanimated :as reanimated] + [react-native.safe-area :as safe-area] + [status-im.contexts.browser.components.dapp-icon :as dapp-icon] + [status-im.contexts.browser.constants :as browser.constants] + [utils.re-frame :as rf])) + +(def preview-width + (/ (- browser.constants/browser-width (* 3 20)) 2)) + +(def preview-height (* preview-width 1.2)) + +(defn on-press-preview + [tab-id] + (rf/dispatch [:browser/show-browser]) + (rf/dispatch [:browser/focus-tab tab-id])) + +(defn tab-preview + [tab-id] + (let [dapp (rf/sub [:browser/dapp-for-tab tab-id]) + preview-url (rf/sub [:browser/tab-preview tab-id])] + [rn/pressable + {:on-press #(on-press-preview tab-id) + :style {:align-items :center + :justify-content :center + :background-color colors/neutral-80 + :border-radius 12 + :width preview-width + :height preview-height}} + [fast-image/fast-image + {:source preview-url + :resize-mode :cover + :style {:border-radius 12 + :width preview-width + :height preview-height}}] + [rn/view + {:style {:position :absolute + :bottom -8 + :left 0 + :right 0 + :justify-content :center + :align-items :center}} + [rn/view + {:style {:width 32 + :height 32 + :background-color colors/neutral-20 + :border-radius 20 + :justify-content :center + :align-items :center}} + [dapp-icon/view + {:dapp dapp + :size 24 + :background-color colors/neutral-80}]]]])) + +(defn use-hide + [show?] + (let [opacity-value (reanimated/use-shared-value (if show? 1 0))] + (rn/use-effect + (fn [] + (if show? + (reanimated/animate-shared-value-with-timing opacity-value 1 200 :easing2) + (reanimated/animate-shared-value-with-timing opacity-value 0 200 :easing2))) + [show?]) + {:opacity opacity-value})) + +(defn view + [] + (let [tab-ids (rf/sub [:browser/tab-ids]) + browser-mode (rf/sub [:browser/mode]) + tabs-style (use-hide (= browser-mode :browser-mode/tabs))] + (when (= :browser-mode/tabs browser-mode) + [reanimated/view + {:style [tabs-style + {:position :absolute + :top safe-area/top + :left 0 + :right 0 + :bottom (+ browser.constants/footer-height safe-area/bottom)}]} + [rn/scroll-view + {:shows-horizontal-scroll-indicator false + :shows-vertical-scroll-indicator false + :content-container-style {:padding-horizontal 20 + :padding-vertical 20 + :flex-direction :row + :flex-wrap :wrap + :gap 20}} + (map-indexed (fn [idx tab-id] + ^{:key (str idx "-" tab-id)} + [tab-preview tab-id]) + tab-ids)]]))) diff --git a/src/status_im/contexts/browser/tabs/view.cljs b/src/status_im/contexts/browser/tabs/view.cljs new file mode 100644 index 00000000000..68325739ea6 --- /dev/null +++ b/src/status_im/contexts/browser/tabs/view.cljs @@ -0,0 +1,39 @@ +(ns status-im.contexts.browser.tabs.view + (:require [react-native.core :as rn] + [react-native.reanimated :as reanimated] + [status-im.contexts.browser.tabs.tab-window :as tab-window] + [utils.re-frame :as rf])) + +(defn use-transition-animation + [show?] + (let [opacity-value (reanimated/use-shared-value (if show? 1 0)) + scale-value (reanimated/use-shared-value (if show? 1 0.8))] + (rn/use-effect + (fn [] + (if show? + (do (reanimated/animate-shared-value-with-timing opacity-value 1 200 :easing2) + (reanimated/animate-shared-value-with-timing scale-value 1 200 :easing2)) + (do (reanimated/animate-shared-value-with-timing opacity-value 0 200 :easing2) + (reanimated/animate-shared-value-with-timing scale-value 0.8 200 :easing2)))) + [show?]) + {:opacity opacity-value + :transform-origin :top + :transform [{:scale scale-value}]})) + +(defn view + [{:keys [scroll-ref x-translation-value]}] + (let [browser-mode (rf/sub [:browser/mode]) + tab-ids (rf/sub [:browser/tab-ids]) + browser-style (use-transition-animation (= browser-mode :browser-mode/browser))] + [reanimated/view + {:style [browser-style {:flex 1}]} + [reanimated/scroll-view + {:horizontal true + :ref scroll-ref + :shows-horizontal-scroll-indicator false + :shows-vertical-scroll-indicator false + :scroll-enabled false} + (map-indexed (fn [idx tab-id] + ^{:key (str idx "-" tab-id)} + [tab-window/view tab-id idx x-translation-value]) + tab-ids)]])) diff --git a/src/status_im/contexts/browser/components/webview_tab.cljs b/src/status_im/contexts/browser/tabs/webview.cljs similarity index 97% rename from src/status_im/contexts/browser/components/webview_tab.cljs rename to src/status_im/contexts/browser/tabs/webview.cljs index 61c8fdb290e..ae4e65ad4fa 100644 --- a/src/status_im/contexts/browser/components/webview_tab.cljs +++ b/src/status_im/contexts/browser/tabs/webview.cljs @@ -1,4 +1,4 @@ -(ns status-im.contexts.browser.components.webview-tab +(ns status-im.contexts.browser.tabs.webview (:require [react-native.webview :as webview] [status-im.contexts.browser.js-scripts.core :as js-scripts] [utils.re-frame :as rf])) diff --git a/src/status_im/contexts/browser/view.cljs b/src/status_im/contexts/browser/view.cljs index 13824c3a2a4..316c7bd875e 100644 --- a/src/status_im/contexts/browser/view.cljs +++ b/src/status_im/contexts/browser/view.cljs @@ -1,259 +1,30 @@ (ns status-im.contexts.browser.view - (:require [quo.context :as quo.context] - [quo.core :as quo] - [quo.foundations.colors :as colors] + (:require [quo.foundations.colors :as colors] [react-native.core :as rn] - [react-native.fast-image :as fast-image] - [react-native.freeze :as freeze] [react-native.gesture :as gesture] [react-native.reanimated :as reanimated] [react-native.safe-area :as safe-area] - [react-native.view-shot :as view-shot] - [reagent.core :as reagent] - [status-im.contexts.browser.components.footer :as footer] - [status-im.contexts.browser.components.webview-tab :as webview-tab] - [status-im.contexts.browser.constants :as browser.constants] - [status-im.contexts.browser.core :as browser] + [status-im.contexts.browser.footer.view :as footer] [status-im.contexts.browser.hooks :as hooks] - [status-im.contexts.browser.native-dapps :as native-dapps] - [utils.re-frame :as rf])) - -(defn interpolate-x-position - [x-value tab-idx output-values] - (let [x-tab (browser/tab-position tab-idx) - x-tab-prev (browser/tab-position (dec tab-idx)) - x-tab-next (browser/tab-position (inc tab-idx))] - (reanimated/interpolate x-value - [x-tab-prev x-tab x-tab-next] - output-values - {:extrapolateLeft "clamp" - :extrapolateRight "clamp"}))) - -(defn tab-container - [{:keys [idx x-translation-value]} & children] - (let [theme (quo.context/use-theme) - scale-down-amount 0.95] - (into [reanimated/view - {:style - [{:transform [{:scale (interpolate-x-position x-translation-value - idx - [scale-down-amount 1 scale-down-amount])}]} - {:width browser.constants/browser-width - :height browser.constants/browser-height - :border-radius 20 - :transform-origin :bottom - :overflow :hidden - :background-color (colors/theme-colors colors/white colors/neutral-95 theme)}]}] - children))) - -(defn use-screenshot-tab - [tab-id] - (let [ref (rn/use-ref) - capture (fn [] - (println "capture" tab-id) - (some-> (.-current ^js ref) - (view-shot/capture) - (.then #(rf/dispatch [:browser/add-tab-preview tab-id %])) - (.catch #(println :capture-error %)))) - options {:fileName (str "tab-screenshot-" tab-id) - :format :jpg - :quality 0.9} - screenshot (rf/sub [:browser/tab-preview tab-id])] - {:capture capture - :options options - :ref ref - :screenshot screenshot})) - -(defn placeholder - [{:keys [url]}] - [rn/view - {:style - {:width browser.constants/browser-width - :height browser.constants/browser-height - :border-radius 20}} - [fast-image/fast-image - {:source url - :style {:flex 1}}]]) - -(defn render-tab - [tab-id idx x-translation-value] - (let [url (rf/sub [:browser/tab-url tab-id]) - tab-type (rf/sub [:browser/tab-type tab-id]) - browser-mode (rf/sub [:browser/mode]) - focused-tab-idx (rf/sub [:browser/focused-tab-idx]) - ;;freeze-tab? (browser/freeze-tab? idx focused-tab-idx) - freeze-tab? (not= idx focused-tab-idx) - {:keys [ref options capture screenshot]} (use-screenshot-tab tab-id) - screenshot-interval (rn/use-ref-atom nil) - [freeze? set-freeze] (rn/use-state freeze-tab?) - placeholder-opacity-value (reanimated/use-shared-value (if freeze-tab? 1 0))] - (rn/use-effect (fn [] - (if freeze-tab? - (when @screenshot-interval - (js/clearInterval @screenshot-interval)) - (let [interval-id (js/setInterval capture 4000)] - (js/setTimeout capture 800) - (reset! screenshot-interval interval-id)))) - [freeze-tab?]) - (rn/use-unmount (fn [] (js/clearInterval @screenshot-interval))) - (rn/use-effect (fn [] - (reanimated/animate-shared-value-with-delay placeholder-opacity-value - (if freeze-tab? 1 0) - 400 - :easing2 - 400) - (js/setTimeout #(set-freeze freeze-tab?) 800)) - [freeze-tab?]) - [view-shot/view - {:ref ref - :style {:flex 1} - :options options} - [tab-container - {:idx idx - :x-translation-value x-translation-value} - [freeze/view - {:freeze freeze-tab? - :placeholder (reagent/as-element - [placeholder {:url screenshot :opacity-value placeholder-opacity-value}]) - } - (if (= :tab/native tab-type) - [rn/view - {:style {:margin-top (- 10 safe-area/top) - :z-index 5 - :flex 1}} - (get native-dapps/views url)] - [webview-tab/view - {:tab-id tab-id - :url url}])] - (when freeze? - [reanimated/view - {:pointer-events :none - :style [{:opacity placeholder-opacity-value} - {:position :absolute :left 0 :right 0 :bottom 0 :top 0}]} - [placeholder {:url screenshot}]])]])) - -(def preview-width - (/ (- browser.constants/browser-width (* 3 20)) 2)) - -(def preview-height (* preview-width 1.2)) - -(defn on-press-preview - [tab-id] - (rf/dispatch [:browser/show-browser]) - (rf/dispatch [:browser/focus-tab tab-id])) - -(defn tab-preview - [tab-id idx] - (let [tab (rf/sub [:browser/tab-by-id tab-id]) - dapp (rf/sub [:browser/dapp-for-tab tab-id]) - preview-url (rf/sub [:browser/tab-preview tab-id])] - [rn/pressable - {:on-press #(on-press-preview tab-id) - :style {:align-items :center - :justify-content :center - :background-color colors/neutral-80 - :border-radius 12 - :width preview-width - :height preview-height}} - [fast-image/fast-image - {:source preview-url - :resize-mode :cover - :style {:border-radius 12 - :width preview-width - :height preview-height}}] - [rn/view - {:style {:position :absolute - :bottom -8 - :left 0 - :right 0 - :justify-content :center - :align-items :center}} - [rn/view - {:style {:width 32 - :height 32 - :background-color colors/neutral-20 - :border-radius 20 - :justify-content :center - :align-items :center}} - (if (= :tab/native (:type tab)) - ;; TODO: remove `when` - (when (:icon dapp) - [rn/view - {:style {:padding 4 - :overflow :hidden}} - [rn/view {:style {:transform [{:scale 0.8}]}} - [quo/icon (:icon dapp) - {:size 24 :color colors/neutral-80}]]]) - [fast-image/fast-image - {:source (:logo-url dapp) - :style {:width 32 :height 32 :border-radius 20}}])]]])) - -(defn tabs-mode - [] - (let [tab-ids (rf/sub [:browser/tab-ids])] - [rn/scroll-view - {:shows-horizontal-scroll-indicator false - :shows-vertical-scroll-indicator false - :content-container-style {:padding-horizontal 20 - :padding-vertical 20 - :flex-direction :row - :flex-wrap :wrap - :gap 20}} - (map-indexed (fn [idx tab-id] - ^{:key (str idx "-" tab-id)} - [tab-preview tab-id idx]) - tab-ids)])) - -(defn use-hide - [show?] - (let [opacity-value (reanimated/use-shared-value (if show? 1 0)) - scale-value (reanimated/use-shared-value (if show? 1 0.8))] - (rn/use-effect - (fn [] - (if show? - (do (reanimated/animate-shared-value-with-timing opacity-value 1 200 :easing2) - (reanimated/animate-shared-value-with-timing scale-value 1 200 :easing2)) - (do (reanimated/animate-shared-value-with-timing opacity-value 0 200 :easing2) - (reanimated/animate-shared-value-with-timing scale-value 0.8 200 :easing2)))) - [show?]) - {:opacity opacity-value - :transform-origin :top - :transform [{:scale scale-value}]})) + [status-im.contexts.browser.tabs.tabs-preview :as tabs-preview] + [status-im.contexts.browser.tabs.view :as tabs])) (defn view [] - (let [tab-ids (rf/sub [:browser/tab-ids]) - browser-mode (rf/sub [:browser/mode]) - browser-style (use-hide (= browser-mode :browser-mode/browser)) - tabs-style (use-hide (= browser-mode :browser-mode/tabs)) - scroll-ref (reanimated/use-animated-ref) + (let [scroll-ref (reanimated/use-animated-ref) {:keys [gesture-prop x-translation-value]} (hooks/use-swipe-gesture scroll-ref)] [rn/view - {:background-color colors/neutral-100 - :padding-top safe-area/top - :flex 1} - [reanimated/view - {:style [browser-style - {:flex 1}]} - [reanimated/scroll-view - {:horizontal true - :ref scroll-ref - :shows-horizontal-scroll-indicator false - :shows-vertical-scroll-indicator false - :scroll-enabled false} - (map-indexed (fn [idx tab-id] - ^{:key (str idx "-" tab-id)} - [render-tab tab-id idx x-translation-value]) - tab-ids)]] - - (when (= :browser-mode/tabs browser-mode) - [reanimated/view - {:style [tabs-style - {:position :absolute - :top safe-area/top - :left 0 - :right 0 - :bottom (+ browser.constants/footer-height safe-area/bottom)}]} - [tabs-mode]]) + {:style {:background-color colors/neutral-100 + :padding-top safe-area/top + :flex 1}} + ;; NOTE: the browser tab windows live in a scroll-view (with disabled gestures) inside + ;; `tabs/view`. The scrolling is handled by the `gesture-detector`, which wraps the + ;; `footer/view`. + [tabs/view + {:scroll-ref scroll-ref + :x-translation-value x-translation-value}] + ;; NOTE: tabs preview is absolutely positioned inside `tabs-preview/view` and is conditionally + ;; rendered depending on the `:browser/mode` state + [tabs-preview/view] [gesture/gesture-detector {:gesture gesture-prop} [footer/view]]])) From a48217c139fe47c7ef75aba46044c0fa542780c2 Mon Sep 17 00:00:00 2001 From: Cristian Lungu Date: Wed, 28 May 2025 13:13:44 +0300 Subject: [PATCH 14/14] fix: fixed screenshots --- src/react_native/view_shot.cljs | 4 + .../contexts/browser/events/core.cljs | 5 +- .../contexts/browser/events/effects.cljs | 1 + .../contexts/browser/events/screenshots.cljs | 33 ++++++++ src/status_im/contexts/browser/hooks.cljs | 23 ++---- .../contexts/browser/screenshots/context.cljs | 47 +++++++++++ .../contexts/browser/screenshots/events.cljs | 0 src/status_im/contexts/browser/subs/core.cljs | 12 +-- .../contexts/browser/tabs/tab_window.cljs | 77 ++++++++++--------- .../contexts/browser/tabs/tabs_preview.cljs | 7 +- src/status_im/contexts/browser/tabs/view.cljs | 6 +- 11 files changed, 148 insertions(+), 67 deletions(-) create mode 100644 src/status_im/contexts/browser/events/screenshots.cljs create mode 100644 src/status_im/contexts/browser/screenshots/context.cljs create mode 100644 src/status_im/contexts/browser/screenshots/events.cljs diff --git a/src/react_native/view_shot.cljs b/src/react_native/view_shot.cljs index 0db7e12b2b6..077fc60b115 100644 --- a/src/react_native/view_shot.cljs +++ b/src/react_native/view_shot.cljs @@ -9,3 +9,7 @@ (some-> ^js ref (.capture))) +(defn release-capture + [^js ref uri] + (some-> ^js ref + (.releaseCapture uri))) diff --git a/src/status_im/contexts/browser/events/core.cljs b/src/status_im/contexts/browser/events/core.cljs index a8f5e01612d..c91a7e5b885 100644 --- a/src/status_im/contexts/browser/events/core.cljs +++ b/src/status_im/contexts/browser/events/core.cljs @@ -4,6 +4,7 @@ [status-im.contexts.browser.db :as browser.db] status-im.contexts.browser.events.effects status-im.contexts.browser.events.rpc + status-im.contexts.browser.events.screenshots [status-im.contexts.browser.messages :as messages] [status-im.contexts.browser.native-dapps :as native-dapps])) @@ -98,7 +99,3 @@ (rf/reg-event-fx :browser/show-browser (fn [{:keys [db]}] {:db (assoc db :browser/mode :browser-mode/browser)})) - -(rf/reg-event-fx :browser/add-tab-preview - (fn [{:keys [db]} [tab-id url]] - {:db (assoc-in db [:browser/tab-previews tab-id] url)})) diff --git a/src/status_im/contexts/browser/events/effects.cljs b/src/status_im/contexts/browser/events/effects.cljs index 4c73a03f910..e8a687cbfbe 100644 --- a/src/status_im/contexts/browser/events/effects.cljs +++ b/src/status_im/contexts/browser/events/effects.cljs @@ -1,5 +1,6 @@ (ns status-im.contexts.browser.events.effects (:require [re-frame.core :as rf] + [react-native.view-shot :as view-shot] [react-native.webview :as webview])) (rf/reg-fx :fx.browser/send-message diff --git a/src/status_im/contexts/browser/events/screenshots.cljs b/src/status_im/contexts/browser/events/screenshots.cljs new file mode 100644 index 00000000000..a67d292202d --- /dev/null +++ b/src/status_im/contexts/browser/events/screenshots.cljs @@ -0,0 +1,33 @@ +(ns status-im.contexts.browser.events.screenshots + (:require [re-frame.core :as rf] + [react-native.core :as rn] + [react-native.view-shot :as view-shot] + [taoensso.timbre :as log])) + +(rf/reg-event-fx :browser.screenshots/capture + (fn [{:keys [db]} [tab-id]] + (let [ref (get-in db [:browser/screenshots tab-id :ref])] + (when ref + {:fx [[:fx.promise + {:promise #(view-shot/capture ref) + :on-success [:browser.screenshots/add-url tab-id] + :on-error #(log/error "Failed to capture tab screenshot" {:error %})}]]})))) + +(rf/reg-event-fx :browser.screenshots/add-url + (fn [{:keys [db]} [tab-id url]] + {:db (assoc-in db [:browser/screenshots tab-id :url] url) + :fx [#_[:dispatch + [:show-bottom-sheet + {:content (fn [] [rn/view {:style {:flex 1 :align-items :center :justify-content :center}} + [rn/image + {:source {:uri url} + :style {:height 300 + :width 300 + :border-width 2 + :border-color :red + :aspect-ratio 0.5 + :transform [{:scale 1}]}}]])}]]]})) + +(rf/reg-event-fx :browser.screenshots/add-ref + (fn [{:keys [db]} [tab-id ref]] + {:db (assoc-in db [:browser/screenshots tab-id :ref] ref)})) diff --git a/src/status_im/contexts/browser/hooks.cljs b/src/status_im/contexts/browser/hooks.cljs index feabe22f437..b4197b77250 100644 --- a/src/status_im/contexts/browser/hooks.cljs +++ b/src/status_im/contexts/browser/hooks.cljs @@ -81,18 +81,11 @@ (defn use-screenshot-tab [tab-id] - (let [ref (rn/use-ref) - capture (fn [] - (println "capture" tab-id) - (some-> (.-current ^js ref) - (view-shot/capture) - (.then #(rf/dispatch [:browser/add-tab-preview tab-id %])) - (.catch #(println :capture-error %)))) - options {:fileName (str "tab-screenshot-" tab-id) - :format :jpg - :quality 0.9} - screenshot (rf/sub [:browser/tab-preview tab-id])] - {:capture capture - :options options - :ref ref - :screenshot screenshot})) + (let [options {:fileName (str "tab-screenshot-" tab-id) + :format :jpg + :quality 0.9}] + {:options options + :capture (fn [] + (rf/dispatch [:browser.screenshots/capture tab-id])) + :ref (fn [ref] + (rf/dispatch [:browser.screenshots/add-ref tab-id ref]))})) diff --git a/src/status_im/contexts/browser/screenshots/context.cljs b/src/status_im/contexts/browser/screenshots/context.cljs new file mode 100644 index 00000000000..4e19a269084 --- /dev/null +++ b/src/status_im/contexts/browser/screenshots/context.cljs @@ -0,0 +1,47 @@ +(ns status-im.contexts.browser.screenshots.context + (:require + ["react" :as react] + [oops.core :as oops] + [react-native.core :as rn] + [status-im.navigation.screens :as screens])) + +(defonce ^:private context (react/createContext nil)) + +(defn provider + [data & children] + (let [[screenshots set-screenshots] (rn/use-state {}) + add-screenshot-url (fn [tab-id url] + (set-screenshots (fn [state] + (assoc-in state [tab-id :url] url)))) + add-ref (fn [tab-id ref] + (set-screenshots (fn [state] + (assoc-in state [tab-id :ref] ref))))] + (into [:> (.-Provider context) {:value #js {:cljData data}}] + children))) + +(def default-data + {:refs {}}) + +(defn- use-context-data + [] + (if-let [data (rn/use-context context)] + (oops/oget data :cljData) + default-data)) + +(defn use-ref + [] + (if-let [data (rn/use-context context)] + (:theme (oops/oget data :cljData)) + :light)) + +(defn use-screen-id + "A hook that returns the current screen id." + [] + (when-let [data (rn/use-context context)] + (:screen-id (oops/oget data :cljData)))) + +(defn use-screen-params + "A hook that returns the current screen params" + [] + (when-let [data (rn/use-context context)] + (:screen-params (oops/oget data :cljData)))) diff --git a/src/status_im/contexts/browser/screenshots/events.cljs b/src/status_im/contexts/browser/screenshots/events.cljs new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/status_im/contexts/browser/subs/core.cljs b/src/status_im/contexts/browser/subs/core.cljs index 3e7cd9c8eba..ff7ab3c2c76 100644 --- a/src/status_im/contexts/browser/subs/core.cljs +++ b/src/status_im/contexts/browser/subs/core.cljs @@ -100,11 +100,11 @@ url (-> dapp :origin-url)] (or title url)))) -(rf/reg-sub :browser/tab-previews +(rf/reg-sub :browser/screenshots (fn [db] - (get db :browser/tab-previews))) + (get db :browser/screenshots))) -(rf/reg-sub :browser/tab-preview - :<- [:browser/tab-previews] - (fn [tab-previews [_ tab-id]] - (get tab-previews tab-id))) +(rf/reg-sub :browser/tab-screenshot + :<- [:browser/screenshots] + (fn [screenshots [_ tab-id]] + (get-in screenshots [tab-id :url]))) diff --git a/src/status_im/contexts/browser/tabs/tab_window.cljs b/src/status_im/contexts/browser/tabs/tab_window.cljs index 35f3150f561..35368cc87ff 100644 --- a/src/status_im/contexts/browser/tabs/tab_window.cljs +++ b/src/status_im/contexts/browser/tabs/tab_window.cljs @@ -2,7 +2,6 @@ (:require [quo.context :as quo.context] [quo.foundations.colors :as colors] [react-native.core :as rn] - [react-native.fast-image :as fast-image] [react-native.freeze :as freeze] [react-native.reanimated :as reanimated] [react-native.safe-area :as safe-area] @@ -44,44 +43,52 @@ children))) (defn freeze-placeholder - [{:keys [url]}] - [rn/view - {:style - {:width browser.constants/browser-width - :height browser.constants/browser-height - :border-radius 20}} - [fast-image/fast-image - {:source url - :style {:flex 1}}]]) + [tab-id] + (let [screenshot-url (rf/sub [:browser/tab-screenshot tab-id])] + [rn/view + {:style + {:width browser.constants/browser-width + :height browser.constants/browser-height + :border-radius 20}} + [rn/image + {:source screenshot-url + :style {:flex 1}}]])) (defn view [tab-id idx x-translation-value] - (let [url (rf/sub [:browser/tab-url tab-id]) - tab-type (rf/sub [:browser/tab-type tab-id]) - focused-tab-idx (rf/sub [:browser/focused-tab-idx]) + (let [url (rf/sub [:browser/tab-url tab-id]) + tab-type (rf/sub [:browser/tab-type tab-id]) + focused-tab-idx (rf/sub [:browser/focused-tab-idx]) ;;freeze-tab? (browser/freeze-tab? idx focused-tab-idx) - freeze-tab? (not= idx focused-tab-idx) - {:keys [ref options capture screenshot]} (hooks/use-screenshot-tab tab-id) - screenshot-interval (rn/use-ref-atom nil) - [freeze? set-freeze] (rn/use-state freeze-tab?) - placeholder-opacity-value (reanimated/use-shared-value (if freeze-tab? 1 0))] - (rn/use-effect (fn [] - (if freeze-tab? - (when @screenshot-interval - (js/clearInterval @screenshot-interval)) - (let [interval-id (js/setInterval capture 4000)] - (js/setTimeout capture 800) - (reset! screenshot-interval interval-id)))) - [freeze-tab?]) - (rn/use-unmount (fn [] (js/clearInterval @screenshot-interval))) + unfocused? (not= idx focused-tab-idx) + {:keys [ref options capture]} (hooks/use-screenshot-tab tab-id) + screenshot-interval (rn/use-ref-atom nil) + [freeze? set-freeze] (rn/use-state unfocused?) + placeholder-opacity-value (reanimated/use-shared-value (if unfocused? 1 0))] + (rn/use-effect (fn [] (reanimated/animate-shared-value-with-delay placeholder-opacity-value - (if freeze-tab? 1 0) - 400 + (if unfocused? 1 0) + 200 :easing2 - 400) - (js/setTimeout #(set-freeze freeze-tab?) 800)) - [freeze-tab?]) + 200) + (js/setTimeout (fn [] (set-freeze unfocused?)) 400)) + [unfocused?]) + + (rn/use-effect (fn [] + (when-not freeze? + (capture))) + [freeze?]) + + ;; (rn/use-unmount (fn [] (js/clearInterval @screenshot-interval))) + ;; (rn/use-effect (fn [] + ;; (if freeze? + ;; (when @screenshot-interval + ;; (js/clearInterval @screenshot-interval)) + ;; (let [interval-id (js/setInterval capture 5000)] + ;; (capture) + ;; (reset! screenshot-interval interval-id)))) + ;; [freeze?]) [view-shot/view {:ref ref :style {:flex 1} @@ -90,9 +97,9 @@ {:idx idx :x-translation-value x-translation-value} [freeze/view - {:freeze freeze-tab? + {:freeze unfocused? :placeholder (reagent/as-element - [freeze-placeholder {:url screenshot :opacity-value placeholder-opacity-value}])} + [freeze-placeholder tab-id])} (if (= :tab/native tab-type) [rn/view {:style {:margin-top (- 10 safe-area/top) @@ -107,4 +114,4 @@ {:pointer-events :none :style [{:opacity placeholder-opacity-value} {:position :absolute :left 0 :right 0 :bottom 0 :top 0}]} - [freeze-placeholder {:url screenshot}]])]])) + [freeze-placeholder tab-id]])]])) diff --git a/src/status_im/contexts/browser/tabs/tabs_preview.cljs b/src/status_im/contexts/browser/tabs/tabs_preview.cljs index 85d953ede4f..5ba559e7dab 100644 --- a/src/status_im/contexts/browser/tabs/tabs_preview.cljs +++ b/src/status_im/contexts/browser/tabs/tabs_preview.cljs @@ -1,7 +1,6 @@ (ns status-im.contexts.browser.tabs.tabs-preview (:require [quo.foundations.colors :as colors] [react-native.core :as rn] - [react-native.fast-image :as fast-image] [react-native.reanimated :as reanimated] [react-native.safe-area :as safe-area] [status-im.contexts.browser.components.dapp-icon :as dapp-icon] @@ -21,7 +20,7 @@ (defn tab-preview [tab-id] (let [dapp (rf/sub [:browser/dapp-for-tab tab-id]) - preview-url (rf/sub [:browser/tab-preview tab-id])] + preview-url (rf/sub [:browser/tab-screenshot tab-id])] [rn/pressable {:on-press #(on-press-preview tab-id) :style {:align-items :center @@ -30,8 +29,8 @@ :border-radius 12 :width preview-width :height preview-height}} - [fast-image/fast-image - {:source preview-url + [rn/image + {:source {:uri preview-url} :resize-mode :cover :style {:border-radius 12 :width preview-width diff --git a/src/status_im/contexts/browser/tabs/view.cljs b/src/status_im/contexts/browser/tabs/view.cljs index 68325739ea6..12dae23be1f 100644 --- a/src/status_im/contexts/browser/tabs/view.cljs +++ b/src/status_im/contexts/browser/tabs/view.cljs @@ -13,10 +13,10 @@ (if show? (do (reanimated/animate-shared-value-with-timing opacity-value 1 200 :easing2) (reanimated/animate-shared-value-with-timing scale-value 1 200 :easing2)) - (do (reanimated/animate-shared-value-with-timing opacity-value 0 200 :easing2) - (reanimated/animate-shared-value-with-timing scale-value 0.8 200 :easing2)))) + (do (reanimated/animate-shared-value-with-timing opacity-value 0 600 :easing2) + (reanimated/animate-shared-value-with-timing scale-value 0.6 200 :easing2)))) [show?]) - {:opacity opacity-value + {:opacity (reanimated/interpolate scale-value [0.6 0.8 1] [0 0.7 1]) :transform-origin :top :transform [{:scale scale-value}]}))