Compare commits

...
Author SHA1 Message Date
codemaster 658f62e8ce Composer-photo selection drawer issues (#19613) 2024-04-12 07:44:35 -07:00
flexsurfer e6b346ca32 migrate reagent part 7 (#19316) 2024-04-12 15:46:17 +02:00
yqrashawn 00b0755a73 feat: system messge icon based on contact customization color (#19558)
Signed-off-by: yqrashawn <namy.19@gmail.com>
2024-04-12 20:46:09 +08:00
yqrashawn f8cc85f5e2 fix: vertical align texts in multiline input on iOS (#19565)
* fix: vertical align texts in multiline input on iOS

Signed-off-by: yqrashawn <namy.19@gmail.com>

* fix(copy): Chat Key -> Chatkey

---------

Signed-off-by: yqrashawn <namy.19@gmail.com>
2024-04-12 20:25:04 +08:00
Ibrahem Khalil 6ba89d0c6e Community channel avatar is missing on Channel screen (#19459) 2024-04-12 01:18:21 +02:00
Ibrahem Khalil 2614131426 [18979] Implement context action Invite contacts to community (#18989) 2024-04-12 00:12:02 +02:00
41 changed files with 984 additions and 554 deletions
+9 -50
View File
@@ -4,6 +4,7 @@
[legacy.status-im.bottom-sheet.events :as bottom-sheet]
legacy.status-im.communities.e2e
[re-frame.core :as re-frame]
[status-im.contexts.shell.activity-center.events :as activity-center]
[status-im.navigation.events :as navigation]
[taoensso.timbre :as log]
[utils.re-frame :as rf]))
@@ -22,60 +23,10 @@
{}
requests))
(defn- fetch-community-id-input
[{:keys [db]}]
(:communities/community-id-input db))
(rf/defn handle-response
[_ response-js]
{:dispatch [:sanitize-messages-and-process-response response-js]})
(rf/defn invite-users
{:events [::invite-people-confirmation-pressed]}
[cofx user-pk contacts]
(let [community-id (fetch-community-id-input cofx)
pks (if (seq user-pk)
(conj contacts user-pk)
contacts)]
(when (seq pks)
{:json-rpc/call [{:method "wakuext_inviteUsersToCommunity"
:params [{:communityId community-id
:users pks}]
:js-response true
:on-success #(re-frame/dispatch [::people-invited %])
:on-error #(do
(log/error "failed to invite-user community" %)
(re-frame/dispatch [::failed-to-invite-people %]))}]})))
(rf/defn share-community
{:events [::share-community-confirmation-pressed]}
[cofx user-pk contacts]
(let [community-id (fetch-community-id-input cofx)
pks (if (seq user-pk)
(conj contacts user-pk)
contacts)]
(when (seq pks)
{:json-rpc/call [{:method "wakuext_shareCommunity"
:params [{:communityId community-id
:users pks}]
:js-response true
:on-success #(re-frame/dispatch [::people-invited %])
:on-error #(do
(log/error "failed to invite-user community" %)
(re-frame/dispatch [::failed-to-share-community %]))}]})))
(re-frame/reg-event-fx :communities/invite-people-pressed
(fn [{:keys [db]} [id]]
{:db (assoc db :communities/community-id-input id)
:fx [[:dispatch [:hide-bottom-sheet]]
[:dispatch [:open-modal :legacy-invite-people-community {:invite? true}]]]}))
(re-frame/reg-event-fx :communities/share-community-pressed
(fn [{:keys [db]} [id]]
{:db (assoc db :communities/community-id-input id)
:fx [[:dispatch [:hide-bottom-sheet]]
[:dispatch [:open-modal :legacy-invite-people-community {}]]]}))
(rf/defn people-invited
{:events [::people-invited]}
[cofx response-js]
@@ -90,6 +41,14 @@
[:sanitize-messages-and-process-response response-js]
[:activity-center.notifications/fetch-unread-count]]}))
(rf/defn member-banned
{:events [::member-banned]}
[cofx response-js]
(rf/merge cofx
(bottom-sheet/hide-bottom-sheet-old)
(handle-response response-js)
(activity-center/notifications-fetch-unread-count)))
(rf/defn member-ban
{:events [::member-ban]}
[cofx community-id public-key]
@@ -13,3 +13,11 @@
:background-color (colors/theme-colors (colors/custom-color customization-color 50)
(colors/custom-color customization-color 60)
theme)})
(defn avatar-identifier
[theme]
{:text-align :center
:font-size 36
:color (colors/theme-colors colors/black
colors/white
theme)})
@@ -1,7 +1,9 @@
(ns quo.components.avatars.group-avatar.view
(:require
[clojure.string :as string]
[quo.components.avatars.group-avatar.style :as style]
[quo.components.icon :as icon]
[quo.components.markdown.text :as text]
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[react-native.core :as rn]
@@ -21,7 +23,7 @@
(defn- view-internal
[_]
(fn [{:keys [size theme customization-color picture icon-name]
(fn [{:keys [size theme customization-color picture icon-name emoji chat-name]
:or {size :size-20
customization-color :blue
picture nil
@@ -38,8 +40,27 @@
{:source picture
:style {:width container-size
:height container-size}}]
[icon/icon icon-name
{:size icon-size
:color colors/white-opa-70}])])))
(cond
emoji
(if (= size :size-80)
[rn/text
{:style (style/avatar-identifier theme)}
emoji]
[text/text
{:size :paragraph-1
:style (dissoc (style/avatar-identifier theme) :font-size)}
emoji])
chat-name
(if (= size :size-80)
[rn/text
{:style (style/avatar-identifier theme)}
((comp first string/upper-case) chat-name)]
[text/text
{:size :paragraph-1}
((comp first string/upper-case) chat-name)])
:else
[icon/icon icon-name
{:size icon-size
:color colors/white-opa-70}]))])))
(def view (quo.theme/with-theme view-internal))
+8 -4
View File
@@ -1,7 +1,8 @@
(ns quo.components.inputs.input.style
(:require
[quo.components.markdown.text :as text]
[quo.foundations.colors :as colors]))
[quo.foundations.colors :as colors]
[react-native.platform :as platform]))
(defn variants-colors
[blur? theme]
@@ -97,9 +98,12 @@
(assoc base-props
:text-align-vertical :top
:line-height 22)
(assoc base-props
:height (if small? 30 38)
:line-height nil))))
(cond-> base-props
:always
(assoc :height (if small? 30 38)
:line-height nil)
platform/ios?
(assoc :padding-top (+ padding 2))))))
(defn right-icon-touchable-area
[small?]
+7 -4
View File
@@ -41,21 +41,24 @@
(defn user
[{:keys [short-chat-key primary-name secondary-name photo-path online? contact? verified?
untrustworthy? on-press on-long-press accessory customization-color theme
allow-multiple-presses?]}]
allow-multiple-presses? disabled?]}]
[rn/touchable-highlight
{:style container-style
:underlay-color (colors/resolve-color customization-color theme 5)
:allow-multiple-presses? allow-multiple-presses?
:accessibility-label :user-list
:on-press (when on-press on-press)
:on-long-press (when on-long-press on-long-press)}
:on-long-press (when on-long-press on-long-press)
:disabled disabled?}
[:<>
[user-avatar/user-avatar
{:full-name primary-name
:profile-picture photo-path
:online? online?
:size :small}]
[rn/view {:style {:margin-horizontal 8 :flex 1}}
[rn/view
{:style {:margin-horizontal 8
:flex 1}}
[author/view
{:primary-name primary-name
:secondary-name secondary-name
@@ -69,4 +72,4 @@
:style {:color (colors/theme-colors colors/neutral-50 colors/neutral-40)}}
short-chat-key])]
(when accessory
[action-icon accessory customization-color theme])]])
[action-icon accessory customization-color disabled? theme])]])
@@ -4,7 +4,6 @@
[:map
[:type {:optional true} [:enum :default :watch-only :add-account :empty :missing-keypair]]
[:customization-color {:optional true} [:maybe :schema.common/customization-color]]
[:theme :schema.common/theme]
[:metrics? {:optional true} [:maybe :boolean]]
[:on-press {:optional true} [:maybe fn?]]])
@@ -11,7 +11,6 @@
[quo.theme :as quo.theme]
[react-native.core :as rn]
[react-native.linear-gradient :as linear-gradient]
[reagent.core :as reagent]
[schema.core :as schema]))
(defn- loading-view
@@ -96,87 +95,87 @@
:end {:x 1 :y 0}}])
(defn- user-account
[_]
(let [pressed? (reagent/atom false)
on-press-in #(reset! pressed? true)
on-press-out #(reset! pressed? false)]
(fn [{:keys [name balance percentage-value loading? amount customization-color type emoji metrics?
theme on-press]}]
(let [watch-only? (= :watch-only type)
missing-keypair? (= :missing-keypair type)]
(if loading?
[loading-view
{:customization-color customization-color
:type type
:theme theme
:metrics? metrics?}]
[rn/pressable
{:on-press-in on-press-in
:on-press-out on-press-out
:style (style/card {:customization-color customization-color
:type type
:theme theme
:pressed? @pressed?
:metrics? metrics?})
:on-press on-press}
(when (and customization-color (and (not watch-only?) (not missing-keypair?)))
[customization-colors/overlay
{:customization-color customization-color
:border-radius 16
:theme theme
:pressed? @pressed?}])
[rn/view {:style style/profile-container}
[rn/view {:style {:padding-bottom 2 :margin-right 2}}
[text/text {:style style/emoji} emoji]]
[rn/view {:style style/watch-only-container}
[text/text
{:size :paragraph-2
:weight :medium
:number-of-lines 1
:max-width 110
:margin-right 4
:ellipis-mode :tail
:style (style/account-name type theme)}
name]
(when watch-only? [icon/icon :i/reveal {:color colors/neutral-50 :size 12}])
(when missing-keypair?
[icon/icon :i/alert {:color (properties/alert-icon-color theme) :size 12}])]]
[text/text
{:size :heading-2
:weight :semi-bold
:style (style/account-value type theme)}
balance]
(when metrics?
[rn/view {:style style/metrics-container}
[metrics-percentage type theme percentage-value]
(when (not= :empty type)
[metrics-info type theme amount])])
(when watch-only?
[gradient-overview theme customization-color])])))))
[{:keys [name balance percentage-value loading? amount customization-color type emoji metrics?
on-press]}]
(let [theme (quo.theme/use-theme-value)
[pressed? set-pressed] (rn/use-state false)
on-press-in (rn/use-callback #(set-pressed true))
on-press-out (rn/use-callback #(set-pressed false))
watch-only? (= :watch-only type)
missing-keypair? (= :missing-keypair type)]
(if loading?
[loading-view
{:customization-color customization-color
:type type
:theme theme
:metrics? metrics?}]
[rn/pressable
{:on-press-in on-press-in
:on-press-out on-press-out
:style (style/card {:customization-color customization-color
:type type
:theme theme
:pressed? pressed?
:metrics? metrics?})
:on-press on-press}
(when (and customization-color (and (not watch-only?) (not missing-keypair?)))
[customization-colors/overlay
{:customization-color customization-color
:border-radius 16
:theme theme
:pressed? pressed?}])
[rn/view {:style style/profile-container}
[rn/view {:style {:padding-bottom 2 :margin-right 2}}
[text/text {:style style/emoji} emoji]]
[rn/view {:style style/watch-only-container}
[text/text
{:size :paragraph-2
:weight :medium
:number-of-lines 1
:max-width 110
:margin-right 4
:ellipis-mode :tail
:style (style/account-name type theme)}
name]
(when watch-only? [icon/icon :i/reveal {:color colors/neutral-50 :size 12}])
(when missing-keypair?
[icon/icon :i/alert {:color (properties/alert-icon-color theme) :size 12}])]]
[text/text
{:size :heading-2
:weight :semi-bold
:style (style/account-value type theme)}
balance]
(when metrics?
[rn/view {:style style/metrics-container}
[metrics-percentage type theme percentage-value]
(when (not= :empty type)
[metrics-info type theme amount])])
(when watch-only?
[gradient-overview theme customization-color])])))
(defn- add-account-view
[_]
(let [pressed? (reagent/atom false)]
(fn [{:keys [on-press customization-color theme metrics?]}]
[rn/pressable
{:on-press on-press
:on-press-in #(reset! pressed? true)
:on-press-out #(reset! pressed? false)
:style (style/add-account-container {:theme theme
:metrics? metrics?
:pressed? @pressed?})}
[button/button
{:type :primary
:size 24
:icon true
:accessibility-label :add-account
:on-press on-press
:pressed? @pressed?
:on-press-in #(reset! pressed? true)
:on-press-out #(reset! pressed? false)
:customization-color customization-color
:icon-only? true}
:i/add]])))
[{:keys [on-press customization-color metrics?]}]
(let [theme (quo.theme/use-theme-value)
[pressed? set-pressed] (rn/use-state false)
on-press-in (rn/use-callback #(set-pressed true))
on-press-out (rn/use-callback #(set-pressed false))]
[rn/pressable
{:on-press on-press
:on-press-in on-press-in
:on-press-out on-press-out
:style (style/add-account-container {:theme theme
:metrics? metrics?
:pressed? pressed?})}
[button/button
{:on-press on-press
:type :primary
:size 24
:icon true
:accessibility-label :add-account
:pressed? pressed?
:icon-only? true
:customization-color customization-color}
:i/add]]))
(defn- view-internal
[{:keys [type] :as props}]
@@ -185,6 +184,4 @@
:add-account [add-account-view props]
nil))
(def view
(quo.theme/with-theme
(schema/instrument #'view-internal component-schema/?schema)))
(def view (schema/instrument #'view-internal component-schema/?schema))
@@ -6,9 +6,7 @@
[:props
[:map {:closed true}
[:status {:optional true} [:maybe [:enum :default :error]]]
[:theme :schema.common/theme]
[:on-inc-press {:optional true} [:maybe fn?]]
[:on-dec-press {:optional true} [:maybe fn?]]
[:on-change-text {:optional true} [:maybe fn?]]
[:container-style {:optional true} [:maybe :map]]
[:min-value {:optional true} [:maybe :int]]
[:max-value {:optional true} [:maybe :int]]
@@ -4,7 +4,7 @@
[quo.components.markdown.text :as text]
[quo.components.wallet.amount-input.schema :as amount-input.schema]
[quo.components.wallet.amount-input.style :as style]
[quo.theme :as quo.theme]
[quo.theme]
[react-native.core :as rn]
[schema.core :as schema]))
@@ -21,35 +21,34 @@
icon])
(defn- view-internal
[{:keys [on-inc-press on-dec-press theme status value min-value max-value
[{:keys [on-inc-press on-dec-press status value min-value max-value
container-style]
:or {value 0
min-value 0
max-value 999999999}}]
[rn/view
{:style (merge style/container container-style)}
[amount-button
{:theme theme
:accessibility-label :amount-input-dec-button
:icon :i/remove
:on-press on-dec-press
:disabled? (>= min-value value)}]
[rn/view {:style style/input-container}
[text/text
{:number-of-lines 1
:accessibility-label :amount-input
:weight :semi-bold
:size :heading-1
:align-self :center
:style (style/input-text theme (or status :default))}
value]]
[amount-button
{:theme theme
:icon :i/add
:accessibility-label :amount-input-inc-button
:on-press on-inc-press
:disabled? (>= value max-value)}]])
(let [theme (quo.theme/use-theme-value)]
[rn/view
{:style (merge style/container container-style)}
[amount-button
{:theme theme
:accessibility-label :amount-input-dec-button
:icon :i/remove
:on-press on-dec-press
:disabled? (>= min-value value)}]
[rn/view {:style style/input-container}
[text/text
{:number-of-lines 1
:accessibility-label :amount-input
:weight :semi-bold
:size :heading-1
:align-self :center
:style (style/input-text theme (or status :default))}
value]]
[amount-button
{:theme theme
:icon :i/add
:accessibility-label :amount-input-inc-button
:on-press on-inc-press
:disabled? (>= value max-value)}]]))
(def view
(quo.theme/with-theme
(schema/instrument #'view-internal amount-input.schema/?schema)))
(def view (schema/instrument #'view-internal amount-input.schema/?schema))
+4 -5
View File
@@ -37,8 +37,9 @@
:border? true}]))
(defn title-view
[{:keys [details action selected? type blur? customization-color on-options-press theme]}]
(let [{:keys [full-name]} details]
[{:keys [details action selected? type blur? customization-color on-options-press]}]
(let [theme (quo.theme/use-theme-value)
{:keys [full-name]} details]
[rn/view
{:style style/title-container
:accessibility-label :title}
@@ -89,7 +90,7 @@
[item & _rest]
[account-list-card/view item])
(defn- view-internal
(defn view
[{:keys [accounts action container-style selected? on-press] :as props}]
[rn/pressable
{:style (style/container (merge props
@@ -108,5 +109,3 @@
:render-fn acc-list-card
:separator [rn/view {:style {:height 8}}]
:style {:padding-horizontal 8}}]])
(def view (quo.theme/with-theme view-internal))
@@ -12,6 +12,5 @@
[:amount :int]
[:max-amount :int]
[:network-name [:or :string :keyword]]]]]]
[:container-style {:optional true} [:maybe :map]]
[:theme :schema.common/theme]]]]
[:container-style {:optional true} [:maybe :map]]]]]
:any])
@@ -29,13 +29,13 @@
:background-color (colors/resolve-color network-name nil 10)})
(defn network-bar
[{:keys [max-width on-top? bar-division? theme]
[{:keys [bar-max-width on-top? bar-division? theme]
{:keys [network-name translate-x-shared-value]} :bar}
width-shared-value]
(reanimated/apply-animations-to-style
{:width width-shared-value
:transform [{:translate-x translate-x-shared-value}]}
{:max-width max-width
{:max-width bar-max-width
:flex-direction :row
:justify-content :flex-end
:background-color (colors/resolve-color network-name nil)
@@ -8,7 +8,6 @@
[react-native.core :as rn]
[react-native.gesture :as gesture]
[react-native.reanimated :as reanimated]
[reagent.core :as reagent]
[schema.core :as schema]
[utils.number]))
@@ -22,60 +21,73 @@
(js/clearTimeout (k @timeouts))
(swap! timeouts assoc k (js/setTimeout exec-fn-and-remove-timeout ms))))
(defn- f-slider
[slider-shared-values]
[rn/view {:style style/slider-container}
[reanimated/view {:style (style/slider slider-shared-values)}]])
(def get-gesture
(memoize
(fn
[[detecting-gesture? slider-width-shared-value max-amount
slider-height-shared-value
amount-shared-value amount-on-gesture-start width->amount
slider-opacity-shared-value on-new-amount set-detecting-gesture]]
(-> (gesture/gesture-pan)
(gesture/enabled detecting-gesture?)
(gesture/on-begin
(fn [_]
(animation/increase-slider slider-width-shared-value slider-height-shared-value)
(reset! amount-on-gesture-start (reanimated/get-shared-value amount-shared-value))))
(gesture/on-update
(fn [event]
(let [new-amount (-> (oops/oget event "translationX")
(width->amount)
(+ @amount-on-gesture-start)
(utils.number/value-in-range 1 max-amount))]
(reanimated/set-shared-value amount-shared-value new-amount))))
(gesture/on-finalize
(fn [_]
(animation/decrease-slider slider-width-shared-value slider-height-shared-value)
(animation/hide-slider slider-opacity-shared-value)
(on-new-amount (reanimated/get-shared-value amount-shared-value))
(add-new-timeout :turn-off-gesture #(set-detecting-gesture false) 20)))))))
(defn f-network-bar
[_]
(let [detecting-gesture? (reagent/atom false)
amount-on-gesture-start (atom 0)]
(fn [{:keys [total-width total-amount on-press on-new-amount allow-press?]
{:keys [amount-shared-value
max-amount]} :bar
:as props}]
(let [slider-width-shared-value (reanimated/use-shared-value 4)
slider-height-shared-value (reanimated/use-shared-value 32)
slider-opacity-shared-value (reanimated/use-shared-value 0)
network-bar-shared-value (reanimated/interpolate amount-shared-value
[0 total-amount]
[0 total-width])
width->amount #(/ (* % total-amount) total-width)]
[rn/pressable
{:on-press (fn []
(when (and (not @detecting-gesture?) allow-press?)
(on-press)
(reset! detecting-gesture? true)
(animation/show-slider slider-opacity-shared-value)))}
[reanimated/view
{:style (style/network-bar props network-bar-shared-value)
:accessibility-label :network-routing-bar}
[gesture/gesture-detector
{:gesture
(-> (gesture/gesture-pan)
(gesture/enabled @detecting-gesture?)
(gesture/on-begin
(fn [_]
(animation/increase-slider slider-width-shared-value slider-height-shared-value)
(reset! amount-on-gesture-start (reanimated/get-shared-value amount-shared-value))))
(gesture/on-update
(fn [event]
(let [new-amount (-> (oops/oget event "translationX")
(width->amount)
(+ @amount-on-gesture-start)
(utils.number/value-in-range 1 max-amount))]
(reanimated/set-shared-value amount-shared-value new-amount))))
(gesture/on-finalize
(fn [_]
(animation/decrease-slider slider-width-shared-value slider-height-shared-value)
(animation/hide-slider slider-opacity-shared-value)
(on-new-amount (reanimated/get-shared-value amount-shared-value))
(add-new-timeout :turn-off-gesture #(reset! detecting-gesture? false) 20))))}
[:f> f-slider
{:width-shared-value slider-width-shared-value
:height-shared-value slider-height-shared-value
:opacity-shared-value slider-opacity-shared-value}]]]]))))
(defn network-bar
[{:keys [total-width total-amount on-press on-new-amount allow-press?
bar-idx bar-width bar-max-width]
{:keys [amount-shared-value
max-amount]
:as bar} :bar
:as props}]
(let [[detecting-gesture?
set-detecting-gesture] (rn/use-state false)
amount-on-gesture-start (rn/use-ref-atom 0)
slider-width-shared-value (reanimated/use-shared-value 4)
slider-height-shared-value (reanimated/use-shared-value 32)
slider-opacity-shared-value (reanimated/use-shared-value 0)
network-bar-shared-value (reanimated/interpolate amount-shared-value
[0 total-amount]
[0 total-width])
width->amount #(/ (* % total-amount) total-width)
gesture (get-gesture
[detecting-gesture? slider-width-shared-value max-amount
slider-height-shared-value
amount-shared-value amount-on-gesture-start width->amount
slider-opacity-shared-value on-new-amount set-detecting-gesture])
on-press (rn/use-callback
(fn []
(when (and (not detecting-gesture?) allow-press?)
(on-press bar bar-idx bar-width bar-max-width)
(set-detecting-gesture true)
(animation/show-slider slider-opacity-shared-value)))
[detecting-gesture? allow-press? on-press])]
[rn/pressable
{:on-press on-press}
[reanimated/view
{:style (style/network-bar props network-bar-shared-value)
:accessibility-label :network-routing-bar}
[gesture/gesture-detector {:gesture gesture}
[rn/view {:style style/slider-container}
[reanimated/view
{:style (style/slider {:width-shared-value slider-width-shared-value
:height-shared-value slider-height-shared-value
:opacity-shared-value slider-opacity-shared-value})}]]]]]))
(defn- add-bar-shared-values
[{:keys [amount] :as network}]
@@ -93,101 +105,113 @@
(interleave (repeat [rn/view {:style (style/dashed-line-line network-name)}])
(repeat [rn/view {:style style/dashed-line-space}])))))
(defn f-network-routing-bars
[_]
(let [selected-network-idx (reagent/atom nil)
press-locked? (reagent/atom false)
lock-press #(reset! press-locked? true)
unlock-press #(reset! press-locked? false)
reset-state-values #(reset! selected-network-idx nil)]
(fn [{:keys [networks total-width total-amount requesting-data? on-amount-selected]}]
(let [bar-opacity-shared-value (reanimated/use-shared-value 0)
network-bars (map add-bar-shared-values networks)
amount->width #(* % (/ total-width total-amount))
bars-widths-negative (map #(-> % get-negative-amount amount->width)
network-bars)
last-bar-idx (dec (count network-bars))]
(rn/use-effect
#(when (and (not requesting-data?) @selected-network-idx)
(let [bar (nth network-bars @selected-network-idx)]
(animation/hide-pressed-bar bar amount->width))
(animation/update-bar-values-and-reset-animations
{:new-network-values networks
:network-bars network-bars
:amount->width amount->width
:reset-values-fn reset-state-values
:lock-press-fn lock-press
:unlock-press-fn unlock-press
:add-new-timeout add-new-timeout}))
[requesting-data?])
[:<>
(doall
(for [[bar-idx bar] (map-indexed vector network-bars)
:let [bar-max-width (amount->width (:max-amount bar))
bar-width (-> (:amount-shared-value bar)
(reanimated/get-shared-value)
(amount->width))
hide-division? (or (= last-bar-idx bar-idx) @selected-network-idx)
this-bar-selected? (= @selected-network-idx bar-idx)]]
^{:key (str "network-bar-" bar-idx)}
[:f> f-network-bar
{:bar bar
:max-width bar-max-width
:total-width total-width
:total-amount total-amount
:bar-division? hide-division?
:on-top? this-bar-selected?
:allow-press? (and (or (not @selected-network-idx) this-bar-selected?)
(not requesting-data?)
(not @press-locked?))
:on-press (fn []
(when-not @selected-network-idx
(let [[previous-bars [_ & next-bars]] (split-at bar-idx network-bars)
number-previous-bars bar-idx]
(animation/move-previous-bars
{:bars previous-bars
:bars-widths-negative bars-widths-negative})
(animation/move-pressed-bar
{:bar bar
:bars-widths-negative bars-widths-negative
:number-previous-bars number-previous-bars})
(animation/move-next-bars
{:bars next-bars
:bars-widths-negative bars-widths-negative
:number-previous-bars (inc number-previous-bars)
:extra-offset (max 0 (- bar-max-width bar-width))
:add-new-timeout add-new-timeout}))
(animation/show-max-limit-bar bar-opacity-shared-value)
(reset! selected-network-idx bar-idx)))
:on-new-amount (fn [new-amount]
(animation/hide-max-limit-bar bar-opacity-shared-value)
(when on-amount-selected
(on-amount-selected new-amount @selected-network-idx)))}]))
(defn- network-routing-bars
[{:keys [networks total-width total-amount requesting-data? on-amount-selected]}]
(let [[selected-network-idx
set-selected-network-idx] (rn/use-state nil)
[press-locked?
set-press-locked] (rn/use-state false)
lock-press (rn/use-callback #(set-press-locked true))
unlock-press (rn/use-callback #(set-press-locked false))
reset-state-values (rn/use-callback #(set-selected-network-idx nil))
bar-opacity-shared-value (reanimated/use-shared-value 0)
network-bars (map add-bar-shared-values networks)
amount->width #(* % (/ total-width total-amount))
bars-widths-negative (map #(-> % get-negative-amount amount->width)
network-bars)
last-bar-idx (dec (count network-bars))
network-bar-on-press (rn/use-callback
(fn [bar bar-idx bar-width bar-max-width]
(when-not selected-network-idx
(let [[previous-bars
[_ & next-bars]] (split-at bar-idx network-bars)]
(animation/move-previous-bars
{:bars previous-bars
:bars-widths-negative bars-widths-negative})
(animation/move-pressed-bar
{:bar bar
:bars-widths-negative bars-widths-negative
:number-previous-bars bar-idx})
(animation/move-next-bars
{:bars next-bars
:bars-widths-negative bars-widths-negative
:number-previous-bars (inc bar-idx)
:extra-offset (max 0 (- bar-max-width bar-width))
:add-new-timeout add-new-timeout}))
(animation/show-max-limit-bar bar-opacity-shared-value)
(set-selected-network-idx bar-idx)))
[selected-network-idx bars-widths-negative network-bars])
on-new-amount (rn/use-callback
(fn [new-amount]
(animation/hide-max-limit-bar bar-opacity-shared-value)
(when on-amount-selected
(on-amount-selected new-amount selected-network-idx)))
[on-amount-selected selected-network-idx])]
(rn/use-effect
#(when (and (not requesting-data?) selected-network-idx)
(let [bar (nth network-bars selected-network-idx)]
(animation/hide-pressed-bar bar amount->width))
(animation/update-bar-values-and-reset-animations
{:new-network-values networks
:network-bars network-bars
:amount->width amount->width
:reset-values-fn reset-state-values
:lock-press-fn lock-press
:unlock-press-fn unlock-press
:add-new-timeout add-new-timeout}))
[requesting-data?])
[:<>
(doall
(for [[bar-idx bar] (map-indexed vector network-bars)
:let [bar-max-width (amount->width (:max-amount bar))
bar-width (-> (:amount-shared-value bar)
(reanimated/get-shared-value)
(amount->width))
hide-division? (or (= last-bar-idx bar-idx) selected-network-idx)
this-bar-selected? (= selected-network-idx bar-idx)]]
^{:key (str "network-bar-" bar-idx)}
[network-bar
{:bar bar
:bar-idx bar-idx
:bar-width bar-width
:bar-max-width bar-max-width
:total-width total-width
:total-amount total-amount
:bar-division? hide-division?
:on-top? this-bar-selected?
:allow-press? (and (or (not selected-network-idx) this-bar-selected?)
(not requesting-data?)
(not press-locked?))
:on-press network-bar-on-press
:on-new-amount on-new-amount}]))
(let [{:keys [max-amount network-name]} (some->> @selected-network-idx
(nth network-bars))
limit-bar-width (amount->width max-amount)]
[reanimated/view
{:style (style/max-limit-bar
{:opacity-shared-value bar-opacity-shared-value
:width limit-bar-width})}
[rn/view {:style (style/max-limit-bar-background network-name)}]
[dashed-line network-name]])]))))
(let [{:keys [max-amount network-name]} (some->> selected-network-idx
(nth network-bars))
limit-bar-width (amount->width max-amount)]
[reanimated/view
{:style (style/max-limit-bar
{:opacity-shared-value bar-opacity-shared-value
:width limit-bar-width})}
[rn/view {:style (style/max-limit-bar-background network-name)}]
[dashed-line network-name]])]))
(defn view-internal
[{:keys [networks container-style theme] :as params}]
(reagent/with-let [total-width (reagent/atom nil)]
[{:keys [networks container-style] :as params}]
(let [theme (quo.theme/use-theme-value)
[total-width
set-total-width] (rn/use-state nil)
on-layout (rn/use-callback #(let [width (oops/oget % "nativeEvent.layout.width")]
(when (not= width total-width)
(set-total-width width))))]
(rn/use-unmount (fn []
(doseq [[_ living-timeout] @timeouts]
(js/clearTimeout living-timeout))))
[rn/view
{:accessibility-label :network-routing
:style (style/container container-style theme)
:on-layout #(reset! total-width (oops/oget % "nativeEvent.layout.width"))}
(when @total-width
:on-layout on-layout}
(when total-width
^{:key (str "network-routing-" (count networks))}
[:f> f-network-routing-bars (assoc params :total-width @total-width)])]
(finally
(doseq [[_ living-timeout] @timeouts]
(js/clearTimeout living-timeout)))))
[network-routing-bars (assoc params :total-width total-width)])]))
(def view
(quo.theme/with-theme
(schema/instrument #'view-internal network-routing-schema/?schema)))
(def view (schema/instrument #'view-internal network-routing-schema/?schema))
@@ -14,6 +14,5 @@
[:networks {:optional true}
[:maybe [:sequential [:map [:source [:maybe :schema.common/image-source]]]]]]
[:customization-color {:optional true} [:maybe :schema.common/customization-color]]
[:value {:optional true} [:maybe :string]]
[:theme :schema.common/theme]]]]
[:value {:optional true} [:maybe :string]]]]]
:any])
+37 -35
View File
@@ -12,7 +12,6 @@
[quo.foundations.common :as common]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[reagent.core :as reagent]
[schema.core :as schema]))
(defn fiat-format
@@ -75,7 +74,8 @@
[token-name-text theme text]])
(defn input-section
[{:keys [on-change-text value value-atom on-selection-change on-token-press]}]
[{:keys [on-change-text value value-internal set-value-internal on-selection-change
on-token-press]}]
(let [input-ref (atom nil)
set-ref #(reset! input-ref %)
focus-input #(when-let [ref ^js @input-ref]
@@ -83,7 +83,7 @@
controlled-input? (some? value)
handle-on-change-text (fn [v]
(when-not controlled-input?
(reset! value-atom v))
(set-value-internal v))
(when on-change-text
(on-change-text v)))
handle-selection-change (fn [^js e]
@@ -117,41 +117,43 @@
:on-selection-change handle-selection-change
:selection (clj->js selection)}
controlled-input? (assoc :value value)
(not controlled-input?) (assoc :default-value @value-atom))]]
(not controlled-input?) (assoc :default-value value-internal))]]
[token-label
{:theme theme
:text (if crypto? token currency)
:value (if controlled-input? value @value-atom)}]])))
:value (if controlled-input? value value-internal)}]])))
(defn- view-internal
[]
(let [width (:width (rn/get-window))
value-atom (reagent/atom nil)
crypto? (reagent/atom true)]
(fn [{:keys [theme container-style value on-swap] :as props}]
(let [handle-on-swap (fn []
(swap! crypto? not)
(when on-swap (on-swap @crypto?)))]
[rn/view {:style (merge (style/main-container width) container-style)}
[rn/view {:style style/amount-container}
[input-section
(assoc props
:value-atom value-atom
:crypto? @crypto?)]
[button/button
{:icon true
:icon-only? true
:size 32
:on-press handle-on-swap
:type :outline
:accessibility-label :reorder}
:i/reorder]]
[divider-line/view {:container-style (style/divider theme)}]
[data-info
(assoc props
:crypto? @crypto?
:amount (or value @value-atom))]]))))
[{:keys [container-style value on-swap] :as props}]
(let [theme (quo.theme/use-theme-value)
width (:width (rn/get-window))
[value-internal set-value-internal] (rn/use-state nil)
[crypto? set-crypto] (rn/use-state true)
handle-on-swap (rn/use-callback
(fn []
(set-crypto (not crypto?))
(when on-swap (on-swap (not crypto?))))
[crypto? on-swap])]
[rn/view {:style (merge (style/main-container width) container-style)}
[rn/view {:style style/amount-container}
[input-section
(assoc props
:value-internal value-internal
:set-value-internal set-value-internal
:crypto? crypto?)]
[button/button
{:icon true
:icon-only? true
:size 32
:on-press handle-on-swap
:type :outline
:accessibility-label :reorder}
:i/reorder]]
[divider-line/view {:container-style (style/divider theme)}]
[data-info
(assoc props
:theme theme
:crypto? crypto?
:amount (or value value-internal))]]))
(def view
(quo.theme/with-theme
(schema/instrument #'view-internal component-schema/?schema)))
(def view (schema/instrument #'view-internal component-schema/?schema))
@@ -13,7 +13,6 @@
[:blur? {:optional true} [:maybe :boolean]]
[:on-press {:optional true} [:maybe fn?]]
[:state {:optional true} [:maybe [:= :disabled]]]
[:theme :schema.common/theme]
[:second-tag-prefix {:optional true} [:maybe :keyword]]
[:third-tag-prefix {:optional true} [:maybe :keyword]]
[:fourth-tag-prefix {:optional true} [:maybe :keyword]]
@@ -8,7 +8,6 @@
[quo.theme :as quo.theme]
[react-native.core :as rn]
[react-native.hole-view :as hole-view]
[reagent.core :as reagent]
[schema.core :as schema]
[utils.i18n :as i18n]))
@@ -100,39 +99,37 @@
[context-tag/view (merge props {:size 24 :blur? blur?})]])
(defn- view-internal
[_]
(let [pressed? (reagent/atom false)]
(fn
[{:keys [state theme blur?
on-press
first-tag second-tag third-tag fourth-tag
second-tag-prefix third-tag-prefix fourth-tag-prefix]
:as props}]
[rn/pressable
{:style (style/wallet-activity-container {:pressed? @pressed?
:theme theme
:blur? blur?})
:accessibility-label :wallet-activity
:disabled (= state :disabled)
:on-press on-press
:on-press-in (fn [] (reset! pressed? true))
:on-press-out (fn [] (reset! pressed? false))}
[rn/view
{:style {:flex-direction :row}}
[transaction-icon-view props]
[rn/view
{:style style/content-container}
[transaction-header props]
[rn/view {:style style/content-line}
(when first-tag [prop-tag first-tag blur?])
(when second-tag-prefix [prop-text second-tag-prefix theme])
(when second-tag [prop-tag second-tag blur?])]
[rn/view {:style style/content-line}
(when third-tag-prefix [prop-text third-tag-prefix theme])
(when third-tag [prop-tag third-tag blur?])
(when fourth-tag-prefix [prop-text fourth-tag-prefix theme])
(when fourth-tag [prop-tag fourth-tag blur?])]]]])))
[{:keys [state blur? first-tag second-tag third-tag fourth-tag on-press
second-tag-prefix third-tag-prefix fourth-tag-prefix]
:as props}]
(let [theme (quo.theme/use-theme-value)
[pressed?
set-pressed] (rn/use-state false)
on-press-in (rn/use-callback #(set-pressed true))
on-press-out (rn/use-callback #(set-pressed false))]
[rn/pressable
{:style (style/wallet-activity-container {:pressed? pressed?
:theme theme
:blur? blur?})
:accessibility-label :wallet-activity
:disabled (= state :disabled)
:on-press on-press
:on-press-in on-press-in
:on-press-out on-press-out}
[rn/view
{:style {:flex-direction :row}}
[transaction-icon-view props]
[rn/view
{:style style/content-container}
[transaction-header props]
[rn/view {:style style/content-line}
(when first-tag [prop-tag first-tag blur?])
(when second-tag-prefix [prop-text second-tag-prefix theme])
(when second-tag [prop-tag second-tag blur?])]
[rn/view {:style style/content-line}
(when third-tag-prefix [prop-text third-tag-prefix theme])
(when third-tag [prop-tag third-tag blur?])
(when fourth-tag-prefix [prop-text fourth-tag-prefix theme])
(when fourth-tag [prop-tag fourth-tag blur?])]]]]))
(def view
(quo.theme/with-theme
(schema/instrument #'view-internal component-schema/?schema)))
(def view (schema/instrument #'view-internal component-schema/?schema))
+4 -1
View File
@@ -10,7 +10,10 @@
(def ui-themed
{:no-funds
{:light (js/require "../resources/images/ui2/no-funds-light.png")
:dark (js/require "../resources/images/ui2/no-funds-dark.png")}})
:dark (js/require "../resources/images/ui2/no-funds-dark.png")}
:no-contacts-to-chat
{:light (js/require "../resources/images/ui2/no-contacts-to-chat-light.png")
:dark (js/require "../resources/images/ui2/no-contacts-to-chat-dark.png")}})
(defn get-themed-image
[k theme]
+1 -1
View File
@@ -12,5 +12,5 @@
(defn contacts-section-header
[{:keys [title]}]
(let [theme (quo.theme/use-theme-value)]
[quo/divider-label {:container-style {:background-color (style/contacts-section-header theme)}}
[quo/divider-label {:container-style (style/contacts-section-header theme)}
title]))
@@ -5,7 +5,7 @@
[utils.re-frame :as rf]))
(defn contact-list-item
[{:keys [on-press on-long-press accessory allow-multiple-presses?]}
[{:keys [on-press on-long-press accessory allow-multiple-presses? disabled?]}
{:keys [primary-name secondary-name public-key compressed-key ens-verified added?]}
theme]
(let [photo-path (rf/sub [:chats/photo-path public-key])
@@ -24,4 +24,5 @@
:contact? added?
:on-press on-press
:on-long-press on-long-press
:accessory accessory}]))
:accessory accessory
:disabled? disabled?}]))
@@ -101,6 +101,7 @@
{:keys [primary-name public-key]} (when one-contact-selected?
(rf/sub [:contacts/contact-by-identity
(first selected-contacts)]))]
(rn/use-unmount #(rf/dispatch [:group-chat/clear-contacts]))
[rn/view {:flex 1}
[rn/view {:padding-horizontal 20}
[quo/button
@@ -41,8 +41,13 @@
(defn f-send-button
[props state animations window-height images? btn-opacity z-index edit]
(let [{:keys [text-value]} state
customization-color (rf/sub [:profile/customization-color])]
(let [{:keys [text-value]} state
profile-customization-color (rf/sub [:profile/customization-color])
{:keys [chat-id chat-type]
chat-color :color} (rf/sub [:chats/current-chat-chat-view])
contact-customization-color (when (= chat-type constants/one-to-one-chat-type)
(rf/sub [:contacts/contact-customization-color-by-address
chat-id]))]
(rn/use-effect (fn []
;; Handle send button opacity animation and z-index when input content changes
(if (or (seq @text-value) images?)
@@ -60,7 +65,7 @@
[quo/button
{:icon-only? true
:size 32
:customization-color customization-color
:customization-color (or contact-customization-color chat-color profile-customization-color)
:accessibility-label :send-message-button
:on-press #(send-message props state animations window-height edit)}
:i/arrow-up]]))
@@ -29,11 +29,15 @@
(defn pinned-message
[{:keys [from quoted-message timestamp-str]}]
(let [[primary-name _] (rf/sub [:contacts/contact-two-names-by-identity from])
customization-color (rf/sub [:profile/customization-color])]
(let [[primary-name _] (rf/sub [:contacts/contact-two-names-by-identity from])
one-to-one-chat? (rf/sub [:current-chat/one-to-one-chat?])
current-chat-color (rf/sub [:chats/current-chat-color])
contact-customization-color (rf/sub [:contacts/contact-customization-color-by-address from])]
[quo/system-message
{:type :pinned
:pinned-by primary-name
:customization-color customization-color
:customization-color (if one-to-one-chat?
contact-customization-color
current-chat-color)
:child [reply/quoted-message quoted-message false true]
:timestamp timestamp-str}]))
@@ -79,16 +79,17 @@
(defn system-message-contact-request
[{:keys [chat-id timestamp-str from]} type]
(let [[primary-name _] (rf/sub [:contacts/contact-two-names-by-identity chat-id])
contact (rf/sub [:contacts/contact-by-address chat-id])
photo-path (when (seq (:images contact)) (rf/sub [:chats/photo-path chat-id]))
customization-color (rf/sub [:profile/customization-color])
public-key (rf/sub [:profile/public-key])]
(let [[primary-name _] (rf/sub [:contacts/contact-two-names-by-identity chat-id])
{:keys [images]
contact-customization-color
:customization-color} (rf/sub [:contacts/contact-by-address chat-id])
photo-path (when (seq images) (rf/sub [:chats/photo-path chat-id]))
public-key (rf/sub [:profile/public-key])]
[quo/system-message
{:type type
:timestamp timestamp-str
:display-name primary-name
:customization-color customization-color
:customization-color contact-customization-color
:photo-path photo-path
:incoming? (not= public-key from)}]))
@@ -1,6 +1,8 @@
(ns status-im.contexts.chat.messenger.messages.list.style
(:require
[quo.foundations.colors :as colors]
[quo.foundations.shadows :as shadows]
[quo.theme :as quo.theme]
[react-native.reanimated :as reanimated]
[status-im.contexts.chat.messenger.messages.constants :as messages.constants]))
@@ -23,10 +25,12 @@
[bottom theme top-margin]
(reanimated/apply-animations-to-style
{:bottom bottom}
{:background-color (colors/theme-colors colors/white colors/neutral-95 theme)
:padding-horizontal 20
:border-radius 20
:margin-top top-margin}))
(merge
(shadows/get 2 (quo.theme/get-theme) :inverted)
{:background-color (colors/theme-colors colors/white colors/neutral-95 theme)
:padding-horizontal 20
:border-radius 20
:margin-top top-margin})))
(defn header-image
[scale top left theme]
@@ -1,5 +1,6 @@
(ns status-im.contexts.chat.messenger.messages.list.view
(:require
[clojure.string :as string]
[legacy.status-im.ui.screens.chat.group :as chat.group]
[oops.core :as oops]
[quo.core :as quo]
@@ -112,19 +113,24 @@
[rn/view {:style {:height height}}]))
(defn list-footer-avatar
[{:keys [distance-from-list-top display-name online? profile-picture theme group-chat color]}]
(let [scale (reanimated/interpolate distance-from-list-top
[0 messages.constants/header-container-top-margin]
[1 0.4]
messages.constants/default-extrapolation-option)
top (reanimated/interpolate distance-from-list-top
[0 messages.constants/header-container-top-margin]
[-44 -12]
messages.constants/default-extrapolation-option)
left (reanimated/interpolate distance-from-list-top
[0 messages.constants/header-container-top-margin]
[16 -8]
messages.constants/default-extrapolation-option)]
[{:keys [distance-from-list-top display-name online? profile-picture theme group-chat color
emoji chat-type chat-name last-message]}]
(let [scale (reanimated/interpolate
distance-from-list-top
[0 (if (seq last-message) messages.constants/header-container-top-margin 0)]
[1 0.4]
messages.constants/default-extrapolation-option)
top (reanimated/interpolate
distance-from-list-top
[0 (if (seq last-message) messages.constants/header-container-top-margin 0)]
[-44 -12]
messages.constants/default-extrapolation-option)
left (reanimated/interpolate
distance-from-list-top
[0 (if (seq last-message) messages.constants/header-container-top-margin 0)]
[16 -8]
messages.constants/default-extrapolation-option)
community-channel? (= chat-type constants/community-chat-type)]
[reanimated/view
{:style (style/header-image scale top left theme)}
(if group-chat
@@ -132,7 +138,10 @@
{:customization-color color
:size :size-80
:picture profile-picture
:override-theme :dark}]
:emoji (when (and (not (string/blank? emoji))
community-channel?)
(string/trim emoji))
:chat-name chat-name}]
[quo/user-avatar
{:full-name display-name
:online? online?
@@ -140,15 +149,17 @@
:size :big}])]))
(defn chat-display-name
[{:keys [distance-from-list-top display-name contact theme]}]
(let [top (reanimated/interpolate distance-from-list-top
[0 messages.constants/header-container-top-margin]
[0 -35]
messages.constants/default-extrapolation-option)
left (reanimated/interpolate distance-from-list-top
[0 messages.constants/header-container-top-margin]
[0 40]
messages.constants/default-extrapolation-option)]
[{:keys [distance-from-list-top display-name contact theme last-message]}]
(let [top (reanimated/interpolate
distance-from-list-top
[0 (if (seq last-message) messages.constants/header-container-top-margin 0)]
[0 -35]
messages.constants/default-extrapolation-option)
left (reanimated/interpolate
distance-from-list-top
[0 (if (seq last-message) messages.constants/header-container-top-margin 0)]
[0 40]
messages.constants/default-extrapolation-option)]
[reanimated/view
{:style (style/user-name-container top left)}
[rn/view
@@ -195,54 +206,55 @@
muted?)))}]}]))
(defn bio-and-actions
[{:keys [distance-from-list-top bio chat-id customization-color]}]
(let [has-bio (seq bio)
[{:keys [distance-from-list-top bio chat-id customization-color last-message description]}]
(let [has-bio (seq (or bio description))
top (reanimated/interpolate
distance-from-list-top
[0 messages.constants/header-container-top-margin]
[0 (if (seq last-message) messages.constants/header-container-top-margin 0)]
[(if has-bio 8 16) (if has-bio -28 -20)]
messages.constants/default-extrapolation-option)]
[reanimated/view
{:style (style/bio-and-actions top)}
(when has-bio
[quo/text bio])
[quo/text (or bio description)])
[actions chat-id customization-color]]))
(defn footer-component
[{:keys [chat distance-from-list-top theme customization-color]}]
(let [{:keys [chat-id chat-name emoji chat-type
group-chat color]} 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))
{:keys [bio]} (rf/sub [:contacts/contact-by-identity chat-id])
online? (rf/sub [:visibility-status-updates/online? chat-id])
contact (when-not group-chat
(rf/sub [:contacts/contact-by-address chat-id]))
photo-path (rf/sub [:chats/photo-path chat-id])
top-margin (+ (safe-area/get-top)
messages.constants/top-bar-height
messages.constants/header-container-top-margin
32)
background-color (colors/theme-colors
(colors/resolve-color customization-color theme 20)
(colors/resolve-color customization-color theme 40)
theme)
bottom (reanimated/interpolate
distance-from-list-top
[0 messages.constants/header-container-top-margin]
[32 -4]
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)]
group-chat color description
last-message]} 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 "# " chat-name)
:else (str emoji chat-name))
{:keys [bio]} (rf/sub [:contacts/contact-by-identity chat-id])
online? (rf/sub [:visibility-status-updates/online? chat-id])
contact (when-not group-chat
(rf/sub [:contacts/contact-by-address chat-id]))
photo-path (rf/sub [:chats/photo-path chat-id])
top-margin (+ (safe-area/get-top)
messages.constants/top-bar-height
messages.constants/header-container-top-margin
32)
background-color (colors/theme-colors
(colors/resolve-color customization-color theme 20)
(colors/resolve-color customization-color theme 40)
theme)
bottom (reanimated/interpolate
distance-from-list-top
[0 messages.constants/header-container-top-margin]
[32 -4]
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)}]
@@ -254,18 +266,25 @@
:theme theme
:profile-picture photo-path
:group-chat group-chat
:color color}]
:color color
:emoji emoji
:chat-type chat-type
:chat-name chat-name
:last-message last-message}]
[chat-display-name
{:distance-from-list-top distance-from-list-top
:display-name display-name
:theme theme
:contact contact
:group-chat group-chat}]
:group-chat group-chat
:last-message last-message}]
[bio-and-actions
{:distance-from-list-top distance-from-list-top
:bio bio
:chat-id chat-id
:customization-color customization-color}]]]))
:customization-color customization-color
:description description
:last-message last-message}]]]))
(defn list-footer
[props]
@@ -1,5 +1,6 @@
(ns status-im.contexts.chat.messenger.messages.navigation.view
(:require
[clojure.string :as string]
[quo.core :as quo]
[quo.foundations.colors :as colors]
[re-frame.db]
@@ -28,7 +29,7 @@
[:contacts/contact-two-names-by-identity
chat-id]))
(= chat-type constants/community-chat-type)
(str (when emoji (str emoji " ")) "# " chat-name)
(str "# " chat-name)
:else (str emoji chat-name))
online? (when-not group-chat (rf/sub [:visibility-status-updates/online? chat-id]))
photo-path (when-not group-chat (rf/sub [:chats/photo-path chat-id]))
@@ -53,7 +54,10 @@
{:customization-color color
:size :size-32
:picture photo-path
:override-theme :dark}]
:override-theme :dark
:emoji (when-not (string/blank? emoji)
(string/trim emoji))
:chat-name chat-name}]
[quo/user-avatar
{:full-name display-name
:online? online?
@@ -111,27 +115,29 @@
(defn view
[{:keys [distance-from-list-top chat-screen-layout-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)
navigation-buttons-opacity (worklets/navigation-buttons-complete-opacity
chat-screen-layout-calculations-complete?)
reached-threshold? (messages.worklets/use-messages-scrolled-to-threshold
distance-from-list-top
top-bar-height)
button-background (if reached-threshold? :photo :blur)]
(let [{:keys [chat-id chat-type last-message]
: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)
navigation-buttons-opacity (worklets/navigation-buttons-complete-opacity
chat-screen-layout-calculations-complete?)
reached-threshold? (messages.worklets/use-messages-scrolled-to-threshold
distance-from-list-top
top-bar-height)
button-background (if reached-threshold? :photo :blur)]
(rn/use-effect (fn [] (reanimated/set-shared-value all-loaded? all-loaded-sub))
[all-loaded-sub])
[rn/view
{:style (style/navigation-view navigation-view-height messages.constants/pinned-banner-height)}
[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?}]
(when (seq last-message)
[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)}
[reanimated/view {:style (style/button-animation-container navigation-buttons-opacity)}
[quo/button
@@ -31,5 +31,5 @@
(def divider
{:padding-horizontal 20
:margin-top 16
:margin-top 12
:margin-bottom 8})
@@ -53,7 +53,8 @@
[{:keys [title]}]
(when-not (= title no-title)
[quo/divider-label
{:container-style style/divider}
{:container-style style/divider
:tight? false}
title]))
(defn key-fn
@@ -7,7 +7,7 @@
[bottom-inset]
{:left 0
:right 0
:height (+ bottom-inset (if platform/ios? 65 85))
:height (+ bottom-inset (if platform/ios? 51 85))
:position :absolute
:bottom 0})
@@ -0,0 +1,48 @@
(ns status-im.contexts.communities.actions.invite-contacts.style
(:require
[quo.foundations.colors :as colors]
[react-native.safe-area :as safe-area]))
(def contact-selection-heading
{:flex-direction :row
:justify-content :space-between
:align-items :flex-end
:margin-top 24
:margin-bottom 16})
(def chat-button
{:position :absolute
:bottom (safe-area/get-bottom)
:left 20
:right 20})
(defn no-contacts
[]
{:margin-bottom (+ 96 (safe-area/get-bottom))
:flex 1
:justify-content :center
:align-items :center})
(def context-tag
{:align-self :flex-start
:margin-top -8
:margin-bottom 12})
(def no-contacts-text
{:margin-bottom 2
:margin-top 12})
(def no-contacts-button-container
{:margin-top 20
:margin-bottom 12})
(defn section-list-container-style
[theme]
{:padding-bottom 70
:background-color (colors/theme-colors colors/white
colors/neutral-95
theme)})
(defn invite-to-community-text
[theme]
{:color (colors/theme-colors colors/neutral-100 colors/white theme)})
@@ -0,0 +1,141 @@
(ns status-im.contexts.communities.actions.invite-contacts.view
(:require
[quo.core :as quo]
[quo.foundations.resources :as resources]
[quo.theme]
[react-native.core :as rn]
[react-native.gesture :as gesture]
[react-native.share :as share]
[status-im.common.contact-list-item.view :as contact-list-item]
[status-im.common.contact-list.view :as contact-list]
[status-im.contexts.communities.actions.invite-contacts.style :as style]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn- no-contacts-view
[{:keys [theme id]}]
(let [customization-color (rf/sub [:profile/customization-color])
{:keys [universal-profile-url]} (rf/sub [:profile/profile])
on-press-share-community (rn/use-callback
#(rf/dispatch [:communities/share-community-url-with-data
id]))
on-press-share-profile (rn/use-callback #(share/open {:url universal-profile-url})
[universal-profile-url])]
[rn/view
{:style (style/no-contacts)}
[rn/image {:source (resources/get-themed-image :no-contacts-to-chat theme)}]
[quo/text
{:weight :semi-bold
:size :paragraph-1
:style style/no-contacts-text}
(i18n/label :t/you-have-no-contacts)]
[quo/text
{:weight :regular
:size :paragraph-2}
(i18n/label :t/dont-yell-at-me)]
[quo/button
{:customization-color customization-color
:theme theme
:type :primary
:size 32
:container-style style/no-contacts-button-container
:on-press on-press-share-community}
(i18n/label :t/send-community-link)]
[quo/button
{:customization-color customization-color
:theme theme
:type :grey
:size 32
:on-press on-press-share-profile}
(i18n/label :t/invite-friends-to-status)]]))
(defn- contact-item
[{:keys [public-key]
:as item}]
(let [user-selected? (rf/sub [:is-contact-selected? public-key])
{:keys [id]} (rf/sub [:get-screen-params])
community-members-keys (set (keys (rf/sub [:communities/community-members id])))
community-member? (boolean (community-members-keys public-key))
on-toggle (fn []
(when-not community-member?
(if user-selected?
(rf/dispatch [:deselect-contact public-key])
(rf/dispatch [:select-contact public-key]))))]
[contact-list-item/contact-list-item
{:on-press on-toggle
:allow-multiple-presses? true
:accessory {:type :checkbox
:disabled? community-member?
:checked? (or community-member? user-selected?)
:on-check on-toggle}
:disabled? community-member?}
item]))
(defn view-internal
[{:keys [theme]}]
(fn []
(rn/use-unmount #(rf/dispatch [:group-chat/clear-contacts]))
(let [customization-color (rf/sub [:profile/customization-color])
{:keys [id]} (rf/sub [:get-screen-params])
contacts (rf/sub [:contacts/filtered-active-sections])
selected (rf/sub [:group/selected-contacts])
{:keys [name images]} (rf/sub [:communities/community id])
selected-contacts-count (count selected)
on-press (fn []
(rf/dispatch [:communities/share-community-confirmation-pressed
selected id])
(rf/dispatch [:navigate-back])
(rf/dispatch [:toasts/upsert
{:type :positive
:theme theme
:text (if (= 1 selected-contacts-count)
(i18n/label :t/one-user-was-invited)
(i18n/label
:t/n-users-were-invited
{:count selected-contacts-count}))}]))
{window-height :height} (rn/get-window)]
[rn/view {:style {:flex 1}}
[rn/view {:style {:padding-horizontal 20}}
[quo/button
{:type :grey
:size 32
:icon-only? true
:on-press #(rf/dispatch [:navigate-back])}
:i/close]
[rn/view {:style style/contact-selection-heading}
[quo/text
{:weight :semi-bold
:size :heading-1
:style (style/invite-to-community-text theme)}
(i18n/label :t/invite-to-community)]]
[quo/context-tag
{:type :community
:size 24
:community-logo (:thumbnail images)
:community-name name
:container-style style/context-tag}]]
(if (empty? contacts)
[no-contacts-view
{:theme theme
:id id}]
[:<>
[gesture/section-list
{:key-fn :public-key
:sticky-section-headers-enabled true
:sections contacts
:render-section-header-fn contact-list/contacts-section-header
:content-container-style (style/section-list-container-style theme)
:render-fn contact-item
:style {:height window-height}}]
(when (pos? selected-contacts-count)
[quo/button
{:type :primary
:accessibility-label :next-button
:customization-color customization-color
:container-style style/chat-button
:on-press on-press}
(if (= 1 selected-contacts-count)
(i18n/label :t/invite-1-user)
(i18n/label :t/invite-n-users {:count selected-contacts-count}))])])])))
(def view (quo.theme/with-theme view-internal))
+93 -39
View File
@@ -1,11 +1,8 @@
(ns status-im.contexts.communities.events
(:require
[clojure.string :as string]
[legacy.status-im.data-store.chats :as data-store.chats]
[legacy.status-im.data-store.communities :as data-store.communities]
[legacy.status-im.mailserver.core :as mailserver]
[react-native.platform :as platform]
[react-native.share :as share]
[schema.core :as schema]
[status-im.constants :as constants]
[status-im.contexts.chat.messenger.messages.link-preview.events :as link-preview.events]
@@ -14,9 +11,9 @@
status-im.contexts.communities.actions.airdrop-addresses.events
status-im.contexts.communities.actions.community-options.events
status-im.contexts.communities.actions.leave.events
[status-im.contexts.communities.utils :as utils]
[status-im.navigation.events :as navigation]
[taoensso.timbre :as log]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn handle-community
@@ -152,43 +149,100 @@
:on-success #(rf/dispatch [:communities/fetched-collapsed-categories-success %])
:on-error #(log/error "failed to fetch collapsed community categories" %)}]}))
(rf/reg-event-fx :communities/get-community-channel-share-data
(fn [_ [chat-id on-success]]
(let [{:keys [community-id channel-id]} (data-store.chats/decode-chat-id chat-id)]
{:json-rpc/call
[{:method "wakuext_shareCommunityChannelURLWithData"
:params [{:CommunityID community-id :ChannelID channel-id}]
:on-success on-success
:on-error (fn [err]
(log/error "failed to retrieve community channel url with data"
{:error err
:chat-id chat-id
:event :communities/get-community-channel-share-data}))}]})))
(defn initialize-permission-addresses
[{:keys [db]} [community-id]]
(when community-id
(let [accounts (utils/sorted-non-watch-only-accounts db)
addresses (set (map :address accounts))]
{:db (update-in db
[:communities community-id]
assoc
:previous-share-all-addresses? true
:share-all-addresses? true
:previous-permission-addresses addresses
:selected-permission-addresses addresses
:airdrop-address (:address (first accounts)))})))
(rf/reg-event-fx :communities/share-community-channel-url-with-data
(fn [_ [chat-id]]
(let [title (i18n/label :t/channel-on-status)
on-success (fn [url]
(share/open
(if platform/ios?
{:activityItemSources [{:placeholderItem {:type "text"
:content title}
:item {:default {:type "url"
:content url}}
:linkMetadata {:title title}}]}
{:title title
:subject title
:message url
:url url
:isNewTask true})))]
{:fx [[:dispatch [:communities/get-community-channel-share-data chat-id on-success]]]})))
(rf/reg-event-fx :communities/initialize-permission-addresses
initialize-permission-addresses)
(rf/reg-event-fx :communities/share-community-channel-url-qr-code
(fn [_ [chat-id]]
(let [on-success #(rf/dispatch [:open-modal :share-community-channel
{:chat-id chat-id
:url %}])]
{:fx [[:dispatch [:communities/get-community-channel-share-data chat-id on-success]]]})))
(defn update-previous-permission-addresses
[{:keys [db]} [community-id]]
(when community-id
(let [accounts (utils/sorted-non-watch-only-accounts db)
selected-permission-addresses (get-in db
[:communities community-id
:selected-permission-addresses])
selected-accounts (filter #(contains? selected-permission-addresses (:address %))
accounts)
current-airdrop-address (get-in db [:communities community-id :airdrop-address])
share-all-addresses? (get-in db [:communities community-id :share-all-addresses?])]
{:db (update-in db
[:communities community-id]
assoc
:previous-share-all-addresses? share-all-addresses?
:previous-permission-addresses selected-permission-addresses
:airdrop-address (if (contains? selected-permission-addresses
current-airdrop-address)
current-airdrop-address
(:address (first selected-accounts))))})))
(rf/reg-event-fx :communities/update-previous-permission-addresses
update-previous-permission-addresses)
(defn toggle-selected-permission-address
[{:keys [db]} [address community-id]]
(let [selected-permission-addresses
(get-in db [:communities community-id :selected-permission-addresses])
updated-selected-permission-addresses
(if (contains? selected-permission-addresses address)
(disj selected-permission-addresses address)
(conj selected-permission-addresses address))]
{:db (assoc-in db
[:communities community-id :selected-permission-addresses]
updated-selected-permission-addresses)
:fx [(when community-id
[:dispatch
[:communities/check-permissions-to-join-community community-id
updated-selected-permission-addresses :based-on-client-selection]])]}))
(rf/reg-event-fx :communities/toggle-selected-permission-address
toggle-selected-permission-address)
(defn toggle-share-all-addresses
[{:keys [db]} [community-id]]
(let [share-all-addresses? (get-in db [:communities community-id :share-all-addresses?])
next-share-all-addresses? (not share-all-addresses?)
accounts (utils/sorted-non-watch-only-accounts db)
addresses (set (map :address accounts))]
{:db (update-in db
[:communities community-id]
assoc
:share-all-addresses? next-share-all-addresses?
:selected-permission-addresses addresses)
:fx [(when (and community-id next-share-all-addresses?)
[:dispatch
[:communities/check-permissions-to-join-community community-id
addresses :based-on-client-selection]])]}))
(rf/reg-event-fx :communities/toggle-share-all-addresses
toggle-share-all-addresses)
(rf/reg-event-fx :communities/reset-selected-permission-addresses
(fn [{:keys [db]} [community-id]]
(when community-id
{:db (update-in db
[:communities community-id]
assoc
:selected-permission-addresses
(get-in db [:communities community-id :previous-permission-addresses])
:share-all-addresses?
(get-in db [:communities community-id :previous-share-all-addresses?]))
:fx [[:dispatch [:communities/check-permissions-to-join-community community-id]]]})))
(rf/reg-event-fx :communities/set-airdrop-address
(fn [{:keys [db]} [address community-id]]
{:db (assoc-in db [:communities community-id :airdrop-address] address)}))
(defn community-fetched
[{:keys [db]} [community-id community]]
@@ -0,0 +1,100 @@
(ns status-im.contexts.communities.sharing.events
(:require [legacy.status-im.data-store.chats :as data-store.chats]
[react-native.platform :as platform]
[react-native.share :as share]
[taoensso.timbre :as log]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(rf/reg-event-fx :communities/invite-people-pressed
(fn [{:keys [db]} [id]]
{:db (assoc db :communities/community-id-input id)
:fx [[:dispatch [:hide-bottom-sheet]]
[:dispatch [:open-modal :invite-people-community {:id id}]]]}))
(rf/reg-event-fx :communities/share-community-pressed
(fn [{:keys [db]} [id]]
{:db (assoc db :communities/community-id-input id)
:fx [[:dispatch [:hide-bottom-sheet]]
[:dispatch [:open-modal :legacy-invite-people-community {:id id}]]]}))
(rf/reg-event-fx :communities/share-community-confirmation-pressed
(fn [_ [users-public-keys community-id]]
{:fx [[:json-rpc/call
[{:method "wakuext_shareCommunity"
:params [{:communityId community-id
:users users-public-keys}]
:js-response true
:on-success [:sanitize-messages-and-process-response]
:on-error (fn [err]
(log/error {:message "failed to share community"
:community-id community-id
:err err}))}]]]}))
(rf/reg-event-fx :communities/share-community-channel-url-qr-code
(fn [_ [chat-id]]
(let [on-success #(rf/dispatch [:open-modal :share-community-channel
{:chat-id chat-id
:url %}])]
{:fx [[:dispatch [:communities/get-community-channel-share-data chat-id on-success]]]})))
(rf/reg-event-fx :communities/share-community-url-with-data
(fn [_ [community-id]]
(let [title (i18n/label :t/community-on-status)
on-success (fn [url]
(share/open
(if platform/ios?
{:activityItemSources [{:placeholderItem {:type "text"
:content title}
:item {:default {:type "url"
:content url}}
:linkMetadata {:title title}}]}
{:title title
:subject title
:message url
:url url
:isNewTask true})))]
{:fx [[:dispatch [:communities/get-community-share-data community-id on-success]]]})))
(rf/reg-event-fx :communities/get-community-channel-share-data
(fn [_ [chat-id on-success]]
(let [{:keys [community-id channel-id]} (data-store.chats/decode-chat-id chat-id)]
{:json-rpc/call
[{:method "wakuext_shareCommunityChannelURLWithData"
:params [{:CommunityID community-id :ChannelID channel-id}]
:on-success on-success
:on-error (fn [err]
(log/error "failed to retrieve community channel url with data"
{:error err
:chat-id chat-id
:event :communities/get-community-channel-share-data}))}]})))
(rf/reg-event-fx :communities/get-community-share-data
(fn [_ [community-id on-success]]
{:json-rpc/call
[{:method "wakuext_shareCommunityURLWithData"
:params [community-id]
:on-success on-success
:on-error (fn [err]
(log/error "failed to retrieve community url with data"
{:error err
:community-id community-id
:event :communities/get-community-share-data}))}]}))
(rf/reg-event-fx :communities/share-community-channel-url-with-data
(fn [_ [chat-id]]
(let [title (i18n/label :t/channel-on-status)
on-success (fn [url]
(share/open
(if platform/ios?
{:activityItemSources [{:placeholderItem {:type "text"
:content title}
:item {:default {:type "url"
:content url}}
:linkMetadata {:title title}}]}
{:title title
:subject title
:message url
:url url
:isNewTask true})))]
{:fx [[:dispatch [:communities/get-community-channel-share-data chat-id on-success]]]})))
+1
View File
@@ -22,6 +22,7 @@
status-im.contexts.chat.messenger.photo-selector.events
status-im.contexts.communities.events
status-im.contexts.communities.overview.events
status-im.contexts.communities.sharing.events
status-im.contexts.onboarding.common.overlay.events
status-im.contexts.onboarding.events
status-im.contexts.profile.events
+7 -1
View File
@@ -14,10 +14,12 @@
[status-im.contexts.chat.messenger.messages.view :as chat]
[status-im.contexts.chat.messenger.photo-selector.view :as photo-selector]
[status-im.contexts.communities.actions.accounts-selection.view :as communities.accounts-selection]
[status-im.contexts.communities.actions.addresses-for-permissions.view :as addresses-for-permissions]
[status-im.contexts.communities.actions.addresses-for-permissions.view :as
addresses-for-permissions]
[status-im.contexts.communities.actions.airdrop-addresses.view :as airdrop-addresses]
[status-im.contexts.communities.actions.channel-view-details.view :as
channel-view-channel-members-and-details]
[status-im.contexts.communities.actions.invite-contacts.view :as communities.invite]
[status-im.contexts.communities.actions.request-to-join.view :as join-menu]
[status-im.contexts.communities.actions.share-community-channel.view :as share-community-channel]
[status-im.contexts.communities.discover.view :as communities.discover]
@@ -461,6 +463,10 @@
{:modalPresentationStyle :overCurrentContext})
:component scan-profile-qr-page/view}
{:name :invite-people-community
:options {:sheet? true}
:component communities.invite/view}
;; Settings
{:name :settings-password
+9 -1
View File
@@ -183,7 +183,15 @@
:synced-to
:synced-from
:community-id
:emoji])))
:emoji
:description
:last-message])))
(re-frame/reg-sub
:chats/current-chat-color
:<- [:chats/current-raw-chat]
(fn [current-chat]
(:color current-chat)))
(re-frame/reg-sub
:chats/community-channel-ui-details-by-id
+7
View File
@@ -253,6 +253,13 @@
multiaccount
(contact.db/find-contact-by-address contacts address))))
(re-frame/reg-sub
:contacts/contact-customization-color-by-address
(fn [[_ address]]
[(re-frame/subscribe [:contacts/contact-by-address address])])
(fn [[contact]]
(:customization-color contact)))
(re-frame/reg-sub
:contacts/filtered-active-sections
:<- [:contacts/active-sections]
+6
View File
@@ -92,6 +92,12 @@
(fn [profile]
(:test-networks-enabled? profile)))
(re-frame/reg-sub
:profile/universal-profile-url
:<- [:profile/profile]
(fn [profile]
(:universal-profile-url profile)))
(re-frame/reg-sub
:profile/is-goerli-enabled?
:<- [:profile/profile]
+2 -3
View File
@@ -500,9 +500,8 @@ class CommunityView(HomeView):
community_element.long_press_until_element_is_shown(self.share_community_button)
self.share_community_button.click()
for user_name in user_names_to_share:
user_contact = self.element_by_text_part(user_name)
user_contact.scroll_and_click()
self.share_community_link_button.click()
Button(self.driver, xpath="//*[@content-desc='author-primary-name'][@text='%s']" % user_name).click()
self.setup_chat_button.click()
class PreviewMessage(ChatElementByText):
+10 -2
View File
@@ -154,6 +154,7 @@
"changed-amount-warning": "Amount was changed from {{old}} to {{new}}",
"changed-asset-warning": "Asset was changed from {{old}} to {{new}}",
"channel-on-status": "Channel on Status",
"community-on-status": "Community on Status",
"chaos-mode": "Chaos mode",
"chaos-unicorn-day": "Chaos Unicorn Day",
"chaos-unicorn-day-details": "🦄🦄🦄🦄🦄🦄🦄🚀!",
@@ -605,7 +606,7 @@
"skip": "Skip",
"password-placeholder": "Password...",
"confirm-password-placeholder": "Confirm your password...",
"ens-or-chat-key": "ENS or Chat key",
"ens-or-chat-key": "ENS or Chatkey",
"user-found": "User found",
"enter-pin": "Enter 6-digit passcode",
"enter-puk-code": "Enter PUK code",
@@ -2574,5 +2575,12 @@
"display": "Display",
"testnet-mode-enabled": "Testnet mode enabled",
"online-community-member": "Online",
"offline-community-member": "Offline"
"offline-community-member": "Offline",
"invite-to-community": "Invite to community",
"invite-n-users": "Invite {{count}} users",
"invite-1-user": "Invite 1 user",
"one-user-was-invited": "1 user was invited",
"n-users-were-invited": "{{count}} users were invited",
"invite-friend-to-status": "Invite friends to Status",
"send-community-link": "Send community link"
}