move chats home (#14419)

* move chats home
This commit is contained in:
flexsurfer 2022-11-23 14:33:40 +01:00 committed by GitHub
parent 32d85d5059
commit bab0fc7ac0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 755 additions and 947 deletions

View File

@ -1,99 +0,0 @@
(ns quo2.components.navigation.top-nav
(:require [react-native.core :as rn]
[quo2.foundations.colors :as colors]
[quo2.components.counter.counter :as counter]
[quo2.components.buttons.button :as quo2.button]
[quo2.components.avatars.user-avatar :as user-avatar]
[react-native.hole-view :as hole-view]))
(defn- get-button-common-props [type]
(let [default? (= type :default)
dark? (colors/dark?)]
{:icon true
:size 32
:style {:margin-left 12}
:type (if default?
(if dark? :grey :dark-grey)
type)
:override-background-color (when (and dark? default?)
colors/neutral-90)}))
(defn- base-button [icon on-press accessibility-label button-common-props]
[quo2.button/button
(merge
{:on-press on-press
:accessibility-label accessibility-label}
button-common-props)
icon])
(defn top-nav
"[top-nav opts]
opts
{:type :default/:blurred/:shell
:new-notifications? true/false
:notification-indicator :unread-dot/:counter
:open-profile fn
:open-search fn
:open-scanner fn
:show-qr fn
:open-activity-center fn
:style override-style
:avatar user-avatar
:counter-label number}
"
[{:keys [type new-notifications? notification-indicator open-profile open-search
open-scanner show-qr open-activity-center style avatar counter-label]}]
(let [button-common-props (get-button-common-props type)]
[rn/view {:style (merge
{:height 56}
style)}
;; Left Section
[rn/touchable-without-feedback {:on-press open-profile}
[rn/view {:style {:position :absolute
:left 20
:top 12}}
[user-avatar/user-avatar
(merge
{:ring? true
:status-indicator? true
:size :small}
avatar)]]]
;; Right Section
[rn/view {:style {:position :absolute
:right 20
:top 12
:flex-direction :row}}
;; TODO component shouldn't know anything about parent system, we should pass buttons as parameters
[base-button :i/search open-search :open-search-button button-common-props]
[base-button :i/scan open-scanner :open-scanner-button button-common-props]
[base-button :i/qr-code show-qr :show-qr-button button-common-props]
[rn/view ;; Keep view instead of "[:<>" to make sure relative
;; position is calculated from this view instead of its parent
[hole-view/hole-view {:key new-notifications? ;; Key is required to force removal of holes
:holes (cond
(not new-notifications?) ;; No new notifications, remove holes
[]
(= notification-indicator :unread-dot)
[{:x 37 :y -3 :width 10 :height 10 :borderRadius 5}]
:else
[{:x 33 :y -7 :width 18 :height 18 :borderRadius 7}])}
[base-button :i/activity-center open-activity-center
:open-activity-center-button button-common-props]]
(when new-notifications?
(if (= notification-indicator :counter)
[counter/counter {:outline false
:override-text-color colors/white
:override-bg-color colors/primary-50
:style {:position :absolute
:left 34
:top -6}}
counter-label]
[rn/view {:style {:width 8
:height 8
:border-radius 4
:top -2
:left 38
:position :absolute
:background-color colors/primary-50}}]))]]]))

View File

@ -12,15 +12,7 @@
(def default-tab-size 32)
(defn tabs [{:keys [default-active on-change style]}]
(let [active-tab-id (reagent/atom default-active)]
(fn [{:keys [data size] :or {size default-tab-size}}]
[rn/view (merge {:flex-direction :row} style)
(doall
(for [{:keys [label id new-info accessibility-label]} data]
^{:key id}
[rn/view {:style {:margin-right (if (= size default-tab-size) 12 8)}}
(when new-info
(defn indicator []
[rn/view {:position :absolute
:z-index 1
:right -2
@ -32,6 +24,17 @@
:align-items :center
:background-color (colors/theme-colors colors/neutral-5 colors/neutral-95)}
[notification-dot]])
(defn tabs [{:keys [default-active on-change style]}]
(let [active-tab-id (reagent/atom default-active)]
(fn [{:keys [data size] :or {size default-tab-size}}]
[rn/view (merge {:flex-direction :row} style)
(doall
(for [{:keys [label id notification-dot? accessibility-label]} data]
^{:key id}
[rn/view {:style {:margin-right (if (= size default-tab-size) 12 8)}}
(when notification-dot?
[indicator])
[tab/tab
{:id id
:size size

View File

@ -35,11 +35,11 @@
quo2.components.tags.context-tags
quo2.components.tabs.tabs
quo2.components.tabs.account-selector
quo2.components.navigation.top-nav
quo2.components.navigation.floating-shell-button
quo2.components.tags.status-tags
quo2.components.navigation.page-nav
quo2.components.selectors.disclaimer))
quo2.components.selectors.disclaimer
quo2.components.selectors.selectors))
(def button quo2.components.buttons.button/button)
(def dynamic-button quo2.components.buttons.dynamic-button/dynamic-button)
@ -61,12 +61,11 @@
(def tabs quo2.components.tabs.tabs/tabs)
(def scrollable-tabs quo2.components.tabs.tabs/scrollable-tabs)
(def account-selector quo2.components.tabs.account-selector/account-selector)
(def top-nav quo2.components.navigation.top-nav/top-nav)
(def floating-shell-button quo2.components.navigation.floating-shell-button/floating-shell-button)
(def status-tag quo2.components.tags.status-tags/status-tag)
(def page-nav quo2.components.navigation.page-nav/page-nav)
(def disclaimer quo2.components.selectors.disclaimer/disclaimer)
(def checkbox quo2.components.selectors.selectors/checkbox)
;;;; AVATAR
(def account-avatar quo2.components.avatars.account-avatar/account-avatar)
(def channel-avatar quo2.components.avatars.channel-avatar/channel-avatar)

View File

@ -392,6 +392,7 @@
(get-node-config)
(communities/fetch)
(logging/set-log-level (:log-level multiaccount))
(notifications-center/get-activity-center-notifications)
(notifications-center/get-activity-center-notifications-count))))
(re-frame/reg-fx

View File

@ -17,8 +17,7 @@
[quo2.components.markdown.text :as quo2.text]
[quo2.components.tabs.tabs :as quo2.tabs]
[status-im.ui.screens.wallet.accounts.common :as common]
[status-im.ui.screens.wallet.account.views :as account.views]
[quo.components.safe-area :as safe-area])
[status-im.ui.screens.wallet.account.views :as account.views])
(:require-macros [status-im.utils.views :as views]))
(views/defview account-card [{:keys [name color address type wallet] :as account} keycard? card-width]
@ -266,10 +265,7 @@
;mainnet? @(re-frame/subscribe [:mainnet?])
selected-account-atom (reagent/atom nil)]
(fn []
[safe-area/consumer
(fn [insets]
[react/view {:style {:flex 1
:padding-top (:top insets)
:background-color (quo2.colors/theme-colors quo2.colors/neutral-5 quo2.colors/neutral-95)}}
[react/view {:padding-horizontal 20}
[react/view {:flex-direction :row :height 56 :align-items :center :justify-content :flex-end}
@ -291,7 +287,7 @@
:i/placeholder]]
[total-value]
[accounts selected-account-atom]]
[account.views/account-new @selected-account-atom]])])))
[account.views/account-new @selected-account-atom]])))
(defn accounts-overview-old []
(let [mnemonic @(re-frame/subscribe [:mnemonic])

View File

@ -1,3 +0,0 @@
(ns status-im.ui2.screens.chat.components.contact-item.test)
;; TODO: tests to be added when RNTL is integrated into our project

View File

@ -1,59 +0,0 @@
(ns status-im.ui2.screens.chat.components.contact-item.view
(:require [quo2.foundations.typography :as typography]
[quo2.components.icon :as icons]
[quo2.foundations.colors :as colors]
[quo2.components.avatars.user-avatar :as user-avatar]
[quo.react-native :as rn]
[status-im.utils.utils :as utils]
[quo.platform :as platform]
[quo2.components.markdown.text :as text]
[status-im.ui2.screens.chat.components.message-home-item.style :as style]
[utils.re-frame :as rf]
[status-im.ui2.screens.chat.actions :as actions]))
(defn open-chat [chat-id]
(rf/dispatch [:dismiss-keyboard])
(if platform/android?
(rf/dispatch [:chat.ui/navigate-to-chat-nav2 chat-id])
(rf/dispatch [:chat.ui/navigate-to-chat chat-id]))
(rf/dispatch [:search/home-filter-changed nil])
(rf/dispatch [:accept-all-activity-center-notifications-from-chat chat-id]))
(defn contact-item [item]
(let [{:keys [public-key ens-verified added? images]} item
display-name (first (rf/sub [:contacts/contact-two-names-by-identity public-key]))
photo-path (when (seq images) (rf/sub [:chats/photo-path public-key]))
current-pk (rf/sub [:multiaccount/public-key])]
[rn/touchable-opacity (merge {:style (style/container)
:on-press #(open-chat public-key)
:on-long-press #(when-not (= current-pk public-key)
(rf/dispatch [:bottom-sheet/show-sheet
{:content (fn [] [actions/actions item])}]))})
[user-avatar/user-avatar {:full-name display-name
:profile-picture photo-path
:status-indicator? true
:online? true
:size :small
:ring? false}]
[rn/view {:style {:margin-left 8}}
[rn/view {:style {:flex-direction :row}}
[text/text {:style (merge typography/paragraph-1 typography/font-semi-bold
{:color (colors/theme-colors colors/neutral-100 colors/white)})}
display-name]
(if ens-verified
[rn/view {:style {:margin-left 5 :margin-top 4}}
[icons/icon :i/verified {:no-color true :size 12 :color (colors/theme-colors colors/success-50 colors/success-60)}]]
(when added?
[rn/view {:style {:margin-left 5 :margin-top 4}}
[icons/icon :i/contact {:no-color true :size 12 :color (colors/theme-colors colors/primary-50 colors/primary-60)}]]))]
[text/text {:size :paragraph-1
:style {:color (colors/theme-colors colors/neutral-50 colors/neutral-40)}}
(utils/get-shortened-address public-key)]]
(when-not (= current-pk public-key)
[rn/touchable-opacity {:style {:position :absolute
:right 20}
:active-opacity 1
:on-press #(rf/dispatch [:bottom-sheet/show-sheet
{:content (fn [] [actions/actions item])}])}
[icons/icon :i/options {:size 20 :color (colors/theme-colors colors/neutral-50 colors/neutral-40)}]])]))

View File

@ -1,35 +0,0 @@
(ns status-im.ui2.screens.chat.components.message-home-item.style
(:require [quo2.foundations.colors :as colors]
[quo2.foundations.typography :as typography]))
(defn container []
{:margin-top 8
:margin-horizontal 8
:padding-vertical 8
:padding-horizontal 12
:border-radius 12
:flex-direction :row
:align-items :center})
(defn group-chat-icon [color]
{:width 32
:height 32
:background-color color
:justify-content :center
:align-items :center
:border-radius 16})
(defn count-container []
{:width 8
:height 8
:border-radius 4
:position :absolute
:right 26
:top 16
:background-color (colors/theme-colors colors/neutral-40 colors/neutral-60)})
(defn timestamp []
(merge typography/font-regular typography/label
{:color (colors/theme-colors colors/neutral-50 colors/neutral-40)
:margin-top 3
:margin-left 8}))

View File

@ -1,3 +0,0 @@
(ns status-im.ui2.screens.chat.components.message-home-item.test)
;; TODO: tests to be added when RNTL is integrated into our project

View File

@ -1,132 +0,0 @@
(ns status-im.ui2.screens.chat.components.message-home-item.view
(:require [clojure.string :as string]
[utils.re-frame :as rf]
[status-im.utils.datetime :as time]
[quo2.foundations.typography :as typography]
[quo2.components.icon :as icons]
[quo2.foundations.colors :as colors]
[quo.react-native :as rn]
[quo.platform :as platform]
[quo2.core :as quo2]
[quo2.components.markdown.text :as text]
[status-im.ui2.screens.chat.actions :as actions]
[status-im.ui2.screens.chat.components.message-home-item.style :as style]))
(def max-subheader-length 50)
(defn truncate-literal [literal]
(when literal
(let [size (min max-subheader-length (.-length literal))]
{:components (.substring literal 0 size)
:length size})))
(defn add-parsed-to-subheader [acc {:keys [type destination literal children]}]
(let [result (case type
"paragraph"
(reduce
(fn [{:keys [_ length] :as acc-paragraph} parsed-child]
(if (>= length max-subheader-length)
(reduced acc-paragraph)
(add-parsed-to-subheader acc-paragraph parsed-child)))
{:components [rn/text]
:length 0}
children)
"mention"
{:components [rn/text (rf/sub [:contacts/contact-name-by-identity literal])]
:length 4} ;; we can't predict name length so take the smallest possible
"status-tag"
(truncate-literal (str "#" literal))
"link"
(truncate-literal destination)
(truncate-literal literal))]
{:components (conj (:components acc) (:components result))
:length (+ (:length acc) (:length result))}))
(defn render-subheader
"Render the preview of a last message to a maximum of max-subheader-length characters"
[parsed-text]
(let [result
(reduce
(fn [{:keys [_ length] :as acc-text} new-text-chunk]
(if (>= length max-subheader-length)
(reduced acc-text)
(add-parsed-to-subheader acc-text new-text-chunk)))
{:components [rn/text {:style (merge typography/paragraph-2 typography/font-regular
{:color (colors/theme-colors colors/neutral-50 colors/neutral-40)
:width "90%"})
:number-of-lines 1
:ellipsize-mode :tail
:accessibility-label :chat-message-text}]
:length 0}
parsed-text)]
(:components result)))
(defn verified-or-contact-icon [{:keys [ens-verified added?]}]
(if ens-verified
[rn/view {:style {:margin-left 5 :margin-top 4}}
[icons/icon :i/verified {:no-color true
:size 12
:color (colors/theme-colors colors/success-50 colors/success-60)}]]
(when added?
[rn/view {:style {:margin-left 5 :margin-top 4}}
[icons/icon :i/contact {:no-color true
:size 12
:color (colors/theme-colors colors/primary-50 colors/primary-60)}]])))
(defn display-name-view [display-name contact timestamp]
[rn/view {:style {:flex-direction :row}}
[text/text {:weight :semi-bold
:accessibility-label :chat-name-text
:size :paragraph-1}
display-name]
[verified-or-contact-icon contact]
[text/text {:style (style/timestamp)}
(time/to-short-str timestamp)]])
(defn display-pic-view [group-chat color display-name photo-path]
(if group-chat
[quo2/group-avatar {:color color
:size :medium}]
[quo2/user-avatar {:full-name display-name
:profile-picture photo-path
:size :small}]))
(defn messages-home-item [item]
(let [{:keys [chat-id
color
group-chat
last-message
timestamp
name
unviewed-mentions-count
unviewed-messages-count]} item
display-name (if-not group-chat (first (rf/sub [:contacts/contact-two-names-by-identity chat-id])) name)
contact (when-not group-chat (rf/sub [:contacts/contact-by-address chat-id]))
photo-path (when-not (empty? (:images contact)) (rf/sub [:chats/photo-path chat-id]))]
[rn/touchable-opacity (merge {:style (style/container)
:on-press (fn []
(rf/dispatch [:dismiss-keyboard])
(if platform/android?
(rf/dispatch [:chat.ui/navigate-to-chat-nav2 chat-id])
(rf/dispatch [:chat.ui/navigate-to-chat chat-id]))
(rf/dispatch [:search/home-filter-changed nil])
(rf/dispatch [:accept-all-activity-center-notifications-from-chat chat-id]))
:on-long-press #(rf/dispatch [:bottom-sheet/show-sheet
{:content (fn [] [actions/actions item false])}])})
[display-pic-view group-chat color display-name photo-path]
[rn/view {:style {:margin-left 8}}
[display-name-view display-name contact timestamp]
(if (string/blank? (get-in last-message [:content :parsed-text]))
[text/text {:size :paragraph-2
:style {:color (colors/theme-colors colors/neutral-50 colors/neutral-40)}}
(get-in last-message [:content :text])]
[render-subheader (get-in last-message [:content :parsed-text])])]
(if (> unviewed-mentions-count 0)
[quo2/info-count unviewed-mentions-count {:top 16}]
(when (> unviewed-messages-count 0)
[rn/view {:style (style/count-container)}]))]))

