Optimize chat screen navigation view (#18055)
This commit is contained in:
parent
1df96ae3e0
commit
d40f70dca4
|
@ -0,0 +1,65 @@
|
||||||
|
import { useDerivedValue, withTiming, interpolate, useAnimatedScrollHandler, runOnJS } from 'react-native-reanimated';
|
||||||
|
|
||||||
|
export function navigationHeaderOpacity(distanceFromListTop, isAllLoaded, isCalculationsComplete, startPosition) {
|
||||||
|
return useDerivedValue(function () {
|
||||||
|
'worklet';
|
||||||
|
const isCalculationsCompleteValue = isCalculationsComplete.value;
|
||||||
|
if (distanceFromListTop.value < startPosition && isAllLoaded.value) {
|
||||||
|
return isCalculationsCompleteValue ? withTiming(0) : 0;
|
||||||
|
} else {
|
||||||
|
return isCalculationsCompleteValue ? withTiming(1) : 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function navigationHeaderPosition(distanceFromListTop, isAllLoaded, topBarHeight, startPosition) {
|
||||||
|
return useDerivedValue(function () {
|
||||||
|
'worklet';
|
||||||
|
return distanceFromListTop.value < startPosition && isAllLoaded.value ? withTiming(topBarHeight) : withTiming(0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function interpolateNavigationViewOpacity(props) {
|
||||||
|
return useDerivedValue(function () {
|
||||||
|
'worklet';
|
||||||
|
const {
|
||||||
|
'all-loaded?': isAllLoaded,
|
||||||
|
'end-position': endPosition,
|
||||||
|
'start-position': startPosition,
|
||||||
|
'distance-from-list-top': distanceFromListTop,
|
||||||
|
} = props;
|
||||||
|
if (isAllLoaded.value) {
|
||||||
|
return interpolate(distanceFromListTop.value, [startPosition, endPosition], [0, 1], {
|
||||||
|
extrapolateLeft: 'clamp',
|
||||||
|
extrapolateRight: 'clamp',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function messagesListOnScroll(distanceFromListTop, callback) {
|
||||||
|
return function (event) {
|
||||||
|
'worklet';
|
||||||
|
const currentY = event.contentOffset.y;
|
||||||
|
const layoutHeight = event.layoutMeasurement.height;
|
||||||
|
const contentSizeY = event.contentSize.height - layoutHeight;
|
||||||
|
distanceFromListTop.value = contentSizeY - currentY;
|
||||||
|
runOnJS(callback)(currentY, layoutHeight);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function placeholderOpacity(isCalculationsComplete) {
|
||||||
|
return useDerivedValue(function () {
|
||||||
|
'worklet';
|
||||||
|
return isCalculationsComplete.value ? 0 : 1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function placeholderZIndex(isCalculationsComplete) {
|
||||||
|
return useDerivedValue(function () {
|
||||||
|
'worklet';
|
||||||
|
return isCalculationsComplete.value ? 0 : 2;
|
||||||
|
});
|
||||||
|
}
|
|
@ -432,7 +432,8 @@
|
||||||
"../src/js/worklets/record_audio.js" #js {}
|
"../src/js/worklets/record_audio.js" #js {}
|
||||||
"../src/js/worklets/scroll_view.js" #js {}
|
"../src/js/worklets/scroll_view.js" #js {}
|
||||||
"../src/js/worklets/onboarding_carousel.js" #js {}
|
"../src/js/worklets/onboarding_carousel.js" #js {}
|
||||||
"../src/js/worklets/lightbox.js" #js {}
|
"../src/js/worklets/chat/lightbox.js" #js {}
|
||||||
|
"../src/js/worklets/chat/messages.js" #js {}
|
||||||
"../src/js/worklets/parallax.js" #js {}
|
"../src/js/worklets/parallax.js" #js {}
|
||||||
"../src/js/worklets/identifiers_highlighting.js" #js {}
|
"../src/js/worklets/identifiers_highlighting.js" #js {}
|
||||||
"./fleets.js" default-fleets
|
"./fleets.js" default-fleets
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
(:require
|
(:require
|
||||||
[quo.foundations.colors :as colors]))
|
[quo.foundations.colors :as colors]))
|
||||||
|
|
||||||
|
|
||||||
(def container
|
(def container
|
||||||
{:height 40
|
{:height 40
|
||||||
:background-color (colors/custom-color :blue 50 20)
|
:background-color (colors/custom-color :blue 50 20)
|
||||||
|
@ -25,4 +24,3 @@
|
||||||
[hide-pin?]
|
[hide-pin?]
|
||||||
{:flex (if hide-pin? 16 15)
|
{:flex (if hide-pin? 16 15)
|
||||||
:margin-right 10})
|
:margin-right 10})
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
SlideOutUp
|
SlideOutUp
|
||||||
LinearTransition
|
LinearTransition
|
||||||
enableLayoutAnimations
|
enableLayoutAnimations
|
||||||
|
useAnimatedScrollHandler
|
||||||
runOnJS)]
|
runOnJS)]
|
||||||
["react-native-redash" :refer (withPause)]
|
["react-native-redash" :refer (withPause)]
|
||||||
[react-native.flat-list :as rn-flat-list]
|
[react-native.flat-list :as rn-flat-list]
|
||||||
|
@ -55,6 +56,7 @@
|
||||||
;; Hooks
|
;; Hooks
|
||||||
(def use-shared-value useSharedValue)
|
(def use-shared-value useSharedValue)
|
||||||
(def use-animated-style useAnimatedStyle)
|
(def use-animated-style useAnimatedStyle)
|
||||||
|
(def use-animated-scroll-handler useAnimatedScrollHandler)
|
||||||
|
|
||||||
;; Animations
|
;; Animations
|
||||||
(def with-timing withTiming)
|
(def with-timing withTiming)
|
||||||
|
|
|
@ -197,11 +197,6 @@
|
||||||
|
|
||||||
(defn init-subs
|
(defn init-subs
|
||||||
[]
|
[]
|
||||||
(let [chat-screen-loaded? (rf/sub [:shell/chat-screen-loaded?])]
|
|
||||||
(merge
|
|
||||||
{:chat-screen-loaded? chat-screen-loaded?
|
|
||||||
:link-previews? false}
|
|
||||||
(when chat-screen-loaded?
|
|
||||||
(let [chat-input (rf/sub [:chats/current-chat-input])]
|
(let [chat-input (rf/sub [:chats/current-chat-input])]
|
||||||
{:images (seq (rf/sub [:chats/sending-image]))
|
{:images (seq (rf/sub [:chats/sending-image]))
|
||||||
:link-previews? (rf/sub [:chats/link-previews?])
|
:link-previews? (rf/sub [:chats/link-previews?])
|
||||||
|
@ -210,8 +205,7 @@
|
||||||
:edit (rf/sub [:chats/edit-message])
|
:edit (rf/sub [:chats/edit-message])
|
||||||
:input-with-mentions (rf/sub [:chat/input-with-mentions])
|
:input-with-mentions (rf/sub [:chat/input-with-mentions])
|
||||||
:input-text (:input-text chat-input)
|
:input-text (:input-text chat-input)
|
||||||
:input-content-height (:input-content-height chat-input)
|
:input-content-height (:input-content-height chat-input)}))
|
||||||
:chat-screen-loaded? chat-screen-loaded?})))))
|
|
||||||
|
|
||||||
(defn init-animations
|
(defn init-animations
|
||||||
[{:keys [input-text images link-previews? reply audio]}
|
[{:keys [input-text images link-previews? reply audio]}
|
||||||
|
|
|
@ -23,7 +23,10 @@
|
||||||
[status-im2.contexts.chat.composer.style :as style]
|
[status-im2.contexts.chat.composer.style :as style]
|
||||||
[status-im2.contexts.chat.composer.sub-view :as sub-view]
|
[status-im2.contexts.chat.composer.sub-view :as sub-view]
|
||||||
[status-im2.contexts.chat.composer.utils :as utils]
|
[status-im2.contexts.chat.composer.utils :as utils]
|
||||||
[utils.i18n :as i18n]))
|
[status-im2.contexts.chat.messages.contact-requests.bottom-drawer.view :as
|
||||||
|
contact-requests.bottom-drawer]
|
||||||
|
[utils.i18n :as i18n]
|
||||||
|
[utils.re-frame :as rf]))
|
||||||
|
|
||||||
(defn sheet-component
|
(defn sheet-component
|
||||||
[{:keys [insets
|
[{:keys [insets
|
||||||
|
@ -35,8 +38,7 @@
|
||||||
background-y
|
background-y
|
||||||
theme
|
theme
|
||||||
messages-list-on-layout-finished?]} props state]
|
messages-list-on-layout-finished?]} props state]
|
||||||
(let [{:keys [chat-screen-loaded?]
|
(let [subscriptions (utils/init-subs)
|
||||||
:as subscriptions} (utils/init-subs)
|
|
||||||
content-height (reagent/atom (or (:input-content-height ; Actual text height
|
content-height (reagent/atom (or (:input-content-height ; Actual text height
|
||||||
subscriptions)
|
subscriptions)
|
||||||
constants/input-height))
|
constants/input-height))
|
||||||
|
@ -80,12 +82,11 @@
|
||||||
(effects/link-previews props state animations subscriptions)
|
(effects/link-previews props state animations subscriptions)
|
||||||
(effects/use-images props state animations subscriptions)
|
(effects/use-images props state animations subscriptions)
|
||||||
[:<>
|
[:<>
|
||||||
(when chat-screen-loaded?
|
|
||||||
[mentions/view props state animations max-height cursor-pos
|
[mentions/view props state animations max-height cursor-pos
|
||||||
(:images subscriptions)
|
(:images subscriptions)
|
||||||
(:link-previews? subscriptions)
|
(:link-previews? subscriptions)
|
||||||
(:reply subscriptions)
|
(:reply subscriptions)
|
||||||
(:edit subscriptions)])
|
(:edit subscriptions)]
|
||||||
[rn/view
|
[rn/view
|
||||||
{:style style/composer-sheet-and-jump-to-container}
|
{:style style/composer-sheet-and-jump-to-container}
|
||||||
[sub-view/shell-button state scroll-to-bottom-fn show-floating-scroll-down-button?]
|
[sub-view/shell-button state scroll-to-bottom-fn show-floating-scroll-down-button?]
|
||||||
|
@ -96,12 +97,11 @@
|
||||||
{:style (style/sheet-container insets state animations theme)
|
{:style (style/sheet-container insets state animations theme)
|
||||||
:on-layout #(handler/layout % state blur-height)}
|
:on-layout #(handler/layout % state blur-height)}
|
||||||
[sub-view/bar]
|
[sub-view/bar]
|
||||||
(when chat-screen-loaded?
|
|
||||||
[:<>
|
[:<>
|
||||||
[reply/view state (:input-ref props)]
|
[reply/view state (:input-ref props)]
|
||||||
[edit/view
|
[edit/view
|
||||||
{:text-value (:text-value state)
|
{:text-value (:text-value state)
|
||||||
:input-ref (:input-ref props)}]])
|
:input-ref (:input-ref props)}]]
|
||||||
[reanimated/touchable-opacity
|
[reanimated/touchable-opacity
|
||||||
{:active-opacity 1
|
{:active-opacity 1
|
||||||
:on-press (fn []
|
:on-press (fn []
|
||||||
|
@ -140,15 +140,14 @@
|
||||||
:theme theme})
|
:theme theme})
|
||||||
:max-length constants/max-text-size
|
:max-length constants/max-text-size
|
||||||
:accessibility-label :chat-message-input}]]]
|
:accessibility-label :chat-message-input}]]]
|
||||||
(when chat-screen-loaded?
|
|
||||||
[:<>
|
[:<>
|
||||||
[gradients/view props state animations show-bottom-gradient?]
|
[gradients/view props state animations show-bottom-gradient?]
|
||||||
[link-preview/view]
|
[link-preview/view]
|
||||||
[images/images-list]])
|
[images/images-list]]
|
||||||
[:f> actions/view props state animations window-height insets scroll-to-bottom-fn
|
[:f> actions/view props state animations window-height insets scroll-to-bottom-fn
|
||||||
subscriptions]]]]]))
|
subscriptions]]]]]))
|
||||||
|
|
||||||
(defn composer
|
(defn f-composer
|
||||||
[{:keys [insets scroll-to-bottom-fn show-floating-scroll-down-button?
|
[{:keys [insets scroll-to-bottom-fn show-floating-scroll-down-button?
|
||||||
messages-list-on-layout-finished?]}]
|
messages-list-on-layout-finished?]}]
|
||||||
(let [window-height (:height (rn/get-window))
|
(let [window-height (:height (rn/get-window))
|
||||||
|
@ -177,3 +176,15 @@
|
||||||
:focused? (:focused? state)
|
:focused? (:focused? state)
|
||||||
:theme theme}]
|
:theme theme}]
|
||||||
[:f> sheet-component extra-params props state]]))
|
[:f> sheet-component extra-params props state]]))
|
||||||
|
|
||||||
|
(defn composer
|
||||||
|
[props]
|
||||||
|
(let [{:keys [chat-id
|
||||||
|
contact-request-state
|
||||||
|
group-chat
|
||||||
|
able-to-send-message?]
|
||||||
|
:as chat} (rf/sub [:chats/current-chat-chat-view])]
|
||||||
|
(when (seq chat)
|
||||||
|
(if able-to-send-message?
|
||||||
|
[:f> f-composer props]
|
||||||
|
[contact-requests.bottom-drawer/view chat-id contact-request-state group-chat]))))
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
[status-im2.contexts.chat.lightbox.constants :as constants]
|
[status-im2.contexts.chat.lightbox.constants :as constants]
|
||||||
[status-im2.contexts.chat.lightbox.top-view :as top-view]
|
[status-im2.contexts.chat.lightbox.top-view :as top-view]
|
||||||
[utils.re-frame :as rf]
|
[utils.re-frame :as rf]
|
||||||
[utils.worklets.lightbox :as worklet]))
|
[utils.worklets.chat.lightbox :as worklet]))
|
||||||
|
|
||||||
(defn clear-timers
|
(defn clear-timers
|
||||||
[timers]
|
[timers]
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
(ns status-im2.contexts.chat.messages.constants)
|
||||||
|
|
||||||
|
;;;; Navigation
|
||||||
|
(def ^:const top-bar-height 56)
|
||||||
|
(def ^:const pinned-banner-height 40)
|
||||||
|
(def ^:const header-container-top-margin 48)
|
||||||
|
(def ^:const header-container-radius 20)
|
||||||
|
(def ^:const header-animation-distance 20)
|
||||||
|
(def ^:const content-animation-start-position 110)
|
||||||
|
;; Note - We should also consider height of bio for banner animation starting position
|
||||||
|
;; Todo - Should be updated once New-profile implemation is complete
|
||||||
|
(def ^:const pinned-banner-animation-start-position 148)
|
||||||
|
|
||||||
|
(def ^:const default-extrapolation-option
|
||||||
|
{:extrapolateLeft "clamp"
|
||||||
|
:extrapolateRight "clamp"})
|
|
@ -0,0 +1,7 @@
|
||||||
|
(ns status-im2.contexts.chat.messages.contact-requests.bottom-drawer.style)
|
||||||
|
|
||||||
|
(def container
|
||||||
|
{:position :absolute
|
||||||
|
:bottom 0
|
||||||
|
:left 0
|
||||||
|
:right 0})
|
|
@ -1,8 +1,9 @@
|
||||||
(ns status-im2.contexts.chat.messages.contact-requests.bottom-drawer
|
(ns status-im2.contexts.chat.messages.contact-requests.bottom-drawer.view
|
||||||
(:require
|
(:require
|
||||||
[quo.core :as quo]
|
[quo.core :as quo]
|
||||||
[react-native.core :as rn]
|
[react-native.core :as rn]
|
||||||
[status-im2.constants :as constants]
|
[status-im2.constants :as constants]
|
||||||
|
[status-im2.contexts.chat.messages.contact-requests.bottom-drawer.style :as style]
|
||||||
[status-im2.contexts.shell.jump-to.constants :as jump-to.constants]
|
[status-im2.contexts.shell.jump-to.constants :as jump-to.constants]
|
||||||
[utils.i18n :as i18n]
|
[utils.i18n :as i18n]
|
||||||
[utils.re-frame :as rf]))
|
[utils.re-frame :as rf]))
|
||||||
|
@ -11,7 +12,7 @@
|
||||||
[contact-id contact-request-state group-chat]
|
[contact-id contact-request-state group-chat]
|
||||||
(let [customization-color (rf/sub [:profile/customization-color])
|
(let [customization-color (rf/sub [:profile/customization-color])
|
||||||
[primary-name _] (rf/sub [:contacts/contact-two-names-by-identity contact-id])]
|
[primary-name _] (rf/sub [:contacts/contact-two-names-by-identity contact-id])]
|
||||||
[rn/view
|
[rn/view {:style style/container}
|
||||||
[quo/permission-context
|
[quo/permission-context
|
||||||
[quo/button
|
[quo/button
|
||||||
{:type :ghost
|
{:type :ghost
|
|
@ -1,56 +1,46 @@
|
||||||
(ns status-im2.contexts.chat.messages.list.style
|
(ns status-im2.contexts.chat.messages.list.style
|
||||||
(:require
|
(:require
|
||||||
[quo.foundations.colors :as colors]
|
[quo.foundations.colors :as colors]
|
||||||
[react-native.reanimated :as reanimated]))
|
[react-native.reanimated :as reanimated]
|
||||||
|
[status-im2.contexts.chat.messages.constants :as messages.constants]))
|
||||||
(defonce ^:const cover-height 168)
|
|
||||||
(defonce ^:const overscroll-cover-height 2000)
|
|
||||||
(defonce ^:const header-avatar-top-offset -36)
|
|
||||||
|
|
||||||
(def keyboard-avoiding-container
|
(def keyboard-avoiding-container
|
||||||
{:flex 1})
|
{:flex 1
|
||||||
|
:z-index 2})
|
||||||
|
|
||||||
(def list-container
|
(def list-container
|
||||||
{:padding-vertical 16})
|
{:padding-vertical 16})
|
||||||
|
|
||||||
(defn header-container
|
(defn background-container
|
||||||
[show? theme]
|
[background-color background-opacity top-margin]
|
||||||
{:display (if show? :flex :none)
|
(reanimated/apply-animations-to-style
|
||||||
:background-color (colors/theme-colors colors/white colors/neutral-100 theme)
|
{:opacity background-opacity}
|
||||||
:top (- overscroll-cover-height)
|
{:background-color background-color
|
||||||
:margin-bottom (- overscroll-cover-height)})
|
:position :absolute
|
||||||
|
:top 0
|
||||||
(defn header-cover
|
:left 0
|
||||||
[cover-bg-color theme]
|
:right 0
|
||||||
{:flex 1
|
:height (+ top-margin messages.constants/header-container-radius)}))
|
||||||
: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
|
(defn header-bottom-part
|
||||||
[animation theme]
|
[border-radius theme top-margin]
|
||||||
(reanimated/apply-animations-to-style
|
(reanimated/apply-animations-to-style
|
||||||
{:border-top-right-radius animation
|
{:border-top-left-radius border-radius
|
||||||
:border-top-left-radius animation}
|
:border-top-right-radius border-radius}
|
||||||
{:top -16
|
{:background-color (colors/theme-colors colors/white colors/neutral-95 theme)
|
||||||
:margin-bottom -16
|
:padding-horizontal 20
|
||||||
:padding-bottom 24
|
:margin-top top-margin}))
|
||||||
: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
|
(defn header-image
|
||||||
[scale-animation side-animation bottom-animation]
|
[scale top left theme]
|
||||||
(reanimated/apply-animations-to-style
|
(reanimated/apply-animations-to-style
|
||||||
{:transform [{:scale scale-animation}
|
{:transform [{:scale scale}]
|
||||||
{:translate-x side-animation}
|
:top top
|
||||||
{:translate-y bottom-animation}]}
|
:left left}
|
||||||
{:align-items :flex-start}))
|
{:position :absolute
|
||||||
|
:border-width 4
|
||||||
|
:border-radius 50
|
||||||
|
:border-color (colors/theme-colors colors/white colors/neutral-95 theme)}))
|
||||||
|
|
||||||
(def bio
|
(def bio
|
||||||
{:margin-top 8})
|
{:margin-top 8})
|
||||||
|
|
|
@ -6,31 +6,27 @@
|
||||||
[quo.theme :as quo.theme]
|
[quo.theme :as quo.theme]
|
||||||
[react-native.background-timer :as background-timer]
|
[react-native.background-timer :as background-timer]
|
||||||
[react-native.core :as rn]
|
[react-native.core :as rn]
|
||||||
|
[react-native.hooks :as hooks]
|
||||||
[react-native.platform :as platform]
|
[react-native.platform :as platform]
|
||||||
[react-native.react-native-intersection-observer :as rnio]
|
|
||||||
[react-native.reanimated :as reanimated]
|
[react-native.reanimated :as reanimated]
|
||||||
|
[react-native.safe-area :as safe-area]
|
||||||
[status-im.ui.screens.chat.group :as chat.group]
|
[status-im.ui.screens.chat.group :as chat.group]
|
||||||
[status-im2.common.home.actions.view :as home.actions]
|
[status-im2.common.home.actions.view :as home.actions]
|
||||||
[status-im2.constants :as constants]
|
[status-im2.constants :as constants]
|
||||||
[status-im2.contexts.chat.composer.constants :as composer.constants]
|
[status-im2.contexts.chat.composer.constants :as composer.constants]
|
||||||
|
[status-im2.contexts.chat.messages.constants :as messages.constants]
|
||||||
[status-im2.contexts.chat.messages.content.view :as message]
|
[status-im2.contexts.chat.messages.content.view :as message]
|
||||||
[status-im2.contexts.chat.messages.list.state :as state]
|
[status-im2.contexts.chat.messages.list.state :as state]
|
||||||
[status-im2.contexts.chat.messages.list.style :as style]
|
[status-im2.contexts.chat.messages.list.style :as style]
|
||||||
[status-im2.contexts.chat.messages.navigation.style :as navigation.style]
|
|
||||||
[status-im2.contexts.shell.jump-to.constants :as jump-to.constants]
|
[status-im2.contexts.shell.jump-to.constants :as jump-to.constants]
|
||||||
[utils.i18n :as i18n]
|
[utils.i18n :as i18n]
|
||||||
[utils.re-frame :as rf]))
|
[utils.re-frame :as rf]
|
||||||
|
[utils.worklets.chat.messages :as worklets]))
|
||||||
|
|
||||||
(defonce ^:const card-border-radius 16)
|
|
||||||
(defonce ^:const threshold-percentage-to-show-floating-scroll-down-button 75)
|
(defonce ^:const threshold-percentage-to-show-floating-scroll-down-button 75)
|
||||||
(defonce ^:const loading-indicator-extra-spacing 250)
|
(defonce ^:const loading-indicator-extra-spacing 250)
|
||||||
(defonce ^:const loading-indicator-page-loading-height 100)
|
(defonce ^:const loading-indicator-page-loading-height 100)
|
||||||
(defonce ^:const scroll-animation-input-range [0 50])
|
|
||||||
(defonce ^:const min-message-height 32)
|
(defonce ^:const min-message-height 32)
|
||||||
(defonce ^:const topbar-visible-scroll-y-value 50)
|
|
||||||
(defonce ^:const topbar-invisible-scroll-y-value 135)
|
|
||||||
(defonce ^:const minimum-scroll-y-topbar-overlaying-avatar 400)
|
|
||||||
(def root-margin-for-big-name-visibility-detector {:bottom -35})
|
|
||||||
(defonce messages-list-ref (atom nil))
|
(defonce messages-list-ref (atom nil))
|
||||||
|
|
||||||
(defn list-key-fn [{:keys [message-id value]}] (or message-id value))
|
(defn list-key-fn [{:keys [message-id value]}] (or message-id value))
|
||||||
|
@ -42,16 +38,15 @@
|
||||||
(.scrollToOffset #js
|
(.scrollToOffset #js
|
||||||
{:animated true})))
|
{:animated true})))
|
||||||
|
|
||||||
(defn on-scroll
|
(defn on-scroll-fn
|
||||||
[evt show-floating-scroll-down-button?]
|
[show-floating-scroll-down-button?]
|
||||||
(let [y (oops/oget evt "nativeEvent.contentOffset.y")
|
(fn [y layout-height]
|
||||||
layout-height (oops/oget evt "nativeEvent.layoutMeasurement.height")
|
(let [threshold-height (* (/ layout-height 100)
|
||||||
threshold-height (* (/ layout-height 100)
|
|
||||||
threshold-percentage-to-show-floating-scroll-down-button)
|
threshold-percentage-to-show-floating-scroll-down-button)
|
||||||
reached-threshold? (> y threshold-height)]
|
reached-threshold? (> y threshold-height)]
|
||||||
(when (not= reached-threshold? @show-floating-scroll-down-button?)
|
(when (not= reached-threshold? @show-floating-scroll-down-button?)
|
||||||
(rn/configure-next (:ease-in-ease-out rn/layout-animation-presets))
|
(rn/configure-next (:ease-in-ease-out rn/layout-animation-presets))
|
||||||
(reset! show-floating-scroll-down-button? reached-threshold?))))
|
(reset! show-floating-scroll-down-button? reached-threshold?)))))
|
||||||
|
|
||||||
(defn on-viewable-items-changed
|
(defn on-viewable-items-changed
|
||||||
[e]
|
[e]
|
||||||
|
@ -69,14 +64,13 @@
|
||||||
first-not-visible))))))
|
first-not-visible))))))
|
||||||
|
|
||||||
(defn list-on-end-reached
|
(defn list-on-end-reached
|
||||||
[scroll-y on-end-reached?]
|
[distance-from-list-top]
|
||||||
;; FIXME: that's a bit of a hack but we need to update `scroll-y` once the new messages
|
;; FIXME: that's a bit of a hack but we need to update `distance-from-list-top` once the new messages
|
||||||
;; are fetched in order for the header to work properly
|
;; are fetched in order for the header to work properly
|
||||||
(let [on-loaded (fn [n]
|
(let [on-loaded (fn [n]
|
||||||
(reanimated/set-shared-value scroll-y
|
(reanimated/set-shared-value distance-from-list-top
|
||||||
(+ (reanimated/get-shared-value scroll-y)
|
(+ (reanimated/get-shared-value distance-from-list-top)
|
||||||
(* n 200))))]
|
(* n 200))))]
|
||||||
(reset! on-end-reached? true)
|
|
||||||
(if @state/scrolling
|
(if @state/scrolling
|
||||||
(rf/dispatch [:chat.ui/load-more-messages-for-current-chat on-loaded])
|
(rf/dispatch [:chat.ui/load-more-messages-for-current-chat on-loaded])
|
||||||
(background-timer/set-timeout #(rf/dispatch [:chat.ui/load-more-messages-for-current-chat
|
(background-timer/set-timeout #(rf/dispatch [:chat.ui/load-more-messages-for-current-chat
|
||||||
|
@ -103,10 +97,6 @@
|
||||||
:size 20
|
:size 20
|
||||||
:color (colors/theme-colors colors/primary-50 colors/primary-60 theme)}]))]))
|
:color (colors/theme-colors colors/primary-50 colors/primary-60 theme)}]))]))
|
||||||
|
|
||||||
(def header-extrapolation-option
|
|
||||||
{:extrapolateLeft "clamp"
|
|
||||||
:extrapolateRight "clamp"})
|
|
||||||
|
|
||||||
(defn- skeleton-list-props
|
(defn- skeleton-list-props
|
||||||
[content parent-height animated?]
|
[content parent-height animated?]
|
||||||
{:content content
|
{:content content
|
||||||
|
@ -114,67 +104,53 @@
|
||||||
:animated? animated?})
|
:animated? animated?})
|
||||||
|
|
||||||
(defn loading-view
|
(defn loading-view
|
||||||
[chat-id messages-view-height messages-view-header-height]
|
[chat-id {:keys [window-height]}]
|
||||||
(let [loading-messages? (rf/sub [:chats/loading-messages? chat-id])
|
(let [messages (rf/sub [:chats/raw-chat-messages-stream 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)
|
loading-first-page? (= (count messages) 0)
|
||||||
top-spacing (if loading-first-page? 0 navigation.style/navigation-bar-height)
|
top-spacing (if loading-first-page?
|
||||||
|
0
|
||||||
|
(+ messages.constants/top-bar-height (safe-area/get-top)))
|
||||||
parent-height (if loading-first-page?
|
parent-height (if loading-first-page?
|
||||||
(- @messages-view-height
|
window-height
|
||||||
@messages-view-header-height
|
|
||||||
composer.constants/composer-default-height
|
|
||||||
loading-indicator-extra-spacing)
|
|
||||||
loading-indicator-page-loading-height)]
|
loading-indicator-page-loading-height)]
|
||||||
(when (or loading-messages? (not all-loaded?))
|
|
||||||
[rn/view {:padding-top top-spacing}
|
[rn/view {:padding-top top-spacing}
|
||||||
;; Only use animated loading skeleton for ios
|
;; Only use animated loading skeleton for ios
|
||||||
;; https://github.com/status-im/status-mobile/issues/17426
|
;; https://github.com/status-im/status-mobile/issues/17426
|
||||||
[quo/skeleton-list (skeleton-list-props :messages parent-height platform/ios?)]])))
|
[quo/skeleton-list (skeleton-list-props :messages parent-height platform/ios?)]]))
|
||||||
|
|
||||||
(defn calc-list-header-height
|
(defn list-header
|
||||||
[insets able-to-send-message? images]
|
[insets able-to-send-message?]
|
||||||
(if able-to-send-message?
|
(let [images (rf/sub [:chats/sending-image])
|
||||||
|
height (if able-to-send-message?
|
||||||
(+ composer.constants/composer-default-height
|
(+ composer.constants/composer-default-height
|
||||||
jump-to.constants/floating-shell-button-height
|
jump-to.constants/floating-shell-button-height
|
||||||
(if (seq images) composer.constants/images-container-height 0)
|
(if (seq images) composer.constants/images-container-height 0)
|
||||||
(:bottom insets))
|
(:bottom insets))
|
||||||
(- 70 (:bottom insets))))
|
(- 70 (:bottom insets)))]
|
||||||
|
[rn/view {:style {:height height}}]))
|
||||||
(defn list-header
|
|
||||||
[insets able-to-send-message? images theme]
|
|
||||||
(let [height (calc-list-header-height insets able-to-send-message? images)]
|
|
||||||
[rn/view
|
|
||||||
{:background-color (colors/theme-colors colors/white colors/neutral-95 theme)
|
|
||||||
:height height}]))
|
|
||||||
|
|
||||||
(defn f-list-footer-avatar
|
(defn f-list-footer-avatar
|
||||||
[{:keys [scroll-y display-name online? profile-picture]}]
|
[{:keys [distance-from-list-top display-name online? profile-picture theme]}]
|
||||||
(let [image-scale-animation (reanimated/interpolate scroll-y
|
(let [scale (reanimated/interpolate distance-from-list-top
|
||||||
scroll-animation-input-range
|
[0 messages.constants/header-container-top-margin]
|
||||||
[1 0.5]
|
[1 0.4]
|
||||||
header-extrapolation-option)
|
messages.constants/default-extrapolation-option)
|
||||||
image-bottom-animation (reanimated/interpolate scroll-y
|
top (reanimated/interpolate distance-from-list-top
|
||||||
scroll-animation-input-range
|
[0 messages.constants/header-container-top-margin]
|
||||||
[0 56]
|
[-40 -8]
|
||||||
header-extrapolation-option)
|
messages.constants/default-extrapolation-option)
|
||||||
image-side-animation (reanimated/interpolate scroll-y
|
left (reanimated/interpolate distance-from-list-top
|
||||||
scroll-animation-input-range
|
[0 messages.constants/header-container-top-margin]
|
||||||
[0 -40]
|
[20 -4]
|
||||||
header-extrapolation-option)]
|
messages.constants/default-extrapolation-option)]
|
||||||
[reanimated/view
|
[reanimated/view
|
||||||
{:style (style/header-image image-scale-animation image-side-animation image-bottom-animation)}
|
{:style (style/header-image scale top left theme)}
|
||||||
[quo/user-avatar
|
[quo/user-avatar
|
||||||
{:full-name display-name
|
{:full-name display-name
|
||||||
:online? online?
|
:online? online?
|
||||||
:profile-picture profile-picture
|
:profile-picture profile-picture
|
||||||
:size :big}]]))
|
:size :big}]]))
|
||||||
|
|
||||||
(defn list-footer-avatar
|
|
||||||
[props]
|
|
||||||
[:f> f-list-footer-avatar props])
|
|
||||||
|
|
||||||
|
|
||||||
(defn actions
|
(defn actions
|
||||||
[chat-id cover-bg-color]
|
[chat-id cover-bg-color]
|
||||||
(let [latest-pin-text (rf/sub [:chats/last-pinned-message-text chat-id])
|
(let [latest-pin-text (rf/sub [:chats/last-pinned-message-text chat-id])
|
||||||
|
@ -210,11 +186,9 @@
|
||||||
muted?)))}]}]))
|
muted?)))}]}]))
|
||||||
|
|
||||||
(defn f-list-footer
|
(defn f-list-footer
|
||||||
[{:keys [chat scroll-y cover-bg-color on-layout theme messages-view-height
|
[{:keys [chat distance-from-list-top cover-bg-color theme]}]
|
||||||
messages-view-header-height big-name-visible?]}]
|
|
||||||
(let [{:keys [chat-id chat-name emoji chat-type
|
(let [{:keys [chat-id chat-name emoji chat-type
|
||||||
group-chat]} chat
|
group-chat]} chat
|
||||||
all-loaded? (rf/sub [:chats/all-loaded? chat-id])
|
|
||||||
display-name (cond
|
display-name (cond
|
||||||
(= chat-type constants/one-to-one-chat-type)
|
(= chat-type constants/one-to-one-chat-type)
|
||||||
(first (rf/sub [:contacts/contact-two-names-by-identity chat-id]))
|
(first (rf/sub [:contacts/contact-two-names-by-identity chat-id]))
|
||||||
|
@ -226,29 +200,39 @@
|
||||||
contact (when-not group-chat
|
contact (when-not group-chat
|
||||||
(rf/sub [:contacts/contact-by-address chat-id]))
|
(rf/sub [:contacts/contact-by-address chat-id]))
|
||||||
photo-path (rf/sub [:chats/photo-path chat-id])
|
photo-path (rf/sub [:chats/photo-path chat-id])
|
||||||
border-animation (reanimated/interpolate scroll-y
|
top-margin (+ (safe-area/get-top)
|
||||||
[30 50]
|
messages.constants/top-bar-height
|
||||||
[card-border-radius 0]
|
messages.constants/header-container-top-margin)
|
||||||
header-extrapolation-option)]
|
background-color (colors/theme-colors
|
||||||
[rn/view {:flex 1}
|
(colors/custom-color cover-bg-color 50 20)
|
||||||
[rn/view
|
(colors/custom-color cover-bg-color 50 40)
|
||||||
{:style (style/header-container all-loaded? theme)
|
theme)
|
||||||
:on-layout on-layout}
|
border-radius (reanimated/interpolate
|
||||||
[rn/view {:style (style/header-cover cover-bg-color theme)}]
|
distance-from-list-top
|
||||||
[reanimated/view {:style (style/header-bottom-part border-animation theme)}
|
[0 messages.constants/header-container-top-margin]
|
||||||
[rn/view {:style style/header-avatar}
|
[20 0]
|
||||||
[rn/view {:style {:align-items :flex-start}}
|
messages.constants/default-extrapolation-option)
|
||||||
|
background-opacity (reanimated/interpolate
|
||||||
|
distance-from-list-top
|
||||||
|
[messages.constants/header-container-top-margin
|
||||||
|
(+ messages.constants/header-animation-distance
|
||||||
|
messages.constants/header-container-top-margin)]
|
||||||
|
[1 0]
|
||||||
|
messages.constants/default-extrapolation-option)]
|
||||||
|
[:<>
|
||||||
|
[reanimated/view
|
||||||
|
{:style (style/background-container background-color background-opacity top-margin)}]
|
||||||
|
[reanimated/view {:style (style/header-bottom-part border-radius theme top-margin)}
|
||||||
(when-not group-chat
|
(when-not group-chat
|
||||||
[list-footer-avatar
|
[:f> f-list-footer-avatar
|
||||||
{:scroll-y scroll-y
|
{:distance-from-list-top distance-from-list-top
|
||||||
:display-name display-name
|
:display-name display-name
|
||||||
:online? online?
|
:online? online?
|
||||||
:profile-picture photo-path}])]
|
:theme theme
|
||||||
[rnio/view
|
:profile-picture photo-path}])
|
||||||
{:on-change (fn [view-visible?]
|
[rn/view
|
||||||
(reset! big-name-visible? view-visible?))
|
{:style {:flex-direction :row
|
||||||
:style {:flex-direction :row
|
:margin-top (if group-chat 94 52)}}
|
||||||
:margin-top (if group-chat 54 12)}}
|
|
||||||
[quo/text
|
[quo/text
|
||||||
{:weight :semi-bold
|
{:weight :semi-bold
|
||||||
:size :heading-1
|
:size :heading-1
|
||||||
|
@ -259,165 +243,102 @@
|
||||||
(when bio
|
(when bio
|
||||||
[quo/text {:style style/bio}
|
[quo/text {:style style/bio}
|
||||||
bio])
|
bio])
|
||||||
[actions chat-id cover-bg-color]]]]
|
[actions chat-id cover-bg-color]]]))
|
||||||
[loading-view chat-id messages-view-height messages-view-header-height]]))
|
|
||||||
|
|
||||||
(defn list-footer
|
(defn list-footer
|
||||||
[props]
|
[props]
|
||||||
[:f> f-list-footer props])
|
(let [chat-id (get-in props [:chat :chat-id])
|
||||||
|
loading-messages? (rf/sub [:chats/loading-messages? chat-id])
|
||||||
|
all-loaded? (rf/sub [:chats/all-loaded? chat-id])]
|
||||||
|
[:<>
|
||||||
|
(if (or loading-messages? (not all-loaded?))
|
||||||
|
[loading-view chat-id props]
|
||||||
|
[:f> f-list-footer props])]))
|
||||||
|
|
||||||
(defn list-group-chat-header
|
(defn list-group-chat-header
|
||||||
[{:keys [chat-id invitation-admin]}]
|
[{:keys [chat-id invitation-admin]}]
|
||||||
[rn/view
|
[rn/view
|
||||||
[chat.group/group-chat-footer chat-id invitation-admin]])
|
[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")]
|
|
||||||
(reset! messages-view-header-height (+ height y))))
|
|
||||||
|
|
||||||
(defn render-fn
|
(defn render-fn
|
||||||
[{:keys [type value content-type theme] :as message-data} _ _
|
[{:keys [type value content-type] :as message-data} _ _
|
||||||
{:keys [context keyboard-shown?]}]
|
{:keys [context keyboard-shown?]}]
|
||||||
(when (not= content-type constants/content-type-contact-request)
|
(when (not= content-type constants/content-type-contact-request)
|
||||||
[rn/view
|
|
||||||
{:background-color (colors/theme-colors colors/white colors/neutral-95 theme)}
|
|
||||||
(cond
|
(cond
|
||||||
(= type :datemark)
|
(= type :datemark)
|
||||||
[quo/divider-date value]
|
[quo/divider-date value]
|
||||||
|
|
||||||
:else
|
:else
|
||||||
[message/message message-data context keyboard-shown?])]))
|
[message/message message-data context keyboard-shown?])))
|
||||||
|
|
||||||
(defn use-scroll-handler
|
(defn on-content-size-change
|
||||||
[{:keys [scroll-y-animated-value
|
[{:keys [distance-from-list-top window-height content-height calculations-complete?]}]
|
||||||
content-size-animated-value
|
(fn [_ content-height-new]
|
||||||
animate-topbar-opacity?
|
(let [change (- content-height-new @content-height)
|
||||||
on-end-reached?
|
distance (if (reanimated/get-shared-value calculations-complete?)
|
||||||
animate-topbar-name?]}]
|
(+ (reanimated/get-shared-value distance-from-list-top) change)
|
||||||
(fn [new-scroll-y new-content-size]
|
(- content-height-new window-height))]
|
||||||
(when (and @on-end-reached? (pos? new-scroll-y))
|
(reanimated/set-shared-value distance-from-list-top distance)
|
||||||
(reset! on-end-reached? false))
|
(reset! content-height content-height-new))
|
||||||
(if (< topbar-visible-scroll-y-value new-scroll-y)
|
(when-not (reanimated/get-shared-value calculations-complete?)
|
||||||
(reset! animate-topbar-opacity? true)
|
(js/setTimeout #(reanimated/set-shared-value calculations-complete? true) 10))))
|
||||||
(reset! animate-topbar-opacity? false))
|
|
||||||
(if (< topbar-invisible-scroll-y-value new-scroll-y)
|
(defn keyboard-offset
|
||||||
(reset! animate-topbar-name? true)
|
[distance-from-list-top keyboard-shown keyboard-height keyboard-offset?]
|
||||||
(reset! animate-topbar-name? false))
|
;; Note - keyboard fires multiple events, we are making sure we only offset once
|
||||||
(reanimated/set-shared-value content-size-animated-value new-content-size)
|
(when (> keyboard-height 0)
|
||||||
(reanimated/set-shared-value scroll-y-animated-value (max new-scroll-y 0))))
|
(let [current-distance-from-top (reanimated/get-shared-value distance-from-list-top)]
|
||||||
|
(when (and keyboard-shown (not @keyboard-offset?))
|
||||||
|
(reanimated/set-shared-value distance-from-list-top
|
||||||
|
(+ current-distance-from-top keyboard-height))
|
||||||
|
(reset! keyboard-offset? true))
|
||||||
|
(when (and (not keyboard-shown) @keyboard-offset?)
|
||||||
|
(reanimated/set-shared-value distance-from-list-top
|
||||||
|
(- current-distance-from-top keyboard-height))
|
||||||
|
(reset! keyboard-offset? false)))))
|
||||||
|
|
||||||
(defn f-messages-list-content
|
(defn f-messages-list-content
|
||||||
[{:keys [chat insets scroll-y content-height cover-bg-color keyboard-shown? inner-state-atoms
|
[{:keys [insets distance-from-list-top keyboard-offset? content-height cover-bg-color
|
||||||
big-name-visible? animate-topbar-opacity? composer-active? messages-list-on-layout-finished?
|
show-floating-scroll-down-button? calculations-complete?
|
||||||
on-end-reached? animate-topbar-name?]}]
|
messages-list-on-layout-finished?]}]
|
||||||
(rn/use-effect (fn []
|
|
||||||
(if (and (not @on-end-reached?)
|
|
||||||
(< topbar-visible-scroll-y-value (reanimated/get-shared-value scroll-y)))
|
|
||||||
(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)
|
(let [theme (quo.theme/use-theme-value)
|
||||||
|
chat (rf/sub [:chats/current-chat-chat-view])
|
||||||
|
{:keys [keyboard-shown keyboard-height]} (hooks/use-keyboard)
|
||||||
{window-height :height} (rn/get-window)
|
{window-height :height} (rn/get-window)
|
||||||
context (rf/sub [:chats/current-chat-message-list-view-context])
|
context (rf/sub [:chats/current-chat-message-list-view-context])
|
||||||
messages (rf/sub [:chats/raw-chat-messages-stream (:chat-id chat)])
|
messages (rf/sub [:chats/raw-chat-messages-stream
|
||||||
images (rf/sub [:chats/sending-image])
|
(:chat-id chat)])
|
||||||
recording? (rf/sub [:chats/recording?])
|
recording? (rf/sub [:chats/recording?])]
|
||||||
{:keys [show-floating-scroll-down-button?
|
(keyboard-offset distance-from-list-top keyboard-shown keyboard-height keyboard-offset?)
|
||||||
messages-scroll-y-value-initialized?
|
[rn/view {:style {:flex 3}} ;; Pushes composer to bottom
|
||||||
messages-view-height
|
[rn/view {:style {:flex-shrink 1}} ;; Keeps flat list on top
|
||||||
messages-view-header-height]} inner-state-atoms
|
[reanimated/flat-list
|
||||||
scroll-handler (use-scroll-handler
|
{:key-fn list-key-fn
|
||||||
{:animate-topbar-name? animate-topbar-name?
|
|
||||||
:animate-topbar-opacity? animate-topbar-opacity?
|
|
||||||
:on-end-reached? on-end-reached?
|
|
||||||
:scroll-y-animated-value scroll-y
|
|
||||||
:content-size-animated-value
|
|
||||||
content-height})]
|
|
||||||
[rn/view {:style {:flex 1}}
|
|
||||||
[rnio/flat-list
|
|
||||||
{:root-margin root-margin-for-big-name-visibility-detector
|
|
||||||
:key-fn list-key-fn
|
|
||||||
:ref list-ref
|
:ref list-ref
|
||||||
:bounces false
|
:bounces false
|
||||||
:header [:<>
|
:header [:<>
|
||||||
[list-header insets (:able-to-send-message? context) images theme]
|
[list-header insets (:able-to-send-message? context)]
|
||||||
(when (= (:chat-type chat) constants/private-group-chat-type)
|
(when (= (:chat-type chat) constants/private-group-chat-type)
|
||||||
[list-group-chat-header chat])]
|
[list-group-chat-header chat])]
|
||||||
:footer [list-footer
|
:footer [list-footer
|
||||||
{:theme theme
|
{:theme theme
|
||||||
:chat chat
|
:chat chat
|
||||||
:scroll-y scroll-y
|
:window-height window-height
|
||||||
:cover-bg-color cover-bg-color
|
:distance-from-list-top distance-from-list-top
|
||||||
:on-layout #(footer-on-layout
|
:cover-bg-color cover-bg-color}]
|
||||||
%
|
|
||||||
messages-view-header-height)
|
|
||||||
:messages-view-header-height messages-view-header-height
|
|
||||||
:messages-view-height messages-view-height
|
|
||||||
:big-name-visible? big-name-visible?}]
|
|
||||||
:data messages
|
:data messages
|
||||||
:render-data {:theme theme
|
:render-data {:theme theme
|
||||||
:context context
|
:context context
|
||||||
:keyboard-shown? keyboard-shown?
|
:keyboard-shown? keyboard-shown
|
||||||
:insets insets}
|
:insets insets}
|
||||||
:render-fn render-fn
|
:render-fn render-fn
|
||||||
:on-viewable-items-changed on-viewable-items-changed
|
:on-viewable-items-changed on-viewable-items-changed
|
||||||
:on-content-size-change
|
:on-content-size-change (on-content-size-change
|
||||||
(fn [_ y]
|
{:distance-from-list-top distance-from-list-top
|
||||||
(if (or
|
:window-height window-height
|
||||||
(< minimum-scroll-y-topbar-overlaying-avatar
|
:content-height content-height
|
||||||
(reanimated/get-shared-value scroll-y))
|
:calculations-complete? calculations-complete?})
|
||||||
(< topbar-visible-scroll-y-value
|
:on-end-reached #(list-on-end-reached distance-from-list-top)
|
||||||
(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)))
|
|
||||||
(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. Also makes sure changes in the content size are reflected in `scroll-y` e.g.
|
|
||||||
;; incoming messages scrolling the content up, which should affect the header
|
|
||||||
;; animations/interpolations.
|
|
||||||
(let [content-size-shared (reanimated/get-shared-value
|
|
||||||
content-height)
|
|
||||||
scroll-y-shared (if
|
|
||||||
@messages-scroll-y-value-initialized?
|
|
||||||
(reanimated/get-shared-value
|
|
||||||
scroll-y)
|
|
||||||
y)
|
|
||||||
;; NOTE: when the keyboard is shown and the message list
|
|
||||||
;; doesn't fill the screen, we use a different value for
|
|
||||||
;; scroll-y, which would make sure the avatar is minimized
|
|
||||||
;; appropriately and the header is shown.
|
|
||||||
keyboard-shown-with-unfilled-list? (< y window-height)
|
|
||||||
keyboard-space (- window-height y)
|
|
||||||
keyboard-scroll-value (- y keyboard-space keyboard-space)
|
|
||||||
;; NOTE: doing the calculations as we would in `on-scroll`, so the animations
|
|
||||||
;; and interpolations are triggered properly.
|
|
||||||
content-size (- y
|
|
||||||
window-height
|
|
||||||
;; NOTE: for some reason android insets are
|
|
||||||
;; included in the content size `y`, while iOS
|
|
||||||
;; are not.
|
|
||||||
(when platform/android? (:top insets)))
|
|
||||||
current-y (max (- content-size-shared
|
|
||||||
scroll-y-shared)
|
|
||||||
0)
|
|
||||||
new-scroll-value (if keyboard-shown-with-unfilled-list?
|
|
||||||
keyboard-scroll-value
|
|
||||||
(- content-size
|
|
||||||
current-y))]
|
|
||||||
(when (and (>= new-scroll-value 0)
|
|
||||||
(or (= scroll-y-shared 0)
|
|
||||||
(> (Math/abs (- content-size-shared y))
|
|
||||||
min-message-height)))
|
|
||||||
(reset! messages-scroll-y-value-initialized? true)
|
|
||||||
(scroll-handler new-scroll-value content-size))))
|
|
||||||
:on-end-reached #(list-on-end-reached scroll-y on-end-reached?)
|
|
||||||
:on-scroll-to-index-failed identity
|
:on-scroll-to-index-failed identity
|
||||||
:scroll-indicator-insets {:top (if (:able-to-send-message? context)
|
:scroll-indicator-insets {:top (if (:able-to-send-message? context)
|
||||||
(- composer.constants/composer-default-height 16)
|
(- composer.constants/composer-default-height 16)
|
||||||
|
@ -430,46 +351,17 @@
|
||||||
:on-momentum-scroll-begin state/start-scrolling
|
:on-momentum-scroll-begin state/start-scrolling
|
||||||
:on-momentum-scroll-end state/stop-scrolling
|
:on-momentum-scroll-end state/stop-scrolling
|
||||||
:scroll-event-throttle 16
|
:scroll-event-throttle 16
|
||||||
:on-scroll (fn [event]
|
:on-scroll (reanimated/use-animated-scroll-handler
|
||||||
(let [content-size-y
|
(worklets/messages-list-on-scroll
|
||||||
(- (oops/oget event "nativeEvent.contentSize.height")
|
distance-from-list-top
|
||||||
(oops/oget event
|
(on-scroll-fn show-floating-scroll-down-button?)))
|
||||||
"nativeEvent.layoutMeasurement.height"))
|
:style {:background-color (colors/theme-colors colors/white
|
||||||
current-y (oops/oget event
|
|
||||||
"nativeEvent.contentOffset.y")
|
|
||||||
scroll-distance (- content-size-y current-y)]
|
|
||||||
(scroll-handler scroll-distance content-size-y))
|
|
||||||
(on-scroll event show-floating-scroll-down-button?))
|
|
||||||
:content-container-style {:justify-content :flex-end
|
|
||||||
:min-height @messages-view-height}
|
|
||||||
:style {:background-color (colors/theme-colors
|
|
||||||
colors/white
|
|
||||||
colors/neutral-95
|
colors/neutral-95
|
||||||
theme)}
|
theme)}
|
||||||
:inverted true
|
:inverted true
|
||||||
:on-layout (fn [e]
|
:on-layout (fn [_]
|
||||||
;; FIXME: the 1s timeout is to assure all effects with
|
|
||||||
;; timeouts that depend on the value are considered.
|
|
||||||
;; Hacky, but we're heavily relying on timeouts in the
|
|
||||||
;; composer and need to react to differently (e.g.
|
|
||||||
;; inside effects/use-edit) when the chat has just
|
|
||||||
;; opened and the subsequent times.
|
|
||||||
(js/setTimeout #(reset! messages-list-on-layout-finished?
|
(js/setTimeout #(reset! messages-list-on-layout-finished?
|
||||||
true)
|
true)
|
||||||
1000)
|
1000))
|
||||||
(let [layout-height (oops/oget e
|
|
||||||
"nativeEvent.layout.height")]
|
|
||||||
(reset! messages-view-height layout-height)))
|
|
||||||
:scroll-enabled (not recording?)
|
:scroll-enabled (not recording?)
|
||||||
:content-inset-adjustment-behavior :never}]]))
|
:content-inset-adjustment-behavior :never}]]]))
|
||||||
|
|
||||||
(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)]
|
|
||||||
(if chat-screen-loaded?
|
|
||||||
[:f> f-messages-list-content props]
|
|
||||||
[rn/view {:style {:padding-top top-spacing :flex 1}}
|
|
||||||
[quo/skeleton-list (skeleton-list-props :messages content-height false)]])))
|
|
||||||
|
|
|
@ -3,90 +3,50 @@
|
||||||
[quo.foundations.colors :as colors]
|
[quo.foundations.colors :as colors]
|
||||||
[react-native.reanimated :as reanimated]))
|
[react-native.reanimated :as reanimated]))
|
||||||
|
|
||||||
(defonce ^:const navigation-bar-height 100)
|
(defn navigation-view
|
||||||
(defonce ^:const header-offset 56)
|
[navigation-view-height pinned-banner-height]
|
||||||
|
{:top 0
|
||||||
(defn background-view
|
|
||||||
[theme]
|
|
||||||
{:position :absolute
|
|
||||||
:top 0
|
|
||||||
:left 0
|
:left 0
|
||||||
:right 0
|
:right 0
|
||||||
:height navigation-bar-height
|
:position :absolute
|
||||||
:background-color (colors/theme-colors colors/white-opa-70 colors/neutral-100-opa-70 theme)
|
:height (+ navigation-view-height pinned-banner-height)
|
||||||
:display :flex
|
:z-index 1})
|
||||||
:flex-direction :row
|
|
||||||
:overflow :hidden})
|
|
||||||
|
|
||||||
(defn animated-background-view
|
(defn animated-background-view
|
||||||
[enabled? animation theme]
|
[background-opacity navigation-view-height]
|
||||||
(reanimated/apply-animations-to-style
|
(reanimated/apply-animations-to-style
|
||||||
(when enabled?
|
{:opacity background-opacity}
|
||||||
{:opacity animation})
|
{:height navigation-view-height
|
||||||
(background-view theme)))
|
|
||||||
|
|
||||||
(def blur-view
|
|
||||||
{:position :absolute
|
|
||||||
:top 0
|
:top 0
|
||||||
:left 0
|
:left 0
|
||||||
:right 0
|
:right 0
|
||||||
:height navigation-bar-height
|
:overflow :hidden
|
||||||
:display :flex
|
:position :absolute}))
|
||||||
|
|
||||||
|
(defn header-container
|
||||||
|
[top-insets top-bar-height]
|
||||||
|
{:margin-top top-insets
|
||||||
:flex-direction :row
|
:flex-direction :row
|
||||||
:overflow :hidden})
|
|
||||||
|
|
||||||
(defn animated-blur-view
|
|
||||||
[enabled? animation]
|
|
||||||
(reanimated/apply-animations-to-style
|
|
||||||
(when enabled?
|
|
||||||
{:opacity animation})
|
|
||||||
blur-view))
|
|
||||||
|
|
||||||
(defn navigation-view
|
|
||||||
[loaded?]
|
|
||||||
{:z-index 1
|
|
||||||
:top 0
|
|
||||||
:right 0
|
|
||||||
:left 0
|
|
||||||
:position :absolute
|
|
||||||
:opacity (if loaded? 1 0)})
|
|
||||||
|
|
||||||
(def header-container
|
|
||||||
{:position :absolute
|
|
||||||
:top header-offset
|
|
||||||
:left 0
|
|
||||||
:right 0
|
|
||||||
:padding-bottom 8
|
|
||||||
:padding-horizontal 20
|
:padding-horizontal 20
|
||||||
:display :flex
|
:overflow :hidden
|
||||||
:flex-direction :row
|
:height top-bar-height
|
||||||
:overflow :hidden})
|
:align-items :center})
|
||||||
|
|
||||||
(def header
|
;;;; Content
|
||||||
{:flex 1})
|
|
||||||
|
|
||||||
(defn animated-header
|
(defn header-content-container
|
||||||
[enabled? y-animation opacity-animation]
|
[header-opacity header-position]
|
||||||
(reanimated/apply-animations-to-style
|
(reanimated/apply-animations-to-style
|
||||||
;; here using `top` won't work on Android, so we are using `translateY`
|
{:transform [{:translate-y header-position}]
|
||||||
(when enabled?
|
:opacity header-opacity}
|
||||||
{:transform [{:translateY y-animation}]
|
|
||||||
:opacity opacity-animation})
|
|
||||||
header))
|
|
||||||
|
|
||||||
(def header-content-container
|
|
||||||
{:flex-direction :row
|
{:flex-direction :row
|
||||||
:align-items :center
|
:align-items :center
|
||||||
:margin-left 12
|
:flex 1
|
||||||
:margin-right 8
|
:margin-horizontal 12
|
||||||
:margin-top -4
|
:height 40}))
|
||||||
:height 40})
|
|
||||||
|
|
||||||
(def header-avatar-container
|
|
||||||
{:margin-right 8})
|
|
||||||
|
|
||||||
(def header-text-container
|
(def header-text-container
|
||||||
{:flex 1})
|
{:margin-left 8})
|
||||||
|
|
||||||
(defn header-display-name
|
(defn header-display-name
|
||||||
[]
|
[]
|
||||||
|
@ -94,4 +54,4 @@
|
||||||
|
|
||||||
(defn header-status
|
(defn header-status
|
||||||
[]
|
[]
|
||||||
{:color (colors/theme-colors colors/neutral-80-opa-50 colors/white-opa-50)})
|
{:color (colors/theme-colors colors/neutral-80-opa-50 colors/white-opa-40)})
|
||||||
|
|
|
@ -2,138 +2,60 @@
|
||||||
(:require
|
(:require
|
||||||
[quo.core :as quo]
|
[quo.core :as quo]
|
||||||
[quo.foundations.colors :as colors]
|
[quo.foundations.colors :as colors]
|
||||||
[quo.theme :as quo.theme]
|
|
||||||
[re-frame.db]
|
[re-frame.db]
|
||||||
[react-native.blur :as blur]
|
[react-native.blur :as blur]
|
||||||
[react-native.core :as rn]
|
[react-native.core :as rn]
|
||||||
[react-native.platform :as platform]
|
[react-native.platform :as platform]
|
||||||
[react-native.reanimated :as reanimated]
|
[react-native.reanimated :as reanimated]
|
||||||
|
[react-native.safe-area :as safe-area]
|
||||||
[status-im2.common.home.actions.view :as actions]
|
[status-im2.common.home.actions.view :as actions]
|
||||||
[status-im2.contexts.chat.messages.list.view :refer [topbar-invisible-scroll-y-value]]
|
[status-im2.constants :as constants]
|
||||||
|
[status-im2.contexts.chat.messages.constants :as messages.constants]
|
||||||
[status-im2.contexts.chat.messages.navigation.style :as style]
|
[status-im2.contexts.chat.messages.navigation.style :as style]
|
||||||
[status-im2.contexts.chat.messages.pin.banner.view :as pin.banner]
|
[status-im2.contexts.chat.messages.pin.banner.view :as pin.banner]
|
||||||
[utils.i18n :as i18n]
|
[utils.i18n :as i18n]
|
||||||
[utils.re-frame :as rf]))
|
[utils.re-frame :as rf]
|
||||||
|
[utils.worklets.chat.messages :as worklets]))
|
||||||
|
|
||||||
(defonce ^:const title-opacity-interpolation-start 50)
|
(defn f-header-content-container
|
||||||
;; This has two possibilities, One when sending messages and one when opening chat.
|
[{:keys [chat distance-from-list-top all-loaded? calculations-complete?]}]
|
||||||
(defonce ^:const minimum-scroll-y-topbar-overlaying-avatar 80)
|
(let [{:keys [chat-id group-chat chat-type chat-name
|
||||||
(defonce ^:const minimum-scroll-y-topbar-overlaying-avatar-2 350)
|
emoji]} chat
|
||||||
(defonce ^:const minimum-scroll-y-topbar-overlaying-avatar-composer-active 85)
|
display-name (cond
|
||||||
|
(= chat-type constants/one-to-one-chat-type)
|
||||||
(defn f-view
|
(first (rf/sub
|
||||||
[{:keys [theme scroll-y chat chat-screen-loaded? all-loaded? display-name online? photo-path
|
[:contacts/contact-two-names-by-identity
|
||||||
back-icon animate-topbar-name? composer-active? big-name-visible? animate-topbar-opacity?
|
chat-id]))
|
||||||
on-end-reached?]}]
|
(= chat-type constants/community-chat-type)
|
||||||
(let [{:keys [group-chat chat-id]} chat
|
(str (when emoji (str emoji " ")) "# " chat-name)
|
||||||
opacity-animation (reanimated/use-shared-value 0)
|
:else (str emoji chat-name))
|
||||||
banner-opacity-animation (reanimated/interpolate
|
online? (when-not group-chat (rf/sub [:visibility-status-updates/online? chat-id]))
|
||||||
scroll-y
|
photo-path (when-not group-chat (rf/sub [:chats/photo-path chat-id]))
|
||||||
[(+ style/navigation-bar-height 150)
|
header-opacity (worklets/navigation-header-opacity
|
||||||
(+ style/navigation-bar-height 200)]
|
distance-from-list-top
|
||||||
[0 1]
|
all-loaded?
|
||||||
{:extrapolateLeft "clamp"
|
calculations-complete?
|
||||||
:extrapolateRight "clamp"})
|
messages.constants/content-animation-start-position)
|
||||||
translate-animation (reanimated/use-shared-value
|
header-position (worklets/navigation-header-position
|
||||||
title-opacity-interpolation-start)
|
distance-from-list-top
|
||||||
title-opacity-animation (reanimated/use-shared-value 0)
|
all-loaded?
|
||||||
messages (rf/sub [:chats/raw-chat-messages-stream
|
messages.constants/top-bar-height
|
||||||
(:chat-id chat)])
|
messages.constants/content-animation-start-position)]
|
||||||
more-than-two-messages? (<= 2 (count messages))
|
|
||||||
more-than-four-messages? (<= 4 (count messages))
|
|
||||||
more-than-eight-messages? (<= 8 (count messages))
|
|
||||||
scroll-y-sending-eight-messages-threshold 469]
|
|
||||||
(rn/use-effect
|
|
||||||
(fn []
|
|
||||||
(if
|
|
||||||
(or
|
|
||||||
(and (not composer-active?)
|
|
||||||
more-than-eight-messages?
|
|
||||||
(= :initial-render @big-name-visible?))
|
|
||||||
(and
|
|
||||||
(< minimum-scroll-y-topbar-overlaying-avatar (reanimated/get-shared-value scroll-y))
|
|
||||||
(not @on-end-reached?))
|
|
||||||
(and (if platform/ios? more-than-two-messages? more-than-four-messages?)
|
|
||||||
composer-active?)
|
|
||||||
(and
|
|
||||||
(not @on-end-reached?)
|
|
||||||
@animate-topbar-opacity?)
|
|
||||||
|
|
||||||
(or
|
|
||||||
(< minimum-scroll-y-topbar-overlaying-avatar-2 (reanimated/get-shared-value scroll-y))
|
|
||||||
(and (pos? (count messages))
|
|
||||||
composer-active?
|
|
||||||
(< minimum-scroll-y-topbar-overlaying-avatar-composer-active
|
|
||||||
(reanimated/get-shared-value scroll-y)))))
|
|
||||||
(reanimated/animate opacity-animation 1)
|
|
||||||
(reanimated/animate opacity-animation 0))
|
|
||||||
(if (when-not (and
|
|
||||||
@on-end-reached?
|
|
||||||
(not composer-active?)
|
|
||||||
(true? @big-name-visible?))
|
|
||||||
(or
|
|
||||||
(and
|
|
||||||
(and composer-active?
|
|
||||||
(not @big-name-visible?))
|
|
||||||
(< topbar-invisible-scroll-y-value (reanimated/get-shared-value scroll-y)))
|
|
||||||
(<= scroll-y-sending-eight-messages-threshold (reanimated/get-shared-value scroll-y))
|
|
||||||
(and (not composer-active?)
|
|
||||||
more-than-eight-messages?
|
|
||||||
(= :initial-render @big-name-visible?))
|
|
||||||
;; Keyboard height increasing is different between iOS and Android, That's why we have
|
|
||||||
;; two values.
|
|
||||||
(and (if platform/ios? more-than-two-messages? more-than-four-messages?)
|
|
||||||
(< title-opacity-interpolation-start (reanimated/get-shared-value scroll-y))
|
|
||||||
composer-active?)
|
|
||||||
(and (if platform/ios? more-than-two-messages? more-than-four-messages?)
|
|
||||||
composer-active?)
|
|
||||||
@animate-topbar-name?))
|
|
||||||
(do
|
|
||||||
(reanimated/animate title-opacity-animation 1)
|
|
||||||
(reanimated/animate translate-animation 0))
|
|
||||||
(do
|
|
||||||
(reanimated/animate title-opacity-animation 0)
|
|
||||||
(reanimated/animate translate-animation title-opacity-interpolation-start))))
|
|
||||||
[@animate-topbar-name? @big-name-visible? @animate-topbar-opacity? composer-active?
|
|
||||||
@on-end-reached?])
|
|
||||||
[rn/view {:style (style/navigation-view chat-screen-loaded?)}
|
|
||||||
[reanimated/view
|
[reanimated/view
|
||||||
{:style (style/animated-background-view all-loaded? opacity-animation nil)}]
|
{:style (style/header-content-container header-opacity header-position)}
|
||||||
[reanimated/view {:style (style/animated-blur-view all-loaded? opacity-animation)}
|
|
||||||
[blur/view
|
|
||||||
{:blur-amount 20
|
|
||||||
:blur-type :transparent
|
|
||||||
:overlay-color (colors/theme-colors colors/white-70-blur colors/neutral-95-opa-70-blur theme)
|
|
||||||
:blur-radius (if platform/ios? 20 10)
|
|
||||||
:style {:flex 1}}]]
|
|
||||||
|
|
||||||
[rn/view {:style style/header-container}
|
|
||||||
[quo/button
|
|
||||||
{:icon-only? true
|
|
||||||
:type :grey
|
|
||||||
:background :blur
|
|
||||||
:size 32
|
|
||||||
:accessibility-label :back-button
|
|
||||||
:on-press #(rf/dispatch [:navigate-back])}
|
|
||||||
back-icon]
|
|
||||||
[reanimated/view
|
|
||||||
{:style (style/animated-header all-loaded? translate-animation title-opacity-animation)}
|
|
||||||
[rn/view {:style style/header-content-container}
|
|
||||||
(when-not group-chat
|
(when-not group-chat
|
||||||
[rn/view {:style style/header-avatar-container}
|
|
||||||
[quo/user-avatar
|
[quo/user-avatar
|
||||||
{:full-name display-name
|
{:full-name display-name
|
||||||
:online? online?
|
:online? online?
|
||||||
:profile-picture photo-path
|
:profile-picture photo-path
|
||||||
:size :small}]])
|
:size :small}])
|
||||||
[rn/view {:style style/header-text-container}
|
[rn/view {:style style/header-text-container}
|
||||||
[rn/view {:style {:flex-direction :row}}
|
|
||||||
[quo/text
|
[quo/text
|
||||||
{:weight :semi-bold
|
{:weight :semi-bold
|
||||||
:size :paragraph-1
|
:size :paragraph-1
|
||||||
:number-of-lines 1
|
:number-of-lines 1
|
||||||
:style (style/header-display-name)}
|
:style (style/header-display-name)}
|
||||||
display-name]]
|
display-name]
|
||||||
(when-not group-chat
|
(when-not group-chat
|
||||||
[quo/text
|
[quo/text
|
||||||
{:number-of-lines 1
|
{:number-of-lines 1
|
||||||
|
@ -141,7 +63,72 @@
|
||||||
:size :paragraph-2
|
:size :paragraph-2
|
||||||
:style (style/header-status)}
|
:style (style/header-status)}
|
||||||
(i18n/label
|
(i18n/label
|
||||||
(if online? :t/online :t/offline))])]]]
|
(if online? :t/online :t/offline))])]]))
|
||||||
|
|
||||||
|
(defn f-animated-background-and-pinned-banner
|
||||||
|
[{:keys [chat-id navigation-view-height distance-from-list-top all-loaded?]}]
|
||||||
|
(let [animation-distance messages.constants/header-animation-distance
|
||||||
|
props {:distance-from-list-top distance-from-list-top
|
||||||
|
:all-loaded? all-loaded?}
|
||||||
|
background-opacity (worklets/interpolate-navigation-view-opacity
|
||||||
|
(assoc props
|
||||||
|
:start-position
|
||||||
|
messages.constants/header-container-top-margin
|
||||||
|
:end-position
|
||||||
|
(+ animation-distance
|
||||||
|
messages.constants/header-container-top-margin)))
|
||||||
|
banner-opacity (worklets/interpolate-navigation-view-opacity
|
||||||
|
(assoc props
|
||||||
|
:start-position
|
||||||
|
(+ navigation-view-height
|
||||||
|
messages.constants/pinned-banner-animation-start-position)
|
||||||
|
:end-position
|
||||||
|
(+ animation-distance
|
||||||
|
navigation-view-height
|
||||||
|
messages.constants/pinned-banner-animation-start-position)))]
|
||||||
|
[:<>
|
||||||
|
[reanimated/view {:style (style/animated-background-view background-opacity navigation-view-height)}
|
||||||
|
[blur/view
|
||||||
|
{:style {:flex 1}
|
||||||
|
:blur-amount 20
|
||||||
|
:blur-type :transparent
|
||||||
|
:overlay-color (colors/theme-colors colors/white-70-blur colors/neutral-95-opa-70-blur)
|
||||||
|
:blur-radius (if platform/ios? 20 10)}]]
|
||||||
|
[pin.banner/banner
|
||||||
|
{:chat-id chat-id
|
||||||
|
:banner-opacity banner-opacity
|
||||||
|
:top-offset navigation-view-height}]]))
|
||||||
|
|
||||||
|
(defn f-view
|
||||||
|
[{:keys [distance-from-list-top calculations-complete?]}]
|
||||||
|
(let [{:keys [chat-id chat-type] :as chat} (rf/sub [:chats/current-chat-chat-view])
|
||||||
|
all-loaded? (reanimated/use-shared-value false)
|
||||||
|
all-loaded-sub (rf/sub [:chats/all-loaded? chat-id])
|
||||||
|
top-insets (safe-area/get-top)
|
||||||
|
top-bar-height messages.constants/top-bar-height
|
||||||
|
navigation-view-height (+ top-bar-height top-insets)]
|
||||||
|
(reanimated/set-shared-value all-loaded? all-loaded-sub)
|
||||||
|
[rn/view
|
||||||
|
{:style (style/navigation-view navigation-view-height messages.constants/pinned-banner-height)}
|
||||||
|
[:f> f-animated-background-and-pinned-banner
|
||||||
|
{:chat-id chat-id
|
||||||
|
:navigation-view-height navigation-view-height
|
||||||
|
:distance-from-list-top distance-from-list-top
|
||||||
|
:all-loaded? all-loaded?}]
|
||||||
|
[rn/view {:style (style/header-container top-insets top-bar-height)}
|
||||||
|
[quo/button
|
||||||
|
{:icon-only? true
|
||||||
|
:type :grey
|
||||||
|
:background :blur
|
||||||
|
:size 32
|
||||||
|
:accessibility-label :back-button
|
||||||
|
:on-press #(rf/dispatch [:navigate-back])}
|
||||||
|
(if (= chat-type constants/community-chat-type) :i/arrow-left :i/close)]
|
||||||
|
[:f> f-header-content-container
|
||||||
|
{:chat chat
|
||||||
|
:distance-from-list-top distance-from-list-top
|
||||||
|
:all-loaded? all-loaded?
|
||||||
|
:calculations-complete? calculations-complete?}]
|
||||||
[quo/button
|
[quo/button
|
||||||
{:icon-only? true
|
{:icon-only? true
|
||||||
:type :grey
|
:type :grey
|
||||||
|
@ -152,16 +139,4 @@
|
||||||
(rf/dispatch [:dismiss-keyboard])
|
(rf/dispatch [:dismiss-keyboard])
|
||||||
(rf/dispatch [:show-bottom-sheet
|
(rf/dispatch [:show-bottom-sheet
|
||||||
{:content (fn [] [actions/chat-actions chat true])}]))}
|
{:content (fn [] [actions/chat-actions chat true])}]))}
|
||||||
:i/options]]
|
:i/options]]]))
|
||||||
[:f>
|
|
||||||
pin.banner/f-banner
|
|
||||||
{:chat-id chat-id
|
|
||||||
:opacity-animation banner-opacity-animation
|
|
||||||
:all-loaded? all-loaded?
|
|
||||||
:top-offset style/navigation-bar-height}]]))
|
|
||||||
|
|
||||||
(defn- internal-navigation-view
|
|
||||||
[params]
|
|
||||||
[:f> f-view params])
|
|
||||||
|
|
||||||
(def navigation-view (quo.theme/with-theme internal-navigation-view))
|
|
||||||
|
|
|
@ -1,41 +1,17 @@
|
||||||
(ns status-im2.contexts.chat.messages.pin.banner.style
|
(ns status-im2.contexts.chat.messages.pin.banner.style
|
||||||
(:require
|
(:require
|
||||||
[quo.foundations.colors :as colors]
|
[react-native.reanimated :as reanimated]
|
||||||
[react-native.platform :as platform]
|
[status-im2.contexts.chat.messages.constants :as messages.constants]))
|
||||||
[react-native.reanimated :as reanimated]))
|
|
||||||
|
|
||||||
(defonce ^:const pinned-banner-height 40)
|
(def container
|
||||||
|
|
||||||
(defn blur-container-style
|
|
||||||
[top-offset opacity-animation enabled?]
|
|
||||||
(reanimated/apply-animations-to-style
|
|
||||||
{:opacity opacity-animation}
|
|
||||||
{:position :absolute
|
{:position :absolute
|
||||||
:display (if enabled? :flex :none)
|
:overflow :hidden
|
||||||
:top top-offset
|
|
||||||
:left 0
|
:left 0
|
||||||
:right 0
|
:right 0
|
||||||
:bottom 0
|
:height messages.constants/pinned-banner-height})
|
||||||
:height pinned-banner-height
|
|
||||||
:overflow :hidden}))
|
|
||||||
|
|
||||||
(defn blur-view-style
|
(defn container-animated-style
|
||||||
[]
|
[top-offset banner-opacity]
|
||||||
{:style {:flex 1}
|
|
||||||
:blur-radius (if platform/ios? 20 10)
|
|
||||||
:blur-type (colors/theme-colors :light :dark)
|
|
||||||
:blur-amount 20})
|
|
||||||
|
|
||||||
(defn pinned-banner
|
|
||||||
[top-offset]
|
|
||||||
{:position :absolute
|
|
||||||
:left 0
|
|
||||||
:right 0
|
|
||||||
:top top-offset})
|
|
||||||
|
|
||||||
(defn animated-pinned-banner
|
|
||||||
[top-offset enabled? animation]
|
|
||||||
(reanimated/apply-animations-to-style
|
(reanimated/apply-animations-to-style
|
||||||
(when enabled?
|
{:opacity banner-opacity}
|
||||||
{:opacity animation})
|
(assoc container :top top-offset)))
|
||||||
(pinned-banner top-offset)))
|
|
||||||
|
|
|
@ -1,27 +1,31 @@
|
||||||
(ns status-im2.contexts.chat.messages.pin.banner.view
|
(ns status-im2.contexts.chat.messages.pin.banner.view
|
||||||
(:require
|
(:require
|
||||||
[quo.core :as quo]
|
[quo.core :as quo]
|
||||||
|
[quo.theme :as theme]
|
||||||
[react-native.blur :as blur]
|
[react-native.blur :as blur]
|
||||||
[react-native.core :as rn]
|
[react-native.platform :as platform]
|
||||||
[react-native.reanimated :as reanimated]
|
[react-native.reanimated :as reanimated]
|
||||||
[status-im2.contexts.chat.messages.pin.banner.style :as style]
|
[status-im2.contexts.chat.messages.pin.banner.style :as style]
|
||||||
[utils.re-frame :as rf]))
|
[utils.re-frame :as rf]))
|
||||||
|
|
||||||
(defn f-blur-view
|
|
||||||
[top-offset opacity-animation enabled?]
|
|
||||||
[reanimated/view {:style (style/blur-container-style top-offset opacity-animation enabled?)}
|
|
||||||
[blur/view (style/blur-view-style)]])
|
|
||||||
|
|
||||||
(defn f-banner
|
(defn f-banner
|
||||||
[{:keys [chat-id opacity-animation all-loaded? top-offset]}]
|
[{:keys [chat-id banner-opacity top-offset]} latest-pin-text pins-count]
|
||||||
(let [latest-pin-text (rf/sub [:chats/last-pinned-message-text chat-id])
|
[reanimated/view {:style (style/container-animated-style top-offset banner-opacity)}
|
||||||
pins-count (rf/sub [:chats/pin-messages-count chat-id])]
|
[blur/view
|
||||||
[rn/view
|
{:style style/container
|
||||||
[:f> f-blur-view top-offset opacity-animation (> pins-count 0)]
|
:blur-radius (if platform/ios? 20 10)
|
||||||
[reanimated/view {:style (style/animated-pinned-banner top-offset all-loaded? opacity-animation)}
|
:blur-type (if (theme/dark?) :dark :light)
|
||||||
|
:blur-amount 20}]
|
||||||
[quo/banner
|
[quo/banner
|
||||||
{:latest-pin-text latest-pin-text
|
{:latest-pin-text latest-pin-text
|
||||||
:pins-count pins-count
|
:pins-count pins-count
|
||||||
:on-press (fn []
|
:on-press (fn []
|
||||||
(rf/dispatch [:dismiss-keyboard])
|
(rf/dispatch [:dismiss-keyboard])
|
||||||
(rf/dispatch [:pin-message/show-pins-bottom-sheet chat-id]))}]]]))
|
(rf/dispatch [:pin-message/show-pins-bottom-sheet chat-id]))}]])
|
||||||
|
|
||||||
|
(defn banner
|
||||||
|
[{:keys [chat-id] :as props}]
|
||||||
|
(let [latest-pin-text (rf/sub [:chats/last-pinned-message-text chat-id])
|
||||||
|
pins-count (rf/sub [:chats/pin-messages-count chat-id])]
|
||||||
|
(when (> pins-count 0)
|
||||||
|
[:f> f-banner props latest-pin-text pins-count])))
|
||||||
|
|
|
@ -1,111 +1,62 @@
|
||||||
(ns status-im2.contexts.chat.messages.view
|
(ns status-im2.contexts.chat.messages.view
|
||||||
(:require
|
(:require
|
||||||
[react-native.core :as rn]
|
[react-native.core :as rn]
|
||||||
[react-native.hooks :as hooks]
|
|
||||||
[react-native.reanimated :as reanimated]
|
[react-native.reanimated :as reanimated]
|
||||||
[react-native.safe-area :as safe-area]
|
[react-native.safe-area :as safe-area]
|
||||||
[reagent.core :as reagent]
|
[reagent.core :as reagent]
|
||||||
[status-im2.constants :as constants]
|
|
||||||
[status-im2.contexts.chat.composer.view :as composer.view]
|
[status-im2.contexts.chat.composer.view :as composer.view]
|
||||||
[status-im2.contexts.chat.messages.contact-requests.bottom-drawer :as contact-requests.bottom-drawer]
|
|
||||||
[status-im2.contexts.chat.messages.list.style :as style]
|
[status-im2.contexts.chat.messages.list.style :as style]
|
||||||
[status-im2.contexts.chat.messages.list.view :as list.view]
|
[status-im2.contexts.chat.messages.list.view :as list.view]
|
||||||
[status-im2.contexts.chat.messages.navigation.view :as messages.navigation]
|
[status-im2.contexts.chat.messages.navigation.view :as messages.navigation]
|
||||||
|
[status-im2.contexts.chat.placeholder.view :as placeholder.view]
|
||||||
[utils.re-frame :as rf]))
|
[utils.re-frame :as rf]))
|
||||||
|
|
||||||
;; NOTE(parvesh) - I am working on refactoring/optimization of the chat screen for performance
|
;; NOTE(parvesh) - I am working on refactoring/optimization of the chat screen for performance
|
||||||
;; improvement. Please avoid refactoring these files. Also if you are not already working on bug
|
;; improvement. Please avoid refactoring these files. Also if you are not already working on bug
|
||||||
;; fixes related to the chat navigation bar, please skip them.
|
;; fixes related to the chat navigation bar, please skip them.
|
||||||
;; And ping me, so I can address them while refactoring
|
;; And ping me, so I can address them while refactoring
|
||||||
(defn f-chat
|
(defn- f-chat-screen
|
||||||
[{:keys [show-floating-scroll-down-button? animate-topbar-name?
|
[calculations-complete?]
|
||||||
big-name-visible? animate-topbar-opacity? on-end-reached? messages-list-on-layout-finished?]
|
|
||||||
:as inner-state-atoms}]
|
|
||||||
(let [insets (safe-area/get-insets)
|
(let [insets (safe-area/get-insets)
|
||||||
scroll-y (reanimated/use-shared-value 0)
|
keyboard-offset? (atom false)
|
||||||
content-height (reanimated/use-shared-value 0)
|
content-height (atom 0)
|
||||||
{:keys [keyboard-shown]} (hooks/use-keyboard)
|
show-floating-scroll-down-button? (reagent/atom false)
|
||||||
{:keys [chat-id
|
messages-list-on-layout-finished? (reagent/atom false)
|
||||||
contact-request-state
|
distance-from-list-top (reanimated/use-shared-value 0)]
|
||||||
group-chat
|
|
||||||
able-to-send-message?
|
|
||||||
chat-type
|
|
||||||
chat-name
|
|
||||||
emoji]
|
|
||||||
:as chat} (rf/sub [:chats/current-chat-chat-view])
|
|
||||||
chat-screen-loaded? (rf/sub [:shell/chat-screen-loaded?])
|
|
||||||
all-loaded? (when chat-screen-loaded?
|
|
||||||
(rf/sub [:chats/all-loaded? (:chat-id chat)]))
|
|
||||||
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))
|
|
||||||
online? (rf/sub [:visibility-status-updates/online? chat-id])
|
|
||||||
photo-path (rf/sub [:chats/photo-path chat-id])
|
|
||||||
back-icon (if (= chat-type constants/one-to-one-chat-type)
|
|
||||||
:i/close
|
|
||||||
:i/arrow-left)
|
|
||||||
{:keys [focused?]} (rf/sub [:chats/current-chat-input])]
|
|
||||||
;; Note - Don't pass `behavior :height` to keyboard avoiding view,. It breaks composer -
|
|
||||||
;; https://github.com/status-im/status-mobile/issues/16595
|
|
||||||
[rn/keyboard-avoiding-view
|
[rn/keyboard-avoiding-view
|
||||||
{:style style/keyboard-avoiding-container
|
{:style style/keyboard-avoiding-container
|
||||||
:keyboard-vertical-offset (- (:bottom insets))}
|
:keyboard-vertical-offset (- (:bottom insets))}
|
||||||
|
[:f> messages.navigation/f-view
|
||||||
[list.view/message-list-content-view
|
{:distance-from-list-top distance-from-list-top
|
||||||
{:chat chat
|
:calculations-complete? calculations-complete?}]
|
||||||
:insets insets
|
[:f> list.view/f-messages-list-content
|
||||||
:scroll-y scroll-y
|
{:insets insets
|
||||||
:content-height content-height
|
:content-height content-height
|
||||||
|
:keyboard-offset? keyboard-offset?
|
||||||
|
:calculations-complete? calculations-complete?
|
||||||
|
:distance-from-list-top distance-from-list-top
|
||||||
|
:messages-list-on-layout-finished? messages-list-on-layout-finished?
|
||||||
:cover-bg-color :turquoise
|
:cover-bg-color :turquoise
|
||||||
:keyboard-shown? keyboard-shown
|
:show-floating-scroll-down-button? show-floating-scroll-down-button?}]
|
||||||
:inner-state-atoms inner-state-atoms
|
[composer.view/composer
|
||||||
:animate-topbar-name? animate-topbar-name?
|
|
||||||
:big-name-visible? big-name-visible?
|
|
||||||
:animate-topbar-opacity? animate-topbar-opacity?
|
|
||||||
:composer-active? focused?
|
|
||||||
:on-end-reached? on-end-reached?
|
|
||||||
:messages-list-on-layout-finished? messages-list-on-layout-finished?}]
|
|
||||||
|
|
||||||
[messages.navigation/navigation-view
|
|
||||||
{:scroll-y scroll-y
|
|
||||||
:animate-topbar-name? animate-topbar-name?
|
|
||||||
:back-icon back-icon
|
|
||||||
:chat chat
|
|
||||||
:chat-screen-loaded? chat-screen-loaded?
|
|
||||||
:all-loaded? all-loaded?
|
|
||||||
:display-name display-name
|
|
||||||
:online? online?
|
|
||||||
:photo-path photo-path
|
|
||||||
:big-name-visible? big-name-visible?
|
|
||||||
:animate-topbar-opacity? animate-topbar-opacity?
|
|
||||||
:composer-active? focused?
|
|
||||||
:on-end-reached? on-end-reached?}]
|
|
||||||
|
|
||||||
(when (seq chat)
|
|
||||||
(if able-to-send-message?
|
|
||||||
[:f> composer.view/composer
|
|
||||||
{:insets insets
|
{:insets insets
|
||||||
:scroll-to-bottom-fn list.view/scroll-to-bottom
|
:scroll-to-bottom-fn list.view/scroll-to-bottom
|
||||||
:show-floating-scroll-down-button? show-floating-scroll-down-button?
|
:messages-list-on-layout-finished? messages-list-on-layout-finished?
|
||||||
:messages-list-on-layout-finished? messages-list-on-layout-finished?}]
|
:show-floating-scroll-down-button? show-floating-scroll-down-button?}]]))
|
||||||
[contact-requests.bottom-drawer/view chat-id contact-request-state group-chat]))]))
|
|
||||||
|
(defn lazy-chat-screen
|
||||||
|
[calculations-complete?]
|
||||||
|
(let [screen-loaded? (rf/sub [:shell/chat-screen-loaded?])]
|
||||||
|
(when screen-loaded?
|
||||||
|
[:f> f-chat-screen calculations-complete?])))
|
||||||
|
|
||||||
|
(defn- f-chat
|
||||||
|
[]
|
||||||
|
(let [calculations-complete? (reanimated/use-shared-value false)]
|
||||||
|
[:<>
|
||||||
|
[lazy-chat-screen calculations-complete?]
|
||||||
|
[:f> placeholder.view/f-view calculations-complete?]]))
|
||||||
|
|
||||||
(defn chat
|
(defn chat
|
||||||
[]
|
[]
|
||||||
(let [inner-state-atoms
|
[:f> f-chat])
|
||||||
{:extra-keyboard-height (reagent/atom 0)
|
|
||||||
:show-floating-scroll-down-button? (reagent/atom false)
|
|
||||||
:messages-scroll-y-value-initialized? (reagent/atom false)
|
|
||||||
:messages-view-height (reagent/atom 0)
|
|
||||||
:messages-view-header-height (reagent/atom 0)
|
|
||||||
:animate-topbar-name? (reagent/atom false)
|
|
||||||
:big-name-visible? (reagent/atom :initial-render)
|
|
||||||
:animate-topbar-opacity? (reagent/atom false)
|
|
||||||
:on-end-reached? (reagent/atom false)
|
|
||||||
:messages-list-on-layout-finished? (reagent/atom false)}]
|
|
||||||
[:f> f-chat inner-state-atoms]))
|
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
(ns status-im2.contexts.chat.placeholder.style
|
||||||
|
(:require [quo.foundations.colors :as colors]
|
||||||
|
[react-native.reanimated :as reanimated]))
|
||||||
|
|
||||||
|
(defn container
|
||||||
|
[top opacity z-index]
|
||||||
|
(reanimated/apply-animations-to-style
|
||||||
|
{:opacity opacity
|
||||||
|
:z-index z-index}
|
||||||
|
{:position :absolute
|
||||||
|
:padding-top top
|
||||||
|
:top 0
|
||||||
|
:left 0
|
||||||
|
:right 0
|
||||||
|
:bottom 0
|
||||||
|
:background-color (colors/theme-colors colors/white colors/neutral-95)}))
|
|
@ -0,0 +1,23 @@
|
||||||
|
(ns status-im2.contexts.chat.placeholder.view
|
||||||
|
(:require
|
||||||
|
[quo.core :as quo]
|
||||||
|
[react-native.core :as rn]
|
||||||
|
[react-native.reanimated :as reanimated]
|
||||||
|
[react-native.safe-area :as safe-area]
|
||||||
|
[status-im2.contexts.chat.placeholder.style :as style]
|
||||||
|
[utils.worklets.chat.messages :as worklets]))
|
||||||
|
|
||||||
|
(defn- loading-skeleton
|
||||||
|
[]
|
||||||
|
[quo/skeleton-list
|
||||||
|
{:content :messages
|
||||||
|
:parent-height (:height (rn/get-window))
|
||||||
|
:animated? false}])
|
||||||
|
|
||||||
|
(defn f-view
|
||||||
|
[calculations-complete?]
|
||||||
|
(let [top (safe-area/get-top)
|
||||||
|
opacity (worklets/placeholder-opacity calculations-complete?)
|
||||||
|
z-index (worklets/placeholder-z-index calculations-complete?)]
|
||||||
|
[reanimated/view {:style (style/container top opacity z-index)}
|
||||||
|
[loading-skeleton]]))
|
|
@ -0,0 +1,7 @@
|
||||||
|
(ns utils.worklets.chat.lightbox)
|
||||||
|
|
||||||
|
(def ^:private layout-worklets (js/require "../src/js/worklets/chat/lightbox.js"))
|
||||||
|
|
||||||
|
(defn info-layout
|
||||||
|
[input top?]
|
||||||
|
(.infoLayout ^js layout-worklets input top?))
|
|
@ -0,0 +1,37 @@
|
||||||
|
(ns utils.worklets.chat.messages)
|
||||||
|
|
||||||
|
(def ^:private messages-worklets (js/require "../src/js/worklets/chat/messages.js"))
|
||||||
|
|
||||||
|
;;;; Navigtion
|
||||||
|
(defn navigation-header-opacity
|
||||||
|
[distance-from-list-top all-loaded? calculations-complete? start-position]
|
||||||
|
(.navigationHeaderOpacity ^js messages-worklets
|
||||||
|
distance-from-list-top
|
||||||
|
all-loaded?
|
||||||
|
calculations-complete?
|
||||||
|
start-position))
|
||||||
|
|
||||||
|
(defn navigation-header-position
|
||||||
|
[distance-from-list-top all-loaded? top-bar-height start-position]
|
||||||
|
(.navigationHeaderPosition ^js messages-worklets
|
||||||
|
distance-from-list-top
|
||||||
|
all-loaded?
|
||||||
|
top-bar-height
|
||||||
|
start-position))
|
||||||
|
|
||||||
|
(defn interpolate-navigation-view-opacity
|
||||||
|
[props]
|
||||||
|
(.interpolateNavigationViewOpacity ^js messages-worklets (clj->js props)))
|
||||||
|
|
||||||
|
(defn messages-list-on-scroll
|
||||||
|
[distance-from-list-top callback]
|
||||||
|
(.messagesListOnScroll ^js messages-worklets distance-from-list-top callback))
|
||||||
|
|
||||||
|
;;;; Placeholder
|
||||||
|
(defn placeholder-opacity
|
||||||
|
[calculations-complete?]
|
||||||
|
(.placeholderOpacity ^js messages-worklets calculations-complete?))
|
||||||
|
|
||||||
|
(defn placeholder-z-index
|
||||||
|
[calculations-complete?]
|
||||||
|
(.placeholderZIndex ^js messages-worklets calculations-complete?))
|
|
@ -1,7 +0,0 @@
|
||||||
(ns utils.worklets.lightbox)
|
|
||||||
|
|
||||||
(def ^:private layout-worklets (js/require "../src/js/worklets/lightbox.js"))
|
|
||||||
|
|
||||||
(defn info-layout
|
|
||||||
[input top?]
|
|
||||||
(.infoLayout ^js layout-worklets input top?))
|
|
Loading…
Reference in New Issue