Compare commits

..
Author SHA1 Message Date
alwx 8ca57cad97 Update 2023-11-21 16:35:56 +01:00
alwx e83e225224 Update 2023-11-20 09:51:47 +01:00
alwx 2535b88c87 Abstracting top bar header 2023-11-20 09:51:47 +01:00
alwx 811deb35ab New community header 2023-11-20 09:51:47 +01:00
11 changed files with 365 additions and 260 deletions
+2 -5
View File
@@ -78,16 +78,13 @@
(fn [can-save?]
(if can-save?
(keychain/get-credentials
(str key-uid "-hashed" "-auth")
(str key-uid "-auth")
#(callback (if % (oops/oget % "password") auth-method-none)))
(callback nil))))))
(defn save-user-password!
[key-uid password]
(keychain/save-credentials key-uid
key-uid
(security/safe-unmask-data password)
#()))
(keychain/save-credentials key-uid key-uid (security/safe-unmask-data password) #()))
(re-frame/reg-fx
:keychain/get-user-password
@@ -112,6 +112,7 @@
:collapsed? collapsed?
:page-nav-props page-nav-props
:overlay-shown? overlay-shown?}]
[rn/scroll-view
{:content-container-style {:flex-grow 1}
:content-inset-adjustment-behavior :never
+41
View File
@@ -0,0 +1,41 @@
(ns status-im2.common.top-bar.style
(:require [quo.foundations.colors :as colors]
[react-native.reanimated :as reanimated]))
(defonce ^:const cover-height 168)
(defonce ^:const overscroll-cover-height 2000)
(defonce ^:const header-avatar-top-offset -36)
(defn header-container
[show? theme]
{:display (if show? :flex :none)
:background-color (colors/theme-colors colors/white colors/neutral-100 theme)
:top (- overscroll-cover-height)
:margin-bottom (- overscroll-cover-height)})
(defn header-cover
[cover-bg-color theme]
{:flex 1
:height (+ overscroll-cover-height cover-height)
:background-color (colors/theme-colors
(colors/custom-color cover-bg-color 50 20)
(colors/custom-color cover-bg-color 50 40)
theme)})
(defn header-bottom-part
[animation theme]
(reanimated/apply-animations-to-style
{:border-top-right-radius animation
:border-top-left-radius animation}
{:top -16
:margin-bottom -16
:padding-bottom 24
:background-color (colors/theme-colors colors/white colors/neutral-95 theme)}))
(def header-avatar
{:top header-avatar-top-offset
:margin-horizontal 20
:margin-bottom header-avatar-top-offset})
(def header-description-text
{:margin-top 8})
+57
View File
@@ -0,0 +1,57 @@
(ns status-im2.common.top-bar.view
(:require
[quo.core :as quo]
[react-native.core :as rn]
[react-native.platform :as platform]
[react-native.reanimated :as reanimated]
[status-im2.common.top-bar.style :as style]))
(def header-extrapolation-option
{:extrapolateLeft "clamp"
:extrapolateRight "clamp"})
(defn- add-inverted-y-android
[style]
(cond-> style
platform/android?
(assoc :scale-y -1)))
;; TODO(alwx): status-im2.contexts.chat.messages.list.view/list-footer should be abstracted
(defn f-abstract-list-header
[{:keys [inverted-y-on-android?
show?
theme
cover-bg-color
scroll-y
on-layout
avatar-component
title-component
description
actions-component
loading-component]}]
(let [border-animation (reanimated/interpolate scroll-y
[30 125]
[14 0]
header-extrapolation-option)]
[rn/view (cond-> {:flex 1}
inverted-y-on-android?
(add-inverted-y-android))
[rn/view {:style (style/header-container show? theme)
:on-layout on-layout}
[rn/view {:style (style/header-cover cover-bg-color theme)}]
[reanimated/view {:style (style/header-bottom-part border-animation theme)}
[rn/view {:style style/header-avatar}
[rn/view {:style {:align-items :flex-start}}
[avatar-component]]
[title-component]
(when description
[quo/text {:style style/header-description-text}
description])
(when actions-component
[actions-component])]]]
(when loading-component
[loading-component])]))
(defn abstract-list-header
[props]
[:f> f-abstract-list-header props])
@@ -18,37 +18,6 @@
(def list-container
{:padding-vertical 16})
(defn header-container
[show? theme]
{:display (if show? :flex :none)
:background-color (colors/theme-colors colors/white colors/neutral-100 theme)
:top (- overscroll-cover-height)
:margin-bottom (- overscroll-cover-height)})
(defn header-cover
[cover-bg-color theme]
{:flex 1
:height (+ overscroll-cover-height cover-height)
:background-color (colors/theme-colors
(colors/custom-color cover-bg-color 50 20)
(colors/custom-color cover-bg-color 50 40)
theme)})
(defn header-bottom-part
[animation theme]
(reanimated/apply-animations-to-style
{:border-top-right-radius animation
:border-top-left-radius animation}
{:top -16
:margin-bottom -16
:padding-bottom 24
:background-color (colors/theme-colors colors/white colors/neutral-95 theme)}))
(def header-avatar
{:top header-avatar-top-offset
:margin-horizontal 20
:margin-bottom header-avatar-top-offset})
(defn header-image
[scale-animation top-margin-animation side-margin-animation]
(reanimated/apply-animations-to-style
@@ -57,6 +26,3 @@
:margin-left side-margin-animation
:margin-bottom side-margin-animation}
{:align-items :flex-start}))
(def bio
{:margin-top 8})
@@ -12,6 +12,7 @@
[react-native.reanimated :as reanimated]
[status-im.ui.screens.chat.group :as chat.group]
[status-im.ui.screens.chat.message.gap :as message.gap]
[status-im2.common.top-bar.view :as top-bar]
[status-im2.constants :as constants]
[status-im2.contexts.chat.composer.constants :as composer.constants]
[status-im2.contexts.chat.messages.content.view :as message]
@@ -40,14 +41,14 @@
[]
(some-> ^js @messages-list-ref
(.scrollToOffset #js
{:animated true})))
{:animated true})))
(defn on-scroll
[evt show-floating-scroll-down-button?]
(let [y (oops/oget evt "nativeEvent.contentOffset.y")
layout-height (oops/oget evt "nativeEvent.layoutMeasurement.height")
threshold-height (* (/ layout-height 100)
threshold-percentage-to-show-floating-scroll-down-button)
(let [y (oops/oget evt "nativeEvent.contentOffset.y")
layout-height (oops/oget evt "nativeEvent.layoutMeasurement.height")
threshold-height (* (/ layout-height 100)
threshold-percentage-to-show-floating-scroll-down-button)
reached-threshold? (> y threshold-height)]
(when (not= reached-threshold? @show-floating-scroll-down-button?)
(rn/configure-next (:ease-in-ease-out rn/layout-animation-presets))
@@ -57,16 +58,16 @@
[e]
(when @messages-list-ref
(reset! state/first-not-visible-item
(when-let [last-visible-element (aget (oops/oget e "viewableItems")
(dec (oops/oget e "viewableItems.length")))]
(let [index (oops/oget last-visible-element "index")
;; Get first not visible element, if it's a datemark/gap
;; we might add messages on receiving as they do not have
;; a clock value, but usually it will be a message
first-not-visible (aget (oops/oget @messages-list-ref "props.data") (inc index))]
(when (and first-not-visible
(= :message (:type first-not-visible)))
first-not-visible))))))
(when-let [last-visible-element (aget (oops/oget e "viewableItems")
(dec (oops/oget e "viewableItems.length")))]
(let [index (oops/oget last-visible-element "index")
;; Get first not visible element, if it's a datemark/gap
;; we might add messages on receiving as they do not have
;; a clock value, but usually it will be a message
first-not-visible (aget (oops/oget @messages-list-ref "props.data") (inc index))]
(when (and first-not-visible
(= :message (:type first-not-visible)))
first-not-visible))))))
(defn list-on-end-reached
[scroll-y on-end-reached?]
@@ -94,9 +95,9 @@
{:no-color true
:size 20
:color (colors/theme-colors
(colors/custom-color :success 50)
(colors/custom-color :success 60)
theme)}]
(colors/custom-color :success 50)
(colors/custom-color :success 60)
theme)}]
(when added?
[quo/icon :i/contact
{:no-color true
@@ -115,17 +116,17 @@
(defn loading-view
[chat-id messages-view-height messages-view-header-height]
(let [loading-messages? (rf/sub [:chats/loading-messages? chat-id])
all-loaded? (rf/sub [:chats/all-loaded? chat-id])
messages (rf/sub [:chats/raw-chat-messages-stream chat-id])
(let [loading-messages? (rf/sub [:chats/loading-messages? chat-id])
all-loaded? (rf/sub [:chats/all-loaded? chat-id])
messages (rf/sub [:chats/raw-chat-messages-stream chat-id])
loading-first-page? (= (count messages) 0)
top-spacing (if loading-first-page? 0 navigation.style/navigation-bar-height)
parent-height (if loading-first-page?
(- @messages-view-height
@messages-view-header-height
composer.constants/composer-default-height
loading-indicator-extra-spacing)
loading-indicator-page-loading-height)]
top-spacing (if loading-first-page? 0 navigation.style/navigation-bar-height)
parent-height (if loading-first-page?
(- @messages-view-height
@messages-view-header-height
composer.constants/composer-default-height
loading-indicator-extra-spacing)
loading-indicator-page-loading-height)]
(when (or loading-messages? (not all-loaded?))
[rn/view {:padding-top top-spacing}
;; Only use animated loading skeleton for ios
@@ -146,14 +147,14 @@
(defn f-list-footer-avatar
[{:keys [scroll-y display-name online? profile-picture]}]
(let [image-scale-animation (reanimated/interpolate scroll-y
scroll-animation-input-range
[1 0.5]
header-extrapolation-option)
image-top-margin-animation (reanimated/interpolate scroll-y
scroll-animation-input-range
[0 40]
header-extrapolation-option)
(let [image-scale-animation (reanimated/interpolate scroll-y
scroll-animation-input-range
[1 0.5]
header-extrapolation-option)
image-top-margin-animation (reanimated/interpolate scroll-y
scroll-animation-input-range
[0 40]
header-extrapolation-option)
image-side-margin-animation (reanimated/interpolate scroll-y
scroll-animation-input-range
[0 -20]
@@ -176,13 +177,13 @@
(defn- add-inverted-y-android
[style]
(cond-> style
platform/android?
(assoc :scale-y -1)))
platform/android?
(assoc :scale-y -1)))
(defn actions
[chat-id cover-bg-color]
(let [latest-pin-text (rf/sub [:chats/last-pinned-message-text chat-id])
pins-count (rf/sub [:chats/pin-messages-count chat-id])]
pins-count (rf/sub [:chats/pin-messages-count chat-id])]
[quo/channel-actions
{:style {:margin-top 16}
:actions [{:accessibility-label :action-button-pinned
@@ -201,53 +202,46 @@
messages-view-header-height big-name-visible?]}]
(let [{:keys [chat-id chat-name emoji chat-type
group-chat]} chat
all-loaded? (rf/sub [:chats/all-loaded? chat-id])
display-name (cond
(= chat-type constants/one-to-one-chat-type)
(first (rf/sub [:contacts/contact-two-names-by-identity chat-id]))
(= chat-type constants/community-chat-type)
(str (when emoji (str emoji " ")) "# " chat-name)
:else (str emoji chat-name))
{:keys [bio]} (rf/sub [:contacts/contact-by-identity chat-id])
online? (rf/sub [:visibility-status-updates/online? chat-id])
contact (when-not group-chat
(rf/sub [:contacts/contact-by-address chat-id]))
photo-path (rf/sub [:chats/photo-path chat-id])
border-animation (reanimated/interpolate scroll-y
[30 125]
[14 0]
header-extrapolation-option)]
[rn/view (add-inverted-y-android {:flex 1})
[rn/view
{:style (style/header-container all-loaded? theme)
:on-layout on-layout}
[rn/view {:style (style/header-cover cover-bg-color theme)}]
[reanimated/view {:style (style/header-bottom-part border-animation theme)}
[rn/view {:style style/header-avatar}
[rn/view {:style {:align-items :flex-start}}
(when-not group-chat
[list-footer-avatar
{:scroll-y scroll-y
:display-name display-name
:online? online?
:profile-picture photo-path}])]
[rnio/view
{:on-change (fn [view-visible?]
(reset! big-name-visible? view-visible?))
:style {:flex-direction :row
:margin-top (if group-chat 54 12)}}
[quo/text
{:weight :semi-bold
:size :heading-1
:style {:flex-shrink 1}
:number-of-lines 1}
display-name]
[contact-icon contact theme]]
(when bio
[quo/text {:style style/bio}
bio])
[actions chat-id cover-bg-color]]]]
[loading-view chat-id messages-view-height messages-view-header-height]]))
all-loaded? (rf/sub [:chats/all-loaded? chat-id])
display-name (cond
(= chat-type constants/one-to-one-chat-type)
(first (rf/sub [:contacts/contact-two-names-by-identity chat-id]))
(= chat-type constants/community-chat-type)
(str (when emoji (str emoji " ")) "# " chat-name)
:else (str emoji chat-name))
{:keys [bio]} (rf/sub [:contacts/contact-by-identity chat-id])
online? (rf/sub [:visibility-status-updates/online? chat-id])
contact (when-not group-chat
(rf/sub [:contacts/contact-by-address chat-id]))
photo-path (rf/sub [:chats/photo-path chat-id])]
[top-bar/abstract-list-header
{:inverted-y-on-android? true
:show? all-loaded?
:theme theme
:cover-bg-color cover-bg-color
:scroll-y scroll-y
:on-layout on-layout
:avatar-component (fn [] (when-not group-chat
[list-footer-avatar
{:scroll-y scroll-y
:display-name display-name
:online? online?
:profile-picture photo-path}]))
:title-component (fn [] [rnio/view
{:on-change (fn [view-visible?]
(reset! big-name-visible? view-visible?))
:style {:flex-direction :row
:margin-top (if group-chat 54 12)}}
[quo/text
{:weight :semi-bold
:size :heading-1
:style {:flex-shrink 1}
:number-of-lines 1}
display-name]
[contact-icon contact theme]])
:description bio
:actions-component (fn [] [actions chat-id cover-bg-color])
:loading-component (fn [] [loading-view chat-id messages-view-height messages-view-header-height])}]))
(defn list-footer
[props]
@@ -257,10 +251,11 @@
[{:keys [chat-id invitation-admin]}]
[rn/view
[chat.group/group-chat-footer chat-id invitation-admin]])
(defn footer-on-layout
[e messages-view-header-height]
(let [height (oops/oget e "nativeEvent.layout.height")
y (oops/oget e "nativeEvent.layout.y")]
y (oops/oget e "nativeEvent.layout.y")]
(reset! messages-view-header-height (+ height y))))
(defn render-fn
@@ -269,7 +264,7 @@
(when (not= content-type constants/content-type-contact-request)
[rn/view
(add-inverted-y-android
{:background-color (colors/theme-colors colors/white colors/neutral-95 theme)})
{:background-color (colors/theme-colors colors/white colors/neutral-95 theme)})
(cond
(= type :datemark)
[quo/divider-date value]
@@ -282,9 +277,9 @@
(defn scroll-handler
[event scroll-y animate-topbar-opacity? on-end-reached? animate-topbar-name?]
(let [content-size-y (- (oops/oget event "nativeEvent.contentSize.height")
(oops/oget event "nativeEvent.layoutMeasurement.height"))
current-y (oops/oget event "nativeEvent.contentOffset.y")
(let [content-size-y (- (oops/oget event "nativeEvent.contentSize.height")
(oops/oget event "nativeEvent.layoutMeasurement.height"))
current-y (oops/oget event "nativeEvent.contentOffset.y")
scroll-distance (- content-size-y current-y)]
(when (and @on-end-reached? (pos? scroll-distance))
(reset! on-end-reached? false))
@@ -306,13 +301,13 @@
(reset! animate-topbar-opacity? true)
(reset! animate-topbar-opacity? false)))
[composer-active? @on-end-reached? @animate-topbar-opacity?])
(let [theme (quo.theme/use-theme-value)
{window-height :height} (rn/get-window)
{:keys [keyboard-height]} (hooks/use-keyboard)
context (rf/sub [:chats/current-chat-message-list-view-context])
messages (rf/sub [:chats/raw-chat-messages-stream (:chat-id chat)])
recording? (rf/sub [:chats/recording?])
all-loaded? (rf/sub [:chats/all-loaded? (:chat-id chat)])
(let [theme (quo.theme/use-theme-value)
{window-height :height} (rn/get-window)
{:keys [keyboard-height]} (hooks/use-keyboard)
context (rf/sub [:chats/current-chat-message-list-view-context])
messages (rf/sub [:chats/raw-chat-messages-stream (:chat-id chat)])
recording? (rf/sub [:chats/recording?])
all-loaded? (rf/sub [:chats/all-loaded? (:chat-id chat)])
{:keys [show-floating-scroll-down-button?
messages-view-height
messages-view-header-height]} inner-state-atoms]
@@ -322,18 +317,20 @@
:key-fn list-key-fn
:ref list-ref
:bounces false
;; TODO(alwx): list-header indicates offsets
:header [:<>
[list-header insets (:able-to-send-message? context) theme]
(when (= (:chat-type chat) constants/private-group-chat-type)
[list-group-chat-header chat])]
;; TODO(alwx): list-footer is basically a header, yes
:footer [list-footer
{:theme theme
:chat chat
:scroll-y scroll-y
:cover-bg-color cover-bg-color
:on-layout #(footer-on-layout
%
messages-view-header-height)
%
messages-view-header-height)
:messages-view-header-height messages-view-header-height
:messages-view-height messages-view-height
:big-name-visible? big-name-visible?}]
@@ -346,26 +343,26 @@
:on-viewable-items-changed on-viewable-items-changed
:on-content-size-change (fn [_ y]
(if (or
(< minimum-scroll-y-topbar-overlaying-avatar
(reanimated/get-shared-value scroll-y))
(< topbar-visible-scroll-y-value
(reanimated/get-shared-value scroll-y)))
(< minimum-scroll-y-topbar-overlaying-avatar
(reanimated/get-shared-value scroll-y))
(< topbar-visible-scroll-y-value
(reanimated/get-shared-value scroll-y)))
(reset! animate-topbar-opacity? true)
(reset! animate-topbar-opacity? false))
(when-not (or
(not @big-name-visible?)
(= :initial-render @big-name-visible?)
(pos? (reanimated/get-shared-value
scroll-y)))
(not @big-name-visible?)
(= :initial-render @big-name-visible?)
(pos? (reanimated/get-shared-value
scroll-y)))
(reset! on-end-reached? false))
;; NOTE(alwx): here we set the initial value of
;; `scroll-y` which is needed because by default the
;; chat is scrolled to the bottom and no initial
;; `on-scroll` event is getting triggered
(let [scroll-y-shared (reanimated/get-shared-value
scroll-y)
(let [scroll-y-shared (reanimated/get-shared-value
scroll-y)
content-height-shared (reanimated/get-shared-value
content-height)]
content-height)]
(when (or (= scroll-y-shared 0)
(> (Math/abs (- content-height-shared y))
min-message-height))
@@ -396,19 +393,19 @@
animate-topbar-name?)
(on-scroll event show-floating-scroll-down-button?))
:style (add-inverted-y-android
{:background-color (if all-loaded?
(colors/theme-colors
(colors/custom-color cover-bg-color
50
20)
(colors/custom-color cover-bg-color
50
40)
theme)
(colors/theme-colors
colors/white
colors/neutral-95
theme))})
{:background-color (if all-loaded?
(colors/theme-colors
(colors/custom-color cover-bg-color
50
20)
(colors/custom-color cover-bg-color
50
40)
theme)
(colors/theme-colors
colors/white
colors/neutral-95
theme))})
;;TODO(rasom) https://github.com/facebook/react-native/issues/30034
:inverted (when platform/ios? true)
:on-layout (fn [e]
@@ -430,9 +427,9 @@
(defn message-list-content-view
[props]
(let [chat-screen-loaded? (rf/sub [:shell/chat-screen-loaded?])
window-height (:height (rn/get-window))
content-height (- window-height composer.constants/composer-default-height)
top-spacing (when (not chat-screen-loaded?) navigation.style/navigation-bar-height)]
window-height (:height (rn/get-window))
content-height (- window-height composer.constants/composer-default-height)
top-spacing (when (not chat-screen-loaded?) navigation.style/navigation-bar-height)]
(if chat-screen-loaded?
[:f> f-messages-list-content props]
[rn/view {:style {:padding-top top-spacing :flex 1}}
@@ -67,6 +67,7 @@
:on-end-reached? on-end-reached?
:messages-list-on-layout-finished? messages-list-on-layout-finished?}]
;; TODO(alwx): this part is basically the top bar itself, shown only when scrolled
[messages.navigation/navigation-view
{:scroll-y scroll-y
:animate-topbar-name? animate-topbar-name?
@@ -5,11 +5,13 @@
[quo.foundations.colors :as colors]
[react-native.blur :as blur]
[react-native.core :as rn]
[react-native.reanimated :as reanimated]
[reagent.core :as reagent]
[status-im2.common.home.actions.view :as actions]
[status-im2.common.password-authentication.view :as password-authentication]
[status-im2.common.scroll-page.style :as scroll-page.style]
[status-im2.common.scroll-page.view :as scroll-page]
[status-im2.common.top-bar.view :as top-bar]
[status-im2.constants :as constants]
[status-im2.contexts.communities.actions.chat.view :as chat-actions]
[status-im2.contexts.communities.actions.community-options.view :as options]
@@ -37,8 +39,8 @@
(defn add-category-height
[categories-heights category height]
(swap! categories-heights
(fn [heights]
(assoc heights category height))))
(fn [heights]
(assoc heights category height))))
(defn collapse-category
[community-id category-id collapsed?]
@@ -51,22 +53,22 @@
(defn- channel-chat-item
[community-id community-color
{:keys [name emoji muted? id mentions-count unread-messages? on-press locked?] :as chat}]
(let [sheet-content [actions/chat-actions
(assoc chat
:chat-type constants/community-chat-type
:chat-id (str community-id id))
false]
notification (cond
muted? :mute
(> mentions-count 0) :mention
unread-messages? :notification
:else nil)
channel-options {:name name
:emoji emoji
:customization-color community-color
:mentions-count mentions-count
:locked? locked?
:notification notification}
(let [sheet-content [actions/chat-actions
(assoc chat
:chat-type constants/community-chat-type
:chat-id (str community-id id))
false]
notification (cond
muted? :mute
(> mentions-count 0) :mention
unread-messages? :notification
:else nil)
channel-options {:name name
:emoji emoji
:customization-color community-color
:mentions-count mentions-count
:locked? locked?
:notification notification}
channel-sheet-data {:selected-item (fn [] [quo/channel channel-options])
:content (fn [] sheet-content)}]
[rn/view {:key id}
@@ -80,8 +82,8 @@
channels-list]
[rn/view
{:on-layout #(on-first-channel-height-changed
(+ 38 (int (Math/ceil (layout-y %))))
(into #{} (map (comp :name second) channels-list)))
(+ 38 (int (Math/ceil (layout-y %))))
(into #{} (map (comp :name second) channels-list)))
:style {:margin-top 8 :flex 1}}
(for [[category-id {:keys [chats name collapsed?]}] channels-list]
[rn/view
@@ -104,9 +106,9 @@
(defn get-access-type
[access]
(condp = access
constants/community-no-membership-access :open
constants/community-no-membership-access :open
constants/community-invitation-only-access :invite-only
constants/community-on-request-access :request-access
constants/community-on-request-access :request-access
:unknown-access))
(defn join-gated-community
@@ -122,15 +124,15 @@
[rn/touchable-without-feedback
{:on-press
#(rf/dispatch
[:show-bottom-sheet
{:content
(fn []
[quo/documentation-drawers
{:title (i18n/label :t/token-gated-communities)
:show-button? true
:button-label (i18n/label :t/read-more)
:button-icon :info}
[quo/text (i18n/label :t/token-gated-communities-info)]])}])}
[:show-bottom-sheet
{:content
(fn []
[quo/documentation-drawers
{:title (i18n/label :t/token-gated-communities)
:show-button? true
:button-label (i18n/label :t/read-more)
:button-icon :info}
[quo/text (i18n/label :t/token-gated-communities-info)]])}])}
[rn/view
[quo/icon :i/info {:no-color true}]]])
@@ -173,9 +175,9 @@
(defn join-community
[{:keys [joined color permissions token-permissions] :as community}
pending?]
(let [access-type (get-access-type (:access permissions))
(let [access-type (get-access-type (:access permissions))
unknown-access? (= access-type :unknown-access)
invite-only? (= access-type :invite-only)]
invite-only? (= access-type :invite-only)]
[:<>
(when-not (or joined pending? invite-only? unknown-access?)
(if (seq token-permissions)
@@ -211,18 +213,18 @@
:or {locked? false}
:as chat}]
(merge
chat
(when (and (not locked?) id)
{:on-press (when joined
(fn []
(rf/dispatch [:dismiss-keyboard])
(debounce/dispatch-and-chill [:chat/navigate-to-chat (str community-id id)]
1000)))
:on-long-press #(rf/dispatch
[:show-bottom-sheet
{:content (fn []
[chat-actions/actions chat false])}])
:community-id community-id})))
chat
(when (and (not locked?) id)
{:on-press (when joined
(fn []
(rf/dispatch [:dismiss-keyboard])
(debounce/dispatch-and-chill [:chat/navigate-to-chat (str community-id id)]
1000)))
:on-long-press #(rf/dispatch
[:show-bottom-sheet
{:content (fn []
[chat-actions/actions chat false])}])
:community-id community-id})))
(defn add-handlers-to-chats
[community-id joined chats]
@@ -246,10 +248,10 @@
scroll-page.style/picture-border-width
12))
:margin-bottom 12}
:avatar logo
:title title
:description description
:title-accessibility-label :community-title
:avatar logo
:title title
:description description
:title-accessibility-label :community-title
:description-accessibility-label :community-description}])
(defn community-content
@@ -300,8 +302,8 @@
[{:icon-name :i/options
:accessibility-label :community-options-for-community
:on-press #(rf/dispatch
[:show-bottom-sheet
{:content (fn [] [options/community-options-bottom-sheet id])}])}])
[:show-bottom-sheet
{:content (fn [] [options/community-options-bottom-sheet id])}])}])
(defn pick-first-category-by-height
[scroll-height first-channel-height categories-heights]
@@ -313,18 +315,18 @@
(defn community-scroll-page
[{:keys [joined]}]
(let [scroll-height (reagent/atom 0)
categories-heights (reagent/atom {})
(let [scroll-height (reagent/atom 0)
categories-heights (reagent/atom {})
first-channel-height (reagent/atom 0)
;; We track the initial value of joined
;; as we open the page to avoid switching
;; from not collapsed to collapsed if the
;; user is on this page
initial-joined? joined]
initial-joined? joined]
(fn [{:keys [id name images] :as community} pending?]
(let [cover {:uri (get-in images [:banner :uri])}
logo {:uri (get-in images [:thumbnail :uri])}
collapsed? (and initial-joined? (:joined community))
(let [cover {:uri (get-in images [:banner :uri])}
logo {:uri (get-in images [:thumbnail :uri])}
collapsed? (and initial-joined? (:joined community))
overlay-shown? (boolean (:sheets (rf/sub [:bottom-sheet])))]
[scroll-page/scroll-page
{:cover-image cover
@@ -343,14 +345,14 @@
:sticky-header [sticky-category-header
{:enabled (> @scroll-height @first-channel-height)
:label (pick-first-category-by-height
@scroll-height
@first-channel-height
@categories-heights)}]}
@scroll-height
@first-channel-height
@categories-heights)}]}
[community-content
community
pending?
{:on-category-layout (partial add-category-height categories-heights)
:collapsed? collapsed?
{:on-category-layout (partial add-category-height categories-heights)
:collapsed? collapsed?
:on-first-channel-height-changed
;; Here we set the height of the component and we filter out the categories, as some
;; might have been removed.
@@ -362,12 +364,12 @@
[id]
(let [{:keys [id]
:as community} (rf/sub [:communities/community id])
pending? (rf/sub [:communities/my-pending-request-to-join id])]
pending? (rf/sub [:communities/my-pending-request-to-join id])]
[community-scroll-page community pending?]))
(defn overview
[id]
(let [id (or id (rf/sub [:get-screen-params :community-overview]))
(let [id (or id (rf/sub [:get-screen-params :community-overview]))
customization-color (rf/sub [:profile/customization-color])]
[rn/view {:style style/community-overview-container}
[community-card-page-view id]
@@ -377,3 +379,54 @@
:label (i18n/label :t/jump-to)}}
{:position :absolute
:bottom 34}]]))
(defn list-header [{:keys [scroll-y theme]}]
[top-bar/abstract-list-header
{:inverted-y-on-android? false
:show? true
:theme theme
:cover-bg-color :red
:scroll-y scroll-y
:on-layout (fn []
)
:avatar-component (fn []
)
:title-component (fn []
)
:description "test"
:actions-component (fn []
)
:loading-component (fn []
)
}])
(defn overview-content-view [{:keys [id community customization-color chats-by-category
scroll-y content-height]}]
(let [categories-heights (reagent/atom {})
first-channel-height (reagent/atom 0)]
[rn/scroll-view
[channel-list-component
{:on-category-layout (partial add-category-height categories-heights)
:community-id id
:community-color (:color community)
:on-first-channel-height-changed (fn [height categories]
(swap! categories-heights select-keys categories)
(reset! first-channel-height height))}
(add-handlers-to-categorized-chats id chats-by-category (:joined community))]]))
(defn overview-old
[id]
(let [scroll-y (reanimated/use-shared-value 0)
content-height (reanimated/use-shared-value 0)
id (or id (rf/sub [:get-screen-params :community-overview]))
customization-color (rf/sub [:profile/customization-color])
community (rf/sub [:communities/community id])
chats-by-category (rf/sub [:communities/categorized-channels id])]
[rn/view {:style style/community-overview-container}
[overview-content-view
{:id id
:customization-color customization-color
:community community
:chats-by-category chats-by-category
:scroll-y scroll-y
:content-height content-height}]]))
@@ -33,14 +33,24 @@
(rf/sub [:profile/customization-color]))
syncing-results? (= :syncing-results @state/root-id)]
[rn/view {:style (style/buttons insets)}
[quo/button
[standard-auth/button
(merge
{:size 40
:accessibility-label :enable-biometrics-button
:icon-left :i/face-id
:customization-color profile-color
:button-label (i18n/label :t/biometric-enable-button {:bio-type-label bio-type-label})}
{:on-press #(rf/dispatch [:onboarding-2/sync-enable-biometrics])})]
(if syncing-results?
{:theme theme
:blur? true
:on-enter-password (fn [entered-password]
(rf/dispatch
[:onboarding-2/authenticate-enable-biometrics
(security/safe-unmask-data
entered-password)])
(rf/dispatch [:hide-bottom-sheet]))
:auth-button-label (i18n/label :t/confirm)}
{:on-press #(rf/dispatch [:onboarding-2/enable-biometrics])}))]
[quo/button
{:accessibility-label :maybe-later-button
:background :blur
@@ -35,10 +35,11 @@
{:biometric/authenticate {:on-success #(rf/dispatch [:onboarding-2/biometrics-done])
:on-fail #(rf/dispatch [:onboarding-2/biometrics-fail %])}})
(rf/defn sync-enable-biometrics
{:events [:onboarding-2/sync-enable-biometrics]}
(rf/defn authenticate-enable-biometrics
{:events [:onboarding-2/authenticate-enable-biometrics]}
[{:keys [db]} password]
{:db (-> db
(assoc-in [:onboarding-2/profile :password] password)
(assoc-in [:onboarding-2/profile :syncing?] true))
:biometric/authenticate {:on-success #(rf/dispatch [:onboarding-2/biometrics-done])
:on-fail #(rf/dispatch [:onboarding-2/biometrics-fail %])}})
@@ -144,15 +145,15 @@
(rf/defn onboarding-new-account-finalize-setup
{:events [:onboarding-2/finalize-setup]}
[{:keys [db]}]
(let [syncing? (get-in db [:onboarding-2/profile :syncing?])
masked-password (get-in db [:onboarding-2/profile :password])
(let [masked-password (get-in db [:onboarding-2/profile :password])
key-uid (get-in db [:profile/profile :key-uid])
syncing? (get-in db [:onboarding-2/profile :syncing?])
biometric-enabled? (= (get-in db [:onboarding-2/profile :auth-method])
constants/auth-method-biometric)]
(cond-> {:db (assoc db :onboarding-2/generated-keys? true)}
biometric-enabled?
(assoc :keychain/save-password-and-auth-method
{:key-uid (if syncing? (str key-uid "-hashed") key-uid)
{:key-uid key-uid
:masked-password masked-password
:on-success (fn []
(if syncing?
@@ -42,19 +42,11 @@
{:db (assoc-in db [:profile/login :processing] true)
::login [key-uid (native-module/sha3 (security/safe-unmask-data password))]}))
(rf/defn biometry-login
{:events [:profile.login/biometry-login]}
[{:keys [db]}]
(let [{:keys [key-uid password]} (:profile/login db)]
{:db (assoc-in db [:profile/login :processing] true)
::login [key-uid (security/safe-unmask-data password)]}))
(rf/defn login-local-paired-user
{:events [:profile.login/local-paired-user]}
[{:keys [db]}]
(let [{:keys [key-uid password]} (get-in db [:syncing :profile])]
{:db (assoc-in db [:onboarding-2/profile :password] (security/mask-data password))
::login [key-uid password]}))
{::login [key-uid password]}))
(rf/defn redirect-to-root
[{:keys [db] :as cofx}]
@@ -166,24 +158,13 @@
{:on-success #(rf/dispatch [:profile.login/biometric-success])
:on-faile #(rf/dispatch [:profile.login/biometric-auth-fail])}})))
;; TODO store the password in the hash keychain
(rf/defn backwards-compatible-biometric-auth-success
{:events [:profile.login/backwards-compatible-biometric-success]}
[{:keys [db] :as cofx}]
(let [key-uid (get-in db [:profile/login :key-uid])]
(keychain/get-user-password cofx
key-uid
#(rf/dispatch [:profile.login/get-user-password-success %]))))
(rf/defn biometric-auth-success
{:events [:profile.login/biometric-success]}
[{:keys [db] :as cofx}]
(let [key-uid (get-in db [:profile/login :key-uid])]
(keychain/get-user-password cofx
(str key-uid "-hashed")
#(if (nil? %)
(rf/dispatch [:profile.login/backwards-compatible-biometric-success])
(rf/dispatch [:profile.login/get-user-password-success %])))))
key-uid
#(rf/dispatch [:profile.login/get-user-password-success %]))))
;; result of :keychain/get-auth-method above
(rf/defn get-user-password-success
@@ -194,7 +175,7 @@
cofx
{:db (assoc-in db [:profile/login :password] password)}
(navigation/init-root :progress)
(biometry-login))))
(login))))
(rf/defn biometric-auth-fail
{:events [:profile.login/biometric-auth-fail]}