View File

@ -5,24 +5,24 @@
[quo2.core :as quo2]
[utils.re-frame :as rf]
[i18n.i18n :as i18n]
[status-im.ui2.screens.chat.components.contact-item.view :as contact-item]))
[status-im2.common.contact-list-item.view :as contact-item]))
(defn back-button []
[quo2/button {:type :grey
:size 32
:width 32
:style {:margin-left 20}
:accessibility-label :back-button
:on-press #(rf/dispatch [:navigate-back])}
[quo2/icon :i/arrow-left {:color (colors/theme-colors colors/neutral-100 colors/white)}]])
:on-press #(rf/dispatch [:navigate-back])
:icon true}
:i/arrow-left])
(defn options-button []
[quo2/button {:type :grey
:size 32
:width 32
:style {:margin-right 20}
:accessibility-label :options-button}
[quo2/icon :i/options {:color (colors/theme-colors colors/neutral-100 colors/white)}]])
:accessibility-label :options-button
:icon true}
:i/options])
(defn count-container [count]
[rn/view {:style (style/count-container)}

View File

@ -1,380 +0,0 @@
(ns status-im.ui2.screens.chat.home
(:require [re-frame.core :as re-frame]
[reagent.core :as reagent]
[status-im.i18n.i18n :as i18n]
[status-im.react-native.resources :as resources]
[status-im.ui.components.connectivity.view :as connectivity]
[status-im.ui.components.icons.icons :as icons]
[status-im.ui.components.list.views :as list]
[status-im.ui.components.react :as react]
[status-im.ui.screens.home.styles :as styles]
[quo.design-system.colors :as quo.colors]
[quo.core :as quo]
[status-im.add-new.core :as new-chat]
[status-im.ui.components.search-input.view :as search-input]
[status-im.add-new.db :as db]
[status-im.utils.debounce :as debounce]
[status-im.utils.utils :as utils]
[status-im.ui.components.topbar :as topbar]
[status-im.ui.components.invite.views :as invite]
[status-im.utils.handlers :refer [<sub >evt]]
[quo2.components.markdown.text :as quo2.text]
[status-im.qr-scanner.core :as qr-scanner]
[status-im.ui.components.chat-icon.styles :as chat-icon.styles]
[react-native.core :as rn]
[quo.react-native :as quo.rn]
[quo.react]
[quo2.foundations.colors :as colors]
[quo2.foundations.typography :as typography]
[quo2.components.buttons.button :as quo2.button]
[quo2.components.tabs.tabs :as quo2.tabs]
[quo2.components.community.discover-card :as discover-card]
[status-im.ui.components.chat-icon.screen :as chat-icon]
[quo2.components.icon :as quo2.icons]
[quo.components.safe-area :as safe-area]
[quo2.components.notifications.info-count :refer [info-count]]
[status-im.ui2.screens.chat.components.received-cr-item :as received-cr-item]
[status-im.ui2.screens.chat.components.message-home-item.view :refer [messages-home-item]]
[status-im.ui2.screens.chat.components.contact-item.view :refer [contact-item]]
[clojure.string :as str]
[status-im2.common.plus-button.view :as components.plus-button]
[status-im2.setup.config :as config])
(:require-macros [status-im.utils.views :as views]))
(defn home-tooltip-view []
[rn/view (styles/chat-tooltip)
[rn/view {:style {:width 66 :position :absolute :top -6 :background-color quo.colors/white
:align-items :center}}
[rn/image {:source (resources/get-image :empty-chats-header)
:style {:width 50 :height 50}}]]
[rn/touchable-highlight
{:style {:position :absolute :right 0 :top 0
:width 44 :height 44 :align-items :center :justify-content :center}
:on-press #(re-frame/dispatch [:multiaccounts.ui/hide-home-tooltip])
:accessibility-label :hide-home-button}
[icons/icon :main-icons/close-circle {:color quo.colors/gray}]]
[react/i18n-text {:style styles/no-chats-text :key :chat-and-transact}]
[rn/view {:align-items :center
:margin-top 8
:margin-bottom 12}
[invite/button]]])
(defn welcome-blank-chats []
[rn/view {:style {:flex 1 :align-items :center :justify-content :center}}
[icons/icon :main-icons/placeholder20 {:width 120 :height 120}]
[rn/text {:style (merge typography/font-semi-bold typography/paragraph-1)} (i18n/label :t/no-messages)]
[rn/text {:style (merge typography/font-regular typography/paragraph-2)} (i18n/label :t/blank-messages-text)]])
(defn welcome-blank-page []
[rn/view {:style {:flex 1
:flex-direction :row
:align-items :center
:justify-content :center}}
[react/i18n-text {:style styles/welcome-blank-text :key :welcome-blank-message}]])
(defonce search-active? (reagent/atom false))
(defn search-input-wrapper [search-filter chats-empty]
[rn/view {:padding-horizontal 16
:padding-vertical 10}
[search-input/search-input
{:search-active? search-active?
:search-filter search-filter
:on-cancel #(re-frame/dispatch [:search/home-filter-changed nil])
:on-blur (fn []
(when chats-empty
(re-frame/dispatch [:search/home-filter-changed nil]))
(re-frame/dispatch [::new-chat/clear-new-identity]))
:on-focus (fn [search-filter]
(when-not search-filter
(re-frame/dispatch [:search/home-filter-changed ""])
(re-frame/dispatch [::new-chat/clear-new-identity])))
:on-change (fn [text]
(re-frame/dispatch [:search/home-filter-changed text])
(re-frame/dispatch [:set-in [:contacts/new-identity :state] :searching])
(debounce/debounce-and-dispatch [:new-chat/set-new-identity text] 300))}]])
(defn start-suggestion [search-value]
(let [{:keys [state ens-name public-key]}
@(re-frame/subscribe [:contacts/new-identity])
valid-private? (= state :valid)
valid-public? (db/valid-topic? search-value)]
(when (or valid-public? valid-private?)
[rn/view
[quo/list-header (i18n/label :t/search-no-chat-found)]
(when valid-private?
[quo/list-item {:theme :accent
:icon :main-icons/private-chat
:title (or ens-name (utils/get-shortened-address public-key))
:subtitle (i18n/label :t/join-new-private-chat)
:on-press (fn []
(debounce/dispatch-and-chill [:contact.ui/contact-code-submitted false] 3000)
(re-frame/dispatch [:search/home-filter-changed nil]))}])
(when valid-public?
[quo/list-item {:theme :accent
:icon :main-icons/public-chat
:title (str "#" search-value)
:subtitle (i18n/label :t/join-new-public-chat)
:on-press (fn []
(re-frame/dispatch [:chat.ui/start-public-chat search-value])
(re-frame/dispatch [:set :public-group-topic nil])
(re-frame/dispatch [:search/home-filter-changed nil]))}])])))
(defn chat-list-key-fn [item]
(or (:chat-id item) (:public-key item) (:id item)))
(defn get-item-layout [_ index]
#js {:length 64 :offset (* 64 index) :index index})
(def selected-tab (reagent/atom :recent))
(defn prepare-items [current-active-tab items]
(if (= current-active-tab :groups)
(filter #(-> % :group-chat (= true)) items)
(filter #(-> % (contains? :chat-id)) items)))
(defn prepare-contacts [contacts]
(let [data (atom {})]
(doseq [i (range (count contacts))]
(let [first-char (get (:alias (nth contacts i)) 0)]
(if-not (contains? @data first-char)
(swap! data #(assoc % first-char {:title first-char :data [(nth contacts i)]}))
(swap! data #(assoc-in % [first-char :data] (conj (:data (get @data first-char)) (nth contacts i)))))))
(swap! data #(sort @data))
(vals @data)))
(defn contacts-section-header [{:keys [title]}]
[rn/view {:style {:border-top-width 1 :border-top-color colors/neutral-20 :padding-vertical 8 :padding-horizontal 20 :margin-top 8}}
[rn/text {:style (merge typography/font-medium typography/paragraph-2 {:color colors/neutral-50})} title]])
(defn find-contact-requests [notifications]
(let [received-requests (atom [])
has-unread? (atom false)]
(doseq [i (range (count notifications))]
(doseq [j (range (count (:data (nth notifications i))))]
(when (= 1 (get-in (nth (:data (nth notifications i)) j) [:message :contact-request-state]))
(swap! received-requests conj (nth (:data (nth notifications i)) j)))
(when (= false (get-in (nth (:data (nth notifications i)) j) [:read]))
(reset! has-unread? true))))
{:received-requests @received-requests :has-unread? @has-unread?}))
(def selected-requests-tab (reagent/atom :received))
(defn contact-requests-sheet []
[:f>
(fn []
(let [{window-height :height} (rn/use-window-dimensions)
safe-area (safe-area/use-safe-area)
notifications (<sub [:activity.center/notifications-grouped-by-date])
{received-requests :received-requests} (find-contact-requests notifications)
sent-requests []]
[rn/view {:style {:margin-left 20
:height (- window-height (:top safe-area))}}
[rn/touchable-opacity
{:on-press #(>evt [:bottom-sheet/hide])
:style
{:background-color (colors/theme-colors colors/neutral-10 colors/neutral-80)
:width 32
:height 32
:border-radius 10
:justify-content :center
:align-items :center
:margin-bottom 24}}
[quo2.icons/icon :i/close {:color (colors/theme-colors "#000000" "#ffffff")}]]
[rn/text {:style (merge
typography/heading-1
typography/font-semi-bold
{:color (colors/theme-colors "#000000" "#ffffff")})}
(i18n/label :t/pending-requests)]
[quo2.tabs/tabs
{:style {:margin-top 12 :margin-bottom 20}
:size 32
:on-change #(reset! selected-requests-tab %)
:default-active :received
:data [{:id :received
:label (i18n/label :t/received)}
{:id :sent
:label (i18n/label :t/sent)}]}]
[list/flat-list
{:key-fn :chat-id
:data (if (= @selected-requests-tab :received) received-requests sent-requests)
:render-fn received-cr-item/received-cr-item}]]))])
(defn get-display-name [{:keys [chat-id message]}]
(let [name (first (<sub [:contacts/contact-two-names-by-identity chat-id]))
no-ens-name (str/blank? (get-in message [:content :ens-name]))]
(if no-ens-name
(first (str/split name " "))
name)))
(defn requests-summary [requests]
(case (count requests)
1
(get-display-name (first requests))
2
(str (get-display-name (first requests)) " " (i18n/label :t/and) " " (get-display-name (second requests)))
(str (get-display-name (first requests)) ", " (get-display-name (second requests)) " " (i18n/label :t/and) " " (- (count requests) 2) " " (i18n/label :t/more))))
(defn contact-requests [requests]
[rn/touchable-opacity
{:active-opacity 1
:on-press #(do
(>evt
[:bottom-sheet/show-sheet
{:content (fn [] [contact-requests-sheet])}])
(>evt [:mark-all-activity-center-notifications-as-read]))
:style {:flex-direction :row
:margin 8
:padding-horizontal 12
:padding-vertical 8
:align-items :center}}
[rn/view {:style {:justify-content :center
:align-items :center
:width 32
:height 32
:border-radius 16
:border-width 1
:border-color (colors/theme-colors colors/neutral-20 colors/neutral-80)}}
[quo2.icons/icon :i/pending-user {:color (colors/theme-colors colors/neutral-50 colors/neutral-40)}]]
[rn/view {:style {:margin-left 8}}
[rn/text {:style
(merge typography/paragraph-1 typography/font-semi-bold {:color (colors/theme-colors "#000000" "#ffffff")})} (i18n/label :t/pending-requests)]
[rn/text {:style (merge typography/paragraph-2 typography/font-regular {:color (colors/theme-colors colors/neutral-50 colors/neutral-40)})} (requests-summary requests)]]
[info-count (count requests)]])
(defn chats []
(let [{:keys [items search-filter]} (<sub [:home-items])
current-active-tab @selected-tab
items (prepare-items current-active-tab items)
contacts (<sub [:contacts/active])
contacts (prepare-contacts contacts)
notifications (<sub [:activity.center/notifications-grouped-by-date])
{requests :received-requests new-info :has-unread?} (find-contact-requests notifications)]
[rn/view {:style {:flex 1}}
[discover-card/discover-card {:title (i18n/label :t/invite-friends-to-status)
:description (i18n/label :t/share-invite-link)}]
[quo2.tabs/tabs {:style {:margin-left 20
:margin-bottom 20
:margin-top 24}
:size 32
:on-change #(reset! selected-tab %)
:default-active @selected-tab
:data [{:id :recent
:label (i18n/label :t/recent)}
{:id :groups
:label (i18n/label :t/groups)}
{:id :contacts
:label (i18n/label :t/contacts)
:new-info new-info}]}]
(if (and (empty? items)
(empty? search-filter)
(not @search-active?))
[welcome-blank-chats]
(if (not= current-active-tab :contacts)
[list/flat-list
{:key-fn chat-list-key-fn
:getItemLayout get-item-layout
:on-end-reached #(re-frame/dispatch [:chat.ui/show-more-chats])
:keyboard-should-persist-taps :always
:data items
:render-fn messages-home-item}]
[rn/view {:style {:flex 1}} (when (> (count requests) 0)
[contact-requests requests])
[rn/section-list
{:key-fn :title
:sticky-section-headers-enabled false
:sections contacts
:render-section-header-fn contacts-section-header
:render-fn contact-item}]]))]))
(views/defview chats-list []
(views/letsubs [loading? [:chats/loading?]]
[:<>
[connectivity/loading-indicator]
(if loading?
[rn/view {:flex 1 :align-items :center :justify-content :center}
[rn/activity-indicator {:animating true}]]
[chats])]))
(views/defview plus-button []
(views/letsubs [logging-in? [:multiaccounts/login]]
[components.plus-button/plus-button
{:on-press (when-not logging-in?
#(re-frame/dispatch [:bottom-sheet/show-sheet :add-new {}]))
:loading logging-in?
:accessibility-label :new-chat-button}]))
(views/defview notifications-button []
(views/letsubs [notif-count [:activity.center/notifications-count]]
[rn/view
[quo2.button/button {:type :grey
:size 32
:width 32
:style {:margin-left 12}
:accessibility-label :notifications-button
:on-press #(do
(re-frame/dispatch [:mark-all-activity-center-notifications-as-read])
(if config/new-activity-center-enabled?
(re-frame/dispatch [:activity-center/open])
(re-frame/dispatch [:navigate-to :notifications-center])))}
[icons/icon :main-icons/notification2 {:color (colors/theme-colors colors/neutral-100 colors/white)}]]
(when (pos? notif-count)
[rn/view {:style (merge (styles/counter-public-container) {:top 5 :right 5})
:pointer-events :none}
[rn/view {:style styles/counter-public
:accessibility-label :notifications-unread-badge}]])]))
(defn qr-button []
[quo2.button/button {:type :grey
:accessibility-label "qr-button"
:size 32
:width 32
:style {:margin-left 12}
:on-press #(do
(re-frame/dispatch [::qr-scanner/scan-code
{:handler ::qr-scanner/on-scan-success}]))}
[icons/icon :main-icons/qr2 {:color (colors/theme-colors colors/neutral-100 colors/white)}]])
(defn scan-button []
[quo2.button/button {:type :grey
:size 32
:width 32
:accessibility-label "scan-button"
:on-press #(do
(re-frame/dispatch [::qr-scanner/scan-code
{:handler ::qr-scanner/on-scan-success}]))}
[icons/icon :main-icons/scan2 {:color (colors/theme-colors colors/neutral-100 colors/white)}]])
(views/defview profile-button []
(views/letsubs [{:keys [public-key preferred-name emoji]} [:multiaccount]]
[rn/view
[chat-icon/emoji-chat-icon-view public-key false preferred-name emoji
{:size 28
:chat-icon chat-icon.styles/chat-icon-chat-list}]]))
(defn home []
[:f>
(fn []
(quo.react/effect! #(re-frame/dispatch [:get-activity-center-notifications]))
[quo.rn/keyboard-avoiding-view {:style {:flex 1
:background-color (colors/theme-colors colors/neutral-5 colors/neutral-95)}
:ignore-offset true}
[topbar/topbar {:navigation :none
:use-insets true
:background (colors/theme-colors colors/neutral-5 colors/neutral-95)
:left-component [rn/view {:flex-direction :row :margin-left 20}
[profile-button]]
:right-component [rn/view {:flex-direction :row :margin-right 20}
[scan-button]
[qr-button]
[notifications-button]]
:border-bottom false}]
[rn/view {:flex-direction :row
:justify-content :space-between
:align-items :center
:margin-horizontal 20
:margin-top 15
:margin-bottom 20}
[quo2.text/text {:size :heading-1 :weight :semi-bold} (i18n/label :t/messages)]
[plus-button]]
[chats-list]])])

View File

@ -38,10 +38,10 @@
[status-im.utils.security :as security]
[quo2.components.icon :as icons]
[status-im.utils.datetime :as time]
[status-im.ui2.screens.chat.components.message-home-item.view :as message-home-item]
[quo2.components.avatars.user-avatar :as user-avatar]
[quo2.components.markdown.text :as text]
[status-im.utils.utils :as utils])
[status-im.utils.utils :as utils]
[status-im2.contexts.chat.home.chat-list-item.view :as home.chat-list-item])
(:require-macros [status-im.utils.views :refer [defview letsubs]]))
(defview mention-element [from]
@ -286,7 +286,7 @@
:number-of-lines 1
:style {:width "45%"}}
display-name]
[message-home-item/verified-or-contact-icon contact]
[home.chat-list-item/verified-or-contact-icon contact]
(when show-key?
(let [props {:size :label
:style {:color (colors/theme-colors colors/neutral-50 colors/neutral-40)}}]
@ -329,12 +329,13 @@
:pointer-events :box-none}
[rn/view {:style {:width 40}}
(when (or (and (seq response-to) (:quoted-message message)) last-in-group? pinned)
[react/touchable-highlight {:on-press #(re-frame/dispatch [:chat.ui/show-profile from])}
[user-avatar/user-avatar {:full-name display-name
:profile-picture photo-path
:status-indicator? true
:online? true
:size :small
:ring? false}])]
:ring? false}]])]
[rn/view {:style (style/message-author-wrapper)}
(when (or (and (seq response-to) (:quoted-message message)) last-in-group? pinned)
[display-name-view display-name contact timestamp true])

View File

@ -1,56 +0,0 @@
(ns status-im.ui2.screens.common.alert.view
(:require
[reagent.core :as reagent]
[status-im.i18n.i18n :as i18n]
[utils.re-frame :as rf]
[quo2.components.markdown.text :as quo2.text]
[react-native.core :as rn]
[quo2.core :as quo2]
[quo2.components.buttons.button :as quo2.button]
[status-im.ui2.screens.common.alert.style :as style]
[quo2.components.selectors.selectors :as selectors]))
(defn avatar [group-chat color display-name photo-path]
(if group-chat
[quo2/group-avatar {:color color
:size :small}]
[quo2/user-avatar {:full-name display-name
:profile-picture photo-path
:size :xxs
:status-indicator false}]))
(defn extra-action-view [extra-action extra-text extra-action-selected?]
(when extra-action
[rn/view {:style {:margin-top 16 :flex-direction :row}}
[selectors/checkbox {:on-change (fn [selected?] (reset! extra-action-selected? selected?))}]
[quo2.text/text {:style {:margin-left 10}} extra-text]]))
(defn alert [{:keys [title description context button-text on-press extra-action extra-text]}]
(let [extra-action-selected? (reagent/atom false)]
(fn []
(let [{:keys [group-chat chat-id public-key color name]} context
id (or chat-id public-key)
display-name (if-not group-chat (first (rf/sub [:contacts/contact-two-names-by-identity id])) name)
contact (when-not group-chat (rf/sub [:contacts/contact-by-address id]))
photo-path (when-not (empty? (:images contact)) (rf/sub [:chats/photo-path id]))]
[rn/view {:style {:margin-horizontal 20}}
[quo2.text/text {:weight :semi-bold
:size :heading-1} title]
[rn/view {:style (style/context-container)}
[avatar group-chat color display-name photo-path]
[quo2.text/text {:weight :medium
:size :paragraph-2
:style {:margin-left 4}} display-name]]
[quo2.text/text description]
[extra-action-view extra-action extra-text extra-action-selected?]
[rn/view {:style (style/buttons-container)}
[quo2.button/button {:type :grey
:style {:flex 0.48}
:on-press #(rf/dispatch [:bottom-sheet/hide])}
(i18n/label :t/close)]
[quo2.button/button {:type :danger
:style {:flex 0.48}
:on-press #(do
(when @extra-action-selected? (extra-action))
(on-press))}
button-text]]]))))

View File

@ -1,4 +0,0 @@
(ns status-im.ui2.screens.common.core
(:require status-im.ui2.screens.common.alert.view))
(def alert status-im.ui2.screens.common.alert.view/alert)

View File

@ -89,7 +89,6 @@
(when id
(clear-timeout id)))))
;;TODO (14/11/22 flexsurfer) haven't moved yet
(defn get-shortened-address
"Takes first and last 4 digits from address including leading 0x
and adds unicode ellipsis in between"
@ -101,6 +100,7 @@
(when address
(get-shortened-address (eip55/address->checksum (ethereum/normalized-hex address)))))
;;TODO (14/11/22 flexsurfer) haven't moved yet
(defn format-decimals [amount places]
(let [decimal-part (get (string/split (str amount) ".") 1)]
(if (> (count decimal-part) places)

View File

@ -1,4 +1,4 @@
(ns status-im.ui2.screens.common.alert.style
(ns status-im2.common.confirmation-drawer.style
(:require [quo2.foundations.colors :as colors]))
(defn context-container []
@ -12,7 +12,7 @@
:margin-left -4
:margin-bottom 16})
(defn buttons-container []
(def buttons-container
{:flex-direction :row
:justify-content :space-between
:margin-top 20})

View File

@ -0,0 +1,52 @@
(ns status-im2.common.confirmation-drawer.view
(:require [reagent.core :as reagent]
[i18n.i18n :as i18n]
[utils.re-frame :as rf]
[react-native.core :as rn]
[quo2.core :as quo]
[status-im2.common.confirmation-drawer.style :as style]))
(defn avatar [group-chat color display-name photo-path]
(if group-chat
[quo/group-avatar {:color color
:size :small}]
[quo/user-avatar {:full-name display-name
:profile-picture photo-path
:size :xxs
:status-indicator false}]))
(defn extra-action-view [extra-action extra-text extra-action-selected?]
(when extra-action
[rn/view {:style {:margin-top 16 :flex-direction :row}}
[quo/checkbox {:on-change (fn [selected?] (reset! extra-action-selected? selected?))}]
[quo/text {:style {:margin-left 10}} extra-text]]))
(defn confirmation-drawer [{:keys [title description context button-text on-press extra-action extra-text]}]
(let [extra-action-selected? (reagent/atom false)]
(fn []
(let [{:keys [group-chat chat-id public-key color name]} context
id (or chat-id public-key)
display-name (if-not group-chat (first (rf/sub [:contacts/contact-two-names-by-identity id])) name)
contact (when-not group-chat (rf/sub [:contacts/contact-by-address id]))
photo-path (when-not (empty? (:images contact)) (rf/sub [:chats/photo-path id]))]
[rn/view {:style {:margin-horizontal 20}}
[quo/text {:weight :semi-bold
:size :heading-1} title]
[rn/view {:style (style/context-container)}
[avatar group-chat color display-name photo-path]
[quo/text {:weight :medium
:size :paragraph-2
:style {:margin-left 4}} display-name]]
[quo/text description]
[extra-action-view extra-action extra-text extra-action-selected?]
[rn/view {:style style/buttons-container}
[quo/button {:type :grey
:style {:flex 0.48} ;;WUT? 0.48 , whats that ?
:on-press #(rf/dispatch [:bottom-sheet/hide])}
(i18n/label :t/close)]
[quo/button {:type :danger
:style {:flex 0.48}
:on-press #(do
(when @extra-action-selected? (extra-action))
(on-press))}
button-text]]]))))

View File

@ -0,0 +1,10 @@
(ns status-im2.common.contact-list-item.style)
(def container
{:margin-top 8
:margin-horizontal 8
:padding-vertical 8
:padding-horizontal 12
:border-radius 12
:flex-direction :row
:align-items :center})

View File

@ -0,0 +1,49 @@
(ns status-im2.common.contact-list-item.view
(:require [utils.re-frame :as rf]
[react-native.core :as rn]
[quo2.core :as quo]
[quo2.foundations.colors :as colors]
[status-im2.common.contact-list-item.style :as style]
[utils.address :as utils.address]
[status-im2.common.home.actions.view :as actions]))
(defn open-chat [chat-id]
(rf/dispatch [:dismiss-keyboard])
(rf/dispatch [:chat.ui/navigate-to-chat-nav2 chat-id])
(rf/dispatch [:search/home-filter-changed nil])
(rf/dispatch [:accept-all-activity-center-notifications-from-chat chat-id]))
(defn contact-item [item]
(let [{:keys [public-key ens-verified added? images]} item
display-name (first (rf/sub [:contacts/contact-two-names-by-identity public-key]))
photo-path (when (seq images) (rf/sub [:chats/photo-path public-key]))
current-pk (rf/sub [:multiaccount/public-key])]
[rn/touchable-opacity (merge {:style style/container
:on-press #(open-chat public-key)
:on-long-press #(when-not (= current-pk public-key)
(rf/dispatch [:bottom-sheet/show-sheet
{:content (fn [] [actions/actions item])}]))})
[quo/user-avatar {:full-name display-name
:profile-picture photo-path
:status-indicator? true
:online? true
:size :small
:ring? false}]
[rn/view {:style {:margin-left 8}}
[rn/view {:style {:flex-direction :row}}
[quo/text {:weight :semi-bold} display-name]
(if ens-verified
[rn/view {:style {:margin-left 5 :margin-top 4}}
[quo/icon :i/verified {:no-color true :size 12 :color (colors/theme-colors colors/success-50 colors/success-60)}]]
(when added?
[rn/view {:style {:margin-left 5 :margin-top 4}}
[quo/icon :i/contact {:no-color true :size 12 :color (colors/theme-colors colors/primary-50 colors/primary-60)}]]))]
[quo/text {:style {:color (colors/theme-colors colors/neutral-50 colors/neutral-40)}}
(utils.address/get-shortened-address public-key)]]
(when-not (= current-pk public-key)
[rn/touchable-opacity {:style {:position :absolute
:right 20}
:active-opacity 1
:on-press #(rf/dispatch [:bottom-sheet/show-sheet
{:content (fn [] [actions/actions item])}])}
[quo/icon :i/options {:size 20 :color (colors/theme-colors colors/neutral-50 colors/neutral-40)}]])]))

View File

@ -1,12 +1,13 @@
(ns status-im.ui2.screens.chat.actions
(:require
[status-im.chat.models :as chat.models]
[status-im.chat.models.pin-message :as models.pin-message]
[status-im.i18n.i18n :as i18n]
(ns status-im2.common.home.actions.view
(:require [i18n.i18n :as i18n]
[utils.re-frame :as rf]
[status-im.ui2.screens.common.core :as common]
[quo2.components.drawers.action-drawers :as drawer]
[status-im2.common.confirmation-drawer.view :as confirmation-drawer]
;;TODO move to status-im2
[status-im.constants :as constants]
[quo2.components.drawers.action-drawers :as drawer]))
[status-im.chat.models :as chat.models]
[status-im.chat.models.pin-message :as models.pin-message]))
(defn- entry [{:keys [icon label on-press danger? sub-label chevron? add-divider?]}]
{:pre [(keyword? icon)
@ -45,7 +46,8 @@
(defn clear-history-action [{:keys [chat-id] :as item}]
(hide-sheet-and-dispatch [:bottom-sheet/show-sheet
{:content (fn []
(common/alert {:title (i18n/label :t/clear-history?)
(confirmation-drawer/confirmation-drawer
{:title (i18n/label :t/clear-history?)
:description (i18n/label :t/clear-history-confirmation-content)
:context item
:button-text (i18n/label :t/clear-history)
@ -54,7 +56,8 @@
(defn delete-chat-action [{:keys [chat-id] :as item}]
(hide-sheet-and-dispatch [:bottom-sheet/show-sheet
{:content (fn []
(common/alert {:title (i18n/label :t/delete-chat?)
(confirmation-drawer/confirmation-drawer
{:title (i18n/label :t/delete-chat?)
:description (i18n/label :t/delete-chat-confirmation)
:context item
:button-text (i18n/label :t/delete-chat)
@ -63,7 +66,8 @@
(defn leave-group-action [{:keys [chat-id] :as item}]
(hide-sheet-and-dispatch [:bottom-sheet/show-sheet
{:content (fn []
(common/alert {:title (i18n/label :t/leave-group?)
(confirmation-drawer/confirmation-drawer
{:title (i18n/label :t/leave-group?)
:description (i18n/label :t/leave-chat-confirmation)
:context item
:button-text (i18n/label :t/leave-group)
@ -71,7 +75,8 @@
(defn block-user-action [{:keys [public-key] :as item}]
(hide-sheet-and-dispatch [:bottom-sheet/show-sheet
{:content (fn [] (common/alert {:title (i18n/label :t/block-user?)
{:content (fn [] (confirmation-drawer/confirmation-drawer
{:title (i18n/label :t/block-user?)
:description (i18n/label :t/block-contact-details)
:context item
:button-text (i18n/label :t/block-user)
@ -330,4 +335,3 @@
constants/private-group-chat-type
[private-group-chat-actions item inside-chat?]
[contact-actions item]))

View File

@ -1,4 +1,4 @@
(ns status-im2.contexts.communities.home.style)
(ns status-im2.common.home.style)
(def title-column
{:flex-direction :row

View File

@ -0,0 +1,110 @@
(ns status-im2.common.home.view
(:require [react-native.core :as rn]
[quo2.core :as quo]
[quo2.foundations.colors :as colors]
[status-im2.common.plus-button.view :as components.plus-button]
[status-im2.common.home.style :as style]
[react-native.hole-view :as hole-view]
[status-im2.setup.config :as config]
[utils.re-frame :as rf]))
(defn navigate-to-activity-center []
(rf/dispatch [:mark-all-activity-center-notifications-as-read])
(if config/new-activity-center-enabled?
(rf/dispatch [:activity-center/open])
(rf/dispatch [:navigate-to :notifications-center])))
(defn title-column [{:keys [label handler accessibility-label]}]
[rn/view style/title-column
[rn/view {:flex 1}
[quo/text style/title-column-text
label]]
[components.plus-button/plus-button
{:on-press handler
:accessibility-label accessibility-label}]])
(defn- get-button-common-props [type]
(let [default? (= type :default)
dark? (colors/dark?)]
{:icon true
:size 32
:style {:margin-left 12}
:type (if default?
(if dark? :grey :dark-grey)
type)
:override-background-color (when (and dark? default?)
colors/neutral-90)}))
(defn- base-button [icon on-press accessibility-label button-common-props]
[quo/button (merge
{:on-press on-press
:accessibility-label accessibility-label}
button-common-props)
icon])
(defn top-nav
"[top-nav opts]
opts
{:type :default/:blurred/:shell
:style override-style
:avatar user-avatar}
"
[{:keys [type open-profile style avatar hide-search]}]
(let [button-common-props (get-button-common-props type)
notif-count (rf/sub [:activity.center/notifications-count])
new-notifications? (pos? notif-count)
notification-indicator :unread-dot
counter-label "0"]
[rn/view {:style (merge
{:height 56}
style)}
;; Left Section
[rn/touchable-without-feedback {:on-press open-profile}
[rn/view {:style {:position :absolute
:left 20
:top 12}}
[quo/user-avatar
(merge
{:ring? true
:status-indicator? true
:size :small}
avatar)]]]
;; Right Section
[rn/view {:style {:position :absolute
:right 20
:top 12
:flex-direction :row}}
(when-not hide-search
[base-button :i/search #() :open-search-button button-common-props])
[base-button :i/scan #() :open-scanner-button button-common-props]
[base-button :i/qr-code #() :show-qr-button button-common-props]
[rn/view ;; Keep view instead of "[:<>" to make sure relative
;; position is calculated from this view instead of its parent
[hole-view/hole-view {:key new-notifications? ;; Key is required to force removal of holes
:holes (cond
(not new-notifications?) ;; No new notifications, remove holes
[]
(= notification-indicator :unread-dot)
[{:x 37 :y -3 :width 10 :height 10 :borderRadius 5}]
:else
[{:x 33 :y -7 :width 18 :height 18 :borderRadius 7}])}
[base-button :i/activity-center navigate-to-activity-center
:open-activity-center-button button-common-props]]
(when new-notifications?
(if (= notification-indicator :counter)
[quo/counter {:outline false
:override-text-color colors/white
:override-bg-color colors/primary-50
:style {:position :absolute
:left 34
:top -6}}
counter-label]
[rn/view {:style {:width 8
:height 8
:border-radius 4
:top -2
:left 38
:position :absolute
:background-color colors/primary-50}}]))]]]))

View File

@ -0,0 +1,25 @@
(ns status-im2.contexts.chat.home.chat-list-item.style
(:require [quo2.foundations.colors :as colors]))
(def container
{:margin-top 8
:margin-horizontal 8
:padding-vertical 8
:padding-horizontal 12
:border-radius 12
:flex-direction :row
:align-items :center})
(defn count-container []
{:width 8
:height 8
:border-radius 4
:position :absolute
:right 26
:top 16
:background-color (colors/theme-colors colors/neutral-40 colors/neutral-60)})
(defn timestamp []
{:color (colors/theme-colors colors/neutral-50 colors/neutral-40)
:margin-top 3
:margin-left 8})

View File

@ -0,0 +1,124 @@
(ns status-im2.contexts.chat.home.chat-list-item.view
(:require [clojure.string :as string]
[utils.re-frame :as rf]
[react-native.core :as rn]
[quo2.foundations.colors :as colors]
[quo2.core :as quo]
[status-im2.contexts.chat.home.chat-list-item.style :as style]
[status-im2.common.home.actions.view :as actions]
;;TODO move to status-im2
[status-im.utils.datetime :as time]))
(def max-subheader-length 50)
(defn open-chat [chat-id]
(fn []
(rf/dispatch [:dismiss-keyboard])
(rf/dispatch [:chat.ui/navigate-to-chat-nav2 chat-id])
(rf/dispatch [:search/home-filter-changed nil])
(rf/dispatch [:accept-all-activity-center-notifications-from-chat chat-id])))
(defn truncate-literal [literal]
(when literal
(let [size (min max-subheader-length (.-length literal))]
{:components (.substring literal 0 size)
:length size})))
(defn add-parsed-to-subheader [acc {:keys [type destination literal children]}]
(let [result (case type
"paragraph"
(reduce
(fn [{:keys [_ length] :as acc-paragraph} parsed-child]
(if (>= length max-subheader-length)
(reduced acc-paragraph)
(add-parsed-to-subheader acc-paragraph parsed-child)))
{:components [quo/text]
:length 0}
children)
"mention"
{:components [quo/text (rf/sub [:contacts/contact-name-by-identity literal])]
:length 4} ;; we can't predict name length so take the smallest possible
"status-tag"
(truncate-literal (str "#" literal))
"link"
(truncate-literal destination)
(truncate-literal literal))]
{:components (conj (:components acc) (:components result))
:length (+ (:length acc) (:length result))}))
(defn render-subheader
"Render the preview of a last message to a maximum of max-subheader-length characters"
[parsed-text]
(let [result
(reduce
(fn [{:keys [_ length] :as acc-text} new-text-chunk]
(if (>= length max-subheader-length)
(reduced acc-text)
(add-parsed-to-subheader acc-text new-text-chunk)))
{:components [quo/text {:size :paragraph-2
:style {:color (colors/theme-colors colors/neutral-50 colors/neutral-40)
:width "90%"}
:number-of-lines 1
:ellipsize-mode :tail
:accessibility-label :chat-message-text}]
:length 0}
parsed-text)]
(:components result)))
(defn verified-or-contact-icon [{:keys [ens-verified added?]}]
(if ens-verified
[rn/view {:style {:margin-left 5 :margin-top 4}}
[quo/icon :i/verified {:no-color true
:size 12
:color (colors/theme-colors colors/success-50 colors/success-60)}]]
(when added?
[rn/view {:style {:margin-left 5 :margin-top 4}}
[quo/icon :i/contact {:no-color true
:size 12
:color (colors/theme-colors colors/primary-50 colors/primary-60)}]])))
(defn name-view [display-name contact timestamp]
[rn/view {:style {:flex-direction :row}}
[quo/text {:weight :semi-bold
:accessibility-label :chat-name-text}
display-name]
[verified-or-contact-icon contact]
[quo/text {:size :label
:style (style/timestamp)}
(time/to-short-str timestamp)]])
(defn avatar-view [group-chat color display-name photo-path]
(if group-chat
[quo/group-avatar {:color color
:size :medium}]
[quo/user-avatar {:full-name display-name
:profile-picture photo-path
:size :small}]))
(defn chat-list-item [item]
(let [{:keys [chat-id color group-chat last-message timestamp name unviewed-mentions-count
unviewed-messages-count]} item
display-name (if group-chat name (first (rf/sub [:contacts/contact-two-names-by-identity chat-id])))
contact (when-not group-chat (rf/sub [:contacts/contact-by-address chat-id]))
photo-path (when-not (empty? (:images contact)) (rf/sub [:chats/photo-path chat-id]))]
[rn/touchable-opacity (merge {:style style/container
:on-press (open-chat chat-id)
:on-long-press #(rf/dispatch [:bottom-sheet/show-sheet
{:content (fn [] [actions/actions item false])}])})
[avatar-view group-chat color display-name photo-path]
[rn/view {:style {:margin-left 8}}
[name-view display-name contact timestamp]
(if (string/blank? (get-in last-message [:content :parsed-text]))
[quo/text {:size :paragraph-2
:style {:color (colors/theme-colors colors/neutral-50 colors/neutral-40)}}
(get-in last-message [:content :text])]
[render-subheader (get-in last-message [:content :parsed-text])])]
(if (> unviewed-mentions-count 0)
[quo/info-count unviewed-mentions-count {:top 16}]
(when (> unviewed-messages-count 0)
[rn/view {:style (style/count-container)}]))]))

View File

@ -0,0 +1,27 @@
(ns status-im2.contexts.chat.home.contact-request.style
(:require [quo2.foundations.colors :as colors]))
(def contact-requests
{:flex-direction :row
:margin 8
:padding-horizontal 12
:padding-vertical 8
:align-items :center})
(defn contact-requests-icon []
{:justify-content :center
:align-items :center
:width 32
:height 32
:border-radius 16
:border-width 1
:border-color (colors/theme-colors colors/neutral-20 colors/neutral-80)})
(defn contact-requests-sheet []
{:background-color (colors/theme-colors colors/neutral-10 colors/neutral-80)
:width 32
:height 32
:border-radius 10
:justify-content :center
:align-items :center
:margin-bottom 24})

View File

@ -0,0 +1,70 @@
(ns status-im2.contexts.chat.home.contact-request.view
(:require [react-native.core :as rn]
[quo2.core :as quo]
[i18n.i18n :as i18n]
[utils.re-frame :as rf]
[clojure.string :as string]
[quo2.foundations.colors :as colors]
[status-im2.contexts.chat.home.contact-request.style :as style]
[reagent.core :as reagent]
;; TODO move to status-im2
[status-im.ui2.screens.chat.components.received-cr-item :as received-cr-item]))
(defn contact-requests-sheet [received-requests]
(let [selected-requests-tab (reagent/atom :received)]
(fn []
(let [sent-requests []]
[rn/view {:style {:margin-left 20}}
[rn/touchable-opacity
{:on-press #(rf/dispatch [:bottom-sheet/hide])
:style (style/contact-requests-sheet)}
[quo/icon :i/close]]
[rn/text {:size :heading-1 :weight :semi-bold}
(i18n/label :t/pending-requests)]
[quo/tabs
{:style {:margin-top 12 :margin-bottom 20}
:size 32
:on-change #(reset! selected-requests-tab %)
:default-active :received
:data [{:id :received
:label (i18n/label :t/received)}
{:id :sent
:label (i18n/label :t/sent)}]}]
[rn/flat-list
{:key-fn :chat-id
:data (if (= @selected-requests-tab :received) received-requests sent-requests)
:render-fn received-cr-item/received-cr-item}]]))))
(defn get-display-name [{:keys [chat-id message]}]
(let [name (first (rf/sub [:contacts/contact-two-names-by-identity chat-id]))
no-ens-name (string/blank? (get-in message [:content :ens-name]))]
(if no-ens-name
(first (string/split name " "))
name)))
(defn requests-summary [requests]
(case (count requests)
1
(get-display-name (first requests))
2
(str (get-display-name (first requests)) " " (i18n/label :t/and) " " (get-display-name (second requests)))
(str (get-display-name (first requests)) ", " (get-display-name (second requests)) " "
(i18n/label :t/and) " " (- (count requests) 2) " " (i18n/label :t/more))))
(defn contact-requests [requests]
[rn/touchable-opacity
{:active-opacity 1
:on-press #(do
(rf/dispatch [:bottom-sheet/show-sheet
{:content (fn [] [contact-requests-sheet requests])}])
(rf/dispatch [:mark-all-activity-center-notifications-as-read]))
:style style/contact-requests}
[rn/view {:style (style/contact-requests-icon)}
[quo/icon :i/pending-user {:color (colors/theme-colors colors/neutral-50 colors/neutral-40)}]]
[rn/view {:style {:margin-left 8}}
[rn/text {:weight :semi-bold} (i18n/label :t/pending-requests)]
[rn/text {:size :paragraph-2
:style {:color (colors/theme-colors colors/neutral-50 colors/neutral-40)}}
(requests-summary requests)]]
[quo/info-count (count requests)]])

View File

@ -0,0 +1,94 @@
(ns status-im2.contexts.chat.home.view
(:require [re-frame.core :as re-frame]
[reagent.core :as reagent]
[i18n.i18n :as i18n]
[react-native.core :as rn]
[quo2.core :as quo]
[utils.re-frame :as rf]
[status-im2.common.home.view :as common.home]
[status-im2.contexts.chat.home.contact-request.view :as contact-request]
[status-im2.contexts.chat.home.chat-list-item.view :as chat-list-item]
[status-im2.common.contact-list-item.view :as contact-list-item]))
(defn get-item-layout [_ index]
#js {:length 64 :offset (* 64 index) :index index})
(defn filter-items-by-tab [tab items]
(if (= tab :groups)
(filter :group-chat items)
(filter :chat-id items)))
(defn welcome-blank-chats []
[rn/view {:style {:flex 1 :align-items :center :justify-content :center}}
[quo/icon :i/placeholder]
[quo/text {:weight :semi-bold} (i18n/label :t/no-messages)]
[quo/text (i18n/label :t/blank-messages-text)]])
(defn chats [selected-tab]
(let [{:keys [items search-filter]} (rf/sub [:home-items])
items (filter-items-by-tab selected-tab items)]
(if (and (empty? items)
(empty? search-filter))
[welcome-blank-chats]
[rn/flat-list
{:key-fn #(or (:chat-id %) (:public-key %) (:id %))
:get-item-layout get-item-layout
:on-end-reached #(re-frame/dispatch [:chat.ui/show-more-chats])
:keyboard-should-persist-taps :always
:data items
:render-fn chat-list-item/chat-list-item}])))
(defn welcome-blank-contacts []
[rn/view {:style {:flex 1 :align-items :center :justify-content :center}}
[quo/icon :i/placeholder]
[quo/text {:weight :semi-bold} (i18n/label :t/no-contacts)]
[quo/text (i18n/label :t/blank-contacts-text)]])
(defn contacts-section-header [{:keys [title]}]
[quo/divider-label {:label title}])
(defn contacts [contact-requests]
(let [items (rf/sub [:contacts/active-sections])]
(if (empty? items)
[welcome-blank-contacts]
[:<>
(when (pos? (count contact-requests))
[contact-request/contact-requests contact-requests])
[rn/section-list
{:key-fn :title
:sticky-section-headers-enabled false
:sections items
:render-section-header-fn contacts-section-header
:render-fn contact-list-item/contact-item}]])))
(defn tabs []
(let [selected-tab (reagent/atom :recent)]
(fn []
(let [contact-requests (rf/sub [:activity.center/notifications-contact-requests])]
[:<>
[quo/discover-card {:title (i18n/label :t/invite-friends-to-status)
:description (i18n/label :t/share-invite-link)}]
[quo/tabs {:style {:margin-left 20
:margin-bottom 20
:margin-top 24}
:size 32
:on-change #(reset! selected-tab %)
:default-active @selected-tab
:data [{:id :recent
:label (i18n/label :t/recent)}
{:id :groups
:label (i18n/label :t/groups)}
{:id :contacts
:label (i18n/label :t/contacts)
:notification-dot? (pos? (count contact-requests))}]}]
(if (= @selected-tab :contacts)
[contacts contact-requests]
[chats @selected-tab])]))))
(defn home []
[:<>
[common.home/top-nav {:type :default}]
[common.home/title-column {:label (i18n/label :t/messages)
:handler #(rf/dispatch [:bottom-sheet/show-sheet :add-new {}])
:accessibility-label :new-chat-button}]
[tabs]])

View File

@ -1,29 +1,12 @@
(ns status-im2.contexts.communities.home.view
(:require [re-frame.core :as re-frame]
[reagent.core :as reagent]
(:require [reagent.core :as reagent]
[react-native.core :as rn]
[react-native.safe-area :as safe-area]
[utils.re-frame :as rf]
[i18n.i18n :as i18n]
[quo2.core :as quo]
[quo2.foundations.colors :as colors]
[quo2.components.community.discover-card :as discover-card]
[quo2.components.navigation.top-nav :as topnav]
[status-im2.setup.config :as config]
[status-im2.common.plus-button.view :as components.plus-button]
[status-im2.contexts.communities.home.actions.view :as home.actions]
[status-im2.contexts.communities.home.style :as style]))
(defn navigate-to-activity-center []
(rf/dispatch [:mark-all-activity-center-notifications-as-read])
(if config/new-activity-center-enabled?
(rf/dispatch [:activity-center/open])
(rf/dispatch [:navigate-to :notifications-center])))
(defn plus-button []
[components.plus-button/plus-button
{:on-press #(re-frame/dispatch [:bottom-sheet/show-sheet :add-new {}])
:accessibility-label :new-chat-button}])
[status-im2.common.home.view :as common.home]))
(defn render-fn [id]
(let [community-item (rf/sub [:communities/home-item id])]
@ -78,29 +61,17 @@
:opened
[communities-list communities])]))
(defn title-column []
[rn/view style/title-column
[rn/view {:flex 1}
[quo/text style/title-column-text
(i18n/label :t/communities)]]
[plus-button]])
(defn home []
[safe-area/consumer
(fn [insets]
(let [selected-tab (reagent/atom :joined)]
(fn []
[rn/view {:style {:flex 1
:padding-top (:top insets)
:background-color (colors/theme-colors
colors/neutral-5
colors/neutral-95)}}
[topnav/top-nav {:type :default
:open-activity-center navigate-to-activity-center}]
[title-column]
[:<>
[common.home/top-nav {:type :default :hide-search true}]
[common.home/title-column {:label (i18n/label :t/communities)
:handler #(rf/dispatch [:bottom-sheet/show-sheet :add-new {}])
:accessibility-label :new-chat-button}]
[discover-card/discover-card {:on-press #(rf/dispatch [:navigate-to :discover-communities])
:title (i18n/label :t/discover)
:description (i18n/label :t/whats-trending)
:accessibility-label :communities-home-discover-card}]
[home-community-segments selected-tab]
[segments-community-lists selected-tab]])))])
[segments-community-lists selected-tab]])))

View File

@ -1,13 +1,13 @@
(ns status-im2.contexts.communities.overview.style
(:require [quo2.foundations.colors :as colors]))
(def container1
(defn container1 []
{:flex 1
:height 20
:border-radius 16
:background-color (colors/theme-colors colors/white colors/neutral-90)})
(def container2
(defn container2 []
{:border-radius 40
:border-width 1
:border-color colors/white

View File

@ -94,9 +94,9 @@
:left-section {:icon :i/close
:icon-background-color icon-color
:on-press #(rf/dispatch [:navigate-back])}}]]]
[rn/view style/container1
[rn/view (style/container1)
[rn/view {:padding-horizontal 20}
[rn/view style/container2
[rn/view (style/container2)
[communities.icon/community-icon-redesign community 80]]
(when (and (not joined)
(= status :gated))

View File

@ -2,7 +2,7 @@
(:require [react-native.core :as rn]
[status-im2.contexts.quo-preview.preview :as preview]
[reagent.core :as reagent]
[quo2.components.navigation.top-nav :as quo2]
[status-im2.common.home.view :as home.view]
[quo2.foundations.colors :as colors]))
(def descriptor [{:label "Type"
@ -40,7 +40,7 @@
[rn/view {:padding-vertical 60
:flex-direction :row
:align-items :center}
[quo2/top-nav @state (:value @state)]]]])))
[home.view/top-nav @state (:value @state)]]]])))
(defn preview-top-nav []
[rn/view {:background-color (colors/theme-colors colors/white colors/neutral-95)

View File

@ -1,14 +1,16 @@
(ns status-im2.contexts.shell.home-stack
(:require [react-native.reanimated :as reanimated]
[react-native.core :as rn]
[status-im2.contexts.shell.style :as styles]
[status-im2.contexts.shell.constants :as constants]
[status-im2.contexts.shell.bottom-tabs :as bottom-tabs]
[status-im2.contexts.communities.home.view :as communities]
[status-im2.contexts.chat.home.view :as chat]
;; TODO move to status-im2
[status-im.ui.screens.profile.user.views :as profile.user]
[status-im.ui.screens.wallet.accounts.views :as wallet.accounts]
[status-im.ui2.screens.chat.home :as chat.home]))
[react-native.safe-area :as safe-area]))
(defn load-stack? [stack-id]
(case stack-id
@ -33,11 +35,13 @@
:accessibility-label stack-id})}
(case stack-id
:communities-stack [communities/home]
:chats-stack [chat.home/home]
:chats-stack [chat/home]
:wallet-stack [wallet.accounts/accounts-overview]
:browser-stack [profile.user/my-profile])])]))
(defn home-stack [shared-values]
[safe-area/consumer
(fn [insets]
[:f>
(fn []
(let [home-stack-original-style (styles/home-stack)
@ -49,7 +53,8 @@
:transform [{:scale (:home-stack-scale shared-values)}]}
home-stack-original-style)]
[reanimated/view {:style home-stack-animated-style}
[rn/view {:margin-top (:top insets) :flex 1}
[stack-view :communities-stack shared-values]
[stack-view :chats-stack shared-values]
[stack-view :browser-stack shared-values]
[stack-view :wallet-stack shared-values]]))])
[stack-view :wallet-stack shared-values]]]))])])

View File

@ -7,7 +7,8 @@
[status-im2.contexts.shell.constants :as constants]
[status-im2.contexts.shell.animation :as animation]
[status-im2.contexts.shell.home-stack :as home-stack]
[status-im2.contexts.shell.bottom-tabs :as bottom-tabs]))
[status-im2.contexts.shell.bottom-tabs :as bottom-tabs]
[status-im2.common.home.view :as common.home]))
(defn placeholder []
[rn/view {:style {:position :absolute
@ -44,7 +45,7 @@
:bottom -1
:position :absolute
:background-color colors/neutral-100}}
[quo/top-nav {:type :shell
[common.home/top-nav {:type :shell
:style {:margin-top (:top insets)}}]
[placeholder]
[rn/scroll-view {:style {:padding-horizontal 20

View File

@ -5,8 +5,7 @@
[status-im2.contexts.shell.view :as shell]
[status-im2.contexts.quo-preview.main :as quo.preview]
;; TODO remove when not used anymore
[status-im.ui2.screens.chat.home :as chat.home]
;; TODO remove when not used anymore\
[status-im.ui2.screens.chat.view :as chat]
[status-im.ui.screens.screens :as old-screens]))
@ -20,9 +19,6 @@
:insets {:top false}
:component shell/shell-stack}
{:name :home
:component chat.home/home}
{:name :chat
:options {:topBar {:visible false}}
:component chat/chat}

View File

@ -73,3 +73,13 @@
:timestamp (or (:timestamp %) (:timestamp (or (:message %) (:last-message %))))
:contact (multiaccounts/contact-by-identity contacts (get-in % [:message :from])))
supported-notifications)))))
(re-frame/reg-sub
:activity.center/notifications-contact-requests
:<- [:activity.center/notifications-grouped-by-date]
(fn [notifications]
(reduce
(fn [acc {:keys [data]}]
(concat acc (filter #(= 1 (get-in % [:message :contact-request-state])) data)))
[]
notifications)))

View File

@ -68,6 +68,21 @@
(fn [contacts]
(contact.db/get-active-contacts contacts)))
(re-frame/reg-sub
:contacts/active-sections
:<- [:contacts/active]
(fn [contacts]
(-> (reduce
(fn [acc contact]
(let [first-char (first (:alias contact))]
(if (get acc first-char)
(update-in acc [first-char :data] #(conj % contact))
(assoc acc first-char {:title first-char :data [contact]}))))
{}
contacts)
sort
vals)))
(re-frame/reg-sub
:contacts/sorted-contacts
:<- [:contacts/active]

15
src/utils/address.cljs Normal file
View File

@ -0,0 +1,15 @@
(ns utils.address
;; TODO move to status-im2
(:require [status-im.ethereum.eip55 :as eip55]
[status-im.ethereum.core :as ethereum]))
(defn get-shortened-address
"Takes first and last 4 digits from address including leading 0x
and adds unicode ellipsis in between"
[address]
(when address
(str (subs address 0 6) "\u2026" (subs address (- (count address) 3) (count address)))))
(defn get-shortened-checksum-address [address]
(when address
(get-shortened-address (eip55/address->checksum (ethereum/normalized-hex address)))))

View File

@ -1839,6 +1839,7 @@
"jump-to": "Jump to",
"untrustworthy": "Untrustworthy",
"blank-messages-text": "Your messages will be here",
"blank-contacts-text": "Your contacts will be here",
"groups": "Groups",
"shell-placeholder-title": "Your apps will run here",
"no-pinned-messages-desc": "This chat doesn't have any pinned messages.",