Online Status Indicator (#12716)

This commit is contained in:
Parvesh Monu 2021-10-29 21:55:01 +05:30 committed by GitHub
parent ec319974bd
commit 1034d542db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 815 additions and 90 deletions

View File

@ -218,4 +218,7 @@
(set! pin-background (:pin-background old-colors-mapping-colors)))
(reset! theme-type type)))
;; Colors related to Visibility Status
(def color-online "#7CDA00")
(def color-dnd "#FA6565")
(def color-inactive "#939BA1")

View File

@ -162,3 +162,9 @@
(def ^:const activity-center-notification-type-private-group-chat 2)
(def ^:const activity-center-notification-type-mention 3)
(def ^:const activity-center-notification-type-reply 4)
(def ^:const visibility-status-unknown 0)
(def ^:const visibility-status-automatic 1)
(def ^:const visibility-status-dnd 2)
(def ^:const visibility-status-always-online 3)
(def ^:const visibility-status-inactive 4)

View File

@ -1,7 +1,8 @@
(ns status-im.data-store.settings
(:require
[status-im.utils.config :as config]
[status-im.ethereum.eip55 :as eip55]))
[status-im.ethereum.eip55 :as eip55]
[status-im.data-store.visibility-status-updates :as visibility-status-updates]))
(defn rpc->networks [networks]
(reduce (fn [acc {:keys [id] :as network}]
@ -50,4 +51,5 @@
(update :link-previews-enabled-sites set)
(update :custom-bootnodes rpc->custom-bootnodes)
(update :custom-bootnodes-enabled? rpc->custom-bootnodes)
(update :currency keyword)))
(update :currency keyword)
(visibility-status-updates/<-rpc-settings)))

View File

@ -0,0 +1,24 @@
(ns status-im.data-store.visibility-status-updates
(:require [clojure.set :as clojure.set]
[re-frame.core :as re-frame]
[status-im.ethereum.json-rpc :as json-rpc]
[status-im.utils.fx :as fx]
[taoensso.timbre :as log]))
(defn <-rpc [visibility-status-update]
(clojure.set/rename-keys visibility-status-update {:publicKey :public-key
:statusType :status-type}))
(defn <-rpc-settings [settings]
(-> settings
(clojure.set/rename-keys
{:current-user-status :current-user-visibility-status})
(update :current-user-visibility-status <-rpc)))
(fx/defn fetch-visibility-status-updates-rpc [_]
{::json-rpc/call [{:method "wakuext_statusUpdates"
:params []
:on-success #(re-frame/dispatch
[:visibility-status-updates/visibility-status-updates-loaded
(:statusUpdates ^js %)])
:on-failure #(log/error
"failed to fetch visibility-status-updates" %)}]})

View File

@ -32,6 +32,7 @@
:tooltips {}
:dimensions/window (dimensions/window)
:registry {}
:visibility-status-updates {}
:stickers/packs-owned #{}
:stickers/packs-pending #{}
:keycard {:nfc-enabled? false

View File

@ -131,6 +131,8 @@
"wakuext_enablePushNotificationsBlockMentions" {}
"wakuext_disablePushNotificationsBlockMentions" {}
"wakuext_unreadActivityCenterNotificationsCount" {}
"wakuext_setUserStatus" {}
"wakuext_statusUpdates" {}
"multiaccounts_getIdentityImages" {}
"multiaccounts_getIdentityImage" {}
"multiaccounts_storeIdentityImage" {}

View File

@ -27,6 +27,8 @@
status-im.wallet.choose-recipient.core
status-im.wallet.accounts.core
status-im.popover.core
status-im.visibility-status-popover.core
status-im.visibility-status-updates.core
status-im.bottom-sheet.core
status-im.add-new.core
status-im.search.core

View File

@ -64,6 +64,17 @@
(str "@" (or username ens-name)))
(or alias (gfycat/generate-gfy public-key)))))
(defn contact-by-identity [contacts identity]
(or (get contacts identity)
(contact-with-names {:public-key identity})))
(defn contact-two-names-by-identity [contact current-multiaccount identity]
(let [me? (= (:public-key current-multiaccount) identity)]
(if me?
[(or (:preferred-name current-multiaccount)
(gfycat/generate-gfy identity))]
(contact-two-names contact false))))
(def photo-quality-thumbnail :thumbnail)
(def photo-quality-large :large)

View File

