diff --git a/src/quo/components/navigation/top_nav/view.cljs b/src/quo/components/navigation/top_nav/view.cljs index 5691e2fba7..99d111858c 100644 --- a/src/quo/components/navigation/top_nav/view.cljs +++ b/src/quo/components/navigation/top_nav/view.cljs @@ -94,13 +94,16 @@ notification-count activity-center-on-press scan-on-press - qr-code-on-press] + qr-code-on-press + right-section-content] :as props}] (let [theme (quo.theme/use-theme) button-common-props (get-button-common-props {:theme theme :jump-to? jump-to? :blur? blur?})] [rn/view {:style style/right-section} + (when right-section-content + right-section-content) [button/button (assoc button-common-props :accessibility-label :open-scanner-button :on-press scan-on-press) :i/scan] @@ -137,6 +140,7 @@ :qr-code-on-press callback :notification-count number :max-unread-notifications used to specify max number for counter + :right-section-content " [{:keys [avatar-on-press avatar-props customization-color container-style] :as props}] [rn/view {:style (merge style/top-nav-container container-style)} diff --git a/src/quo/components/tags/network_status_tag/component_spec.cljs b/src/quo/components/tags/network_status_tag/component_spec.cljs new file mode 100644 index 0000000000..cb52000fba --- /dev/null +++ b/src/quo/components/tags/network_status_tag/component_spec.cljs @@ -0,0 +1,14 @@ +(ns quo.components.tags.network-status-tag.component-spec + (:require + [quo.components.tags.network-status-tag.view :as network-status-tag] + [test-helpers.component :as h])) + +(h/describe "Network status component test" + (h/test "5 min ago render" + (h/render [network-status-tag/view + {:label "Updated 5 min ago"}]) + (h/is-truthy (h/get-by-text "Updated 5 min ago"))) + (h/test "15 min ago render" + (h/render [network-status-tag/view + {:label "Updated 15 min ago"}]) + (h/is-truthy (h/get-by-text "Updated 15 min ago")))) diff --git a/src/quo/components/tags/network_status_tag/style.cljs b/src/quo/components/tags/network_status_tag/style.cljs new file mode 100644 index 0000000000..b033846d28 --- /dev/null +++ b/src/quo/components/tags/network_status_tag/style.cljs @@ -0,0 +1,28 @@ +(ns quo.components.tags.network-status-tag.style + (:require + [quo.foundations.colors :as colors])) + +(def main + {:justify-content :center + :align-items :center + :height 24}) + +(defn inner + [theme] + {:border-width 1 + :border-radius 12 + :flex 1 + :flex-direction :row + :border-color (colors/theme-colors colors/neutral-20 colors/neutral-80 theme) + :align-items :center + :padding-horizontal 8}) + +(def label + {:color colors/warning-50 + :margin-left 6}) + +(def dot + {:width 8 + :height 8 + :border-radius 8 + :background-color colors/warning-50}) diff --git a/src/quo/components/tags/network_status_tag/view.cljs b/src/quo/components/tags/network_status_tag/view.cljs new file mode 100644 index 0000000000..3ded89ec26 --- /dev/null +++ b/src/quo/components/tags/network_status_tag/view.cljs @@ -0,0 +1,19 @@ +(ns quo.components.tags.network-status-tag.view + (:require + [quo.components.markdown.text :as text] + [quo.components.tags.network-status-tag.style :as style] + [quo.theme :as quo.theme] + [react-native.core :as rn])) + +(defn view + [{:keys [label]}] + (let [theme (quo.theme/use-theme)] + [rn/view {:style style/main} + [rn/view {:style (style/inner theme)} + [rn/view {:style style/dot}] + [text/text + {:style style/label + :weight :medium + :size :label + :align :center} + label]]])) diff --git a/src/quo/core.cljs b/src/quo/core.cljs index d34de74bc4..437a4bbdb9 100644 --- a/src/quo/core.cljs +++ b/src/quo/core.cljs @@ -155,6 +155,7 @@ quo.components.tabs.tabs.view quo.components.tags.collectible-tag.view quo.components.tags.context-tag.view + quo.components.tags.network-status-tag.view quo.components.tags.network-tags.view quo.components.tags.number-tag.view quo.components.tags.permission-tag @@ -438,6 +439,7 @@ (def context-tag quo.components.tags.context-tag.view/view) (def network-tags quo.components.tags.network-tags.view/view) (def number-tag quo.components.tags.number-tag.view/view) +(def network-status-tag quo.components.tags.network-status-tag.view/view) (def permission-tag quo.components.tags.permission-tag/tag) (def status-tag quo.components.tags.status-tags/status-tag) (def summary-tag quo.components.tags.summary-tag.view/view) diff --git a/src/status_im/common/home/top_nav/view.cljs b/src/status_im/common/home/top_nav/view.cljs index 3c3944cb9f..f3166b615c 100644 --- a/src/status_im/common/home/top_nav/view.cljs +++ b/src/status_im/common/home/top_nav/view.cljs @@ -1,11 +1,26 @@ (ns status-im.common.home.top-nav.view (:require + [cljs-time.core :as t] [quo.core :as quo] + [react-native.core :as rn] [status-im.common.home.top-nav.style :as style] [status-im.constants :as constants] [status-im.contexts.profile.utils :as profile.utils] + [utils.datetime :as datetime] + [utils.i18n :as i18n] [utils.re-frame :as rf])) +(defn- network-status-label + [now wallet-latest-update] + (when (t/after? now (t/plus wallet-latest-update (t/minutes 5))) + (let [units [{:name :t/datetime-second-short :limit 60 :in-second 1} + {:name :t/datetime-minute-mid :limit 3600 :in-second 60} + {:name :t/datetime-hour-short-lowercase :limit 86400 :in-second 3600} + {:name :t/datetime-day :limit nil :in-second 86400}] + time-ago (datetime/time-ago-long wallet-latest-update units)] + [rn/view {:style {:justify-content :center}} + [quo/network-status-tag {:label (i18n/label :t/wallet-updated-at {:at time-ago})}]]))) + (defn view "[top-nav props] props @@ -17,6 +32,8 @@ online? (rf/sub [:visibility-status-updates/online? public-key]) customization-color (rf/sub [:profile/customization-color]) + wallet-latest-update (rf/sub [:wallet/latest-update]) + wallet-blockchain-status (rf/sub [:wallet/blockchain-status]) avatar {:online? online? :full-name (profile.utils/displayed-name profile) :profile-picture (profile.utils/photo profile)} @@ -37,6 +54,9 @@ {:avatar-on-press #(rf/dispatch [:open-modal :settings]) :scan-on-press #(rf/dispatch [:open-modal :shell-qr-reader]) :activity-center-on-press #(rf/dispatch [:activity-center/open]) + :right-section-content (when (and (= (:status wallet-blockchain-status) "down") + wallet-latest-update) + [network-status-label (t/now) wallet-latest-update]) :qr-code-on-press #(rf/dispatch [:open-modal :screen/share-shell {:initial-tab initial-share-tab}]) :container-style (merge style/top-nav-container container-style) diff --git a/src/status_im/contexts/preview/quo/main.cljs b/src/status_im/contexts/preview/quo/main.cljs index 8c846eda92..927c6ce507 100644 --- a/src/status_im/contexts/preview/quo/main.cljs +++ b/src/status_im/contexts/preview/quo/main.cljs @@ -180,6 +180,7 @@ [status-im.contexts.preview.quo.tabs.tabs :as tabs] [status-im.contexts.preview.quo.tags.collectible-tag :as collectible-tag] [status-im.contexts.preview.quo.tags.context-tags :as context-tags] + [status-im.contexts.preview.quo.tags.network-status-tag :as network-status-tag] [status-im.contexts.preview.quo.tags.network-tags :as network-tags] [status-im.contexts.preview.quo.tags.number-tag :as number-tag] [status-im.contexts.preview.quo.tags.permission-tag :as permission-tag] @@ -520,6 +521,8 @@ :component collectible-tag/view} {:name :context-tags :component context-tags/view} + {:name :network-status-tag + :component network-status-tag/view} {:name :network-tags :component network-tags/view} {:name :number-tag diff --git a/src/status_im/contexts/preview/quo/tags/network_status_tag.cljs b/src/status_im/contexts/preview/quo/tags/network_status_tag.cljs new file mode 100644 index 0000000000..4e3084603a --- /dev/null +++ b/src/status_im/contexts/preview/quo/tags/network_status_tag.cljs @@ -0,0 +1,19 @@ +(ns status-im.contexts.preview.quo.tags.network-status-tag + (:require + [quo.core :as quo] + [react-native.core :as rn] + [reagent.core :as reagent] + [status-im.contexts.preview.quo.preview :as preview])) + +(def descriptor + [{:key :label :type :text}]) + +(defn view + [] + (let [state (reagent/atom {:label "Updated 5 min ago"})] + (fn [] + [preview/preview-container + {:state state + :descriptor descriptor} + [rn/view {:style {:align-items :center}} + [quo/network-status-tag @state]]]))) diff --git a/src/status_im/contexts/wallet/events.cljs b/src/status_im/contexts/wallet/events.cljs index 2e820eb880..197e119c0d 100644 --- a/src/status_im/contexts/wallet/events.cljs +++ b/src/status_im/contexts/wallet/events.cljs @@ -1,6 +1,7 @@ (ns status-im.contexts.wallet.events (:require [camel-snake-kebab.extras :as cske] + [cljs-time.coerce :as time-coerce] [clojure.set] [clojure.string :as string] [react-native.platform :as platform] @@ -224,7 +225,8 @@ {:error error :event :wallet/get-wallet-token-for-account :params address}) - {:db (assoc-in db [:wallet :ui :tokens-loading address] false)})) + {:fx [[:dispatch [:wallet/get-last-wallet-token-update-if-needed]]] + :db (assoc-in db [:wallet :ui :tokens-loading address] false)})) (rf/reg-event-fx :wallet/store-wallet-token @@ -240,10 +242,34 @@ accounts)) stored-accounts tokens-per-account))] - {:db (-> db + {:fx [[:dispatch [:wallet/get-last-wallet-token-update-if-needed]]] + :db (-> db (update-in [:wallet :accounts] add-tokens tokens) (assoc-in [:wallet :ui :tokens-loading address] false))}))) +(rf/reg-event-fx + :wallet/get-last-wallet-token-update-if-needed + (fn [{:keys [db]}] + (let [all-tokens-loaded? (->> (get-in db [:wallet :ui :tokens-loading]) + vals + (every? false?))] + (when all-tokens-loaded? + {:fx [[:json-rpc/call + [{:method "wallet_getLastWalletTokenUpdate" + :params [] + :on-success [:wallet/get-last-wallet-token-update-success] + :on-error [:wallet/log-rpc-error + {:event :wallet/get-last-wallet-token-update-if-needed}]}]]]})))) + +(rf/reg-event-fx + :wallet/get-last-wallet-token-update-success + (fn [{:keys [db]} [data]] + (let [last-updates (reduce (fn [acc [k v]] + (assoc acc k (time-coerce/from-long (* 1000 v)))) + {} + data)] + {:db (assoc-in db [:wallet :ui :last-updates-per-address] last-updates)}))) + (rf/defn scan-address-success {:events [:wallet/scan-address-success]} [{:keys [db]} address] @@ -653,3 +679,10 @@ new-account-addresses))))) (rf/reg-event-fx :wallet/reconcile-keypairs reconcile-keypairs) + +(rf/reg-event-fx + :wallet/blockchain-health-changed + (fn [{:keys [db]} [{:keys [message]}]] + (let [full-status (cske/transform-keys message transforms/->kebab-case-keyword)] + {:db (assoc-in db [:wallet :blockchain] full-status)}))) + diff --git a/src/status_im/contexts/wallet/signals.cljs b/src/status_im/contexts/wallet/signals.cljs index 2d1f882c20..29433d56bc 100644 --- a/src/status_im/contexts/wallet/signals.cljs +++ b/src/status_im/contexts/wallet/signals.cljs @@ -93,6 +93,9 @@ :dispatch [:wallet/blockchain-status-changed (transforms/js->clj event-js)]}]]]} + "wallet-blockchain-health-changed" + {:fx [[:dispatch [:wallet/blockchain-health-changed (transforms/js->clj event-js)]]]} + "wallet-activity-filtering-done" {:fx [[:dispatch diff --git a/src/status_im/subs/wallet/wallet.cljs b/src/status_im/subs/wallet/wallet.cljs index a1a708b3d5..40265a5a1f 100644 --- a/src/status_im/subs/wallet/wallet.cljs +++ b/src/status_im/subs/wallet/wallet.cljs @@ -1,5 +1,6 @@ (ns status-im.subs.wallet.wallet - (:require [clojure.string :as string] + (:require [cljs-time.core :as t] + [clojure.string :as string] [re-frame.core :as rf] [status-im.constants :as constants] [status-im.contexts.wallet.common.utils :as utils] @@ -39,6 +40,33 @@ :<- [:wallet/ui] :-> :scanned-address) +(rf/reg-sub + :wallet/last-updates-per-address + :<- [:wallet/ui] + :-> :last-updates-per-address) + +(rf/reg-sub + :wallet/latest-update + :<- [:wallet/last-updates-per-address] + (fn [last-updates-per-address] + (->> last-updates-per-address + vals + (reduce (fn [earliest-time last-update-time] + (if (or (nil? earliest-time) + (t/before? last-update-time earliest-time)) + last-update-time + earliest-time)))))) + +(rf/reg-sub + :wallet/blockchain + :<- [:wallet] + :-> :blockchain) + +(rf/reg-sub + :wallet/blockchain-status + :<- [:wallet/blockchain] + :-> :status) + (rf/reg-sub :wallet/tokens :<- [:wallet] diff --git a/src/utils/datetime.cljs b/src/utils/datetime.cljs index d666578947..ffca333637 100644 --- a/src/utils/datetime.cljs +++ b/src/utils/datetime.cljs @@ -46,7 +46,7 @@ (def day (* 24 hour)) (def week (* 7 day)) (defn weeks [w] (* w week)) -(def units +(def default-units [{:name :t/datetime-second-short :limit 60 :in-second 1} {:name :t/datetime-minute-short :limit 3600 :in-second 60} {:name :t/datetime-hour-short :limit 86400 :in-second 3600} @@ -272,27 +272,29 @@ (let [diff (seconds-ago date-time) unit (first (drop-while #(and (>= diff (:limit %)) (:limit %)) - units))] + default-units))] (-> (/ diff (:in-second unit)) Math/floor int (format-time-ago unit)))) (defn time-ago-long - [date-time] - (let [time-ago-seconds (seconds-ago date-time) - unit (first (drop-while #(and (>= time-ago-seconds (:limit %)) - (:limit %)) - units)) - diff (-> (/ time-ago-seconds (:in-second unit)) - Math/floor - int) + ([date-time units] + (let [time-ago-seconds (seconds-ago date-time) + unit (first (drop-while #(and (>= time-ago-seconds (:limit %)) + (:limit %)) + units)) + diff (-> (/ time-ago-seconds (:in-second unit)) + Math/floor + int) - name (i18n/label-pluralize diff (:name unit))] - (i18n/label :t/datetime-ago-format - {:ago (i18n/label :t/datetime-ago) - :number diff - :time-intervals name}))) + name (i18n/label-pluralize diff (:name unit))] + (i18n/label :t/datetime-ago-format + {:ago (i18n/label :t/datetime-ago) + :number diff + :time-intervals name}))) + ([date-time] + (time-ago-long date-time default-units))) (defn to-date [ms] diff --git a/translations/en.json b/translations/en.json index 1ea91b0e91..566c5a9097 100644 --- a/translations/en.json +++ b/translations/en.json @@ -690,10 +690,18 @@ "one": "H", "other": "H" }, + "datetime-hour-short-lowercase": { + "one": "h", + "other": "h" + }, "datetime-minute": { "one": "minute", "other": "minutes" }, + "datetime-minute-mid": { + "one": "min", + "other": "min" + }, "datetime-minute-short": { "one": "M", "other": "M" @@ -2804,6 +2812,7 @@ "wallet-settings": "Wallet settings", "wallet-total-value": "Total value", "wallet-transaction-total-fee": "Total Fee", + "wallet-updated-at": "Updated {{at}}", "wants-to-access-profile": "wants to access to your profile", "wants-to-join": "wants to join", "warning": "Warning",