diff --git a/src/js/worklets/communities.js b/src/js/worklets/communities.js index d158c753d32..3c9ae2acb5f 100644 --- a/src/js/worklets/communities.js +++ b/src/js/worklets/communities.js @@ -10,20 +10,45 @@ import { import { Platform } from 'react-native'; +export function useStartScrollValue(isCollapsed, collapseThreshold) { + return useDerivedValue(() => { + return isCollapsed ? -collapseThreshold.value : 0; + }); +} + +export function useScrollValue(isCollapsed, collapseThreshold) { + return useDerivedValue(() => { + return isCollapsed ? collapseThreshold.value : 0; + }); +} + +export function useDerivedValueAdd(sharedValue, value) { + return useDerivedValue(() => { + return sharedValue.value + value; + }); +} + +export function useDerivedValueMul(sharedValue, value) { + return useDerivedValue(() => { + return sharedValue.value * value; + }); +} + export function useLogoStyles({ + initialState, scrollAmount, - expandHeaderThreshold, + collapseThreshold, sheetDisplacementThreshold, textMovementThreshold, }) { return useAnimatedStyle(() => { - const firstDisplacement = scrollAmount.value < expandHeaderThreshold; - if (firstDisplacement) { + const isFirstDisplacement = initialState.value === 'expanded' || scrollAmount.value < collapseThreshold.value; + if (isFirstDisplacement) { return { transform: [ { translateX: 20 }, - { translateY: interpolate(scrollAmount.value, [0, expandHeaderThreshold], [0, -42.5], 'clamp') }, - { scale: interpolate(scrollAmount.value, [0, textMovementThreshold], [1, 0.4], 'clamp') }, + { translateY: interpolate(scrollAmount.value, [0, collapseThreshold.value], [0, -42.5], 'clamp') }, + { scale: interpolate(scrollAmount.value, [0, textMovementThreshold.value], [1, 0.4], 'clamp') }, ], }; } else { @@ -33,7 +58,7 @@ export function useLogoStyles({ { translateY: interpolate( scrollAmount.value, - [expandHeaderThreshold, sheetDisplacementThreshold], + [collapseThreshold.value, sheetDisplacementThreshold.value], [-42.5, -50.5], 'clamp', ), @@ -45,19 +70,19 @@ export function useLogoStyles({ }); } -export function useSheetStyles({ scrollAmount, expandHeaderThreshold, sheetDisplacementThreshold }) { +export function useSheetStyles({ initialState, scrollAmount, collapseThreshold, sheetDisplacementThreshold }) { return useAnimatedStyle(() => { - const firstDisplacement = scrollAmount.value < expandHeaderThreshold; - if (firstDisplacement) { + const isFirstDisplacement = initialState.value === 'expanded' || scrollAmount.value < collapseThreshold.value; + if (isFirstDisplacement) { return { - transform: [{ translateY: interpolate(scrollAmount.value, [0, expandHeaderThreshold], [40, 0], 'clamp') }], + transform: [{ translateY: interpolate(scrollAmount.value, [0, collapseThreshold.value], [40, 0], 'clamp') }], borderTopLeftRadius: 20, borderTopRightRadius: 20, }; } else { const radius = interpolate( scrollAmount.value, - [expandHeaderThreshold, sheetDisplacementThreshold], + [collapseThreshold.value, sheetDisplacementThreshold.value], [20, 0], 'clamp', ); @@ -66,7 +91,7 @@ export function useSheetStyles({ scrollAmount, expandHeaderThreshold, sheetDispl { translateY: interpolate( scrollAmount.value, - [expandHeaderThreshold, sheetDisplacementThreshold], + [collapseThreshold.value, sheetDisplacementThreshold.value], [0, -8], 'clamp', ), @@ -79,35 +104,56 @@ export function useSheetStyles({ scrollAmount, expandHeaderThreshold, sheetDispl }); } -export function useNameStyles({ scrollAmount, expandHeaderThreshold, textMovementThreshold }) { +export function useNameStyles({ initialState, scrollAmount, collapseThreshold, textMovementThreshold }) { return useAnimatedStyle(() => { - const animationProgress = interpolate( - scrollAmount.value, - [textMovementThreshold, expandHeaderThreshold], - [0, 40], - 'clamp', - ); + let horizontalPosition; + if (initialState.value === 'collapsed') { + horizontalPosition = 40; + } else if (initialState.value === 'expanded') { + horizontalPosition = 0; + } else { + horizontalPosition = interpolate( + scrollAmount.value, + [textMovementThreshold.value, collapseThreshold.value], + [0, 40], + 'clamp', + ); + } + + let verticalPosition; + if (initialState.value === 'collapsed') { + verticalPosition = -44.5; + } else if (initialState.value === 'expanded') { + verticalPosition = 0; + } else { + verticalPosition = interpolate( + scrollAmount.value, + [textMovementThreshold.value, collapseThreshold.value], + [0, -44.5], + 'clamp', + ); + } + return { - marginRight: animationProgress, - transform: [ - { translateX: animationProgress }, - { - translateY: interpolate( - scrollAmount.value, - [textMovementThreshold, expandHeaderThreshold], - [0, -44.5], - 'clamp', - ), - }, - ], + marginRight: horizontalPosition, + transform: [{ translateX: horizontalPosition }, { translateY: verticalPosition }], }; }); } -export function useInfoStyles({ scrollAmount, infoOpacityThreshold }) { +export function useInfoStyles({ initialState, scrollAmount, collapseThreshold, infoOpacityThresholdFactor }) { return useAnimatedStyle(() => { + let opacity; + if (initialState.value === 'collapsed') { + opacity = 0; + } else if (initialState.value === 'expanded') { + opacity = 1; + } else { + const infoOpacityThreshold = collapseThreshold.value * infoOpacityThresholdFactor; + opacity = interpolate(scrollAmount.value, [0, infoOpacityThreshold], [1, 0.2], 'extend'); + } return { - opacity: interpolate(scrollAmount.value, [0, infoOpacityThreshold], [1, 0.2], 'extend'), + opacity: opacity, }; }); } @@ -115,19 +161,24 @@ export function useInfoStyles({ scrollAmount, infoOpacityThreshold }) { export function useChannelsStyles({ scrollAmount, headerHeight, - expandHeaderThreshold, + collapseThreshold, sheetDisplacementThreshold, expandHeaderLimit, }) { return useAnimatedStyle(() => { const headerDisplacement = (headerHeight.value - 55.5) * -1; - const firstDisplacement = scrollAmount.value < expandHeaderThreshold; - const secondDisplacement = scrollAmount.value > sheetDisplacementThreshold; + const firstDisplacement = scrollAmount.value < collapseThreshold.value; + const secondDisplacement = scrollAmount.value > sheetDisplacementThreshold.value; if (firstDisplacement) { return { transform: [ { - translateY: interpolate(scrollAmount.value, [0, expandHeaderThreshold], [39, headerDisplacement], 'clamp'), + translateY: interpolate( + scrollAmount.value, + [0, collapseThreshold.value], + [39, headerDisplacement], + 'clamp', + ), }, ], }; @@ -137,7 +188,7 @@ export function useChannelsStyles({ { translateY: interpolate( scrollAmount.value, - [sheetDisplacementThreshold, expandHeaderLimit], + [sheetDisplacementThreshold.value, expandHeaderLimit.value], [headerDisplacement - 8, headerDisplacement - 64], 'clamp', ), @@ -150,7 +201,7 @@ export function useChannelsStyles({ { translateY: interpolate( scrollAmount.value, - [expandHeaderThreshold, sheetDisplacementThreshold], + [collapseThreshold.value, sheetDisplacementThreshold.value], [headerDisplacement, headerDisplacement - 8], 'clamp', ), @@ -158,19 +209,24 @@ export function useChannelsStyles({ ], }; } - }, [headerHeight.value]); + }); } export function useScrollTo({ animatedRef, scrollAmount, expandHeaderLimit }) { const isAndroid = Platform.OS === 'android'; return useDerivedValue(() => { - scrollTo(animatedRef, 0, scrollAmount.value - expandHeaderLimit, isAndroid); + scrollTo(animatedRef, 0, scrollAmount.value - expandHeaderLimit.value, isAndroid); }); } -export function useHeaderOpacity({ scrollAmount, expandHeaderThreshold, sheetDisplacementThreshold }) { +export function useHeaderOpacity({ scrollAmount, collapseThreshold, sheetDisplacementThreshold }) { return useDerivedValue(() => { - return interpolate(scrollAmount.value, [expandHeaderThreshold, sheetDisplacementThreshold], [0, 1], 'clamp'); + return interpolate( + scrollAmount.value, + [collapseThreshold.value, sheetDisplacementThreshold.value], + [0, 1], + 'clamp', + ); }); } @@ -180,9 +236,15 @@ export function useOppositeHeaderOpacity(headerOpacity) { }); } -export function useNavContentOpacity({ scrollAmount, sheetDisplacementThreshold, expandHeaderLimit }) { +export function useNavContentOpacity({ + scrollAmount, + navbarContentThresholdFactor, + sheetDisplacementThreshold, + expandHeaderLimit, +}) { return useDerivedValue(() => { - return interpolate(scrollAmount.value, [sheetDisplacementThreshold, expandHeaderLimit], [0, 1], 'clamp'); + const navbarContentThreshold = sheetDisplacementThreshold.value + navbarContentThresholdFactor; + return interpolate(scrollAmount.value, [navbarContentThreshold, expandHeaderLimit.value], [0, 1], 'clamp'); }); } @@ -240,7 +302,7 @@ export function onPanUpdate({ scrollStart, scrollAmount, maxScroll, expandHeader if (newScrollAmount <= 0) { scrollAmount.value = 0; } else { - const limit = expandHeaderLimit + maxScroll.value; + const limit = expandHeaderLimit.value + maxScroll.value; scrollAmount.value = newScrollAmount <= limit ? newScrollAmount : limit; } }; @@ -251,26 +313,27 @@ export function onPanEnd({ scrollAmount, maxScroll, expandHeaderLimit, - expandHeaderThreshold, - snapHeaderThreshold, + collapseThreshold, + snapHeaderThresholdFactor, animationDuration, }) { const isIOS = Platform.OS === 'ios'; return function (event) { 'worklet'; scrollStart.value = -scrollAmount.value; + const snapHeaderThreshold = collapseThreshold.value * snapHeaderThresholdFactor; const endAnimation = onScrollAnimationEnd( scrollAmount, scrollStart, - expandHeaderThreshold, + collapseThreshold.value, snapHeaderThreshold, - expandHeaderLimit, + expandHeaderLimit.value, animationDuration, ); - if (scrollAmount.value < expandHeaderLimit) { + if (scrollAmount.value < expandHeaderLimit.value) { endAnimation(); } else { - const maxValue = maxScroll.value + expandHeaderLimit; + const maxValue = maxScroll.value + expandHeaderLimit.value; const decelerationRate = isIOS ? { deceleration: 0.998 } : { deceleration: 0.996 }; scrollStart.value = withDecay({ diff --git a/src/mocks/js_dependencies.cljs b/src/mocks/js_dependencies.cljs index d2931eb6bc9..7df510839ad 100644 --- a/src/mocks/js_dependencies.cljs +++ b/src/mocks/js_dependencies.cljs @@ -453,6 +453,10 @@ "../src/js/worklets/core.js" worklet-factory "../src/js/worklets/communities.js" #js {"useLogoStyles" #js {} "useSheetStyles" #js {} + "useStartScrollValue" #js {} + "useScrollValue" #js {} + "useDerivedValueAdd" #js {} + "useDerivedValueMul" #js {} "useNameStyles" #js {} "useInfoStyles" #js {} "useChannelsStyles" #js {} diff --git a/src/quo/components/info/information_box/view.cljs b/src/quo/components/info/information_box/view.cljs index 50344d73237..68e9a1746c0 100644 --- a/src/quo/components/info/information_box/view.cljs +++ b/src/quo/components/info/information_box/view.cljs @@ -27,7 +27,8 @@ :container-style style/close-button}]]) (defn- content - [{:keys [theme type button-label on-button-press message colors-map customization-color]}] + [{:keys [theme type button-label on-button-press message colors-map customization-color + button-icon-right]}] [rn/view {:style {:flex 1}} [text/text {:size :paragraph-2 @@ -40,22 +41,24 @@ :size 24 :customization-color customization-color :on-press on-button-press - :container-style style/content-button} + :container-style style/content-button + :icon-right button-icon-right} button-label])]) (defn view "[view opts \"message\"] opts - {:type :default/:informative/:error - :closed? bool (false) ;; Information box's state - :icon keyword, required (:i/info) - :icon-size int (16) - :no-icon-color? bool (false) - :style map - :button-label string - :on-button-press function - :on-close function" - [{:keys [type closed? blur? icon style button-label + {:type :default/:informative/:error + :closed? bool (false) ;; Information box's state + :icon keyword, required (:i/info) + :icon-size int (16) + :no-icon-color? bool (false) + :style map + :button-label string + :on-button-press function + :on-close function + :button-icon-right keyword to place an icon to the right of the button's label" + [{:keys [type closed? blur? icon style button-label button-icon-right on-button-press on-close no-icon-color? icon-size customization-color] :or {customization-color :primary}} message] @@ -67,12 +70,12 @@ include-button? (not (string/blank? button-label))] [rn/view {:accessibility-label :information-box - :style (merge (style/container {:theme theme - :colors-map colors-map - :customization-color customization-color - :type type - :include-button? include-button?}) - style)} + :style [(style/container {:theme theme + :colors-map colors-map + :customization-color customization-color + :type type + :include-button? include-button?}) + style]} [icons/icon (or icon :i/info) {:color (style/get-color-by-type colors-map theme type :icon) :no-color no-icon-color? @@ -85,6 +88,7 @@ :on-button-press on-button-press :colors-map colors-map :customization-color customization-color - :message message}] + :message message + :button-icon-right button-icon-right}] (when on-close [close-button {:theme theme :colors-map colors-map :on-close on-close}])]))) diff --git a/src/react_native/core.cljs b/src/react_native/core.cljs index 6e5a7752c43..95a5b43e0e9 100644 --- a/src/react_native/core.cljs +++ b/src/react_native/core.cljs @@ -187,13 +187,15 @@ (react/useMemo handler (get-js-deps deps))) (defn delay-render - [content] - (let [[render? set-render] (use-state false)] - (use-mount - (fn [] - (js/setTimeout #(set-render true) 0))) - (when render? - content))) + ([content] + (delay-render {:ms 0} content)) + ([{:keys [ms]} & children] + (let [[render? set-render] (use-state false)] + (use-mount + (fn [] + (js/setTimeout #(set-render true) ms))) + (when render? + (into [:<>] children))))) (def layout-animation (.-LayoutAnimation ^js react-native)) (def configure-next (.-configureNext ^js layout-animation)) diff --git a/src/status_im/constants.cljs b/src/status_im/constants.cljs index 90ca9497148..9bf20559dba 100644 --- a/src/status_im/constants.cljs +++ b/src/status_im/constants.cljs @@ -274,6 +274,7 @@ (def ^:const sticker-pack-status-owned 3) (def ^:const community-member-role-moderator 3) +(def ^:const community-member-role-owner 1) (def ^:const delete-message-undo-time-limit-ms 4000) (def ^:const delete-message-for-me-undo-time-limit-ms 4000) @@ -495,3 +496,6 @@ ;; Community help links (def ^:const create-community-help-url "https://status.app/help/communities/create-a-status-community#create-a-status-community") + +(def ^:const community-vote-help-url + "https://status.app/help/communities/about-voting-to-change-the-community-visibility#about-voting-to-change-the-community-visibility") diff --git a/src/status_im/contexts/communities/overview/style.cljs b/src/status_im/contexts/communities/overview/style.cljs index 391747576a6..90d90794314 100644 --- a/src/status_im/contexts/communities/overview/style.cljs +++ b/src/status_im/contexts/communities/overview/style.cljs @@ -2,6 +2,7 @@ (:require [quo.foundations.colors :as colors] [react-native.core :as rn] + [react-native.platform :as platform] [react-native.safe-area :as safe-area])) (defn fetching-placeholder @@ -16,8 +17,11 @@ [background-color] {:width "100%" :background-color background-color - :height (+ 20 ;; Area hidden by sheet on top but visible with rounded borders + :height (+ 20 ; Area hidden by sheet on top but visible with rounded borders 92 + ;; On Android we count the navigation bar page-nav padding top + ;; because it isn't overlapped with the safe-area top. + (when platform/android? 12) safe-area/top)}) (def cover-image {:flex 1}) @@ -36,16 +40,18 @@ theme)}]) (def ^:private page-nav-container-base-style - {:height (+ 12 ;; padding-top - 12 ;; padding-bottom - 32) ;; button size + {:height (+ 12 ; padding-top + 12 ; padding-bottom + 32) ; button size :width "100%"}) (defn page-nav-container [opposite-header-opacity] [rn/stylesheet-absolute-fill page-nav-container-base-style - {:top (- safe-area/top 12) ;; -12 to place the button next to the safe-area + {:top (if platform/android? + safe-area/top + (- safe-area/top 12)) ; -12 to place the button next to the safe-area :opacity 1} {:opacity opposite-header-opacity}]) @@ -53,7 +59,9 @@ [header-opacity] [rn/stylesheet-absolute-fill page-nav-container-base-style - {:top (- safe-area/top 12) ;; -12 to place the button next to the safe-area + {:top (if platform/android? + safe-area/top + (- safe-area/top 12)) ; -12 to place the button next to the safe-area :opacity 0} {:opacity header-opacity}]) @@ -135,3 +143,8 @@ (def community-sheet-position {:top (+ -20 -40)}) + +(def promote-community + {:padding-top 4 + :padding-bottom 20 + :padding-horizontal 20}) diff --git a/src/status_im/contexts/communities/overview/view.cljs b/src/status_im/contexts/communities/overview/view.cljs index e8f22b63a3f..e6aa1987943 100644 --- a/src/status_im/contexts/communities/overview/view.cljs +++ b/src/status_im/contexts/communities/overview/view.cljs @@ -8,6 +8,7 @@ [react-native.platform :as platform] [react-native.reanimated :as reanimated] [react-native.safe-area :as safe-area] + [reagent.core :as reagent] [status-im.common.events-helper :as events.helper] [status-im.common.home.actions.view :as actions] [status-im.common.resources :as resources] @@ -20,32 +21,18 @@ [utils.re-frame :as rf] [utils.worklets.communities :as worklets])) -;; NOTE: values compared against `scroll-amount` to trigger animations. -(def expand-header-threshold - "Dragging distance to collapse/extend the community." - 150) - -(def sheet-displacement-threshold - "Dragging distance to round sheet borders and move the sheet 8 units." - (+ expand-header-threshold 20)) +(def snap-header-threshold-factor + "Threshold to automatically move the header to a collapsed/expanded state and avoid an + intermediate state. Applied to `collapse-threshold`." + 0.65) -(def text-movement-threshold - "Dragging distance to start the text movement from/to the bottom to/from the right." - (* expand-header-threshold 0.7)) +(def navbar-content-threshold-factor + "When the community name and logo start to appear. Applied to sheet-displacement-threshold." + 32) -(def info-opacity-threshold +(def info-opacity-threshold-factor "Dragging distance to appear/disappear the community info (description, tags & stats)." - (* expand-header-threshold 0.5)) - -(def snap-header-threshold - "Threshold to automatically move the header to a collapsed/expanded state and avoid an - intermediate state." - (* expand-header-threshold 0.75)) - -(def expand-header-limit - "Max dragging distance where the header animation ends. It works to identify when to - start the flat-list scrolling." - (+ sheet-displacement-threshold 56)) + 0.5) (defn- collapse-category [community-id category-id collapsed?] @@ -66,6 +53,22 @@ (rf/dispatch [:open-modal :screen/community-account-selection-sheet {:community-id community-id}]))) +(defn- promotional-info-box-for-owners + [{:keys [theme info-styles]}] + (let [[dismissed? set-dismissed] (rn/use-state false)] + (when-not dismissed? + [reanimated/view {:style [style/promote-community info-styles]} + [quo/information-box + {:type :informative + :closed? false + :on-close #(set-dismissed true) + :theme theme + :button-label (i18n/label :t/initiate-the-vote) + :button-icon-right :i/external + :on-button-press (fn [] + (rf/dispatch [:browser.ui/open-url constants/community-vote-help-url]))} + (i18n/label :t/help-discover-your-community)]]))) + (defn token-gated-communities-info [] [quo/documentation-drawers {:title (i18n/label :t/token-gated-communities)} @@ -208,7 +211,9 @@ :separator 8}) (def unusable-area-height - (+ 32 11 safe-area/bottom)) ;;top page buttons, button's padding & safe area + ;;top page buttons, button's padding & safe area, on Android we count page-nav top + ;; because it isn't overlapped with the safe-area. + (+ 32 11 safe-area/bottom (when platform/android? 12))) (defn- calc-scrollable-content [scrollable-height] @@ -221,62 +226,66 @@ safe-area/bottom)) (defn- channel-listing - [{:keys [community-id scroll-amount header-height set-max-scroll]}] - (let [theme (quo.context/use-theme) - channels-styles (worklets/use-channels-styles - {:scroll-amount scroll-amount - :header-height header-height - :expand-header-threshold expand-header-threshold - :sheet-displacement-threshold sheet-displacement-threshold - :expand-header-limit expand-header-limit}) - flat-list-ref (reanimated/use-animated-ref) - _scroll-to-animation (worklets/use-scroll-to - {:animated-ref flat-list-ref - :scroll-amount scroll-amount - :expand-header-limit expand-header-limit}) + [{:keys [community-id scroll-amount header-height set-max-scroll collapse-threshold + sheet-displacement-threshold expand-header-limit]}] + (let [theme (quo.context/use-theme) + channels-styles (worklets/use-channels-styles + {:scroll-amount scroll-amount + :header-height header-height + :collapse-threshold collapse-threshold + :sheet-displacement-threshold sheet-displacement-threshold + :expand-header-limit expand-header-limit}) + flat-list-ref (reanimated/use-animated-ref) + _scroll-to-animation (worklets/use-scroll-to + {:animated-ref flat-list-ref + :scroll-amount scroll-amount + :expand-header-limit expand-header-limit}) {:keys [joined? - spectated?]} (rf/sub [:communities/community-overview community-id]) - joined-or-spectated? (or joined? spectated?) - render-fn (rn/use-callback - (channel-listing-item {:community-id community-id - :joined-or-spectated? joined-or-spectated?}) - [joined-or-spectated?]) - flatten-channels (rf/sub [:communities/flatten-channels-and-categories community-id]) - categories-indexes (keep-indexed (fn [idx {:keys [render-as]}] - (when (= render-as :category) idx)) - flatten-channels) - scrollable-area-height (->> flatten-channels - (map (comp channel-component-heights :render-as)) - (reduce +)) - listing-height (calc-listing-height)] + spectated?]} (rf/sub [:communities/community-overview community-id]) + joined-or-spectated? (or joined? spectated?) + render-fn (rn/use-callback + (channel-listing-item {:community-id community-id + :joined-or-spectated? joined-or-spectated?}) + [joined-or-spectated?]) + flatten-channels (rf/sub [:communities/flatten-channels-and-categories community-id]) + categories-indexes (keep-indexed (fn [idx {:keys [render-as]}] + (when (= render-as :category) idx)) + flatten-channels) + channels-height (->> flatten-channels + (map (comp channel-component-heights :render-as)) + (reduce +)) + listing-height (calc-listing-height)] (rn/use-effect (fn [] - (let [max-scroll-offset (calc-scrollable-content scrollable-area-height)] + (let [max-scroll-offset (calc-scrollable-content channels-height)] (if (neg? max-scroll-offset) (set-max-scroll 0) (set-max-scroll max-scroll-offset)))) - [scrollable-area-height]) - [reanimated/flat-list - {:ref flat-list-ref - :style [(style/channel-listing theme listing-height) channels-styles] - :data flatten-channels - :content-container-style [(when platform/ios? {:padding-bottom safe-area/bottom})] - :sticky-header-indices categories-indexes - :scroll-enabled false - :render-fn render-fn - :key-fn :id}])) + [channels-height]) + [rn/delay-render {:ms 120} + [reanimated/flat-list + {:ref flat-list-ref + :style [(style/channel-listing theme listing-height) + channels-styles] + :data flatten-channels + :content-container-style [(when platform/ios? {:padding-bottom safe-area/bottom})] + :sticky-header-indices categories-indexes + :scroll-enabled false + :render-fn render-fn + :key-fn :id}]])) (defn- header-cover-image [{:keys [cover-image background-color header-opacity]}] (let [theme (quo.context/use-theme)] [rn/view {:style (style/header-cover-image background-color)} [reanimated/image {:style style/cover-image :source {:uri cover-image}}] - [reanimated/view {:style (style/cover-image-blur-container header-opacity)} - [rn/image - {:style style/cover-image - :source {:uri cover-image} - :blur-radius 20}] - [rn/view {:style (style/cover-image-blur-layer theme)}]]])) + [rn/delay-render + [reanimated/view {:style (style/cover-image-blur-container header-opacity)} + [rn/image + {:style style/cover-image + :source {:uri cover-image} + :blur-radius 20}] + [rn/view {:style (style/cover-image-blur-layer theme)}]]]])) (defn- open-community-options [community-id] @@ -308,16 +317,18 @@ :background :photo)]))) (defn- header - [community-id scroll-amount] + [{:keys [community-id scroll-amount collapse-threshold sheet-displacement-threshold + expand-header-limit]}] (let [header-opacity (worklets/use-header-opacity {:scroll-amount scroll-amount - :expand-header-threshold expand-header-threshold + :collapse-threshold collapse-threshold :sheet-displacement-threshold sheet-displacement-threshold}) opposite-header-opacity (worklets/use-opposite-header-opacity header-opacity) nav-content-opacity (worklets/use-nav-content-opacity - {:scroll-amount scroll-amount - :sheet-displacement-threshold sheet-displacement-threshold - :expand-header-limit expand-header-limit}) + {:scroll-amount scroll-amount + :sheet-displacement-threshold sheet-displacement-threshold + :navbar-content-threshold-factor navbar-content-threshold-factor + :expand-header-limit expand-header-limit}) {:keys [community-name color logo cover-image]} (rf/sub [:communities/community-overview community-id])] [:<> @@ -325,38 +336,41 @@ {:cover-image cover-image :background-color color :header-opacity header-opacity}] - [reanimated/view {:style (style/page-nav-container opposite-header-opacity)} - [page-nav - {:blur-version? false - :community-id community-id}]] - [reanimated/view {:style (style/page-nav-container-blur header-opacity)} - [page-nav - {:blur-version? true - :community-id community-id - :nav-content-opacity nav-content-opacity - :community-name community-name - :community-logo logo}]]])) + [rn/delay-render + [:<> + [reanimated/view {:style (style/page-nav-container opposite-header-opacity)} + [page-nav + {:blur-version? false + :community-id community-id}]] + [reanimated/view {:style (style/page-nav-container-blur header-opacity)} + [page-nav + {:blur-version? true + :community-id community-id + :nav-content-opacity nav-content-opacity + :community-name community-name + :community-logo logo}]]]]])) (defn- community-logo - [{:keys [scroll-amount community-id]}] + [{:keys [initial-state scroll-amount community-id collapse-threshold + sheet-displacement-threshold text-movement-threshold]}] (let [theme (quo.context/use-theme) + {:keys [logo]} (rf/sub [:communities/community-overview community-id]) logo-styles (worklets/use-logo-styles - {:scroll-amount scroll-amount - :expand-header-threshold expand-header-threshold + {:initial-state initial-state + :scroll-amount scroll-amount + :collapse-threshold collapse-threshold :sheet-displacement-threshold sheet-displacement-threshold - :text-movement-threshold text-movement-threshold}) - {:keys [logo]} (rf/sub [:communities/community-overview community-id])] - [reanimated/view - {:style [style/community-logo - (style/community-logo-bg-color theme) - logo-styles]} + :text-movement-threshold text-movement-threshold})] + [reanimated/view {:style [style/community-logo (style/community-logo-bg-color theme) logo-styles]} [rn/image {:style style/community-logo-image :source logo}]])) (defn- name-and-description - [{:keys [scroll-amount community-name community-description info-styles]}] + [{:keys [scroll-amount community-name community-description info-styles + collapse-threshold text-movement-threshold initial-state]}] (let [name-styles (worklets/use-name-styles - {:scroll-amount scroll-amount - :expand-header-threshold expand-header-threshold + {:initial-state initial-state + :scroll-amount scroll-amount + :collapse-threshold collapse-threshold :text-movement-threshold text-movement-threshold})] [rn/view {:style style/community-name-and-description} [reanimated/view {:style name-styles} @@ -396,40 +410,54 @@ :value active-members-count}]]]) (defn- community-info - [{:keys [scroll-amount header-height community-id]}] - (let [theme (quo.context/use-theme) + [{:keys [initial-state scroll-amount header-height community-id collapse-threshold + sheet-displacement-threshold text-movement-threshold]}] + (let [{:keys [community-name description active-members-count tags role-permissions? + permissions color owner? + joined?]} (rf/sub [:communities/community-overview community-id]) + theme (quo.context/use-theme) sheet-styles (worklets/use-sheet-styles - {:scroll-amount scroll-amount - :expand-header-threshold expand-header-threshold + {:initial-state initial-state + :scroll-amount scroll-amount + :collapse-threshold collapse-threshold :sheet-displacement-threshold sheet-displacement-threshold}) info-styles (worklets/use-info-styles - {:scroll-amount scroll-amount - :info-opacity-threshold info-opacity-threshold}) - set-header-height (rn/use-callback + {:initial-state initial-state + :scroll-amount scroll-amount + :collapse-threshold collapse-threshold + :info-opacity-threshold-factor info-opacity-threshold-factor}) + get-dimensions (rn/use-callback (fn [e] (let [height (oops/oget e "nativeEvent.layout.height")] - (reanimated/set-shared-value header-height (or height 0))))) - {:keys [community-name description active-members-count tags role-permissions? - permissions color - joined?]} (rf/sub [:communities/community-overview community-id]) - members-count (count (rf/sub [:communities/community-members community-id]))] + (reanimated/set-shared-value header-height (or height 0)) + (reanimated/set-shared-value collapse-threshold (or (- height 16.5) 0)) + (reagent/next-tick #(reanimated/set-shared-value initial-state + "finalized"))))) + + members-count (rf/sub [:communities/community-members-count community-id])] [reanimated/view {:style [(style/community-info theme) sheet-styles] - :on-layout set-header-height} - [status-tag - {:community-id community-id - :joined? joined? - :info-styles info-styles}] + :on-layout get-dimensions} + [rn/delay-render + [status-tag + {:community-id community-id + :joined? joined? + :info-styles info-styles}]] [name-and-description - {:scroll-amount scroll-amount - :community-name community-name - :community-description description - :info-styles info-styles}] + {:initial-state initial-state + :scroll-amount scroll-amount + :community-name community-name + :community-description description + :info-styles info-styles + :collapse-threshold collapse-threshold + :text-movement-threshold text-movement-threshold}] [community-info-stats {:members-count members-count :active-members-count active-members-count :info-styles info-styles}] [community-info-tags tags info-styles] + (when owner? + [promotional-info-box-for-owners {:theme theme :info-styles info-styles}]) [join-community {:community-id community-id :joined? joined? @@ -439,55 +467,82 @@ :color color}]])) (defn- community-sheet - [{:keys [community-id scroll-amount set-max-scroll]}] - (let [header-height (reanimated/use-shared-value 0)] + [{:keys [collapsed? community-id scroll-amount set-max-scroll collapse-threshold + sheet-displacement-threshold expand-header-limit]}] + (let [header-height (reanimated/use-shared-value 0) + initial-state (reanimated/use-shared-value (if collapsed? "collapsed" "expanded")) + text-movement-threshold (worklets/use-derived-value-mul collapse-threshold 0.7)] [rn/view {:style style/community-sheet-position} [community-logo - {:community-id community-id - :scroll-amount scroll-amount}] + {:initial-state initial-state + :community-id community-id + :scroll-amount scroll-amount + :collapse-threshold collapse-threshold + :sheet-displacement-threshold sheet-displacement-threshold + :text-movement-threshold text-movement-threshold}] [community-info - {:scroll-amount scroll-amount - :header-height header-height - :community-id community-id}] + {:initial-state initial-state + :scroll-amount scroll-amount + :header-height header-height + :community-id community-id + :collapse-threshold collapse-threshold + :sheet-displacement-threshold sheet-displacement-threshold + :text-movement-threshold text-movement-threshold}] [channel-listing - {:community-id community-id - :scroll-amount scroll-amount - :header-height header-height - :set-max-scroll set-max-scroll}]])) + {:community-id community-id + :scroll-amount scroll-amount + :header-height header-height + :set-max-scroll set-max-scroll + :collapse-threshold collapse-threshold + :sheet-displacement-threshold sheet-displacement-threshold + :expand-header-limit expand-header-limit}]])) (defn- community-overview [community-id collapsed?] - (let [max-scroll (reanimated/use-shared-value 0) - set-max-scroll (rn/use-callback - (fn [max-scroll-amount] - (reanimated/set-shared-value max-scroll max-scroll-amount))) - scroll-start (reanimated/use-shared-value (if collapsed? (- expand-header-threshold) 0)) - scroll-amount (reanimated/use-shared-value (if collapsed? expand-header-threshold 0)) - on-pan-start (worklets/on-pan-start scroll-start scroll-amount) - on-pan-update (worklets/on-pan-update - {:scroll-start scroll-start - :scroll-amount scroll-amount - :max-scroll max-scroll - :expand-header-limit expand-header-limit}) - on-pan-end (worklets/on-pan-end - {:scroll-start scroll-start - :scroll-amount scroll-amount - :max-scroll max-scroll - :expand-header-limit expand-header-limit - :expand-header-threshold expand-header-threshold - :snap-header-threshold snap-header-threshold - :animation-duration 300}) - pan-gesture (-> (gesture/gesture-pan) - (gesture/on-start on-pan-start) - (gesture/on-update on-pan-update) - (gesture/on-end on-pan-end))] + (let [collapse-threshold (reanimated/use-shared-value 0) + sheet-displacement-threshold (worklets/use-derived-value-add collapse-threshold 8) + expand-header-limit (worklets/use-derived-value-add sheet-displacement-threshold 56) + max-scroll (reanimated/use-shared-value 0) + set-max-scroll (rn/use-callback + (fn [max-scroll-amount] + (reanimated/set-shared-value max-scroll max-scroll-amount))) + scroll-start (worklets/use-start-scroll-value collapsed? collapse-threshold) + scroll-amount (worklets/use-scroll-value collapsed? collapse-threshold) + on-pan-start (worklets/on-pan-start scroll-start scroll-amount) + on-pan-update (worklets/on-pan-update + {:scroll-start scroll-start + :scroll-amount scroll-amount + :max-scroll max-scroll + :expand-header-limit expand-header-limit}) + on-pan-end (worklets/on-pan-end + {:scroll-start scroll-start + :scroll-amount scroll-amount + :max-scroll max-scroll + :expand-header-limit expand-header-limit + :collapse-threshold collapse-threshold + :snap-header-threshold-factor snap-header-threshold-factor + :animation-duration 300}) + pan-gesture (-> (gesture/gesture-pan) + (gesture/on-start on-pan-start) + (gesture/on-update on-pan-update) + (gesture/on-end on-pan-end))] [gesture/gesture-detector {:gesture pan-gesture} [rn/view {:style {:flex 1}} - [header community-id scroll-amount] - [community-sheet - {:community-id community-id - :scroll-amount scroll-amount - :set-max-scroll set-max-scroll}]]])) + [header + {:community-id community-id + :scroll-amount scroll-amount + :collapse-threshold collapse-threshold + :sheet-displacement-threshold sheet-displacement-threshold + :expand-header-limit expand-header-limit}] + [rn/delay-render + [community-sheet + {:collapsed? collapsed? + :community-id community-id + :scroll-amount scroll-amount + :set-max-scroll set-max-scroll + :collapse-threshold collapse-threshold + :sheet-displacement-threshold sheet-displacement-threshold + :expand-header-limit expand-header-limit}]]]])) (defn- community-fetching-placeholder [id] @@ -514,9 +569,9 @@ (defn view [id] - (let [community-id (or id (quo.context/use-screen-params)) - community (rf/sub [:communities/community-overview community-id]) - collapsed? (:joined? community)] + (let [community-id (or id (quo.context/use-screen-params)) + {:keys [collapsed?] + :as community} (rf/sub [:communities/community-overview community-id])] [rn/view {:style style/community-overview-container} (if community [community-overview community-id collapsed?] diff --git a/src/status_im/subs/communities.cljs b/src/status_im/subs/communities.cljs index 8abbd43599b..39a71f6552b 100644 --- a/src/status_im/subs/communities.cljs +++ b/src/status_im/subs/communities.cljs @@ -73,6 +73,13 @@ (fn [community _] (js-keys (:members community)))) +(re-frame/reg-sub + :communities/community-members-count + (fn [[_ community-id]] + (re-frame/subscribe [:communities/community-members community-id])) + (fn [members _] + (count members))) + (re-frame/reg-sub :communities/community-chat-members (fn [[_ community-id]] @@ -393,20 +400,23 @@ (fn [[_ community-id]] (re-frame/subscribe [:communities/community community-id])) (fn [{:keys [joined spectated images description color activeMembersCount tags - permissions role-permissions?] + permissions role-permissions? memberRole] :as community}] (when community - {:joined? joined - :spectated? spectated - :cover-image (-> images :banner :uri) - :logo (-> images :large :uri) - :community-name (:name community) - :description description - :color color - :active-members-count activeMembersCount - :tags tags - :permissions permissions - :role-permissions? role-permissions?}))) + (let [owner? (= memberRole constants/community-member-role-owner)] + {:joined? joined + :spectated? spectated + :cover-image (-> images :banner :uri) + :logo (-> images :large :uri) + :community-name (:name community) + :description description + :color color + :active-members-count activeMembersCount + :tags tags + :permissions permissions + :role-permissions? role-permissions? + :owner? owner? + :collapsed? (and joined (not owner?))})))) (re-frame/reg-sub :communities/collapsed-categories-for-community diff --git a/src/utils/worklets/communities.cljs b/src/utils/worklets/communities.cljs index 5476f8c3269..86c43389ea9 100644 --- a/src/utils/worklets/communities.cljs +++ b/src/utils/worklets/communities.cljs @@ -18,6 +18,10 @@ {:name worklet-name :file "../src/js/worklets/communities.js"}))))) +(def use-start-scroll-value (worklet-wrapper "useStartScrollValue")) +(def use-scroll-value (worklet-wrapper "useScrollValue")) +(def use-derived-value-add (worklet-wrapper "useDerivedValueAdd")) +(def use-derived-value-mul (worklet-wrapper "useDerivedValueMul")) (def use-logo-styles (worklet-wrapper "useLogoStyles")) (def use-sheet-styles (worklet-wrapper "useSheetStyles")) (def use-name-styles (worklet-wrapper "useNameStyles")) diff --git a/translations/en.json b/translations/en.json index 878a5063942..c29751a9cc5 100644 --- a/translations/en.json +++ b/translations/en.json @@ -1229,6 +1229,7 @@ "help": "help", "help-capitalized": "Help", "help-center": "Help Center", + "help-discover-your-community": "Help more people discover your community - start or join the vote to be featured.", "help-improve-status": "Help improve Status", "help-us-improve-status": "Help us improve Status", "help-us-improve-status-bullet-point-1": "Collect basic usage data like taps and page views", @@ -1301,6 +1302,7 @@ "increase-gas": "Increase Gas", "information-you-input-and-send": "Information you input and send", "initialization": "Initialization", + "initiate-the-vote": "Initiate the vote", "input-data": "Input data", "install": "↓ Install", "instruction-after-qr-generated": "On your other device, navigate to the Syncing screen and select “Scan sync”",