@ -37,7 +37,8 @@
[status-im.notifications-center.core :as notifications-center]
[status-im.navigation :as navigation]
[status-im.signing.eip1559 :as eip1559]
[status-im.data-store.chats :as data-store.chats]))
[status-im.data-store.chats :as data-store.chats]
[status-im.data-store.visibility-status-updates :as visibility-status-updates-store]))
(re-frame/reg-fx
::initialize-communities-enabled
@ -338,7 +339,8 @@
(get-group-chat-invitations)
(multiaccounts/get-profile-picture)
(multiaccounts/switch-preview-privacy-mode-flag)
(link-preview/request-link-preview-whitelist))))
(link-preview/request-link-preview-whitelist)
(visibility-status-updates-store/fetch-visibility-status-updates-rpc))))
(defn get-new-auth-method [auth-method save-password?]
(when save-password?

View File

@ -136,7 +136,9 @@
(fn [^js evn]
(let [view-id (keyword (.-componentName evn))]
(when (get views/screens view-id)
(when (and (not= view-id :bottom-sheet) (not= view-id :popover))
(when (and (not= view-id :bottom-sheet)
(not= view-id :popover)
(not= view-id :visibility-status-popover))
(set-view-id view-id)
(when-not @curr-modal
(reset! pushed-screen-id view-id))))))))
@ -146,7 +148,8 @@
(.registerComponentDidDisappearListener
(.events Navigation)
(fn [^js evn]
(when-not (#{"popover" "bottom-sheet" "signing-sheet"} (.-componentName evn))
(when-not (#{"popover" "bottom-sheet" "signing-sheet" "visibility-status-popover"}
(.-componentName evn))
(doseq [[_ {:keys [ref value]}] @quo.text-input/text-input-refs]
(.setNativeProps ^js ref (clj->js {:text value})))
(doseq [[^js text-input default-value] @react/text-input-refs]
@ -290,6 +293,18 @@
(re-frame/reg-fx :show-popover (fn [] (show-overlay "popover")))
(re-frame/reg-fx :hide-popover (fn [] (dissmiss-overlay "popover")))
;; VISIBILITY STATUS POPOVER
(defonce visibility-status-popover-reg
(.registerComponent Navigation
"visibility-status-popover"
(fn [] (gestureHandlerRootHOC views/visibility-status-popover-comp))
(fn [] views/visibility-status-popover-comp)))
(re-frame/reg-fx :show-visibility-status-popover
(fn [] (show-overlay "visibility-status-popover")))
(re-frame/reg-fx :hide-visibility-status-popover
(fn [] (dissmiss-overlay "visibility-status-popover")))
;; BOTTOM SHEETS
(defonce bottom-sheet-reg
(.registerComponent Navigation

View File

@ -7,6 +7,7 @@
[status-im.notifications.local :as local-notifications]
[status-im.chat.models.message :as models.message]
[status-im.chat.models.link-preview :as link.preview]
[status-im.visibility-status-updates.core :as visibility-status-updates]
[status-im.utils.fx :as fx]
[taoensso.timbre :as log]))
@ -41,7 +42,8 @@
{:db (assoc db
:peers-summary peers-summary
:peers-count peers-count)}
(mailserver/peers-summary-change previous-summary))))
(mailserver/peers-summary-change previous-summary)
(visibility-status-updates/peers-summary-change peers-count))))
(fx/defn wakuv2-peer-stats
[{:keys [db]} peer-stats]

View File

@ -40,7 +40,8 @@
[status-im.notifications.core :as notifications]
[status-im.utils.currency :as currency]
[clojure.set :as clojure.set]
[quo.design-system.colors :as colors]))
[quo.design-system.colors :as colors]
[status-im.ui.screens.profile.visibility-status.utils :as visibility-status-utils]))
;; TOP LEVEL ===========================================================================================================
@ -86,6 +87,7 @@
(reg-root-key-sub :app-state :app-state)
(reg-root-key-sub :home-items-show-number :home-items-show-number)
(reg-root-key-sub :waku/v2-peer-stats :peer-stats)
(reg-root-key-sub :visibility-status-updates :visibility-status-updates)
;;NOTE this one is not related to ethereum network
;; it is about cellular network/ wifi network
@ -200,6 +202,7 @@
(reg-root-key-sub :intro-wizard-state :intro-wizard)
(reg-root-key-sub :popover/popover :popover/popover)
(reg-root-key-sub :visibility-status-popover/popover :visibility-status-popover/popover)
(reg-root-key-sub :add-account :add-account)
(reg-root-key-sub :keycard :keycard)
@ -292,6 +295,36 @@
(fn [communities [_ id]]
(get-in communities [id :chats])))
(re-frame/reg-sub
:communities/community-members
:<- [:communities]
(fn [communities [_ id]]
(get-in communities [id :members])))
(re-frame/reg-sub
:communities/sorted-community-members
(fn [[_ community-id]]
(let [contacts (re-frame/subscribe [:contacts/contacts])
multiaccount (re-frame/subscribe [:multiaccount])
members (re-frame/subscribe
[:communities/community-members community-id])]
[contacts multiaccount members]))
(fn [[contacts multiaccount members] _]
(let [names (reduce
(fn [acc identity]
(let [me? (= (:public-key multiaccount)
identity)
contact (when-not me?
(multiaccounts/contact-by-identity
contacts identity))
name (first
(multiaccounts/contact-two-names-by-identity
contact multiaccount identity))]
(assoc acc identity name))) {} (keys members))]
(->> members
(sort-by #(get names (get % 0)))
(sort-by #(visibility-status-utils/visibility-status-order (get % 0)))))))
(re-frame/reg-sub
:communities/communities
:<- [:communities/enabled?]
@ -355,6 +388,11 @@
;;GENERAL ==============================================================================================================
(re-frame/reg-sub
:visibility-status-updates/visibility-status-update
:<- [:visibility-status-updates]
(fn [visibility-status-updates [_ public-key]]
(get visibility-status-updates public-key)))
(re-frame/reg-sub
:multiaccount/logged-in?
@ -784,6 +822,12 @@
(string/blank? (security/safe-unmask-data seed))
false))))
(re-frame/reg-sub
:multiaccount/current-user-visibility-status
:<- [:multiaccount]
(fn [{:keys [current-user-visibility-status]}]
current-user-visibility-status))
;;CHAT ==============================================================================================================
(re-frame/reg-sub
@ -2208,6 +2252,15 @@
(fn [contacts]
(contact.db/get-active-contacts contacts)))
(re-frame/reg-sub
:contacts/sorted-contacts
:<- [:contacts/active]
(fn [active-contacts]
(->> active-contacts
(sort-by :alias)
(sort-by
#(visibility-status-utils/visibility-status-order (:public-key %))))))
(re-frame/reg-sub
:contacts/active-count
:<- [:contacts/active]
@ -2265,8 +2318,7 @@
:contacts/contact-by-identity
:<- [:contacts/contacts]
(fn [contacts [_ identity]]
(or (get contacts identity)
(multiaccounts/contact-with-names {:public-key identity}))))
(multiaccounts/contact-by-identity contacts identity)))
(re-frame/reg-sub
:contacts/contact-added?
@ -2281,11 +2333,8 @@
[(re-frame/subscribe [:contacts/contact-by-identity identity])
(re-frame/subscribe [:multiaccount])])
(fn [[contact current-multiaccount] [_ identity]]
(let [me? (= (:public-key current-multiaccount) identity)]
(if me?
[(or (:preferred-name current-multiaccount)
(gfycat/generate-gfy identity))]
(multiaccounts/contact-two-names contact false)))))
(multiaccounts/contact-two-names-by-identity contact current-multiaccount
identity)))
(re-frame/reg-sub
:contacts/contact-name-by-identity

View File

@ -19,6 +19,7 @@
[status-im.constants :as constants]
[status-im.multiaccounts.model :as multiaccounts.model]
[status-im.notifications-center.core :as notifications-center]
[status-im.visibility-status-updates.core :as models.visibility-status-updates]
[clojure.string :as string]))
(fx/defn process-next
@ -42,6 +43,8 @@
^js activity-notifications (.-activityCenterNotifications response-js)
^js pin-messages (.-pinMessages response-js)
^js removed-messages (.-removedMessages response-js)
^js visibility-status-updates (.-statusUpdates response-js)
^js current-visibility-status (.-currentStatus response-js)
sync-handler (when-not process-async process-response)]
(cond
@ -149,7 +152,23 @@
(js-delete response-js "removedMessages")
(fx/merge cofx
(process-next response-js sync-handler)
(models.message/handle-removed-messages removed-messages-clj))))))
(models.message/handle-removed-messages removed-messages-clj)))
(seq visibility-status-updates)
(let [visibility-status-updates-clj (types/js->clj visibility-status-updates)]
(js-delete response-js "statusUpdates")
(fx/merge cofx
(process-next response-js sync-handler)
(models.visibility-status-updates/handle-visibility-status-updates
visibility-status-updates-clj)))
(some? current-visibility-status)
(let [current-visibility-status-clj (types/js->clj current-visibility-status)]
(js-delete response-js "currentStatus")
(fx/merge cofx
(process-next response-js sync-handler)
(models.visibility-status-updates/sync-visibility-status-update
current-visibility-status-clj))))))
(defn group-by-and-update-unviewed-counts
"group messages by current chat, profile updates, transactions and update unviewed counters in db for not curent chats"

View File

@ -6,7 +6,9 @@
[status-im.ui.components.chat-icon.styles :as styles]
[quo.design-system.colors :as colors]
[status-im.ui.components.react :as react]
[status-im.ui.screens.chat.photos :as photos]))
[status-im.ui.screens.chat.photos :as photos]
[status-im.profile.db :as profile.db]
[status-im.ui.screens.profile.visibility-status.utils :as visibility-status-utils]))
;;TODO REWORK THIS NAMESPACE
@ -42,6 +44,24 @@
[react/view (:default-chat-icon styles)
[react/text {:style (:default-chat-icon-text styles)} emoji]]))
(defn profile-photo-plus-dot-view
[{:keys [public-key photo-container photo-path community?]}]
(let [photo-path (if (nil? photo-path)
@(re-frame.core/subscribe [:chats/photo-path public-key])
photo-path)
photo-container (if (nil? photo-container)
styles/container-chat-list photo-container)
size (:width photo-container)
identicon? (when photo-path (profile.db/base64-png? photo-path))
dot-styles (visibility-status-utils/icon-visibility-status-dot
public-key size identicon?)]
[react/view {:style photo-container
:accessibility-label :profile-photo}
[photos/photo photo-path {:size size}]
(when-not community?
[react/view {:style dot-styles
:accessibility-label :profile-photo-dot}])]))
(defn emoji-chat-icon-view
[chat-id group-chat name emoji styles]
[react/view (:container styles)
@ -49,8 +69,8 @@
(if (string/blank? emoji)
[default-chat-icon name styles]
[emoji-chat-icon emoji styles])
(let [photo-path @(re-frame.core/subscribe [:chats/photo-path chat-id])]
[photos/photo photo-path styles]))])
[profile-photo-plus-dot-view {:public-key chat-id
:photo-container (:default-chat-icon styles)}])])
(defn chat-icon-view-toolbar
[chat-id group-chat name color emoji]
@ -131,7 +151,8 @@
(if-not (string/blank? photo-path)
[photos/photo photo-path styles]))))
(defn profile-icon-view [photo-path name color emoji edit? size override-styles]
(defn profile-icon-view
[photo-path name color emoji edit? size override-styles public-key community?]
(let [styles (merge {:container {:width size :height size}
:size size
:chat-icon styles/chat-icon-profile
@ -141,7 +162,10 @@
(styles/emoji-chat-icon-text size))} override-styles)]
[react/view (:container styles)
(if (and photo-path (seq photo-path))
[photos/photo photo-path styles]
[profile-photo-plus-dot-view {:photo-path photo-path
:public-key public-key
:photo-container (:container styles)
:community? community?}]
(if (string/blank? emoji)
[default-chat-icon name styles]
[emoji-chat-icon emoji styles]))

View File

@ -70,51 +70,6 @@
:height 64
:border-radius 32}))
(def online-view-wrapper
{:position :absolute
:bottom -2
:right -2
:width 17
:height 17
:border-radius 11
:background-color colors/white})
(def online-view
{:position :absolute
:bottom 2
:right 2
:width 13
:height 13
:border-radius 9
:background-color colors/blue})
(def online-view-profile
(merge online-view
{:width 24
:height 24
:border-radius 12}))
(def online-dot
{:position :absolute
:top 5
:width 3
:height 3
:border-radius 2
:background-color colors/white})
(def online-dot-left (merge online-dot {:left 2.8}))
(def online-dot-right (merge online-dot {:left 7.2}))
(def online-dot-profile
(merge online-dot
{:top 8
:width 4
:height 4}))
(def online-dot-left-profile
(merge online-dot-profile {:left 5}))
(def online-dot-right-profile
(merge online-dot-profile {:left 11}))
(def container-chat-list
{:width 40
:height 40})

View File

@ -37,8 +37,10 @@
(when-not minimized
{:padding-top subtitle-margin})))
(defn extended-header [{:keys [title photo color subtitle subtitle-icon on-edit on-press monospace bottom-separator emoji]
:or {bottom-separator true}}]
(defn extended-header
[{:keys [title photo color subtitle subtitle-icon on-edit on-press monospace
bottom-separator emoji public-key community?]
:or {bottom-separator true}}]
(fn [{:keys [animation minimized]}]
(let [wrapper (if on-press
[rn/touchable-opacity {:on-press on-press}]
@ -57,7 +59,7 @@
[chat-icon.screen/profile-icon-view
photo title color emoji (and (not minimized) on-edit)
(if minimized avatar-minimized-size avatar-extended-size)
nil]]])
nil public-key community?]]])
[animated/view {:style (header-text)
:pointer-events :box-none}
[quo/text {:animated? true

View File

@ -51,8 +51,9 @@
{:title first-name
:subtitle second-name
:accessibility-label :member-item
:icon [chat-icon/contact-icon-contacts-tab
(multiaccounts/displayed-photo member)]
:icon [chat-icon/profile-photo-plus-dot-view
{:public-key public-key
:photo-path (multiaccounts/displayed-photo member)}]
:accessory (when (not= public-key my-public-key)
[quo/button {:on-press
#(>evt [:bottom-sheet/show-sheet
@ -89,17 +90,17 @@
(let [{:keys [community-id]} (<sub [:get-screen-params])]
(fn []
(let [my-public-key (<sub [:multiaccount/public-key])
{:keys [members
permissions
can-manage-users?]} (<sub [:communities/community community-id])]
{:keys [permissions
can-manage-users?]} (<sub [:communities/community community-id])
sorted-members (<sub [:communities/sorted-community-members
community-id])]
[:<>
[topbar/topbar {:title (i18n/label :t/community-members-title)
:subtitle (str (count members))}]
:subtitle (str (count sorted-members))}]
[header community-id]
(when (and can-manage-users? (= constants/community-on-request-access (:access permissions)))
[requests-to-join community-id])
[rn/flat-list {:data (keys members)
[rn/flat-list {:data (keys sorted-members)
:render-data {:community-id community-id
:my-public-key my-public-key
:can-kick-users? (and can-manage-users?

View File

@ -41,7 +41,8 @@
(get-in community [:images :large :uri]))
:subtitle (if show-members-count?
(i18n/label-pluralize members-count :t/community-members {:count members-count})
(i18n/label :t/open-membership))})
(i18n/label :t/open-membership))
:community? true})
:use-insets true}
[:<>
(when-not (string/blank? description)

View File

@ -15,8 +15,9 @@
[quo/list-item
{:title first-name
:subtitle second-name
:icon [chat-icon.screen/contact-icon-contacts-tab
(multiaccounts/displayed-photo contact)]
:icon [chat-icon.screen/profile-photo-plus-dot-view
{:public-key public-key
:photo-path (multiaccounts/displayed-photo contact)}]
:chevron true
:on-press #(re-frame/dispatch [:chat.ui/show-profile public-key])}]))
@ -30,7 +31,7 @@
(defview contacts-list []
(letsubs [blocked-contacts-count [:contacts/blocked-count]
contacts [:contacts/active]]
sorted-contacts [:contacts/sorted-contacts]]
[react/scroll-view {:flex 1}
[add-new-contact]
(when (pos? blocked-contacts-count)
@ -44,9 +45,9 @@
:accessory :text
:accessory-text blocked-contacts-count
:on-press #(re-frame/dispatch [:navigate-to :blocked-users-list])}]])
(if (seq contacts)
(if (seq sorted-contacts)
[list.views/flat-list
{:data contacts
{:data sorted-contacts
:key-fn :address
:render-fn contacts-list-item}]
[react/view {:align-items :center

View File

@ -205,7 +205,8 @@
:title first-name
:photo (multiaccounts/displayed-photo contact)
:monospace (not ens-verified)
:subtitle second-name})]
:subtitle second-name
:public-key public-key})]
[react/view {:height 1 :background-color colors/gray-lighter :margin-top 8}]
[nickname-settings contact]
[pin-settings public-key (count pinned-messages)]

View File

@ -17,7 +17,9 @@
[status-im.ui.components.profile-header.view :as profile-header]
[status-im.ui.screens.profile.user.edit-picture :as edit]
[status-im.utils.utils :as utils]
[status-im.ethereum.stateofus :as stateofus])
[status-im.ethereum.stateofus :as stateofus]
[quo.design-system.spacing :as spacing]
[status-im.ui.screens.profile.visibility-status.views :as visibility-status])
(:require-macros [status-im.utils.views :as views]))
(views/defview share-chat-key []
@ -64,6 +66,9 @@
chain @(re-frame/subscribe [:chain-keyword])
registrar (stateofus/get-cached-registrar chain)]
[:<>
[visibility-status/visibility-status-button
visibility-status/calculate-button-height-and-dispatch-popover]
[quo/separator {:style {:margin-top (:tiny spacing/spacing)}}]
[quo/list-item
(cond-> {:title (or (when registrar preferred-name)
(i18n/label :t/ens-usernames))

View File

@ -0,0 +1,76 @@
(ns status-im.ui.screens.profile.visibility-status.styles
(:require [quo.design-system.colors :as colors]))
(defn visibility-status-button-container []
{:background-color (:interactive-02 @colors/theme)
:margin-left 16
:border-radius 16
:border-top-left-radius 4
:align-self :flex-start
:flex-direction :row
:align-items :center
:justify-content :center
:padding 6
:padding-right 12})
(defn visibility-status-dot [dot-color size]
{:background-color dot-color
:width size
:height size
:border-radius (/ size 2)
:border-width 1
:border-color colors/white})
(defn visibility-status-profile-dot [color size border-width margin-left]
(merge (visibility-status-dot color size)
{:margin-right 6
:margin-left margin-left
:border-width border-width}))
(defn visibility-status-text []
{:color (:text-01 @colors/theme)
:font-size 16
:text-align :center})
(defn visibility-status-subtitle []
{:color (:text-02 @colors/theme)
:font-size 16
:margin-left 22
:margin-right 6})
(defn visibility-status-option []
{:flex-direction :row
:align-items :center})
(defn visibility-status-options [scale position]
{:background-color (if (colors/dark?)
(:interactive-02 @colors/theme)
colors/white)
:border-radius 16
:border-top-left-radius 4
:justify-content :center
:align-self :flex-start
:left 16
:top -76
:padding-bottom 6
:padding-top 8
:transform [{:scaleY scale}
{:translateY position}]})
(defn visibility-status-popover-container []
{:position :absolute
:top 0
:bottom 0
:left 0
:right 0})
(defn visibility-status-popover-ios-backdrop [alpha-value]
{:flex 1
:background-color colors/black-persist
:opacity alpha-value})
(defn visibility-status-popover-child-container [window-height]
{:position :absolute
:height window-height
:left 0
:right 0})

View File

@ -0,0 +1,95 @@
(ns status-im.ui.screens.profile.visibility-status.utils
(:require [status-im.constants :as constants]
[status-im.i18n.i18n :as i18n]
[status-im.ui.screens.profile.visibility-status.styles :as styles]
[status-im.utils.handlers :refer [<sub]]
[status-im.utils.datetime :as datetime]
[quo.design-system.colors :as colors]
[clojure.string :as string]))
;; Specs:
;; :visibility-status-automatic
;; To Send - "visibility-status-automatic" status ping every 5 minutes
;; Display - Online for up to 5 minutes from the last clock, after that Offline
;; :visibility-status-always-online
;; To Send - "visibility-status-always-online" status ping every 5 minutes
;; Display - Online for up to 2 weeks from the last clock, after that Offline
;; :visibility-status-inactive
;; To Send - A single "visibility-status-inactive" status ping
;; Display - Offline forever
;; Note: Only send pings if the user interacted with the app in the last x minutes.
(def visibility-status-type-data
{constants/visibility-status-unknown
{:color colors/red
:title (i18n/label :t/error)}
constants/visibility-status-automatic
{:color colors/color-online
:title (i18n/label :t/status-automatic)
:subtitle (i18n/label :t/status-automatic-subtitle)}
constants/visibility-status-dnd
{:color colors/color-dnd
:title (i18n/label :t/status-dnd)
:subtitle (i18n/label :t/status-dnd-subtitle)}
constants/visibility-status-always-online
{:color colors/color-online
:title (i18n/label :t/status-always-online)}
constants/visibility-status-inactive
{:color colors/color-inactive
:title (i18n/label :t/status-inactive)
:subtitle (i18n/label :t/status-inactive-subtitle)}})
;; Currently, Another user is broadcasting their status updates at the rate of 5 minutes.
;; So for status-type automatic, we need to show
;; that user online a little longer than that time. (broadcast receiving delay)
(defn calculate-real-status-type-and-time-left
[{:keys [status-type clock]}]
(let [status-lifespan (if (= status-type
constants/visibility-status-automatic)
(datetime/minutes 5.05)
(datetime/weeks 2))
status-expire-time (+ (datetime/to-ms clock) status-lifespan)
time-left (- status-expire-time (datetime/timestamp))
status-type (if (or (nil? status-type)
(and
(not= status-type
constants/visibility-status-inactive)
(neg? time-left)))
constants/visibility-status-inactive
status-type)]
{:real-status-type status-type
:time-left time-left}))
(defn dot-color
[{:keys [status-type] :as visibility-status-update} my-icon?]
(if my-icon?
(if (= status-type constants/visibility-status-inactive)
colors/color-inactive colors/color-online)
(let [{:keys [real-status-type]}
(calculate-real-status-type-and-time-left visibility-status-update)]
(:color (get visibility-status-type-data real-status-type)))))
(defn my-icon? [public-key]
(or (string/blank? public-key)
(= public-key (<sub [:multiaccount/public-key]))))
(defn visibility-status-update [public-key my-icon?]
(if my-icon?
(<sub [:multiaccount/current-user-visibility-status])
(<sub [:visibility-status-updates/visibility-status-update public-key])))
(defn icon-visibility-status-dot [public-key container-size identicon?]
(let [my-icon? (my-icon? public-key)
visibility-status-update (visibility-status-update public-key my-icon?)
size (/ container-size 4)
margin (if identicon? (/ size 6) (/ size 7))
dot-color (dot-color visibility-status-update my-icon?)]
(merge (styles/visibility-status-dot dot-color size)
{:bottom margin
:right margin
:position :absolute})))
(defn visibility-status-order [public-key]
(let [my-icon? (my-icon? public-key)
visibility-status-update (visibility-status-update public-key my-icon?)
dot-color (dot-color visibility-status-update my-icon?)]
(if (= dot-color colors/color-online) 0 1)))

View File

@ -0,0 +1,200 @@
(ns status-im.ui.screens.profile.visibility-status.views
(:require-macros [status-im.utils.views :as views])
(:require ["react-native" :refer (BackHandler)]
[quo.core :as quo]
[quo.design-system.colors :as colors]
[quo.react-native :as rn]
[re-frame.core :as re-frame]
[reagent.core :as reagent]
[status-im.ui.components.animation :as anim]
[status-im.ui.components.react :as react]
[status-im.utils.platform :as platform]
[status-im.utils.handlers :refer [<sub]]
[status-im.constants :as constants]
[status-im.ui.screens.profile.visibility-status.styles :as styles]
[status-im.ui.screens.profile.visibility-status.utils :as utils]))
;; === Code Related to visibility-status-button ===
(def button-ref (atom nil))
(defn dispatch-popover [top]
(re-frame/dispatch [:show-visibility-status-popover {:top top}]))
(defn dispatch-visibility-status-update [status-type]
(re-frame/dispatch
[:visibility-status-updates/delayed-visibility-status-update status-type]))
(defn calculate-button-height-and-dispatch-popover []
(.measure @button-ref
(fn [_ _ _ _ _ py]
(dispatch-popover py))))
(defn profile-visibility-status-dot [status-type color]
(let [automatic? (= status-type
constants/visibility-status-automatic)
[border-width margin-left size] (if automatic? [1 -10 12] [0 6 10])]
[:<>
(when automatic?
[rn/view {:style (styles/visibility-status-profile-dot
colors/color-inactive size border-width 6)}])
[rn/view {:style (styles/visibility-status-profile-dot
color size border-width margin-left)}]]))
(defn visibility-status-button [on-press props]
(let [logged-in? (<sub [:multiaccount/logged-in?])
{:keys [status-type]} (<sub [:multiaccount/current-user-visibility-status])
status-type (if (and logged-in? (nil? status-type))
(do
(dispatch-visibility-status-update
constants/visibility-status-automatic)
constants/visibility-status-automatic)
status-type)
{:keys [color title]} (get utils/visibility-status-type-data status-type)]
[rn/touchable-opacity
(merge
{:on-press on-press
:accessibility-label :visibility-status-button
:style (styles/visibility-status-button-container)
:ref #(reset! button-ref ^js %)} props)
[profile-visibility-status-dot status-type color]
[rn/text {:style (styles/visibility-status-text)} title]]))
;; === Code Related to visibility-status-popover ===
(def scale (anim/create-value 0))
(def position (anim/create-value 0))
(def alpha-value (anim/create-value 0))
(defn hide-options []
(anim/start
(anim/parallel
[(anim/timing scale {:toValue 0
:duration 140
:useNativeDriver true})
(anim/timing position {:toValue 50
:duration 210
:useNativeDriver true})
(anim/timing alpha-value {:toValue 0
:duration 200
:useNativeDriver true})])))
(defn show-options []
(anim/start
(anim/parallel
[(anim/timing scale {:toValue 1
:duration 210
:useNativeDriver true})
(anim/timing position {:toValue 80
:duration 70
:useNativeDriver true})
(anim/timing alpha-value {:toValue 0.4
:duration 200
:useNativeDriver true})])))
(defn status-option-pressed [request-close status-type]
(request-close)
(dispatch-visibility-status-update status-type))
(defn status-option [{:keys [request-close status-type]}]
(let [{:keys [color title subtitle]}
(get utils/visibility-status-type-data status-type)]
[rn/touchable-opacity {:style {:padding 6}
:accessibility-label :visibility-status-option
:on-press #(status-option-pressed
request-close status-type)}
[rn/view {:style (styles/visibility-status-option)}
[profile-visibility-status-dot status-type color]
[rn/text {:style (styles/visibility-status-text)} title]]
(when-not (nil? subtitle)
[rn/text {:style (styles/visibility-status-subtitle)} subtitle])]))
(defn visibility-status-options [request-close top]
[react/view {:position :absolute
:top (int top)}
[visibility-status-button request-close {:ref nil :active-opacity 1}]
[react/animated-view {:style
(styles/visibility-status-options scale position)
:accessibility-label :visibility-status-options}
[status-option
{:status-type constants/visibility-status-always-online
:request-close request-close}]
[quo/separator {:style {:margin-top 8}}]
[status-option
{:status-type constants/visibility-status-inactive
:request-close request-close}]
[quo/separator]
[status-option
{:status-type constants/visibility-status-automatic
:request-close request-close}]]])
(defn popover-view [_ window-height]
(let [clear-timeout (atom nil)
current-popover (reagent/atom nil)
update? (reagent/atom nil)
request-close (fn []
(reset! clear-timeout
(js/setTimeout
#(do (reset! current-popover nil)
(re-frame/dispatch
[:hide-visibility-status-popover]))
200))
(hide-options)
true)
on-show (fn []
(show-options)
(when platform/android?
(.removeEventListener BackHandler
"hardwareBackPress"
request-close)
(.addEventListener BackHandler
"hardwareBackPress"
request-close)))
on-hide (fn []
(when platform/android?
(.removeEventListener BackHandler
"hardwareBackPress"
request-close)))]
(reagent/create-class
{:UNSAFE_componentWillUpdate
(fn [_ [_ popover _]]
(when @clear-timeout (js/clearTimeout @clear-timeout))
(cond
@update?
(do (reset! update? false)
(on-show))
(and @current-popover popover)
(do (reset! update? true)
(js/setTimeout #(reset! current-popover popover) 600)
(hide-options))
popover
(do (reset! current-popover popover)
(on-show))
:else
(do (reset! current-popover nil)
(on-hide))))
:component-will-unmount on-hide
:reagent-render
(fn []
(when @current-popover
(let [{:keys [top]} @current-popover]
[react/view
{:style (styles/visibility-status-popover-container)}
(when platform/ios?
[react/animated-view
{:style (styles/visibility-status-popover-ios-backdrop
alpha-value)}])
[react/view
{:style (styles/visibility-status-popover-child-container
window-height)}
[react/touchable-highlight
{:style {:flex 1}
:on-press request-close}
[visibility-status-options request-close top]]]])))})))
(views/defview visibility-status-popover []
(views/letsubs [popover [:visibility-status-popover/popover]
{window-height :height} [:dimensions/window]]
[popover-view popover window-height]))

View File

@ -5,6 +5,7 @@
[status-im.ui.screens.screens :as screens]
[oops.core :refer [oget]]
[status-im.ui.screens.popover.views :as popover]
[status-im.ui.screens.profile.visibility-status.views :as visibility-status-views]
[status-im.ui.screens.bottom-sheets.views :as bottom-sheets]
[status-im.ui.screens.signing.views :as signing]
[status-im.ui.screens.wallet.send.views :as wallet.send.views]
@ -86,6 +87,16 @@
(when js/goog.DEBUG
[reloader/reload-view])])))
(def visibility-status-popover-comp
(reagent/reactify-component
(fn []
^{:key (str "visibility-status-popover" @reloader/cnt)}
[react/safe-area-provider
[inactive]
[visibility-status-views/visibility-status-popover]
(when js/goog.DEBUG
[reloader/reload-view])])))
(def sheet-comp
(reagent/reactify-component
(fn []

View File

@ -20,6 +20,8 @@
(def hour (* 60 minute))
(def day (* 24 hour))
(def week (* 7 day))
(defn weeks [w]
(* w week))
(def 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}
@ -144,6 +146,9 @@
(defn timestamp []
(inst-ms (js/Date.)))
(defn timestamp-sec []
(int (/ (timestamp) 1000)))
(defn timestamp->year-month-day-date [ms]
(unparse (:year-month-day formatters) (to-date ms)))
@ -167,3 +172,6 @@
(str day (or (s (mod (- m 20) 10))
(s m)
(s 0)))))
(defn to-ms [sec]
(* 1000 sec))

View File

@ -0,0 +1,21 @@
(ns status-im.visibility-status-popover.core
(:require [status-im.utils.fx :as fx]))
(fx/defn show-visibility-status-popover
{:events [:show-visibility-status-popover]}
[_ value]
{:show-visibility-status-popover nil
:dispatch-later [{:ms 250
:dispatch [:show-visibility-status-popover-db value]}]
:dismiss-keyboard nil})
(fx/defn show-visibility-status-popover-db
{:events [:show-visibility-status-popover-db]}
[{:keys [db]} value]
{:db (assoc db :visibility-status-popover/popover value)})
(fx/defn hide-visibility-status-popover
{:events [:hide-visibility-status-popover]}
[{:keys [db]}]
{:db (dissoc db :visibility-status-popover/popover)
:hide-visibility-status-popover nil})

View File

@ -0,0 +1,179 @@
(ns status-im.visibility-status-updates.core
(:require [status-im.data-store.visibility-status-updates :as visibility-status-updates-store]
[status-im.ethereum.json-rpc :as json-rpc]
[status-im.utils.fx :as fx]
[status-im.constants :as constants]
[status-im.multiaccounts.update.core :as multiaccounts.update]
[status-im.utils.datetime :as datetime]
[status-im.ui.screens.profile.visibility-status.utils :as utils]))
(defn valid-status-type? [status-type]
(some #(= status-type %)
(list constants/visibility-status-always-online
constants/visibility-status-inactive
constants/visibility-status-automatic)))
(defn process-visibility-status-update
[acc {:keys [public-key clock] :as visibility-status-update}]
(let [{:keys [real-status-type time-left]}
(utils/calculate-real-status-type-and-time-left visibility-status-update)]
(cond-> (assoc-in acc
[:visibility-status-updates public-key]
visibility-status-update)
(= real-status-type constants/visibility-status-automatic)
(update :dispatch-later
#(conj % {:ms time-left
:dispatch [:visibility-status-updates/timeout-user-online-status
public-key clock]})))))
(fx/defn load-visibility-status-updates
{:events [:visibility-status-updates/visibility-status-updates-loaded]}
[{:keys [db]} visibility-status-updates-loaded]
(let [{:keys [visibility-status-updates dispatch-later]}
(reduce (fn [acc visibility-status-update-loaded]
(let [{:keys [public-key] :as visibility-status-update}
(visibility-status-updates-store/<-rpc
visibility-status-update-loaded)]
(process-visibility-status-update acc visibility-status-update)))
{} visibility-status-updates-loaded)]
(merge {:db (assoc db :visibility-status-updates visibility-status-updates)}
(when dispatch-later {:utils/dispatch-later dispatch-later}))))
(defn handle-my-visibility-status-updates
[acc my-current-status clock visibility-status-update]
(let [status-type (:status-type visibility-status-update)]
(if (and (valid-status-type? status-type)
(or
(nil? my-current-status)
(> clock (:clock my-current-status))))
(-> acc
(update :current-user-visibility-status
merge {:clock clock :status-type status-type})
(assoc :dispatch [:visibility-status-updates/send-visibility-status-updates?
(not= status-type constants/visibility-status-inactive)]))
acc)))
(defn handle-other-visibility-status-updates
[acc public-key clock visibility-status-update]
(let [status-type (:status-type visibility-status-update)
visibility-status-update-old
(get-in acc [:visibility-status-updates public-key])]
(if (and (valid-status-type? status-type)
(or
(nil? visibility-status-update-old)
(> clock (:clock visibility-status-update-old))))
(process-visibility-status-update acc visibility-status-update)
acc)))
(fx/defn handle-visibility-status-updates
[{:keys [db]} visibility-status-updates-received]
(let [visibility-status-updates-old (get db :visibility-status-updates {})
my-public-key (get-in
db [:multiaccount :public-key])
my-current-status (get-in
db [:multiaccount :current-user-visibility-status])
{:keys [visibility-status-updates
current-user-visibility-status
dispatch dispatch-later]}
(reduce (fn [acc visibility-status-update-received]
(let [{:keys [public-key clock] :as visibility-status-update}
(visibility-status-updates-store/<-rpc
visibility-status-update-received)]
(if (= public-key my-public-key)
(handle-my-visibility-status-updates
acc my-current-status clock visibility-status-update)
(handle-other-visibility-status-updates
acc public-key clock visibility-status-update))))
{:visibility-status-updates visibility-status-updates-old
:current-user-visibility-status my-current-status}
visibility-status-updates-received)]
(merge {:db (-> db
(update-in [:visibility-status-updates]
merge visibility-status-updates)
(update-in [:multiaccount :current-user-visibility-status]
merge current-user-visibility-status))}
(when dispatch {:dispatch dispatch})
(when dispatch-later {:utils/dispatch-later dispatch-later}))))
(fx/defn update-visibility-status
{:events [:visibility-status-updates/update-visibility-status]}
[{:keys [db] :as cofx} status-type]
{:db (update-in db [:multiaccount :current-user-visibility-status]
merge {:status-type status-type
:clock (datetime/timestamp-sec)})
::json-rpc/call [{:method "wakuext_setUserStatus"
:params [status-type ""]
:on-success #()}]})
(fx/defn send-visibility-status-updates?
{:events [:visibility-status-updates/send-visibility-status-updates?]}
[cofx val]
(multiaccounts.update/multiaccount-update cofx
:send-status-updates? val
{}))
(fx/defn visibility-status-option-pressed
{:events [:visibility-status-updates/visibility-status-option-pressed]}
[{:keys [db] :as cofx} status-type]
(let [events-to-dispatch-later
(cond-> [{:ms 10 :dispatch
[:visibility-status-updates/update-visibility-status
status-type]}]
(and
(= status-type constants/visibility-status-inactive)
(> (:peers-count db) 0))
;; Disable broadcasting further updates
(conj {:ms 1000
:dispatch
[:visibility-status-updates/send-visibility-status-updates? false]}))]
(fx/merge cofx
{:dispatch-later events-to-dispatch-later}
;; Enable broadcasting for current broadcast
(send-visibility-status-updates? true))))
(fx/defn timeout-user-online-status
{:events [:visibility-status-updates/timeout-user-online-status]}
[{:keys [db]} public-key clock]
(let [current-clock (get-in db [:visibility-status-updates public-key :clock] 0)]
(when (= current-clock clock)
{:db (update-in db [:visibility-status-updates public-key]
merge {:status-type constants/visibility-status-inactive})})))
(fx/defn delayed-visibility-status-update
{:events [:visibility-status-updates/delayed-visibility-status-update]}
[{:keys [db]} status-type]
{:dispatch-later
[{:ms 200
:dispatch
[:visibility-status-updates/visibility-status-option-pressed status-type]}]})
(fx/defn peers-summary-change
[{:keys [db] :as cofx} peers-count]
(let [send-visibility-status-updates?
(get-in db [:multiaccount :send-status-updates?])
status-type
(get-in db [:multiaccount :current-user-visibility-status :status-type])]
(when (and
(> peers-count 0)
send-visibility-status-updates?
(= status-type constants/visibility-status-inactive))
(fx/merge cofx
{:dispatch-later [{:ms 1000 :dispatch
[:visibility-status-updates/send-visibility-status-updates? false]}]
:db (assoc-in db [:multiaccount :send-status-updates?] false)}
(update-visibility-status status-type)))))
(fx/defn sync-visibility-status-update
[{:keys [db] :as cofx} visibility-status-update-received]
(let [my-current-status (get-in db [:multiaccount :current-user-visibility-status])
{:keys [status-type clock]} (visibility-status-updates-store/<-rpc
visibility-status-update-received)]
(when (and (valid-status-type? status-type)
(or
(nil? my-current-status)
(> clock (:clock my-current-status))))
(fx/merge cofx
{:db (update-in db [:multiaccount :current-user-visibility-status]
merge {:clock clock :status-type status-type})}
(send-visibility-status-updates?
(not= status-type constants/visibility-status-inactive))))))

View File

@ -3,7 +3,7 @@
"_comment": "Instead use: scripts/update-status-go.sh <rev>",
"owner": "status-im",
"repo": "status-go",
"version": "v0.89.14",
"commit-sha1": "c20e8ebdeffd14a881d8092ee64e5180ad53449e",
"src-sha256": "0jbmgj0m1hv1nx3frbzs7lsn8nqspsir5kpzn8lldfgkfgpv96h7"
"version": "v0.89.15",
"commit-sha1": "9693d59e614899557e8b2f4f19ad541bbad3be39",
"src-sha256": "19nl5g5vhrsm510mi0nddi5n67h6z27nx6vixk72bwyvlpz22sij"
}

View File

@ -1674,5 +1674,12 @@
"disable-later-in-settings": "You can disable this later in Settings",
"use-as-profile-picture": "Use as profile picture",
"view-on-opensea": "View on OpenSea",
"profile-picture-updated": "Profile picture updated"
"profile-picture-updated": "Profile picture updated",
"status-automatic": "Automatic",
"status-automatic-subtitle": "Set status automatically",
"status-dnd": "Do not disturb",
"status-dnd-subtitle": "Mutes all notifications",
"status-always-online": "Always Online",
"status-inactive": "Inactive",
"status-inactive-subtitle": "Hides your online status"
}