Compare commits

...
16 Commits
Author SHA1 Message Date
ibrkhalil 9f46269f5e Display last sent message on chats screen 2022-10-29 21:33:40 +02:00
du64 ab1dd7e794 Fix typo (replace backtick with apostrophe) (#14255) 2022-10-29 17:24:29 +02:00
Churikova Tetiana 0967582639 e2e: non-latin 2022-10-28 15:54:08 +02:00
Icaro Motta 91b237d3d3 Fix unbounded number of calls to fetch notifications (#14250) 2022-10-27 16:21:41 -03:00
Icaro Motta 69303cd7d1 Implement positive button state (#14252) 2022-10-27 16:03:18 -03:00
Andrea Maria Piana b774ecbcb4 Add chat actions in home screen
This commit adds the following chat actions on the home screen:

- Mute chat
- Delete chat
- View profile (one to ones only)
- Clear history

It adds also integration tests for muting and deleting a chat.

To accommodate multiple dividers in the bottom sheet, the interface has
been changed to accept a sequence of sequences, instead of a map.
2022-10-27 17:53:30 +01:00
Roman Volosovskyi b492ed2969 [#14230] Reset :current-chat-id on pressing back from chat screen 2022-10-27 15:01:10 +02:00
Mohamed Javid 6d557d735a Activity Center UI Fixes (#14244)
* [Fixes] Activity Center UI Issues

* [Chore] Lint Fixes
2022-10-27 20:20:03 +08:00
Roman Volosovskyi f29ed58445 [#14230] Load newly received messages on reopening chat (ISSUE 1) 2022-10-27 11:13:22 +02:00
Churikova Tetiana 1941591110 new ui e2e: reaction and text message in 1-1 2022-10-26 17:14:25 +02:00
Ibrahem Khalil a91e9dfb38 Fix message showing behind message composer (#14238) 2022-10-26 16:40:46 +02:00
Omar Basem c423aa7970 Unpin messages (#14204)
* feat: pinned messages new ui
2022-10-26 17:18:50 +04:00
Icaro Motta 910bafc5f4 Display contact verification requests and allow users to decline them (#14223) 2022-10-26 07:48:07 -03:00
Omar Basem 2ae57f7b21 Messages contact requests (#14221)
* feat: messages contact requests
2022-10-26 10:42:53 +04:00
Jamie Caprani 14a5edb24b fix: adjust selectors to designs (#14214) 2022-10-25 01:50:39 -07:00
Parvesh Monu 0ff6fb25f4 Shell & Bottom Tabs Migration (#14099)
* Shell & Bottom Tabs Migration

* Added accessibility ids for elements in the new UI
2022-10-24 18:35:06 +05:30
92 changed files with 2639 additions and 1624 deletions
+1 -1
View File
@@ -272,7 +272,7 @@ run-android: export TARGET := android
run-android: ##@run Build Android APK and start it on the device
npx react-native run-android --appIdSuffix debug
SIMULATOR=
SIMULATOR=iPhone 13
run-ios: export TARGET := ios
run-ios: ##@run Build iOS app and start it in a simulator/device
ifneq ("$(SIMULATOR)", "")
+1 -1
View File
@@ -62,7 +62,7 @@ pipeline {
--rerun_count=2 \
--testrail_report=True \
-m testrail_id \
-m \"not upgrade\" \
-m \"new_ui_critical\" \
-k \"${params.KEYWORD_EXPRESSION}\" \
--apk=${params.APK_NAME}
"""
+1 -1
View File
@@ -14,7 +14,7 @@ pipeline {
string(
name: 'TEST_MARKERS',
description: 'Marker expression for matching tests to run.',
defaultValue: 'critical',
defaultValue: 'new_ui_critical',
) */
string(
name: 'APK_NAME',
Binary file not shown.

After

Width:  |  Height:  |  Size: 286 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 396 B

+70 -9
View File
@@ -1,4 +1,4 @@
import { useDerivedValue } from 'react-native-reanimated';
import { useDerivedValue, withTiming, withSequence, withDelay, Easing } from 'react-native-reanimated';
// Generic Worklets
@@ -32,38 +32,99 @@ export function applyAnimationsToStyle(animations, style) {
// Switcher Worklets
export function switcherCloseButtonOpacity (switcherButtonOpacity) {
export function stackOpacity (stackId, selectedStackId) {
return useDerivedValue(
function () {
'worklet'
return 1 - switcherButtonOpacity.value;
return selectedStackId.value == stackId ? 1 : 0;
}
);
}
export function switcherScreenRadius (switcherScreenSize) {
export function stackPointer (stackId, selectedStackId) {
return useDerivedValue(
function () {
'worklet'
return switcherScreenSize.value/2;
return selectedStackId.value == stackId ? "auto" : "none";
}
);
}
export function switcherScreenBottomPosition (switcherScreenRadius, switcherPressedRadius, initalPosition) {
export function bottomTabIconColor (stackId, selectedStackId, passThrough, selectedTabColor, defaultColor, passThroughColor) {
return useDerivedValue(
function () {
'worklet'
return initalPosition + switcherPressedRadius - switcherScreenRadius.value;
if (selectedStackId.value == stackId){
return selectedTabColor;
}
else if (passThrough.value){
return passThroughColor;
}
else {
return defaultColor;
}
}
);
}
export function switcherContainerBottomPosition (switcherScreenBottom, heightOffset) {
// Home Stack
const defaultDurationAndEasing = {
duration: 300,
easing: Easing.bezier(0, 0, 1, 1),
}
export function homeStackOpacity (homeStackOpen) {
return useDerivedValue(
function () {
'worklet'
return - (switcherScreenBottom.value + heightOffset);
return withTiming(homeStackOpen.value ? 1 : 0, defaultDurationAndEasing);
}
);
}
export function homeStackTop (homeStackOpen, top) {
return useDerivedValue(
function () {
'worklet'
return withTiming(homeStackOpen.value ? 0 : top, defaultDurationAndEasing);
}
);
}
export function homeStackLeft (selectedStackId, animateHomeStackLeft, homeStackOpen, left) {
return useDerivedValue(
function () {
'worklet'
if (animateHomeStackLeft.value) {
var leftValue = left[selectedStackId.value];
if (homeStackOpen.value) {
return withSequence(withTiming(leftValue, {duration: 0}), withTiming(0, defaultDurationAndEasing))
} else {
return withTiming(leftValue, defaultDurationAndEasing);
}
} else {
return 0;
}
}
);
}
export function homeStackPointer (homeStackOpen) {
return useDerivedValue(
function () {
'worklet'
return homeStackOpen.value ? "auto" : "none";
}
);
}
export function homeStackScale (homeStackOpen, minimizeScale) {
return useDerivedValue(
function () {
'worklet'
return withTiming(homeStackOpen.value ? 1 : minimizeScale, defaultDurationAndEasing);
}
);
}
+12 -14
View File
@@ -44,7 +44,7 @@
(+ 50 keyboard-height-android-delta) ;; TODO : remove 50 when react-native-navigation v8 will be implemented https://github.com/wix/react-native-navigation/issues/7225
0))
min-height (+ (* styles/vertical-padding 2) (:bottom safe-area))
max-height (- window-height (:top safe-area) styles/margin-top)
max-height (- window-height (:top safe-area))
visible (react/state false)
master-translation-y (animated/use-value 0)
@@ -202,17 +202,15 @@
:wait-for master-ref
:enabled (and (not disable-drag?)
(not= sheet-height max-height))})
[animated/view {:height sheet-height}
[animated/scroll-view {:bounces false
:flex 1
:scroll-enabled (= sheet-height max-height)}
[animated/view {:style {:padding-top styles/vertical-padding
:padding-bottom (+ styles/vertical-padding
(if (and platform/ios? keyboard-shown)
keyboard-height
(:bottom safe-area)))}
:on-layout #(reset! height (.-nativeEvent.layout.height ^js %))}
(into [:<>]
(react/get-children children))]]]]]])))
[animated/view {:height sheet-height
:flex 1}
[animated/view {:style {:padding-top styles/vertical-padding
:padding-bottom (+ styles/vertical-padding
(if (and platform/ios? keyboard-shown)
keyboard-height
(:bottom safe-area)))}
:on-layout #(reset! height (.-nativeEvent.layout.height ^js %))}
(into [:<>]
(react/get-children children))]]]]])))
(def bottom-sheet (reagent/adapt-react-class (react/memo bottom-sheet-hooks)))
(def bottom-sheet (reagent/adapt-react-class (react/memo bottom-sheet-hooks)))
+2
View File
@@ -56,6 +56,8 @@
{:keyboardVerticalOffset (+ 44 (:status-bar-height @navigation-const))})]
(reagent/children this))))
(def status-bar (.-StatusBar ^js rn))
(def keyboard (.-Keyboard ^js rn))
(def dismiss-keyboard! #(.dismiss ^js keyboard))
+10
View File
@@ -44,6 +44,11 @@
:background-color {:default colors/danger-50
:pressed colors/danger-60
:disabled colors/danger-50}}
:positive {:icon-color colors/white
:label {:style {:color colors/white}}
:background-color {:default colors/success-50
:pressed colors/success-60
:disabled colors/success-50-opa-30}}
:photo-bg {:icon-color colors/neutral-100
:icon-secondary-color colors/neutral-80-opa-40
:label {:style {:color colors/neutral-100}}
@@ -104,6 +109,11 @@
:background-color {:default colors/danger-60
:pressed colors/danger-50
:disabled colors/danger-60}}
:positive {:icon-color colors/white
:label {:style {:color colors/white}}
:background-color {:default colors/success-60
:pressed colors/success-50
:disabled colors/success-60-opa-30}}
:photo-bg {:icon-color colors/white
:icon-secondary-color colors/neutral-30
:label {:style {:color colors/white}}
@@ -62,9 +62,10 @@
(let [pressed? (reagent/atom false)]
(fn [{:keys [type on-press count customization-color style]}]
[rn/touchable-without-feedback
{:on-press-in #(reset! pressed? true)
:on-press-out #(reset! pressed? false)
:on-press on-press}
{:on-press-in #(reset! pressed? true)
:on-press-out #(reset! pressed? false)
:on-press on-press
:accessibility-label type}
[rn/view {:style (merge
{:flex-direction :row
:height 24
@@ -0,0 +1,94 @@
(ns quo2.components.community.discover-card
(:require [quo2.components.markdown.text :as text]
[quo2.foundations.colors :as colors]
[status-im.ui.screens.communities.styles :as styles]
[status-im.ui.components.react :as react]))
;; Discover card placeholders images.
;; TODO replaced when real data is available
(def images
{:images [{:id 1 :column-images [{:id 1 :image ""}]}
{:id 2 :column-images [{}]}]})
(defn card-title-and-description [title description]
[react/view
{:flex 1
:padding-top 8
:padding-bottom 8
:border-radius 12}
[react/view {:flex 1
:padding-horizontal 12}
[text/text {:accessibility-label :community-name-text
:ellipsize-mode :tail
:number-of-lines 1
:weight :semi-bold
:size :paragraph-1}
title]
[text/text {:accessibility-label :community-name-text
:ellipsize-mode :tail
:number-of-lines 1
:color (colors/theme-colors
colors/neutral-50
colors/neutral-40)
:weight :regular
:size :paragraph-2}
description]]])
(defn placeholder-list-images [{:keys [images width height border-radius]}]
[react/view
[react/view {:justify-content :center}
(for [{:keys [id]} images]
^{:key id}
[react/view {:border-radius border-radius
:margin-top 4
:margin-right 4
:width width
:height height
:background-color colors/neutral-10}])]])
(defn placeholder-row-images [{:keys [first-image last-image images width height
border-radius]}]
[react/view
(when first-image
[react/view {:border-bottom-right-radius 6
:border-bottom-left-radius 6
:margin-right 4
:width width
:height height
:background-color colors/neutral-10}])
(when images
[placeholder-list-images {:images images
:width 32
:height 32
:border-radius 6}])
(when last-image
[react/view {:border-top-left-radius border-radius
:border-top-right-radius 6
:margin-top 4
:width width
:height height
:background-color colors/neutral-10}])])
(defn discover-card [{:keys [title description on-press]}]
(let [on-joined-images (get images :images)]
[react/touchable-without-feedback
{:on-press on-press}
[react/view (merge (styles/community-card 16)
{:background-color (colors/theme-colors
colors/white
colors/neutral-90)}
{:flex-direction :row
:margin-horizontal 20
:height 56
:padding-right 12})
[card-title-and-description title description]
(for [{:keys [id column-images]} on-joined-images]
^{:key id}
[placeholder-row-images {:images (when (= id 1)
column-images)
:width 32
:height (if (= id 1) 8 26)
:border-radius 6
:first-image "" ; TODO replace with real data
:last-image ""}]) ; TODO replace with real data
]]))
+60 -54
View File
@@ -5,64 +5,70 @@
[quo2.components.icon :as icon]
[quo2.foundations.colors :as colors]))
(defn- get-icon-color [section]
(if (= section :bottom)
(defn- get-icon-color [danger?]
(if danger?
(colors/theme-colors colors/danger-50 colors/danger-60)
(colors/theme-colors colors/neutral-50 colors/neutral-40)))
(defn action [section]
(fn [{:keys [icon label sub-label right-icon on-press]}]
[rn/touchable-opacity {:on-press on-press}
[react/view {:style
{:flex 1
:height (if sub-label 56 47)
:margin-horizontal 20
:flex-direction :row}}
[react/view {:style
{:height 20
:margin-top :auto
:margin-bottom :auto
:margin-right 12
:width 20}}
[icon/icon icon
{:color (get-icon-color section)
:size 20}]]
[react/view
{:style
{:flex 1
:justify-content :center}}
(defn action [{:keys [icon
label
sub-label
right-icon
danger?
on-press]}]
[rn/touchable-opacity {:on-press on-press}
[react/view {:style
{:height (if sub-label 56 47)
:margin-horizontal 20
:flex-direction :row}}
[react/view {:style
{:height 20
:margin-top :auto
:margin-bottom :auto
:margin-right 12
:width 20}}
[icon/icon icon
{:color (get-icon-color danger?)
:size 20}]]
[react/view
{:style
{:flex 1
:justify-content :center}}
[text/text
{:size :paragraph-1
:weight :medium
:style {:color
(when danger?
(colors/theme-colors colors/danger-50 colors/danger-60))}}
label]
(when sub-label
[text/text
{:size :paragraph-1
:weight :medium
:style
{:color (when (= section :bottom)
(colors/theme-colors colors/danger-50 colors/danger-60))}}
label]
(when sub-label [text/text
{:size :paragraph-2
:style
{:color (colors/theme-colors colors/neutral-50 colors/neutral-40)}}
sub-label])]
(when right-icon
[react/view {:style
{:height 20
:margin-top :auto
:margin-bottom :auto
:width 20}}
[icon/icon right-icon
{:color (get-icon-color section)
:size 20}]])]]))
{:size :paragraph-2
:style {:color
(colors/theme-colors colors/neutral-50 colors/neutral-40)}}
sub-label])]
(when right-icon
[react/view {:style
{:height 20
:margin-top :auto
:margin-bottom :auto
:width 20}}
[icon/icon right-icon
{:color (get-icon-color danger?)
:size 20}]])]])
(defn action-drawer [{:keys [actions actions-with-consequence]}]
(defn divider []
[rn/view {:style {:border-top-width 1
:border-top-color (colors/theme-colors colors/neutral-10 colors/neutral-80)
:margin-top 8
:margin-bottom 7
:align-items :center
:flex-direction :row}}])
(defn action-drawer [sections]
[:<> {:style
{:flex 1}}
(map (action :top) actions)
(when actions-with-consequence
[:<>
[rn/view {:style {:border-top-width 1
:border-top-color (colors/theme-colors colors/neutral-10 colors/neutral-80)
:margin-top 8
:margin-bottom 7
:align-items :center
:flex-direction :row}}]
(map (action :bottom) actions-with-consequence)])])
(interpose
[divider]
(for [actions sections]
(map action actions)))])
@@ -0,0 +1,84 @@
(ns quo2.components.list-items.received-contact-request
(:require [quo.react-native :as rn]
[quo2.foundations.colors :as colors]
[status-im.utils.handlers :refer [<sub >evt]]
[quo2.components.avatars.user-avatar :as user-avatar]
[quo2.foundations.typography :as typography]
[clojure.string :as str]
[status-im.utils.utils :as utils.utils]
[status-im.utils.datetime :as time]
[status-im.i18n.i18n :as i18n]
[quo2.components.notifications.notification-dot :refer [notification-dot]]))
(defn get-display-name [chat-id no-ens-name no-nickname]
(let [name (first (<sub [:contacts/contact-two-names-by-identity chat-id]))]
(if (and no-ens-name no-nickname)
(let [[word1 word2] (str/split name " ")]
(str word1 " " word2))
name)))
(defn list-item [{:keys [chat-id image contact message timestamp read]}]
(let [no-ens-name (str/blank? (get-in message [:content :ens-name]))
no-nickname (nil? (get-in contact [:names :nickname]))
display-name (get-display-name chat-id no-ens-name no-nickname)]
[rn/view {:style {:flex-direction :row
:padding-top 8
:margin-top 4
:padding-bottom 12
:flex 1}}
(when-not read
[notification-dot {:right 32 :top 16}])
[user-avatar/user-avatar {:full-name display-name
:status-indicator? true
:online? true
:size :small
:profile-picture image
:ring? false}]
[rn/view {:style {:margin-horizontal 8}}
[rn/view {:style {:flex-direction :row}}
[rn/text {:style (merge typography/font-semi-bold typography/paragraph-1
{:color (colors/theme-colors colors/neutral-100 colors/white)
:margin-right 8})} display-name]
(when no-ens-name [rn/text {:style (merge typography/font-regular typography/label
{:color (colors/theme-colors colors/neutral-50 colors/neutral-40)
:margin-top 4})}
(str (utils.utils/get-shortened-address chat-id) " · ")])
[rn/text {:style (merge typography/font-regular typography/label
{:color (colors/theme-colors colors/neutral-50 colors/neutral-40)
:margin-top 4})}
(time/to-short-str timestamp)]]
[rn/view {:style {:border-radius 12
:margin-top 10
:padding-horizontal 12
:padding-vertical 8
:border-width 1
:border-color (colors/theme-colors colors/neutral-20 colors/neutral-70)}}
[rn/text {:style (merge typography/font-regular
typography/paragraph-1
{:color (colors/theme-colors colors/neutral-100 colors/white)})}
(:text (:content message))]]
[rn/view {:style {:margin-top 12
:flex-direction :row}}
[rn/touchable-opacity {:accessibility-label :decline-cr
:on-press #(>evt [:contact-requests.ui/decline-request (:message-id message)])
:active-opacity 1
:style {:background-color (colors/theme-colors colors/danger-50 colors/danger-60)
:justify-content :center
:align-items :center
:align-self :flex-start
:border-radius 8
:padding-vertical 4
:padding-horizontal 8}}
[rn/text {:style (merge typography/font-medium typography/paragraph-2 {:color colors/white})} (i18n/label :t/decline)]]
[rn/touchable-opacity {:accessibility-label :accept-cr
:on-press #(>evt [:contact-requests.ui/accept-request (:message-id message)])
:active-opacity 1
:style {:background-color (colors/theme-colors colors/success-50 colors/success-60)
:justify-content :center
:align-items :center
:align-self :flex-start
:border-radius 8
:padding-vertical 4
:padding-horizontal 8
:margin-left 8}}
[rn/text {:style (merge typography/font-medium typography/paragraph-2 {:color colors/white})} (i18n/label :t/accept)]]]]]))
@@ -1,71 +1,77 @@
(ns quo2.components.navigation.bottom-nav-tab
(:require [quo.react-native :as rn]
[reagent.core :as reagent]
[quo2.reanimated :as reanimated]
[quo2.foundations.colors :as colors]
[quo2.components.icon :as icon]
[quo2.components.icons.icons :as icons]
[quo2.components.counter.counter :as counter]))
(defn toggle-background-color [background-color press-out? pass-through?]
(let [color (cond
press-out? nil
pass-through? colors/white-opa-5
:else colors/neutral-70)]
(reset! background-color color)))
(reanimated/set-shared-value
background-color
(cond
press-out? "transparent"
pass-through? colors/white-opa-5
:else colors/neutral-70)))
(defn bottom-nav-tab
"[bottom-nav-tab opts]
opts
{:icon :main-icons2/communities
:selected? true/false
:new-notifications? true/false
:notification-indicator :unread-dot/:counter
:counter-label number
:on-press bottom-tab on-press function
:pass-through? true/false
:icon-color-anim reanimated shared value
"
[_]
(let [background-color (reagent/atom nil)]
(fn [{:keys [icon selected? new-notifications? notification-indicator counter-label on-press pass-through?]}]
[rn/touchable-without-feedback
{:on-press on-press
:on-press-in #(toggle-background-color background-color false pass-through?)
:on-press-out #(toggle-background-color background-color true pass-through?)}
[rn/view {:style {:width 90
:height 40
:background-color @background-color
:border-radius 10}}
[rn/hole-view {:style {:padding-left 33
:padding-top 8}
:key new-notifications? ;; Key is required to force removal of holes
:holes (cond
(not new-notifications?) ;; No new notifications, remove holes
[]
[{:keys [icon new-notifications? notification-indicator counter-label
on-press pass-through? icon-color-anim accessibility-label]}]
[:f>
(fn []
(let [icon-animated-style (reanimated/apply-animations-to-style
{:tint-color icon-color-anim}
{:width 24
:height 24})
background-color (reanimated/use-shared-value "transparent")
background-animated-style (reanimated/apply-animations-to-style
{:background-color background-color}
{:width 90
:height 40
:border-radius 10})]
[rn/touchable-without-feedback
{:on-press on-press
:on-press-in #(toggle-background-color background-color false pass-through?)
:on-press-out #(toggle-background-color background-color true pass-through?)
:accessibility-label accessibility-label}
[reanimated/view {:style background-animated-style}
[rn/hole-view {:style {:padding-left 33
:padding-top 8}
:key new-notifications? ;; Key is required to force removal of holes
:holes (cond
(not new-notifications?) ;; No new notifications, remove holes
[]
(= notification-indicator :unread-dot)
[{:x 50 :y 5 :width 10 :height 10 :borderRadius 5}]
(= notification-indicator :unread-dot)
[{:x 50 :y 5 :width 10 :height 10 :borderRadius 5}]
:else
[{:x 47 :y 1 :width 18 :height 18 :borderRadius 7}])}
[icon/icon
icon
{:size 24
:color (cond
selected? colors/white
pass-through? colors/white-opa-40
:else colors/neutral-50)}]]
(when new-notifications?
(if (= notification-indicator :counter)
[counter/counter {:outline false
:override-text-color colors/white
:override-bg-color colors/primary-50
:style {:position :absolute
:left 48
:top 2}}
counter-label]
[rn/view {:style {:width 8
:height 8
:border-radius 4
:top 6
:left 51
:position :absolute
:background-color colors/primary-50}}]))]])))
:else
[{:x 47 :y 1 :width 18 :height 18 :borderRadius 7}])}
[reanimated/image
{:style icon-animated-style
:source (icons/icon-source (keyword (str icon 24)))}]]
(when new-notifications?
(if (= notification-indicator :counter)
[counter/counter {:outline false
:override-text-color colors/white
:override-bg-color colors/primary-50
:style {:position :absolute
:left 48
:top 2}}
counter-label]
[rn/view {:style {:width 8
:height 8
:border-radius 4
:top 6
:left 51
:position :absolute
:background-color colors/primary-50}}]))]]))])
+1 -2
View File
@@ -44,8 +44,7 @@
open-scanner show-qr open-activity-center style avatar counter-label]}]
(let [button-common-props (get-button-common-props type)]
[rn/view {:style (merge
{:height 56
:flex 1}
{:height 56}
style)}
;; Left Section
[rn/touchable-without-feedback {:on-press open-profile}
@@ -0,0 +1,12 @@
(ns quo2.components.notifications.notification-dot
(:require [quo.react-native :as rn]
[quo2.foundations.colors :as colors]))
(defn notification-dot [style]
[rn/view {:style (merge {:background-color (colors/theme-colors colors/primary-50 colors/primary-60)
:width 8
:height 8
:border-radius 4
:position :absolute
:z-index 1}
style)}])
+28 -22
View File
@@ -16,14 +16,16 @@
(defn open-reactions-menu
[{:keys [on-press]}]
(let [dark? (theme/dark?)]
[rn/touchable-opacity {:on-press on-press
:style (merge reaction-styling
[rn/touchable-opacity
{:on-press on-press
:accessibility-label :emoji-reaction-add
:style (merge reaction-styling
{:padding-horizontal 9
:border-width 1
:margin-top 5
:border-color (if dark?
colors/neutral-70
colors/neutral-30)})}
:border-width 1
:margin-top 5
:border-color (if dark?
colors/neutral-70
colors/neutral-30)})}
[icons/icon :main-icons2/add
{:size 20
:color (if dark?
@@ -32,26 +34,30 @@
(defn reaction
"Add your emoji as a param here"
[{:keys [emoji clicks neutral? on-press]}]
[{:keys [emoji clicks neutral? on-press accessibility-label]}]
(let [dark? (theme/dark?)
text-color (if dark? colors/white
colors/neutral-100)
numeric-value (int clicks)
clicks-positive? (pos? numeric-value)]
[rn/touchable-opacity {:on-press on-press
:style (merge reaction-styling
(cond-> {:background-color
(if dark?
(if neutral?
colors/neutral-70
:transparent)
(if neutral?
colors/neutral-30
:transparent))}
(and dark? (not neutral?)) (assoc :border-color colors/neutral-70
:border-width 1)
(and (not dark?) (not neutral?)) (assoc :border-color colors/neutral-30
:border-width 1)))}
[rn/touchable-opacity
{:on-press on-press
:accessibility-label accessibility-label
:style (merge reaction-styling
(cond-> {:background-color
(if dark?
(if neutral?
colors/neutral-70
:transparent)
(if neutral?
colors/neutral-30
:transparent))}
(and dark? (not neutral?))
(assoc :border-color colors/neutral-70
:border-width 1)
(and (not dark?) (not neutral?))
(assoc :border-color colors/neutral-30
:border-width 1)))}
[icons/icon emoji {:no-color true
:size 16}]
[quo2.text/text {:size :paragraph-2
+5 -5
View File
@@ -39,7 +39,7 @@
[rn/view {:style
{:height 20
:width 20}}
[icons/icon :main-icons2/check
[icons/icon :main-icons2/check-small
{:size 20
:color (colors/theme-colors
(colors/alpha colors/neutral-100 (if disabled? 0.3 1))
@@ -53,8 +53,8 @@
[rn/view
{:style (merge
container-style
{:height 21
:width 21})}
{:height 20
:width 20})}
[rn/view
{:style {:flex 1
:border-radius 6
@@ -72,9 +72,9 @@
[rn/view {:style
{:height 20
:width 20}}
[icons/icon :main-icons2/check
[icons/icon :main-icons2/check-small
{:size 20
:color (colors/alpha colors/white (if disabled? 0.3 1))}]])]]])))
:color colors/white}]])]]])))
(defn radio [{:keys [default-checked?]}]
(let [checked? (reagent/atom (or default-checked? false))]
+27 -13
View File
@@ -5,7 +5,9 @@
[reagent.core :as reagent]
[status-im.ui.components.react :as react]
[status-im.utils.core :as utils]
[status-im.utils.number :as number-utils]))
[status-im.utils.number :as number-utils]
[quo2.foundations.colors :as colors]
[quo2.components.notifications.notification-dot :refer [notification-dot]]))
(def default-tab-size 32)
@@ -14,9 +16,21 @@
(fn [{:keys [data size] :or {size default-tab-size}}]
[rn/view (merge {:flex-direction :row} style)
(doall
(for [{:keys [label id]} data]
(for [{:keys [label id new-info]} data]
^{:key id}
[rn/view {:style {:margin-right (if (= size default-tab-size) 12 8)}}
(when new-info
[rn/view {:position :absolute
:z-index 1
:right -2
:top -2
:width 10
:height 10
:border-radius 5
:justify-content :center
:align-items :center
:background-color (colors/theme-colors colors/neutral-5 colors/neutral-95)}
[notification-dot]])
[tab/tab
{:id id
:size size
@@ -132,15 +146,15 @@
[tab/tab {:id id
:size size
:override-theme override-theme
:blur? blur?
:active (= id @active-tab-id)
:on-press (fn [id]
(reset! active-tab-id id)
(when scroll-on-press?
(.scrollToIndex @flat-list-ref
#js {:animated true
:index index
:viewPosition 0.5}))
(when on-change
(on-change id)))}
:blur? blur?
:active (= id @active-tab-id)
:on-press (fn [id]
(reset! active-tab-id id)
(when scroll-on-press?
(.scrollToIndex @flat-list-ref
#js {:animated true
:index index
:viewPosition 0.5}))
(when on-change
(on-change id)))}
label]])})])))))
+20 -1
View File
@@ -45,7 +45,6 @@
;;Blur
(def neutral-5-opa-70 (alpha neutral-5 0.7))
(def neutral-90-opa-70 (alpha neutral-90 0.7))
(def neutral-95-opa-70 (alpha neutral-95 0.7))
;;80 with transparency
(def neutral-80-opa-5 (alpha neutral-80 0.05))
@@ -60,6 +59,20 @@
(def neutral-80-opa-90 (alpha neutral-80 0.9))
(def neutral-80-opa-95 (alpha neutral-80 0.95))
;;95 with transparency
(def neutral-95-opa-60 (alpha neutral-95 0.6))
(def neutral-95-opa-70 (alpha neutral-95 0.7))
(def neutral-95-opa-80 (alpha neutral-95 0.8))
(def neutral-95-opa-90 (alpha neutral-95 0.9))
(def neutral-95-opa-95 (alpha neutral-95 0.95))
;;100 with transparency
(def neutral-100-opa-60 (alpha neutral-100 0.6))
(def neutral-100-opa-70 (alpha neutral-100 0.7))
(def neutral-100-opa-80 (alpha neutral-100 0.8))
(def neutral-100-opa-90 (alpha neutral-100 0.9))
(def neutral-100-opa-95 (alpha neutral-100 0.95))
;;;;White
;;Solid
@@ -104,6 +117,12 @@
(def success-50-opa-30 (alpha success-50 0.3))
(def success-50-opa-40 (alpha success-50 0.4))
(def success-60-opa-5 (alpha success-60 0.05))
(def success-60-opa-10 (alpha success-60 0.1))
(def success-60-opa-20 (alpha success-60 0.2))
(def success-60-opa-30 (alpha success-60 0.3))
(def success-60-opa-40 (alpha success-60 0.4))
;;;;Danger
;;Solid
+3 -1
View File
@@ -21,7 +21,9 @@
{:key :ghost
:value "Ghost"}
{:key :danger
:value "Danger"}]}
:value "Danger"}
{:key :positive
:value "Positive"}]}
{:label "Size:"
:key :size
:type :select
+19 -16
View File
@@ -14,24 +14,27 @@
:key :show-red-options?
:type :boolean}])
(def options-with-consequences [{:icon :main-icons2/share-context
:label "Clear history"}])
(def options-with-consequences [{:icon :main-icons2/delete
:danger? true
:label "Clear history"}])
(defn render-action-sheet [state]
[quo2/action-drawer {:actions-with-consequence (when (:show-red-options? @state) options-with-consequences)
:actions [{:icon :main-icons2/share-context
:label "View channel members and details"}
{:icon :main-icons2/communities
:label "Mark as read"}
{:icon :main-icons2/muted
:label (if (:muted? @state) "Unmute channel" "Mute channel")
:right-icon :main-icons2/chevron-right
:sub-label (when (:muted? @state) "Muted for 15 min")}
{:icon :main-icons2/scan
:right-icon :main-icons2/chevron-right
:label "Fetch messages"}
{:icon :main-icons2/add-user
:label "Share link to the channel"}]}])
[quo2/action-drawer (cond-> [[{:icon :main-icons2/friend
:label "View channel members and details"}
{:icon :main-icons2/communities
:label "Mark as read"}
{:icon :main-icons2/muted
:label (if (:muted? @state) "Unmute channel" "Mute channel")
:right-icon :main-icons2/chevron-right
:sub-label (when (:muted? @state) "Muted for 15 min")}
{:icon :main-icons2/scan
:right-icon :main-icons2/chevron-right
:label "Fetch messages"}
{:icon :main-icons2/add-user
:label "Share link to the channel"}]]
(:show-red-options? @state)
(conj options-with-consequences))])
(defn cool-preview []
(let [state (reagent/atom {:muted? true
+28 -14
View File
@@ -2,6 +2,7 @@
(:require [quo.react-native :as rn]
[quo.previews.preview :as preview]
[reagent.core :as reagent]
[quo2.reanimated :as reanimated]
[quo2.components.navigation.bottom-nav-tab :as quo2]
[quo2.foundations.colors :as colors]))
@@ -36,21 +37,34 @@
:key :counter-label
:type :text}])
(defn get-icon-color [selected? pass-through?]
(cond
selected? colors/white
pass-through? colors/white-opa-40
:else colors/neutral-50))
(defn cool-preview []
(let [state (reagent/atom {:icon :main-icons2/communities
:selected? true
:pass-through? true
:new-notifications? true
:notification-indicator :counter
:counter-label 8
:preview-label-color colors/white})]
(fn []
[rn/touchable-without-feedback {:on-press rn/dismiss-keyboard!}
[rn/view {:padding-bottom 150}
[preview/customizer state descriptor]
[rn/view {:padding-vertical 60
:align-items :center}
[quo2/bottom-nav-tab @state (:value @state)]]]])))
(let [state (reagent/atom {:icon :main-icons2/communities
:new-notifications? true
:notification-indicator :counter
:counter-label 8
:preview-label-color colors/white})
selected? (reagent/cursor state [:selected?])
pass-through? (reagent/cursor state [:pass-through?])]
[:f>
(fn []
(let [icon-color-anim (reanimated/use-shared-value colors/white)]
(reanimated/set-shared-value
icon-color-anim
(get-icon-color @selected? @pass-through?))
[rn/touchable-without-feedback {:on-press rn/dismiss-keyboard!}
[rn/view {:padding-bottom 150}
[preview/customizer state descriptor]
[rn/view {:padding-vertical 60
:align-items :center}
[quo2/bottom-nav-tab
(merge @state {:icon-color-anim icon-color-anim})
(:value @state)]]]]))]))
(defn preview-bottom-nav-tab []
[rn/view {:background-color colors/neutral-100
+48 -20
View File
@@ -1,6 +1,6 @@
(ns status-im.activity-center.core
(:require [re-frame.core :as re-frame]
[status-im.constants :as constants]
(:require [re-frame.core :as rf]
[status-im.constants :as const]
[status-im.data-store.activities :as data-store.activities]
[status-im.ethereum.json-rpc :as json-rpc]
[status-im.utils.fx :as fx]
@@ -18,21 +18,23 @@
~excessively~ big, this implementation will probably need to be revisited."
[db-notifications new-notifications]
(reduce (fn [acc {:keys [id type read] :as notification}]
(let [filter-status (if read :read :unread)]
(cond-> (-> acc
(update-in [type :read :data]
(fn [data]
(remove #(= id (:id %)) data)))
(update-in [type :unread :data]
(fn [data]
(remove #(= id (:id %)) data))))
(not (or (:dismissed notification) (:accepted notification)))
(update-in [type filter-status :data]
(fn [data]
(->> notification
(conj data)
(sort-by (juxt :timestamp :id))
reverse))))))
(let [filter-status (if read :read :unread)
remove-notification (fn [data]
(remove #(= id (:id %)) data))
insert-and-sort (fn [data]
(->> notification
(conj data)
(sort-by (juxt :timestamp :id))
reverse))]
(as-> acc $
(update-in $ [type :read :data] remove-notification)
(update-in $ [type :unread :data] remove-notification)
(update-in $ [const/activity-center-notification-type-no-type :read :data] remove-notification)
(update-in $ [const/activity-center-notification-type-no-type :unread :data] remove-notification)
(if (or (:dismissed notification) (:accepted notification))
$
(-> $ (update-in [type filter-status :data] insert-and-sort)
(update-in [const/activity-center-notification-type-no-type filter-status :data] insert-and-sort))))))
db-notifications
new-notifications))
@@ -43,11 +45,37 @@
{:db (update-in db [:activity-center :notifications]
update-notifications new-notifications)}))
;;;; Contact verification
(fx/defn contact-verification-decline
{:events [:activity-center.contact-verification/decline]}
[_ contact-verification-id]
{::json-rpc/call [{:method "wakuext_declineContactVerificationRequest"
:params [contact-verification-id]
:on-success #(rf/dispatch [:activity-center.contact-verification/decline-success %])
:on-error #(rf/dispatch [:activity-center.contact-verification/decline-error contact-verification-id %])}]})
(fx/defn contact-verification-decline-success
{:events [:activity-center.contact-verification/decline-success]}
[cofx response]
(->> response
:activityCenterNotifications
(map data-store.activities/<-rpc)
(notifications-reconcile cofx)))
(fx/defn contact-verification-decline-error
{:events [:activity-center.contact-verification/decline-error]}
[_ contact-verification-id error]
(log/warn "Failed to decline contact verification"
{:contact-verification-id contact-verification-id
:error error})
nil)
;;;; Notifications fetching and pagination
(def defaults
{:filter-status :unread
:filter-type constants/activity-center-notification-type-no-type
:filter-type const/activity-center-notification-type-no-type
:notifications-per-page 10})
(def start-or-end-cursor
@@ -70,8 +98,8 @@
{:db (assoc-in db [:activity-center :notifications filter-type filter-status :loading?] true)
::json-rpc/call [{:method (filter-status->rpc-method filter-status)
:params [cursor (defaults :notifications-per-page) filter-type]
:on-success #(re-frame/dispatch [:activity-center.notifications/fetch-success filter-type filter-status reset-data? %])
:on-error #(re-frame/dispatch [:activity-center.notifications/fetch-error filter-type filter-status %])}]}))
:on-success #(rf/dispatch [:activity-center.notifications/fetch-success filter-type filter-status reset-data? %])
:on-error #(rf/dispatch [:activity-center.notifications/fetch-error filter-type filter-status %])}]}))
(fx/defn notifications-fetch-first-page
{:events [:activity-center.notifications/fetch-first-page]}
+444 -319
View File
@@ -2,366 +2,491 @@
(:require [cljs.test :refer [deftest is testing]]
[day8.re-frame.test :as rf-test]
[re-frame.core :as rf]
[status-im.constants :as c]
[status-im.constants :as const]
[status-im.ethereum.json-rpc :as json-rpc]
status-im.events
[status-im.test-helpers :as h]))
[status-im.test-helpers :as h]
[status-im.utils.config :as config]))
(defn setup []
(h/register-helper-events)
(rf/dispatch [:init/app-started]))
(defn remove-color-key
"Remove `:color` key from notifications because they have random values that we
can't assert against."
[grouped-notifications {:keys [type status]}]
(update-in grouped-notifications
[type status :data]
(fn [old _]
(map #(dissoc % :color) old))
nil))
;;;; Contact verification
(deftest contact-verification-decline-test
(with-redefs [config/new-activity-center-enabled? true]
(testing "successfully declines and reconciles returned notification"
(rf-test/run-test-sync
(setup)
(let [spy-queue (atom [])
contact-verification-id 24
expected-notification {:accepted false
:author "0x04d03f"
:chat-id "0x04d03f"
:contact-verification-status 3
:dismissed false
:id 24
:last-message nil
:message {:command-parameters nil
:content {:chat-id nil
:ens-name nil
:image nil
:line-count nil
:links nil
:parsed-text nil
:response-to nil
:rtl? nil
:sticker nil
:text nil}
:outgoing false
:outgoing-status nil
:quoted-message nil}
:name "0x04d03f"
:read true
:reply-message nil
:timestamp 1666647286000
:type const/activity-center-notification-type-contact-verification}]
(h/stub-fx-with-callbacks
::json-rpc/call
:on-success (constantly {:activityCenterNotifications
[{:accepted false
:author "0x04d03f"
:chatId "0x04d03f"
:contactVerificationStatus 3
:dismissed false
:id contact-verification-id
:message {}
:name "0x04d03f"
:read true
:timestamp 1666647286000
:type const/activity-center-notification-type-contact-verification}]}))
(h/spy-fx spy-queue ::json-rpc/call)
(rf/dispatch [:activity-center.contact-verification/decline contact-verification-id])
(is (= {:method "wakuext_declineContactVerificationRequest"
:params [contact-verification-id]}
(-> @spy-queue
(get-in [0 :args 0])
(select-keys [:method :params]))))
(is (= {const/activity-center-notification-type-no-type
{:read {:data [expected-notification]}
:unread {:data []}}
const/activity-center-notification-type-contact-verification
{:read {:data [expected-notification]}
:unread {:data []}}}
(get-in (h/db) [:activity-center :notifications]))))))
(testing "logs failure"
(rf-test/run-test-sync
(setup)
(let [contact-verification-id 666]
(h/using-log-test-appender
(fn [logs]
(h/stub-fx-with-callbacks ::json-rpc/call :on-error (constantly :fake-error))
(rf/dispatch [:activity-center.contact-verification/decline contact-verification-id])
(is (= {:args ["Failed to decline contact verification"
{:contact-verification-id contact-verification-id
:error :fake-error}]
:level :warn}
(last @logs))))))))))
;;;; Notification reconciliation
(deftest notifications-reconcile-test
(testing "does nothing when there are no new notifications"
(rf-test/run-test-sync
(setup)
(let [notifications {c/activity-center-notification-type-one-to-one-chat
{:read {:cursor ""
:data [{:id "0x1"
:read true
:type c/activity-center-notification-type-one-to-one-chat}
{:id "0x2"
:read true
:type c/activity-center-notification-type-one-to-one-chat}]}
:unread {:cursor ""
:data [{:id "0x3"
:read false
:type c/activity-center-notification-type-one-to-one-chat}]}}
c/activity-center-notification-type-private-group-chat
{:unread {:cursor ""
:data [{:id "0x4"
:read false
:type c/activity-center-notification-type-private-group-chat}]}}}]
(rf/dispatch [:test/assoc-in [:activity-center :notifications] notifications])
(with-redefs [config/new-activity-center-enabled? true]
(testing "does nothing when there are no new notifications"
(rf-test/run-test-sync
(setup)
(let [notifications {const/activity-center-notification-type-one-to-one-chat
{:read {:cursor ""
:data [{:id "0x1"
:read true
:type const/activity-center-notification-type-one-to-one-chat}
{:id "0x2"
:read true
:type const/activity-center-notification-type-one-to-one-chat}]}
:unread {:cursor ""
:data [{:id "0x3"
:read false
:type const/activity-center-notification-type-one-to-one-chat}]}}
const/activity-center-notification-type-private-group-chat
{:unread {:cursor ""
:data [{:id "0x4"
:read false
:type const/activity-center-notification-type-private-group-chat}]}}}]
(rf/dispatch [:test/assoc-in [:activity-center :notifications] notifications])
(rf/dispatch [:activity-center.notifications/reconcile nil])
(rf/dispatch [:activity-center.notifications/reconcile nil])
(is (= notifications (get-in (h/db) [:activity-center :notifications]))))))
(is (= notifications (get-in (h/db) [:activity-center :notifications]))))))
(testing "removes dismissed or accepted notifications"
(rf-test/run-test-sync
(setup)
(rf/dispatch [:test/assoc-in [:activity-center :notifications]
{c/activity-center-notification-type-one-to-one-chat
{:read {:cursor ""
:data [{:id "0x1" :read true :type c/activity-center-notification-type-one-to-one-chat}
{:id "0x2" :read true :type c/activity-center-notification-type-one-to-one-chat}]}
:unread {:cursor ""
:data [{:id "0x3" :read false :type c/activity-center-notification-type-one-to-one-chat}]}}
2 {:unread {:cursor ""
:data [{:id "0x4" :read false :type 2}
{:id "0x6" :read false :type 2}]}}}])
(testing "removes dismissed or accepted notifications"
(rf-test/run-test-sync
(setup)
(rf/dispatch [:test/assoc-in [:activity-center :notifications]
{const/activity-center-notification-type-one-to-one-chat
{:read {:cursor ""
:data [{:id "0x1" :read true :type const/activity-center-notification-type-one-to-one-chat}
{:id "0x2" :read true :type const/activity-center-notification-type-one-to-one-chat}]}
:unread {:cursor ""
:data [{:id "0x3" :read false :type const/activity-center-notification-type-one-to-one-chat}]}}
const/activity-center-notification-type-private-group-chat
{:unread {:cursor ""
:data [{:id "0x4" :read false :type const/activity-center-notification-type-private-group-chat}
{:id "0x6" :read false :type const/activity-center-notification-type-private-group-chat}]}}}])
(rf/dispatch [:activity-center.notifications/reconcile
[{:id "0x1"
:read true
:type c/activity-center-notification-type-one-to-one-chat
:dismissed true}
{:id "0x3"
:read false
:type c/activity-center-notification-type-one-to-one-chat
:accepted true}
{:id "0x4"
:read false
:type c/activity-center-notification-type-private-group-chat
:dismissed true}
{:id "0x5"
:read false
:type c/activity-center-notification-type-private-group-chat
:accepted true}]])
(rf/dispatch [:activity-center.notifications/reconcile
[{:id "0x1"
:read true
:type const/activity-center-notification-type-one-to-one-chat
:dismissed true}
{:id "0x3"
:read false
:type const/activity-center-notification-type-one-to-one-chat
:accepted true}
{:id "0x4"
:read false
:type const/activity-center-notification-type-private-group-chat
:dismissed true}
{:id "0x5"
:read false
:type const/activity-center-notification-type-private-group-chat
:accepted true}]])
(is (= {c/activity-center-notification-type-one-to-one-chat
{:read {:cursor ""
:data [{:id "0x2"
:read true
:type c/activity-center-notification-type-one-to-one-chat}]}
:unread {:cursor ""
:data []}}
c/activity-center-notification-type-private-group-chat
{:read {:data []}
:unread {:cursor ""
:data [{:id "0x6"
(is (= {const/activity-center-notification-type-no-type
{:read {:data []}
:unread {:data []}}
const/activity-center-notification-type-one-to-one-chat
{:read {:cursor ""
:data [{:id "0x2"
:read true
:type const/activity-center-notification-type-one-to-one-chat}]}
:unread {:cursor ""
:data []}}
const/activity-center-notification-type-private-group-chat
{:read {:data []}
:unread {:cursor ""
:data [{:id "0x6"
:read false
:type const/activity-center-notification-type-private-group-chat}]}}}
(get-in (h/db) [:activity-center :notifications])))))
(testing "replaces old notifications with newly arrived ones"
(rf-test/run-test-sync
(setup)
(rf/dispatch [:test/assoc-in [:activity-center :notifications]
{const/activity-center-notification-type-no-type
{:read {:cursor ""
:data [{:id "0x1"
:read true
:type const/activity-center-notification-type-one-to-one-chat}]}
:unread {:cursor ""
:data [{:id "0x4"
:read false
:type const/activity-center-notification-type-private-group-chat}
{:id "0x6"
:read false
:type const/activity-center-notification-type-private-group-chat}]}}
const/activity-center-notification-type-one-to-one-chat
{:read {:cursor ""
:data [{:id "0x1"
:read true
:type const/activity-center-notification-type-one-to-one-chat}]}}
const/activity-center-notification-type-private-group-chat
{:unread {:cursor ""
:data [{:id "0x4"
:read false
:type const/activity-center-notification-type-private-group-chat}
{:id "0x6"
:read false
:type const/activity-center-notification-type-private-group-chat}]}}}])
(rf/dispatch [:activity-center.notifications/reconcile
[{:id "0x1"
:read true
:type const/activity-center-notification-type-one-to-one-chat
:last-message {}}
{:id "0x4"
:read false
:type const/activity-center-notification-type-private-group-chat
:author "0xabc"}
{:id "0x6"
:read false
:type const/activity-center-notification-type-private-group-chat}]])
(is (= {const/activity-center-notification-type-no-type
{:read {:cursor ""
:data [{:id "0x1"
:read true
:type const/activity-center-notification-type-one-to-one-chat
:last-message {}}]}
:unread {:cursor ""
:data [{:id "0x6"
:read false
:type const/activity-center-notification-type-private-group-chat}
{:id "0x4"
:read false
:type const/activity-center-notification-type-private-group-chat
:author "0xabc"}]}}
const/activity-center-notification-type-one-to-one-chat
{:read {:cursor ""
:data [{:id "0x1"
:read true
:type const/activity-center-notification-type-one-to-one-chat
:last-message {}}]}
:unread {:data []}}
const/activity-center-notification-type-private-group-chat
{:read {:data []}
:unread {:cursor ""
:data [{:id "0x6"
:read false
:type const/activity-center-notification-type-private-group-chat}
{:id "0x4"
:read false
:type const/activity-center-notification-type-private-group-chat
:author "0xabc"}]}}}
(get-in (h/db) [:activity-center :notifications])))))
(testing "reconciles notifications that switched their read/unread status"
(rf-test/run-test-sync
(setup)
(rf/dispatch [:test/assoc-in [:activity-center :notifications]
{const/activity-center-notification-type-one-to-one-chat
{:read {:cursor ""
:data [{:id "0x1"
:read true
:type const/activity-center-notification-type-one-to-one-chat}]}}}])
(rf/dispatch [:activity-center.notifications/reconcile
[{:id "0x1"
:read false
:type const/activity-center-notification-type-one-to-one-chat}]])
(is (= {const/activity-center-notification-type-no-type
{:read {:data []}
:unread {:data [{:id "0x1"
:read false
:type c/activity-center-notification-type-private-group-chat}]}}}
(get-in (h/db) [:activity-center :notifications])))))
:type const/activity-center-notification-type-one-to-one-chat}]}}
(testing "replaces old notifications with newly arrived ones"
(rf-test/run-test-sync
(setup)
(rf/dispatch [:test/assoc-in [:activity-center :notifications]
{c/activity-center-notification-type-one-to-one-chat
{:read {:cursor ""
:data [{:id "0x1"
:read true
:type c/activity-center-notification-type-one-to-one-chat}]}}
c/activity-center-notification-type-private-group-chat
{:unread {:cursor ""
:data [{:id "0x4"
:read false
:type c/activity-center-notification-type-private-group-chat}
{:id "0x6"
:read false
:type c/activity-center-notification-type-private-group-chat}]}}}])
(rf/dispatch [:activity-center.notifications/reconcile
[{:id "0x1"
:read true
:type c/activity-center-notification-type-one-to-one-chat
:last-message {}}
{:id "0x4"
:read false
:type c/activity-center-notification-type-private-group-chat
:author "0xabc"}
{:id "0x6"
:read false
:type c/activity-center-notification-type-private-group-chat}]])
(is (= {c/activity-center-notification-type-one-to-one-chat
{:read {:cursor ""
:data [{:id "0x1"
:read true
:type c/activity-center-notification-type-one-to-one-chat
:last-message {}}]}
:unread {:data []}}
c/activity-center-notification-type-private-group-chat
{:read {:data []}
:unread {:cursor ""
:data [{:id "0x6"
const/activity-center-notification-type-one-to-one-chat
{:read {:cursor ""
:data []}
:unread {:data [{:id "0x1"
:read false
:type c/activity-center-notification-type-private-group-chat}
{:id "0x4"
:read false
:type c/activity-center-notification-type-private-group-chat
:author "0xabc"}]}}}
(get-in (h/db) [:activity-center :notifications])))))
:type const/activity-center-notification-type-one-to-one-chat}]}}}
(get-in (h/db) [:activity-center :notifications])))))
(testing "reconciles notifications that switched their read/unread status"
(rf-test/run-test-sync
(setup)
(rf/dispatch [:test/assoc-in [:activity-center :notifications]
{c/activity-center-notification-type-one-to-one-chat
{:read {:cursor ""
:data [{:id "0x1"
:read true
:type c/activity-center-notification-type-one-to-one-chat}]}}}])
;; Sorting by timestamp and ID is compatible with what the backend does when
;; returning paginated results.
(testing "sorts notifications by timestamp and id in descending order"
(rf-test/run-test-sync
(setup)
(rf/dispatch [:test/assoc-in [:activity-center :notifications]
{const/activity-center-notification-type-one-to-one-chat
{:read {:cursor ""
:data [{:id "0x1" :read true :type const/activity-center-notification-type-one-to-one-chat :timestamp 1}
{:id "0x2" :read true :type const/activity-center-notification-type-one-to-one-chat :timestamp 1}]}
:unread {:cursor ""
:data [{:id "0x3" :read false :type const/activity-center-notification-type-one-to-one-chat :timestamp 50}
{:id "0x4" :read false :type const/activity-center-notification-type-one-to-one-chat :timestamp 100}
{:id "0x5" :read false :type const/activity-center-notification-type-one-to-one-chat :timestamp 100}]}}}])
(rf/dispatch [:activity-center.notifications/reconcile
[{:id "0x1"
:read false
:type c/activity-center-notification-type-one-to-one-chat}]])
(rf/dispatch [:activity-center.notifications/reconcile
[{:id "0x1" :read true :type const/activity-center-notification-type-one-to-one-chat :timestamp 1 :last-message {}}
{:id "0x4" :read false :type const/activity-center-notification-type-one-to-one-chat :timestamp 100 :last-message {}}]])
(is (= {c/activity-center-notification-type-one-to-one-chat
{:read {:cursor ""
:data []}
:unread {:data [{:id "0x1"
:read false
:type c/activity-center-notification-type-one-to-one-chat}]}}}
(get-in (h/db) [:activity-center :notifications])))))
;; Sorting by timestamp and ID is compatible with what the backend does when
;; returning paginated results.
(testing "sorts notifications by timestamp and id in descending order"
(rf-test/run-test-sync
(setup)
(rf/dispatch [:test/assoc-in [:activity-center :notifications]
{c/activity-center-notification-type-one-to-one-chat
{:read {:cursor ""
:data [{:id "0x1" :read true :type c/activity-center-notification-type-one-to-one-chat :timestamp 1}
{:id "0x2" :read true :type c/activity-center-notification-type-one-to-one-chat :timestamp 1}]}
:unread {:cursor ""
:data [{:id "0x3" :read false :type c/activity-center-notification-type-one-to-one-chat :timestamp 50}
{:id "0x4" :read false :type c/activity-center-notification-type-one-to-one-chat :timestamp 100}
{:id "0x5" :read false :type c/activity-center-notification-type-one-to-one-chat :timestamp 100}]}}}])
(rf/dispatch [:activity-center.notifications/reconcile
[{:id "0x1" :read true :type c/activity-center-notification-type-one-to-one-chat :timestamp 1 :last-message {}}
{:id "0x4" :read false :type c/activity-center-notification-type-one-to-one-chat :timestamp 100 :last-message {}}]])
(is (= {c/activity-center-notification-type-one-to-one-chat
{:read {:cursor ""
:data [{:id "0x2"
:read true
:type c/activity-center-notification-type-one-to-one-chat
:timestamp 1}
{:id "0x1"
(is (= {const/activity-center-notification-type-no-type
{:read {:data [{:id "0x1"
:read true
:type c/activity-center-notification-type-one-to-one-chat
:type const/activity-center-notification-type-one-to-one-chat
:timestamp 1
:last-message {}}]}
:unread {:cursor ""
:data [{:id "0x5"
:read false
:type c/activity-center-notification-type-one-to-one-chat
:timestamp 100}
{:id "0x4"
:unread {:data [{:id "0x4"
:read false
:type c/activity-center-notification-type-one-to-one-chat
:type const/activity-center-notification-type-one-to-one-chat
:timestamp 100
:last-message {}}
{:id "0x3"
:read false
:type c/activity-center-notification-type-one-to-one-chat
:timestamp 50}]}}}
(get-in (h/db) [:activity-center :notifications]))))))
:last-message {}}]}}
const/activity-center-notification-type-one-to-one-chat
{:read {:cursor ""
:data [{:id "0x2"
:read true
:type const/activity-center-notification-type-one-to-one-chat
:timestamp 1}
{:id "0x1"
:read true
:type const/activity-center-notification-type-one-to-one-chat
:timestamp 1
:last-message {}}]}
:unread {:cursor ""
:data [{:id "0x5"
:read false
:type const/activity-center-notification-type-one-to-one-chat
:timestamp 100}
{:id "0x4"
:read false
:type const/activity-center-notification-type-one-to-one-chat
:timestamp 100
:last-message {}}
{:id "0x3"
:read false
:type const/activity-center-notification-type-one-to-one-chat
:timestamp 50}]}}}
(get-in (h/db) [:activity-center :notifications])))))))
;;;; Notifications fetching and pagination
(deftest notifications-fetch-test
(testing "fetches first page"
(rf-test/run-test-sync
(setup)
(let [spy-queue (atom [])]
(h/stub-fx-with-callbacks
::json-rpc/call
:on-success (constantly {:cursor "10"
:notifications [{:id "0x1"
:type c/activity-center-notification-type-one-to-one-chat
:read false
:chatId "0x9"}]}))
(h/spy-fx spy-queue ::json-rpc/call)
(with-redefs [config/new-activity-center-enabled? true]
(testing "fetches first page"
(rf-test/run-test-sync
(setup)
(let [spy-queue (atom [])]
(h/stub-fx-with-callbacks
::json-rpc/call
:on-success (constantly {:cursor "10"
:notifications [{:id "0x1"
:type const/activity-center-notification-type-one-to-one-chat
:read false
:chatId "0x9"}]}))
(h/spy-fx spy-queue ::json-rpc/call)
(rf/dispatch [:activity-center.notifications/fetch-first-page
{:filter-type c/activity-center-notification-type-one-to-one-chat}])
(rf/dispatch [:activity-center.notifications/fetch-first-page
{:filter-type const/activity-center-notification-type-one-to-one-chat}])
(is (= :unread (get-in (h/db) [:activity-center :filter :status])))
(is (= "" (get-in @spy-queue [0 :args 0 :params 0]))
"Should be called with empty cursor when fetching first page")
(is (= {c/activity-center-notification-type-one-to-one-chat
{:unread {:cursor "10"
:data [{:chat-id "0x9"
:chat-name nil
:chat-type c/activity-center-notification-type-one-to-one-chat
:group-chat false
:id "0x1"
:public? false
:last-message nil
:message nil
:read false
:reply-message nil
:type c/activity-center-notification-type-one-to-one-chat}]}}}
(remove-color-key (get-in (h/db) [:activity-center :notifications])
{:status :unread
:type c/activity-center-notification-type-one-to-one-chat}))))))
(is (= :unread (get-in (h/db) [:activity-center :filter :status])))
(is (= "" (get-in @spy-queue [0 :args 0 :params 0]))
"Should be called with empty cursor when fetching first page")
(is (= {const/activity-center-notification-type-one-to-one-chat
{:unread {:cursor "10"
:data [{:chat-id "0x9"
:chat-name nil
:chat-type const/activity-center-notification-type-one-to-one-chat
:group-chat false
:id "0x1"
:public? false
:last-message nil
:message nil
:read false
:reply-message nil
:type const/activity-center-notification-type-one-to-one-chat}]}}}
(get-in (h/db) [:activity-center :notifications]))))))
(testing "does not fetch next page when pagination cursor reached the end"
(rf-test/run-test-sync
(setup)
(let [spy-queue (atom [])]
(h/spy-fx spy-queue ::json-rpc/call)
(rf/dispatch [:test/assoc-in [:activity-center :filter :status]
:unread])
(rf/dispatch [:test/assoc-in [:activity-center :filter :type]
c/activity-center-notification-type-one-to-one-chat])
(rf/dispatch [:test/assoc-in [:activity-center :notifications c/activity-center-notification-type-one-to-one-chat :unread :cursor]
""])
(testing "does not fetch next page when pagination cursor reached the end"
(rf-test/run-test-sync
(setup)
(let [spy-queue (atom [])]
(h/spy-fx spy-queue ::json-rpc/call)
(rf/dispatch [:test/assoc-in [:activity-center :filter :status]
:unread])
(rf/dispatch [:test/assoc-in [:activity-center :filter :type]
const/activity-center-notification-type-one-to-one-chat])
(rf/dispatch [:test/assoc-in [:activity-center :notifications const/activity-center-notification-type-one-to-one-chat :unread :cursor]
""])
(rf/dispatch [:activity-center.notifications/fetch-next-page])
(rf/dispatch [:activity-center.notifications/fetch-next-page])
(is (= [] @spy-queue)))))
(is (= [] @spy-queue)))))
;; The cursor can be nil sometimes because the reconciliation doesn't care
;; about updating the cursor value, but we have to make sure the next page is
;; only fetched if the current cursor is valid.
(testing "does not fetch next page when cursor is nil"
(rf-test/run-test-sync
(setup)
(let [spy-queue (atom [])]
(h/spy-fx spy-queue ::json-rpc/call)
(rf/dispatch [:test/assoc-in [:activity-center :filter :status]
:unread])
(rf/dispatch [:test/assoc-in [:activity-center :filter :type]
c/activity-center-notification-type-one-to-one-chat])
(rf/dispatch [:test/assoc-in [:activity-center :notifications c/activity-center-notification-type-one-to-one-chat :unread :cursor]
nil])
;; The cursor can be nil sometimes because the reconciliation doesn't care
;; about updating the cursor value, but we have to make sure the next page is
;; only fetched if the current cursor is valid.
(testing "does not fetch next page when cursor is nil"
(rf-test/run-test-sync
(setup)
(let [spy-queue (atom [])]
(h/spy-fx spy-queue ::json-rpc/call)
(rf/dispatch [:test/assoc-in [:activity-center :filter :status]
:unread])
(rf/dispatch [:test/assoc-in [:activity-center :filter :type]
const/activity-center-notification-type-one-to-one-chat])
(rf/dispatch [:test/assoc-in [:activity-center :notifications const/activity-center-notification-type-one-to-one-chat :unread :cursor]
nil])
(rf/dispatch [:activity-center.notifications/fetch-next-page])
(rf/dispatch [:activity-center.notifications/fetch-next-page])
(is (= [] @spy-queue)))))
(is (= [] @spy-queue)))))
(testing "fetches next page when pagination cursor is not empty"
(rf-test/run-test-sync
(setup)
(let [spy-queue (atom [])]
(h/stub-fx-with-callbacks
::json-rpc/call
:on-success (constantly {:cursor ""
:notifications [{:id "0x1"
:type c/activity-center-notification-type-mention
:read false
:chatId "0x9"}]}))
(h/spy-fx spy-queue ::json-rpc/call)
(rf/dispatch [:test/assoc-in [:activity-center :filter :status]
:unread])
(rf/dispatch [:test/assoc-in [:activity-center :filter :type]
c/activity-center-notification-type-mention])
(rf/dispatch [:test/assoc-in [:activity-center :notifications c/activity-center-notification-type-mention :unread :cursor]
"10"])
(testing "fetches next page when pagination cursor is not empty"
(rf-test/run-test-sync
(setup)
(let [spy-queue (atom [])]
(h/stub-fx-with-callbacks
::json-rpc/call
:on-success (constantly {:cursor ""
:notifications [{:id "0x1"
:type const/activity-center-notification-type-mention
:read false
:chatId "0x9"}]}))
(h/spy-fx spy-queue ::json-rpc/call)
(rf/dispatch [:test/assoc-in [:activity-center :filter :status]
:unread])
(rf/dispatch [:test/assoc-in [:activity-center :filter :type]
const/activity-center-notification-type-mention])
(rf/dispatch [:test/assoc-in [:activity-center :notifications const/activity-center-notification-type-mention :unread :cursor]
"10"])
(rf/dispatch [:activity-center.notifications/fetch-next-page])
(rf/dispatch [:activity-center.notifications/fetch-next-page])
(is (= "wakuext_unreadActivityCenterNotifications" (get-in @spy-queue [0 :args 0 :method])))
(is (= "10" (get-in @spy-queue [0 :args 0 :params 0]))
"Should be called with current cursor")
(is (= {c/activity-center-notification-type-mention
{:unread {:cursor ""
:data [{:chat-id "0x9"
:chat-name nil
:chat-type 3
:id "0x1"
:last-message nil
:message nil
:read false
:reply-message nil
:type c/activity-center-notification-type-mention}]}}}
(remove-color-key (get-in (h/db) [:activity-center :notifications])
{:status :unread
:type c/activity-center-notification-type-mention}))))))
(is (= "wakuext_unreadActivityCenterNotifications" (get-in @spy-queue [0 :args 0 :method])))
(is (= "10" (get-in @spy-queue [0 :args 0 :params 0]))
"Should be called with current cursor")
(is (= {const/activity-center-notification-type-mention
{:unread {:cursor ""
:data [{:chat-id "0x9"
:chat-name nil
:chat-type 3
:id "0x1"
:last-message nil
:message nil
:read false
:reply-message nil
:type const/activity-center-notification-type-mention}]}}}
(get-in (h/db) [:activity-center :notifications]))))))
(testing "does not fetch next page while it is still loading"
(rf-test/run-test-sync
(setup)
(let [spy-queue (atom [])]
(h/spy-fx spy-queue ::json-rpc/call)
(rf/dispatch [:test/assoc-in [:activity-center :filter :status]
:read])
(rf/dispatch [:test/assoc-in [:activity-center :filter :type]
c/activity-center-notification-type-one-to-one-chat])
(rf/dispatch [:test/assoc-in [:activity-center :notifications c/activity-center-notification-type-one-to-one-chat :read :cursor]
"10"])
(rf/dispatch [:test/assoc-in [:activity-center :notifications c/activity-center-notification-type-one-to-one-chat :read :loading?]
true])
(testing "does not fetch next page while it is still loading"
(rf-test/run-test-sync
(setup)
(let [spy-queue (atom [])]
(h/spy-fx spy-queue ::json-rpc/call)
(rf/dispatch [:test/assoc-in [:activity-center :filter :status]
:read])
(rf/dispatch [:test/assoc-in [:activity-center :filter :type]
const/activity-center-notification-type-one-to-one-chat])
(rf/dispatch [:test/assoc-in [:activity-center :notifications const/activity-center-notification-type-one-to-one-chat :read :cursor]
"10"])
(rf/dispatch [:test/assoc-in [:activity-center :notifications const/activity-center-notification-type-one-to-one-chat :read :loading?]
true])
(rf/dispatch [:activity-center.notifications/fetch-next-page])
(rf/dispatch [:activity-center.notifications/fetch-next-page])
(is (= [] @spy-queue)))))
(is (= [] @spy-queue)))))
(testing "resets loading flag after an error"
(rf-test/run-test-sync
(setup)
(let [spy-queue (atom [])]
(h/stub-fx-with-callbacks ::json-rpc/call :on-error (constantly :fake-error))
(h/spy-event-fx spy-queue :activity-center.notifications/fetch-error)
(h/spy-fx spy-queue ::json-rpc/call)
(rf/dispatch [:test/assoc-in [:activity-center :filter :status]
:unread])
(rf/dispatch [:test/assoc-in [:activity-center :filter :type]
c/activity-center-notification-type-one-to-one-chat])
(rf/dispatch [:test/assoc-in [:activity-center :notifications c/activity-center-notification-type-one-to-one-chat :unread :cursor]
""])
(testing "resets loading flag after an error"
(rf-test/run-test-sync
(setup)
(let [spy-queue (atom [])]
(h/stub-fx-with-callbacks ::json-rpc/call :on-error (constantly :fake-error))
(h/spy-event-fx spy-queue :activity-center.notifications/fetch-error)
(h/spy-fx spy-queue ::json-rpc/call)
(rf/dispatch [:test/assoc-in [:activity-center :filter :status]
:unread])
(rf/dispatch [:test/assoc-in [:activity-center :filter :type]
const/activity-center-notification-type-one-to-one-chat])
(rf/dispatch [:test/assoc-in [:activity-center :notifications const/activity-center-notification-type-one-to-one-chat :unread :cursor]
""])
(rf/dispatch [:activity-center.notifications/fetch-first-page])
(rf/dispatch [:activity-center.notifications/fetch-first-page])
(is (nil? (get-in (h/db) [:activity-center :notifications c/activity-center-notification-type-one-to-one-chat :unread :loading?])))
(is (= [:activity-center.notifications/fetch-error
c/activity-center-notification-type-one-to-one-chat
:unread
:fake-error]
(:args (last @spy-queue))))))))
(is (nil? (get-in (h/db) [:activity-center :notifications const/activity-center-notification-type-one-to-one-chat :unread :loading?])))
(is (= [:activity-center.notifications/fetch-error
const/activity-center-notification-type-one-to-one-chat
:unread
:fake-error]
(:args (last @spy-queue)))))))))
+16 -7
View File
@@ -176,6 +176,11 @@
:on-error #(log/error "failed to clear history " chat-id %)}]}
(clear-history chat-id remove-chat?)))
(fx/defn chat-deactivated
{:events [::chat-deactivated]}
[_ chat-id]
(log/debug "chat deactivated" chat-id))
(fx/defn deactivate-chat
"Deactivate chat in db, no side effects"
[{:keys [db now] :as cofx} chat-id]
@@ -185,10 +190,10 @@
(assoc-in db [:chats chat-id :active] false)
(update db :chats dissoc chat-id))
(update :chats-home-list disj chat-id)
(assoc-in [:current-chat-id] nil))
(assoc :current-chat-id nil))
::json-rpc/call [{:method "wakuext_deactivateChat"
:params [{:id chat-id}]
:on-success #(log/debug "chat deactivated" chat-id)
:on-success #(re-frame/dispatch [::chat-deactivated chat-id])
:on-error #(log/error "failed to create public chat" chat-id %)}]}
(clear-history chat-id true)))
@@ -227,9 +232,7 @@
{:clear-message-notifications
[[chat-id] (get-in db [:multiaccount :remote-push-notifications-enabled?])]}
(deactivate-chat chat-id)
(offload-messages chat-id)
(when (not (= (:view-id db) :home))
(navigation/pop-to-root-tab :chat-stack))))
(offload-messages chat-id)))
(fx/defn show-more-chats
{:events [:chat.ui/show-more-chats]}
@@ -265,6 +268,7 @@
[{db :db :as cofx} chat-id from-switcher?]
(fx/merge cofx
{:db (assoc db :current-chat-id chat-id)}
(offload-messages chat-id)
(preload-chat-data chat-id)
(navigation2/navigate-to-nav2 :chat chat-id nil from-switcher?)))
@@ -384,6 +388,11 @@
(log/error "mute chat failed" chat-id error)
{:db (assoc-in db [:chats chat-id :muted] (not muted?))})
(fx/defn mute-chat-toggled-successfully
{:events [::mute-chat-toggled-successfully]}
[_ chat-id]
(log/debug "muted chat successfully" chat-id))
(fx/defn mute-chat
{:events [::mute-chat-toggled]}
[{:keys [db] :as cofx} chat-id muted?]
@@ -392,7 +401,7 @@
::json-rpc/call [{:method method
:params [chat-id]
:on-error #(re-frame/dispatch [::mute-chat-failed chat-id muted? %])
:on-success #(log/debug "muted chat successfully")}]}))
:on-success #(re-frame/dispatch [::mute-chat-toggled-successfully chat-id])}]}))
(fx/defn show-profile
{:events [:chat.ui/show-profile]}
@@ -496,4 +505,4 @@
(update-in [:chats chat-id :unviewed-messages-count]
#(max (- % count) 0))
(update-in [:chats chat-id :unviewed-mentions-count]
#(max (- % countWithMentions) 0)))})
#(max (- % countWithMentions) 0)))})
+15 -1
View File
@@ -91,7 +91,8 @@
:pinned (pin-message :pinned)})
(when pinned
(protocol/send-chat-messages [{:chat-id (pin-message :chat-id)
:content-type constants/content-type-pin
:content-type constants/content-type-system-text
:text "pinned a message"
:response-to (pin-message :message-id)
:ens-name preferred-name}])))))
@@ -99,3 +100,16 @@
{:events [::load-pin-messages]}
[{:keys [db] :as cofx} chat-id]
(load-more-pin-messages cofx chat-id true))
(fx/defn show-pin-limit-modal
{:events [::show-pin-limit-modal]}
[{:keys [db] :as cofx} chat-id]
(fx/merge
{:db (assoc-in db [:pin-modal chat-id] true)}))
(fx/defn hide-pin-limit-modal
{:events [::hide-pin-limit-modal]}
[{:keys [db] :as cofx} chat-id]
(fx/merge
{:db (assoc-in db [:pin-modal chat-id] false)}))
+7
View File
@@ -84,3 +84,10 @@
:group-chat true}
"opened" {}
"1-1" {}}})
(deftest navigate-to-chat-nav2
(let [chat-id "test_chat"
db {:pagination-info {chat-id {:all-loaded? true}}}]
(testing "Pagination info should be reset on navigation"
(let [res (chat/navigate-to-chat-nav2 {:db db} chat-id false)]
(is (nil? (get-in res [:db :pagination-info chat-id :all-loaded?])))))))
+12 -2
View File
@@ -14,7 +14,6 @@
(def ^:const content-type-community 9)
(def ^:const content-type-gap 10)
(def ^:const content-type-contact-request 11) ;; TODO: temp, will be removed
(def ^:const content-type-pin 13)
(def ^:const contact-request-state-none 0)
(def ^:const contact-request-state-mutual 1)
@@ -22,6 +21,13 @@
(def ^:const contact-request-state-received 3)
(def ^:const contact-request-state-dismissed 4)
(def ^:const contact-verification-state-unknown 0)
(def ^:const contact-verification-state-pending 1)
(def ^:const contact-verification-state-accepted 2)
(def ^:const contact-verification-state-declined 3)
(def ^:const contact-verification-state-cancelled 4)
(def ^:const contact-verification-state-trusted 5)
(def ^:const emoji-reaction-love 1)
(def ^:const emoji-reaction-thumbs-up 2)
(def ^:const emoji-reaction-thumbs-down 3)
@@ -183,11 +189,15 @@
(def ^:const activity-center-notification-type-mention 3)
(def ^:const activity-center-notification-type-reply 4)
(def ^:const activity-center-notification-type-contact-request 5)
(def ^:const activity-center-notification-type-contact-verification 6)
;; TODO: Remove this constant once the old Notification Center code is removed.
;; Its value clashes with the new constant `activity-center-notification-type-contact-verification`
;; used in status-go.
(def ^:const activity-center-notification-type-contact-request-retracted 6)
;; TODO: Replace with correct enum values once status-go implements them.
(def ^:const activity-center-notification-type-admin 66610)
(def ^:const activity-center-notification-type-identity-verification 66611)
(def ^:const activity-center-notification-type-tx 66612)
(def ^:const activity-center-notification-type-membership 66613)
(def ^:const activity-center-notification-type-system 66614)
+5
View File
@@ -18,6 +18,8 @@
[status-im.utils.logging.core :as utils.logs]
[status-im.utils.platform :as platform]
[status-im.utils.snoopy :as snoopy]
[status-im.switcher.animation :as animation]
[status-im.async-storage.core :as async-storage]
[status-im.utils.universal-links.core :as utils.universal-links]))
(set! interop/next-tick js/setTimeout)
@@ -39,6 +41,9 @@
(utils.universal-links/initialize)
;; TODO(parvesh) - Remove while moving functionality to status-go
(async-storage/get-item :selected-stack-id #(animation/selected-stack-id-loaded %))
;;DEV
(snoopy/subscribe!)
(when (and js/goog.DEBUG platform/ios? DevSettings)
+22 -20
View File
@@ -1,46 +1,48 @@
(ns status-im.data-store.activities
(:require [status-im.data-store.messages :as messages]
[status-im.constants :as constants]
(:require [clojure.set :as set]
[quo.design-system.colors :as colors]
clojure.set))
[status-im.constants :as constants]
[status-im.data-store.messages :as messages]
[status-im.utils.config :as config]))
(defn rpc->type [{:keys [type name] :as chat}]
(cond
(= constants/activity-center-notification-type-reply type)
(defn- rpc->type [{:keys [type name] :as chat}]
(case type
constants/activity-center-notification-type-reply
(assoc chat
:chat-name name
:chat-type constants/private-group-chat-type)
(= constants/activity-center-notification-type-mention type)
constants/activity-center-notification-type-mention
(assoc chat
:chat-type constants/private-group-chat-type
:chat-name name)
(= constants/activity-center-notification-type-private-group-chat type)
constants/activity-center-notification-type-private-group-chat
(assoc chat
:chat-type constants/private-group-chat-type
:chat-name name
:public? false
:group-chat true)
(= constants/activity-center-notification-type-one-to-one-chat type)
constants/activity-center-notification-type-one-to-one-chat
(assoc chat
:chat-type constants/one-to-one-chat-type
:chat-name name
:public? false
:group-chat false)
:else
chat))
(defn <-rpc [item]
(-> item
rpc->type
(clojure.set/rename-keys {:lastMessage :last-message
:replyMessage :reply-message
:chatId :chat-id})
(assoc :color (rand-nth colors/chat-colors))
(update :last-message #(when % (messages/<-rpc %)))
(update :message #(when % (messages/<-rpc %)))
(update :reply-message #(when % (messages/<-rpc %)))
(dissoc :chatId)))
(cond-> (-> item
rpc->type
(set/rename-keys {:lastMessage :last-message
:replyMessage :reply-message
:chatId :chat-id
:contactVerificationStatus :contact-verification-status})
(update :last-message #(when % (messages/<-rpc %)))
(update :message #(when % (messages/<-rpc %)))
(update :reply-message #(when % (messages/<-rpc %)))
(dissoc :chatId))
(not config/new-activity-center-enabled?)
(assoc :color (rand-nth colors/chat-colors))))
@@ -0,0 +1,99 @@
(ns status-im.data-store.activities-test
(:require [cljs.test :refer [deftest is testing]]
[status-im.constants :as constants]
[status-im.data-store.activities :as store]
[status-im.utils.config :as config]))
(def chat-id
"0x04c66155")
(def chat-name
"0x04c661")
(def raw-notification
{:chatId chat-id
:contactVerificationStatus constants/contact-verification-state-pending
:lastMessage {}
:name chat-name
:replyMessage {}})
(deftest <-rpc-test
(with-redefs [config/new-activity-center-enabled? true]
(testing "renames keys"
(is (= {:name chat-name
:chat-id chat-id
:contact-verification-status constants/contact-verification-state-pending}
(-> raw-notification
store/<-rpc
(dissoc :last-message :message :reply-message)))))
(testing "transforms messages from RPC response"
(is (= {:last-message {:quoted-message nil
:outgoing-status nil
:command-parameters nil
:content {:sticker nil
:rtl? nil
:ens-name nil
:parsed-text nil
:response-to nil
:chat-id nil
:image nil
:line-count nil
:links nil
:text nil}
:outgoing false}
:message nil
:reply-message {:quoted-message nil
:outgoing-status nil
:command-parameters nil
:content {:sticker nil
:rtl? nil
:ens-name nil
:parsed-text nil
:response-to nil
:chat-id nil
:image nil
:line-count nil
:links nil
:text nil}
:outgoing false}}
(-> raw-notification
store/<-rpc
(select-keys [:last-message :message :reply-message])))))
(testing "augments notification based on its type"
(is (= {:chat-name chat-name
:chat-type constants/private-group-chat-type
:name chat-name}
(-> raw-notification
(assoc :type constants/activity-center-notification-type-reply)
store/<-rpc
(select-keys [:name :chat-type :chat-name :public? :group-chat]))))
(is (= {:chat-name chat-name
:chat-type constants/private-group-chat-type
:name chat-name}
(-> raw-notification
(assoc :type constants/activity-center-notification-type-mention)
store/<-rpc
(select-keys [:name :chat-type :chat-name :public? :group-chat]))))
(is (= {:chat-name chat-name
:chat-type constants/private-group-chat-type
:group-chat true
:name chat-name
:public? false}
(-> raw-notification
(assoc :type constants/activity-center-notification-type-private-group-chat)
store/<-rpc
(select-keys [:name :chat-type :chat-name :public? :group-chat]))))
(is (= {:chat-name chat-name
:chat-type constants/one-to-one-chat-type
:group-chat false
:name chat-name
:public? false}
(-> raw-notification
(assoc :type constants/activity-center-notification-type-one-to-one-chat)
store/<-rpc
(select-keys [:name :chat-type :chat-name :public? :group-chat])))))))
+1
View File
@@ -20,6 +20,7 @@
(clojure.set/rename-keys {:id :message-id
:whisperTimestamp :whisper-timestamp
:editedAt :edited-at
:contactVerificationState :contact-verification-state
:contactRequestState :contact-request-state
:commandParameters :command-parameters
:gapParameters :gap-parameters
@@ -20,6 +20,8 @@
:response-to "a"
:links nil}
:whisper-timestamp 1
:contact-verification-state 1
:contact-request-state 2
:outgoing-status :sending
:command-parameters nil
:outgoing true
@@ -35,6 +37,8 @@
:whisperTimestamp 1
:parsedText "parsed-text"
:ensName "ens-name"
:contactVerificationState 1
:contactRequestState 2
:localChatId chat-id
:from from
:text "hta"
+61
View File
@@ -4,6 +4,7 @@
[clojure.string :as string]
[re-frame.core :as rf]
status-im.events
[status-im.chat.models :as chat.models]
[status-im.utils.security :as security]
[status-im.multiaccounts.logout.core :as logout]
[status-im.transport.core :as transport]
@@ -231,5 +232,65 @@
(logout!) (rf-test/wait-for [::logout/logout-method] ; we need to logout to make sure the node is not in an inconsistent state between tests
(assert-logout))))))))
(deftest delete-chat-test
(log/info "========= delete-chat-test ==================")
(rf-test/run-test-async
(initialize-app!)
(rf-test/wait-for
[:status-im.init.core/initialize-view]
(generate-and-derive-addresses!)
(rf-test/wait-for
[:multiaccount-generate-and-derive-addresses-success] ; wait for the keys
(assert-multiaccount-loaded)
(create-multiaccount!)
(rf-test/wait-for
[::transport/messenger-started]
(assert-messenger-started)
(rf/dispatch-sync [:chat.ui/start-chat chat-id]) ;; start a new chat
(rf-test/wait-for
[:status-im.chat.models/one-to-one-chat-created]
(rf/dispatch-sync [:chat.ui/navigate-to-chat chat-id])
(is (= chat-id @(rf/subscribe [:chats/current-chat-id])))
(is @(rf/subscribe [:chats/chat chat-id]))
(rf/dispatch-sync [:chat.ui/remove-chat-pressed chat-id])
(rf/dispatch-sync [:chat.ui/remove-chat chat-id])
(rf-test/wait-for
[::chat.models/chat-deactivated]
(is (not @(rf/subscribe [:chats/chat chat-id])))
(logout!) (rf-test/wait-for [::logout/logout-method] ; we need to logout to make sure the node is not in an inconsistent state between tests
(assert-logout)))))))))
(deftest mute-chat-test
(log/info "========= mute-chat-test ==================")
(rf-test/run-test-async
(initialize-app!)
(rf-test/wait-for
[:status-im.init.core/initialize-view]
(generate-and-derive-addresses!)
(rf-test/wait-for
[:multiaccount-generate-and-derive-addresses-success] ; wait for the keys
(assert-multiaccount-loaded)
(create-multiaccount!)
(rf-test/wait-for
[::transport/messenger-started]
(assert-messenger-started)
(rf/dispatch-sync [:chat.ui/start-chat chat-id]) ;; start a new chat
(rf-test/wait-for
[:status-im.chat.models/one-to-one-chat-created]
(rf/dispatch-sync [:chat.ui/navigate-to-chat chat-id])
(is (= chat-id @(rf/subscribe [:chats/current-chat-id])))
(is @(rf/subscribe [:chats/chat chat-id]))
(rf/dispatch-sync [::chat.models/mute-chat-toggled chat-id true])
(rf-test/wait-for
[::chat.models/mute-chat-toggled-successfully]
(is @(rf/subscribe [:chats/muted chat-id]))
(rf/dispatch-sync [::chat.models/mute-chat-toggled chat-id false])
(rf-test/wait-for
[::chat.models/mute-chat-toggled-successfully]
(is (not @(rf/subscribe [:chats/muted chat-id])))
(logout!) (rf-test/wait-for [::logout/logout-method] ; we need to logout to make sure the node is not in an inconsistent state between tests
(assert-logout))))))))))
(comment
(run-tests))
+1 -1
View File
@@ -28,7 +28,7 @@
name)]
(cond-> {:nickname nickname
:three-words-name (or alias (gfycat/generate-gfy public-key))}
;; Preferred name is our own otherwise we make sure it's verified
;; Preferred name is our own otherwise we make sure it's verified
(or preferred-name (and ens-verified name))
(assoc :ens-name (str "@" (or (stateofus/username ens-name) ens-name))))))
+2 -2
View File
@@ -468,7 +468,7 @@
"Decides which root should be initialised depending on user and app state"
[db]
(if (get db :tos/accepted?)
(re-frame/dispatch [:init-root (if config/new-ui-enabled? :home-stack :chat-stack)])
(re-frame/dispatch [:init-root (if config/new-ui-enabled? :shell-stack :chat-stack)])
(re-frame/dispatch [:init-root :tos])))
(fx/defn login-only-events
@@ -517,7 +517,7 @@
(logging/set-log-level (:log-level multiaccount))
(if config/new-ui-enabled?
(navigation/init-root :home-stack)
(navigation/init-root :shell-stack)
;; if it's a first account, the ToS will be accepted at welcome carousel
;; if not a first account, the ToS might have been accepted by other account logins
(if (or first-account? tos-accepted?)
+9 -5
View File
@@ -3,14 +3,14 @@
[status-im.reloader :as reloader]
[status-im.utils.datetime :as datetime]))
(def parent-stack (atom :home-stack))
(def parent-stack (atom :shell-stack))
(fx/defn reload-new-ui
{:events [:reload-new-ui]}
[_]
(reloader/reload)
{:new-ui/reset-bottom-tabs nil
:dispatch [:init-root :home-stack]})
:dispatch [:init-root :shell-stack]})
(fx/defn init-root-nav2
{:events [:init-root-nav2]}
@@ -27,7 +27,7 @@
[_ modal]
{:close-modal-fx-nav2 modal})
(defn navigate-from-home-stack [go-to-view-id id db]
(defn navigate-from-shell-stack [go-to-view-id id db]
(reset! parent-stack go-to-view-id)
{:navigate-to-fx-nav2 [go-to-view-id id]
:db (assoc-in db [:navigation2/navigation2-stacks id] {:type go-to-view-id
@@ -49,8 +49,12 @@
(if from-switcher?
(navigate-from-switcher go-to-view-id id db from-home?)
(if from-home?
(navigate-from-home-stack go-to-view-id id db)
(navigate-from-shell-stack go-to-view-id id db)
;; TODO(parvesh) - new stacks created from other screens should be stacked on current stack, instead of creating new entry
(navigate-from-home-stack go-to-view-id id db)))))
(navigate-from-shell-stack go-to-view-id id db)))))
(fx/defn change-root-status-bar-style
{:events [:change-root-status-bar-style]}
[_ style]
{:change-root-status-bar-style-fx style})
+7 -3
View File
@@ -13,8 +13,10 @@
:wallet 2
:browser 3})
;; (defonce set-navigation-default-options
;; (.setDefaultOptions Navigation (clj->js {:options {:topBar {:visible false}}})))
(defn change-root-status-bar-style [style]
(.mergeOptions Navigation
"shell-stack"
(clj->js {:statusBar {:style style}})))
;; TODO (parvesh) - improve open-modal and close-modal
(defn open-modal [comp]
@@ -55,7 +57,7 @@
(let [{:keys [options]} (get views/screens comp)]
(reset! nav2-utils/container-stack-view-id comp)
(.push Navigation
(name :home-stack)
"shell-stack"
(clj->js {:stack {:id comp
:children [{:component {:id comp
:name comp
@@ -79,3 +81,5 @@
(re-frame/reg-fx :navigate-to-fx-nav2 navigate)
(re-frame/reg-fx :navigate-from-switcher-fx navigate-from-switcher)
(re-frame/reg-fx :change-root-status-bar-style-fx change-root-status-bar-style)
+6 -7
View File
@@ -1,20 +1,19 @@
(ns status-im.navigation2.roots
(:require [quo.theme :as theme]
[quo2.foundations.colors :as colors]
(:require [quo2.foundations.colors :as colors]
[status-im.utils.platform :as platform]))
(defn status-bar-options []
(if platform/android?
{:navigationBar {:backgroundColor colors/neutral-80}
{:navigationBar {:backgroundColor colors/neutral-100}
:statusBar {:backgroundColor :transparent
:style (if (theme/dark?) :light :dark)
:style :light
:drawBehind true}}
{:statusBar {:style (if (theme/dark?) :light :dark)}}))
{:statusBar {:style :light}}))
(defn roots []
{:home-stack
{:shell-stack
{:root
{:stack {:id :home-stack
{:stack {:id :shell-stack
:children [{:component {:name :chat-stack
:id :chat-stack
:options (merge (status-bar-options)
+4 -5
View File
@@ -1,15 +1,14 @@
(ns status-im.navigation2.screens
(:require [status-im.ui2.screens.chat.view :as chat]
[status-im.switcher.home-stack :as home-stack]
[status-im.navigation2.stack-with-switcher :as stack-with-switcher]))
[status-im.switcher.shell-stack :as shell-stack]))
;; We have to use the home screen name :chat-stack for now, for compatibility with navigation.cljs
(def screens [{:name :chat-stack ;; TODO(parvesh) - rename to home-stack
(def screens [{:name :chat-stack ;; TODO(parvesh) - rename to shell-stack
:insets {:top false}
:component home-stack/home}])
:component shell-stack/shell-stack}])
;; These screens will overwrite navigation/screens.cljs screens on enabling new UI toggle
(def screen-overwrites
[{:name :chat
:options {:topBar {:visible false}}
:component #(stack-with-switcher/overlap-stack chat/chat :chat)}])
:component chat/chat}])
@@ -1,10 +0,0 @@
(ns status-im.navigation2.stack-with-switcher
(:require [quo.react-native :as rn]
[status-im.utils.platform :as platform]
[status-im.switcher.switcher :as switcher]))
(defn overlap-stack [comp view-id]
[rn/view {:style {:flex 1
:margin-bottom (if platform/ios? 30 0)}}
[comp]
[switcher/switcher view-id]])
+6
View File
@@ -27,6 +27,12 @@
vals
(sort-by :pinned-at <))))
(re-frame/reg-sub
:chats/pin-modal
:<- [:messages/pin-modal]
(fn [pin-modal [_ chat-id]]
(get pin-modal chat-id)))
(re-frame/reg-sub
:chats/message-reactions
:<- [:multiaccount/public-key]
+2 -1
View File
@@ -112,6 +112,7 @@
(reg-root-key-sub :messages/pagination-info :pagination-info)
(reg-root-key-sub :messages/pin-message-lists :pin-message-lists)
(reg-root-key-sub :messages/pin-messages :pin-messages)
(reg-root-key-sub :messages/pin-modal :pin-modal)
;;browser
(reg-root-key-sub :browsers :browser/browsers)
@@ -252,4 +253,4 @@
(reg-root-key-sub :messenger/started? :messenger/started?)
(reg-root-key-sub :information-box-states :information-box-states)
(reg-root-key-sub :information-box-states :information-box-states)
+102 -59
View File
@@ -1,65 +1,108 @@
(ns status-im.switcher.animation
(:require [quo2.reanimated :as reanimated]
(:require [re-frame.core :as re-frame]
[quo2.reanimated :as reanimated]
[quo2.foundations.colors :as colors]
[status-im.async-storage.core :as async-storage]
[status-im.switcher.constants :as constants]))
;;;; Switcher Animations
;; Component Animations
(defn switcher-touchable-on-press-in
[touchable-scale]
(reanimated/animate-shared-value-with-timing touchable-scale constants/switcher-pressed-scale 300 :easing1))
(defn switcher-touchable-on-press-out [switcher-opened? view-id shared-values]
(let [{:keys [width height]} (constants/dimensions)
switcher-bottom-position (constants/switcher-pressed-bottom-position view-id)
switcher-target-radius (Math/hypot
(/ width 2)
(- height constants/switcher-pressed-radius switcher-bottom-position))
switcher-size (* 2 switcher-target-radius)]
(reanimated/animate-shared-value-with-timing (:button-touchable-scale shared-values) 1 300 :easing1)
(if @switcher-opened?
(do
(reanimated/animate-shared-value-with-timing (:switcher-button-opacity shared-values) 1 300 :easing1)
(reanimated/animate-shared-value-with-timing (:switcher-screen-size shared-values) constants/switcher-pressed-size 300 :linear)
(reanimated/animate-shared-value-with-timing (:switcher-container-scale shared-values) 0.9 300 :linear))
(do
(reanimated/animate-shared-value-with-timing (:switcher-button-opacity shared-values) 0 300 :easing1)
(reanimated/animate-shared-value-with-timing (:switcher-screen-size shared-values) switcher-size 300 :linear)
(reanimated/animate-shared-value-with-timing (:switcher-container-scale shared-values) 1 300 :linear)))
(swap! switcher-opened? not)))
;; Derived Values
(defn switcher-close-button-opacity [switcher-button-opacity]
(.switcherCloseButtonOpacity ^js reanimated/worklet-factory switcher-button-opacity))
(defn switcher-screen-radius [switcher-screen-size]
(.switcherScreenRadius ^js reanimated/worklet-factory switcher-screen-size))
(defn switcher-screen-bottom-position [switcher-screen-radius view-id]
(.switcherScreenBottomPosition ^js reanimated/worklet-factory
switcher-screen-radius
constants/switcher-pressed-radius
(constants/switcher-pressed-bottom-position view-id)))
(defn switcher-container-bottom-position [switcher-screen-bottom]
(.switcherContainerBottomPosition ^js reanimated/worklet-factory
switcher-screen-bottom
(+ constants/switcher-container-height-padding
constants/switcher-height-offset)))
;;;; Bottom Tabs & Home Stack Animations
(def selected-stack-id (atom nil))
(def home-stack-open? (atom false))
(def pass-through? (atom false))
(def bottom-nav-tab-width 90)
(defn selected-stack-id-loaded [stack-id]
(reset! selected-stack-id stack-id)
(reset! home-stack-open? (some? stack-id)))
(defn calculate-home-stack-position []
(let [{:keys [width height]} (constants/dimensions)
minimize-scale (/ bottom-nav-tab-width width)
empty-space-half-scale (/ (- 1 minimize-scale) 2)
left-margin (/ (- width (* 4 bottom-nav-tab-width)) 2)
left-empty-space (* empty-space-half-scale width)
top-empty-space (* empty-space-half-scale
(- height (constants/bottom-tabs-container-height)))]
{:left (reduce
(fn [acc stack-id]
(assoc acc stack-id (+ (- left-margin left-empty-space)
(* (.indexOf constants/stacks-ids stack-id)
bottom-nav-tab-width))))
{:none 0} constants/stacks-ids)
:top (+ top-empty-space (constants/bottom-tabs-container-height))
:scale minimize-scale}))
(defn get-shared-values []
(let [selected-stack-id-sv (reanimated/use-shared-value
;; passing keywords or nil is not working with reanimated
(name (if @selected-stack-id @selected-stack-id :none)))
;; Second shared value of selected-stack-id required to make sure stack is still visible while minimizing
selected-stack-id-sv2 (reanimated/use-shared-value
(name (if @selected-stack-id @selected-stack-id :none)))
pass-through-sv (reanimated/use-shared-value @pass-through?)
home-stack-open-sv (reanimated/use-shared-value @home-stack-open?)
animate-home-stack-left (reanimated/use-shared-value (not @home-stack-open?))
home-stack-position (calculate-home-stack-position)]
(reduce
(fn [acc id]
(let [tabs-icon-color-keyword (get constants/tabs-icon-color-keywords id)
stack-opacity-keyword (get constants/stacks-opacity-keywords id)
stack-pointer-keyword (get constants/stacks-pointer-keywords id)]
(assoc
acc
stack-opacity-keyword (.stackOpacity
^js reanimated/worklet-factory
(name id) selected-stack-id-sv2)
stack-pointer-keyword (.stackPointer
^js reanimated/worklet-factory
(name id) selected-stack-id-sv2)
tabs-icon-color-keyword (.bottomTabIconColor
^js reanimated/worklet-factory
(name id) selected-stack-id-sv pass-through-sv
colors/white colors/neutral-50 colors/white-opa-40))))
{:selected-stack-id selected-stack-id-sv
:selected-stack-id2 selected-stack-id-sv2
:pass-through? pass-through-sv
:home-stack-open? home-stack-open-sv
:animate-home-stack-left animate-home-stack-left
:home-stack-left (.homeStackLeft
^js reanimated/worklet-factory
selected-stack-id-sv2 animate-home-stack-left home-stack-open-sv
(clj->js (:left home-stack-position)))
:home-stack-top (.homeStackTop
^js reanimated/worklet-factory
home-stack-open-sv (:top home-stack-position))
:home-stack-opacity (.homeStackOpacity
^js reanimated/worklet-factory home-stack-open-sv)
:home-stack-pointer (.homeStackPointer
^js reanimated/worklet-factory home-stack-open-sv)
:home-stack-scale (.homeStackScale
^js reanimated/worklet-factory home-stack-open-sv
(:scale home-stack-position))}
constants/stacks-ids)))
;; Animation
(defn change-tab [shared-values stack-id]
(when-not (colors/dark?)
(js/setTimeout #(re-frame/dispatch [:change-root-status-bar-style :dark]) 300))
(if @home-stack-open?
(reanimated/set-shared-value (:animate-home-stack-left shared-values) false)
(reset! home-stack-open? true))
(reset! selected-stack-id stack-id)
(reanimated/set-shared-value (:selected-stack-id2 shared-values) (name stack-id))
(reanimated/set-shared-value (:selected-stack-id shared-values) (name stack-id))
(reanimated/set-shared-value (:home-stack-open? shared-values) true)
(async-storage/set-item! :selected-stack-id stack-id))
(defn close-home-stack [shared-values]
(re-frame/dispatch [:change-root-status-bar-style :light])
(reanimated/set-shared-value (:animate-home-stack-left shared-values) true)
(reset! home-stack-open? false)
(reset! selected-stack-id nil)
(reanimated/set-shared-value (:home-stack-open? shared-values) false)
(reanimated/set-shared-value (:selected-stack-id shared-values) "none")
(async-storage/set-item! :selected-stack-id nil))
(defn bottom-tab-on-press [shared-values selected-stack-id]
(doseq [id constants/stacks-ids]
(let [selected-tab? (= id selected-stack-id)
tab-opacity-shared-value (get shared-values (get constants/tabs-opacity-keywords id))
stack-opacity-shared-value (get shared-values (get constants/stacks-opacity-keywords id))
stack-pointer-shared-value (get shared-values (get constants/stacks-pointer-keywords id))]
(reanimated/animate-shared-value-with-timing tab-opacity-shared-value (if selected-tab? 1 0) 300 :easing3)
(reanimated/set-shared-value stack-pointer-shared-value (if selected-tab? "auto" "none"))
(if selected-tab?
(reanimated/animate-shared-value-with-delay stack-opacity-shared-value 1 300 :easing3 150)
(reanimated/animate-shared-value-with-timing stack-opacity-shared-value 0 300 :easing3)))))
+38 -46
View File
@@ -2,66 +2,58 @@
(:require [quo.react-native :as rn]
[reagent.core :as reagent]
[re-frame.core :as re-frame]
[quo2.reanimated :as reanimated]
[status-im.switcher.styles :as styles]
[status-im.utils.platform :as platform]
[status-im.switcher.constants :as constants]
[status-im.switcher.animation :as animation]
[quo2.components.icon :as icons]))
(def selected-stack-id (atom :communities-stack))
[quo2.components.navigation.bottom-nav-tab :as bottom-nav-tab]))
;; Reagent atoms used for lazily loading home screen tabs
(def load-communities-tab? (reagent/atom true))
(def load-communities-tab? (reagent/atom false))
(def load-chats-tab? (reagent/atom false))
(def load-wallet-tab? (reagent/atom false))
(def load-browser-tab? (reagent/atom false))
(defn load-selected-stack [stack-id]
(case stack-id
:communities-stack (reset! load-communities-tab? true)
:chats-stack (reset! load-chats-tab? true)
:wallet-stack (reset! load-wallet-tab? true)
:browser-stack (reset! load-browser-tab? true)
""))
(re-frame/reg-fx
:new-ui/reset-bottom-tabs
(fn []
(reset! selected-stack-id :communities-stack)
(reset! load-communities-tab? true)
(reset! load-chats-tab? false)
(reset! load-wallet-tab? false)
(reset! load-browser-tab? false)))
(let [selected-stack-id @animation/selected-stack-id]
(reset! load-communities-tab? (= selected-stack-id :communities-stack))
(reset! load-chats-tab? (= selected-stack-id :chats-stack))
(reset! load-wallet-tab? (= selected-stack-id :wallet-stack))
(reset! load-browser-tab? (= selected-stack-id :browser-stack)))))
(defn bottom-tab-on-press [shared-values stack-id]
(when-not (= stack-id @selected-stack-id)
(reset! selected-stack-id stack-id)
(animation/bottom-tab-on-press shared-values stack-id)
(case stack-id
:communities-stack (reset! load-communities-tab? true)
:chats-stack (reset! load-chats-tab? true)
:wallet-stack (reset! load-wallet-tab? true)
:browser-stack (reset! load-browser-tab? true))))
(when-not (= stack-id @animation/selected-stack-id)
(let [stack-load-delay (cond
@animation/home-stack-open? 0
platform/android? 250
:else 300)]
(animation/change-tab shared-values stack-id)
(js/setTimeout #(load-selected-stack stack-id) stack-load-delay))))
;; TODO(parvesh) - reimplement tab with counter, once design is complete
(defn bottom-tab [icon stack-id icons-only? shared-values]
[:f>
(fn []
(let [bottom-tab-original-style {:padding 16}]
(if icons-only?
[rn/touchable-opacity {:active-opacity 1
:style bottom-tab-original-style
:on-press #(bottom-tab-on-press shared-values stack-id)}
[reanimated/view {:style (reanimated/apply-animations-to-style
{:opacity (get
shared-values
(get constants/tabs-opacity-keywords stack-id))}
{})}
[icons/icon icon (styles/bottom-tab-icon :bottom-tabs-selected-tab)]]]
[rn/view {:style bottom-tab-original-style}
[icons/icon icon (styles/bottom-tab-icon :bottom-tabs-non-selected-tab)]])))])
(defn tabs [shared-values icons-only?]
[rn/view {:style (styles/bottom-tabs icons-only?)}
[bottom-tab :main-icons2/communities :communities-stack icons-only? shared-values]
[bottom-tab :main-icons2/messages :chats-stack icons-only? shared-values]
[rn/view {:width 50}]
[bottom-tab :main-icons2/wallet :wallet-stack icons-only? shared-values]
[bottom-tab :main-icons2/browser :browser-stack icons-only? shared-values]])
(defn bottom-tab [icon stack-id shared-values]
[bottom-nav-tab/bottom-nav-tab
{:icon icon
:icon-color-anim (get
shared-values
(get constants/tabs-icon-color-keywords stack-id))
:on-press #(bottom-tab-on-press shared-values stack-id)
:accessibility-label (str (name stack-id) "-tab")}])
(defn bottom-tabs [shared-values]
[:<>
[tabs shared-values false]
[tabs shared-values true]])
(load-selected-stack @animation/selected-stack-id)
[rn/view {:style (styles/bottom-tabs-container false)}
[rn/view {:style (styles/bottom-tabs)}
[bottom-tab :main-icons2/communities :communities-stack shared-values]
[bottom-tab :main-icons2/messages :chats-stack shared-values]
[bottom-tab :main-icons2/wallet :wallet-stack shared-values]
[bottom-tab :main-icons2/browser :browser-stack shared-values]]])
@@ -1,32 +0,0 @@
(ns status-im.switcher.cards.messaging-card
(:require [quo.react-native :as rn]
[quo2.components.markdown.text :as text]
[status-im.constants :as constants]
[quo2.components.buttons.button :as button]
[status-im.utils.handlers :refer [>evt <sub]]
[status-im.switcher.cards.styles :as styles]))
;; TODO - Add switcher close animation (fade) while opening screen from cards
;; currently dealy is added to avoid default circular animation
(defn on-press [id toggle-switcher-screen]
(js/setTimeout toggle-switcher-screen 100)
(>evt [:chat.ui/navigate-to-chat-nav2 id true]))
;; TODO - add last message for other content types
(defn last-message [{:keys [content content-type]}]
(cond
(= constants/content-type-text content-type)
[text/text (styles/messaging-card-last-message-text-props) (:text content)]))
(defn card [{:keys [id toggle-switcher-screen]}]
(let [chat (<sub [:chats/chat id])]
[rn/touchable-without-feedback {:on-press #(on-press id toggle-switcher-screen)}
[rn/view {:style (styles/messaging-card-main-container)}
[rn/view {:style (styles/messaging-card-secondary-container)}
[text/text (styles/messaging-card-title-props) (:alias chat)]
[text/text (styles/messaging-card-subtitle-props) "Message"]
[rn/view {:style (styles/messaging-card-details-container)}
[last-message (:last-message chat)]]]
[rn/view {:style (styles/messaging-card-avatar-container)}]
[button/button (styles/messaging-card-close-button-props) :main-icons/close]]]))
-103
View File
@@ -1,103 +0,0 @@
(ns status-im.switcher.cards.styles
(:require [quo.theme :as theme]
[quo2.foundations.colors :as colors]))
(def themes
{:light {:messaging-card-container-background-color "#26A69A"
:messaging-card-secondary-container-background-color colors/white
:messaging-card-title-color colors/neutral-100
:messaging-card-subtitle-color colors/neutral-50
:messaging-card-last-message-text-color colors/neutral-100
:messaging-card-close-button-bg-color colors/white-opa-50
:messaging-card-close-button-icon-color colors/neutral-100}
:dark {:messaging-card-container-background-color "#26A69A"
:messaging-card-secondary-container-background-color colors/neutral-90
:messaging-card-title-color colors/white
:messaging-card-subtitle-color colors/neutral-40
:messaging-card-last-message-text-color colors/white
:messaging-card-close-button-bg-color colors/neutral-80-opa-60
:messaging-card-close-button-icon-color colors/white}})
(defn get-color [key]
(get-in themes [(theme/get-theme) key]))
;; Messaging Card
(defn messaging-card-main-container []
{:width 160
:height 172
:border-radius 16
:margin 8
:background-color (get-color :messaging-card-container-background-color)})
(defn messaging-card-secondary-container []
{:width 160
:height 132
:background-color (get-color :messaging-card-secondary-container-background-color)
:border-radius 16
:position :absolute
:bottom 0})
(defn messaging-card-title []
{:position :absolute
:top 32
:margin-horizontal 12
:color (get-color :messaging-card-title-color)})
(defn messaging-card-title-props []
{:size :paragraph-1
:weight :semi-bold
:number-of-lines 1
:ellipsize-mode :tail
:style (messaging-card-title)})
(defn messaging-card-subtitle []
{:position :absolute
:top 54
:margin-horizontal 12
:color (get-color :messaging-card-subtitle-color)})
(defn messaging-card-subtitle-props []
{:size :paragraph-2
:weight :medium
:style (messaging-card-subtitle)})
(defn messaging-card-details-container []
{:position :absolute
:bottom 12
:margin-horizontal 12
:width 136
:height 36})
(defn messaging-card-last-message-text []
{:color (get-color :messaging-card-last-message-text-color)})
(defn messaging-card-last-message-text-props []
{:size :paragraph-2
:weight :regular
:number-of-lines 2
:ellipsize-mode :tail
:style (messaging-card-last-message-text)})
(defn messaging-card-close-button []
{:position :absolute
:right 8
:top 8
:background-color (get-color :messaging-card-close-button-bg-color)
:icon-color (get-color :messaging-card-icon-color)})
(defn messaging-card-close-button-props []
{:size 24
:type :grey
:icon true
:on-press #(print "close pressed")
:style (messaging-card-close-button)})
(defn messaging-card-avatar-container []
{:width 48
:height 48
:border-radius 24
:position :absolute
:left 12
:top 16
:background-color :pink})
+19 -48
View File
@@ -1,57 +1,26 @@
(ns status-im.switcher.constants
(:require [quo.react-native :as rn]
[reagent.core :as reagent]
[status-im.utils.handlers :refer [<sub]]
[status-im.utils.platform :as platform]))
;; For translucent status bar(android), dimensions/window also includes status bar's height,
;; this offset is used for correctly calculating switcher position
(def switcher-height-offset
(if platform/android? (:status-bar-height @rn/navigation-const) 0))
(defn bottom-tabs-container-height []
(if platform/android? 57 82))
;; extra height of switcher container for show/peek hidden cards while opening animation
(def switcher-container-height-padding 100)
(defn bottom-tabs-extended-container-height []
(if platform/android? 90 120))
(def switcher-button-radius 24)
(def switcher-button-size
(* switcher-button-radius 2))
(def switcher-pressed-scale 0.9)
(def switcher-pressed-radius
(* switcher-pressed-scale switcher-button-radius))
(def switcher-pressed-size
(* 2 switcher-pressed-radius))
(def switcher-bottom-positions
{:android
{:home-stack 15
:chat 140}
:ios
{:home-stack 40
:chat 140}})
(defn switcher-bottom-position [view-id]
(get-in
switcher-bottom-positions
[(keyword platform/os) view-id]))
(defn switcher-pressed-bottom-position [view-id]
(+
(get-in
switcher-bottom-positions
[(keyword platform/os) view-id])
(- switcher-button-radius switcher-pressed-radius)))
;; TODO(parvesh) - use different height for android and ios(Confirm from Design)
(defn bottom-tabs-height []
(if platform/android? 55 80))
(def status-bar-offset
(if platform/android? (.-currentHeight ^js rn/status-bar) 0))
;; status bar height is not included in : the dimensions/window for devices with a notch
;; https://github.com/facebook/react-native/issues/23693#issuecomment-662860819
(defn dimensions []
(let [{:keys [width height]} (<sub [:dimensions/window])]
{:width width
:height (+ height switcher-height-offset)}))
:height (if (> status-bar-offset 28)
(+ height status-bar-offset)
height)}))
(def stacks-ids [:communities-stack :chats-stack :wallet-stack :browser-stack])
@@ -67,8 +36,10 @@
:wallet-stack :wallet-stack-pointer
:browser-stack :browser-stack-pointer})
(def tabs-opacity-keywords
{:communities-stack :communities-tab-opacity
:chats-stack :chats-tab-opacity
:wallet-stack :wallet-tab-opacity
:browser-stack :browser-tab-opacity})
(def tabs-icon-color-keywords
{:communities-stack :communities-tab-icon-color
:chats-stack :chats-tab-icon-opacity
:wallet-stack :wallet-tab-icon-opacity
:browser-stack :browser-tab-icon-opacity})
(def pass-through? (reagent/atom false))
+36 -40
View File
@@ -1,33 +1,36 @@
(ns status-im.switcher.home-stack
(:require [quo2.reanimated :as reanimated]
[status-im.utils.platform :as platform]
[status-im.switcher.switcher :as switcher]
[status-im.ui2.screens.chat.home :as chat.home]
[status-im.switcher.styles :as styles]
[status-im.switcher.animation :as animation]
[status-im.switcher.constants :as constants]
[status-im.ui2.screens.chat.home :as chat.home]
[status-im.switcher.bottom-tabs :as bottom-tabs]
[status-im.ui.screens.profile.user.views :as profile.user]
[status-im.ui.screens.communities.communities-list-redesign :as communities]
[status-im.ui.screens.wallet.accounts.views :as wallet.accounts]))
[status-im.ui.screens.wallet.accounts.views :as wallet.accounts]
[quo2.components.navigation.floating-shell-button :as floating-shell-button]
[status-im.ui.screens.communities.communities-list-redesign :as communities]))
(defn load-stack? [stack-id]
(case stack-id
:communities-stack @bottom-tabs/load-communities-tab?
:chats-stack @bottom-tabs/load-chats-tab?
:browser-stack @bottom-tabs/load-browser-tab?
:wallet-stack @bottom-tabs/load-wallet-tab?))
:chats-stack @bottom-tabs/load-chats-tab?
:browser-stack @bottom-tabs/load-browser-tab?
:wallet-stack @bottom-tabs/load-wallet-tab?))
(defn stack-view [stack-id shared-values]
(when (load-stack? stack-id)
[:f>
(fn []
[reanimated/view {:style (reanimated/apply-animations-to-style
{:opacity (get shared-values (get constants/stacks-opacity-keywords stack-id))
:pointer-events (get shared-values (get constants/stacks-pointer-keywords stack-id))}
{:top 0
:bottom (if platform/ios? 79 54)
:left 0
:right 0
:position :absolute})}
[reanimated/view
{:style (reanimated/apply-animations-to-style
{:opacity (get shared-values (get constants/stacks-opacity-keywords stack-id))
:pointer-events (get shared-values (get constants/stacks-pointer-keywords stack-id))}
{:position :absolute
:top 0
:bottom 0
:left 0
:right 0
:accessibility-label stack-id})}
(case stack-id
:communities-stack [communities/communities-list]
:chats-stack [chat.home/home]
@@ -35,29 +38,22 @@
:browser-stack [profile.user/my-profile])])]))
(defn home-stack [shared-values]
[:<>
[stack-view :communities-stack shared-values]
[stack-view :chats-stack shared-values]
[stack-view :browser-stack shared-values]
[stack-view :wallet-stack shared-values]])
(defn home []
[:f>
(fn []
(let [selected-stack-id @bottom-tabs/selected-stack-id
shared-values (reduce (fn [acc id]
(let [selected-tab? (= id selected-stack-id)
tab-opacity-keyword (get constants/tabs-opacity-keywords id)
stack-opacity-keyword (get constants/stacks-opacity-keywords id)
stack-pointer-keyword (get constants/stacks-pointer-keywords id)]
(assoc
acc
tab-opacity-keyword (reanimated/use-shared-value (if selected-tab? 1 0))
stack-opacity-keyword (reanimated/use-shared-value (if selected-tab? 1 0))
stack-pointer-keyword (reanimated/use-shared-value (if selected-tab? "auto" "none")))))
{}
constants/stacks-ids)]
[:<>
[home-stack shared-values]
[bottom-tabs/bottom-tabs shared-values]
[switcher/switcher :home-stack]]))])
(let [home-stack-original-style (styles/home-stack)
home-stack-animated-style (reanimated/apply-animations-to-style
{:top (:home-stack-top shared-values)
:left (:home-stack-left shared-values)
:opacity (:home-stack-opacity shared-values)
:pointer-events (:home-stack-pointer shared-values)
:transform [{:scale (:home-stack-scale shared-values)}]}
home-stack-original-style)]
[reanimated/view {:style home-stack-animated-style}
[stack-view :communities-stack shared-values]
[stack-view :chats-stack shared-values]
[stack-view :browser-stack shared-values]
[stack-view :wallet-stack shared-values]
[floating-shell-button/floating-shell-button
{:jump-to {:on-press #(animation/close-home-stack shared-values)}}
{:position :absolute
:bottom 12}]]))])
+54
View File
@@ -0,0 +1,54 @@
(ns status-im.switcher.shell
(:require [quo.react-native :as rn]
[status-im.i18n.i18n :as i18n]
[quo2.foundations.colors :as colors]
[quo2.components.markdown.text :as text]
[quo.components.safe-area :as safe-area]
[quo2.components.navigation.top-nav :as top-nav]))
(defn placeholder []
[rn/view {:style {:position :absolute
:top 0
:left 0
:right 0
:bottom -1
:justify-content :center
:align-items :center
:accessibility-label :shell-placeholder-view}}
[rn/view {:style {:margin-top 12
:width 80
:height 80
:border-radius 16
:background-color colors/neutral-90}}]
[text/text {:size :heading-2
:weight :semi-bold
:style {:margin-top 20
:color colors/white}}
(i18n/label :t/shell-placeholder-title)]
[text/text {:size :paragraph-1
:weight :regular
:align :center
:style {:margin-top 8
:color colors/white}}
(i18n/label :t/shell-placeholder-subtitle)]])
(defn shell []
[safe-area/consumer
(fn [insets]
[rn/view {:style {:top 0
:left 0
:right 0
:bottom -1
:position :absolute
:background-color colors/neutral-100}}
[top-nav/top-nav {:type :shell
:style {:margin-top (:top insets)}}]
[placeholder]
[rn/scroll-view {:style {:padding-horizontal 20
:flex-direction :row}}
[text/text {:size :heading-1
:weight :semi-bold
:style {:color colors/white
:margin-top 12}}
(i18n/label :t/jump-to)]]])])
+14
View File
@@ -0,0 +1,14 @@
(ns status-im.switcher.shell-stack
(:require [status-im.switcher.shell :as shell]
[status-im.switcher.animation :as animation]
[status-im.switcher.home-stack :as home-stack]
[status-im.switcher.bottom-tabs :as bottom-tabs]))
(defn shell-stack []
[:f>
(fn []
(let [shared-values (animation/get-shared-values)]
[:<>
[shell/shell]
[bottom-tabs/bottom-tabs shared-values]
[home-stack/home-stack shared-values]]))])
+27 -81
View File
@@ -1,89 +1,35 @@
(ns status-im.switcher.styles
(:require [quo.theme :as theme]
[quo2.foundations.colors :as colors]
(:require [quo2.foundations.colors :as colors]
[status-im.utils.platform :as platform]
[status-im.switcher.constants :as constants]))
(def themes
{:light {:bottom-tabs-bg-color colors/neutral-80
:bottom-tabs-on-scroll-bg-color colors/neutral-80-opa-80
:bottom-tabs-non-selected-tab colors/neutral-50
:bottom-tabs-selected-tab colors/white
:switcher-close-button-bg-color colors/white}
:dark {:bottom-tabs-bg-color colors/neutral-80
:bottom-tabs-on-scroll-bg-color colors/neutral-80-opa-80
:bottom-tabs-non-selected-tab colors/neutral-50
:bottom-tabs-selected-tab colors/white
:switcher-close-button-bg-color colors/white}})
(defn get-color [key]
(get-in themes [(theme/get-theme) key]))
;; Bottom Tabs
(defn bottom-tab-icon [tab-state]
{:size 24
:color (get-color tab-state)})
(defn bottom-tabs-container [pass-through?]
{:background-color (if pass-through? colors/neutral-100-opa-70 colors/neutral-100)
:flex 1
:align-items :center
:flex-direction :column
:height (constants/bottom-tabs-container-height)
:position :absolute
:bottom -1
:right 0
:left 0
:accessibility-label :bottom-tabs-container})
(defn bottom-tabs [icons-only?]
{:background-color (if icons-only? nil (get-color :bottom-tabs-bg-color))
:flex-direction :row
:flex 1
:justify-content :space-between
:height (constants/bottom-tabs-height)
:position :absolute
:bottom -1
:right 0
:left 0
:padding-horizontal 16})
(defn bottom-tabs []
{:flex-direction :row
:position :absolute
:bottom (if platform/android? 8 34)
:flex 1
:accessibility-label :bottom-tabs})
;; Switcher
(defn switcher-button []
{:width constants/switcher-button-size
:height constants/switcher-button-size
:z-index 2})
(defn merge-switcher-button-common-styles [style]
(merge
{:width constants/switcher-button-size
:height constants/switcher-button-size
:border-radius constants/switcher-button-radius
:position :absolute
:z-index 2
:align-items :center
:align-self :center
:justify-content :center}
style))
(defn switcher-button-touchable [view-id]
(merge-switcher-button-common-styles
{:bottom (constants/switcher-bottom-position view-id)}))
(defn switcher-close-button []
(merge-switcher-button-common-styles
{:backgroundColor (get-color :switcher-close-button-bg-color)}))
(defn switcher-screen []
(cond-> (merge-switcher-button-common-styles
{:background-color colors/neutral-80-opa-80
:z-index 1
:overflow :hidden})
platform/android? (dissoc :background-color)
true (dissoc :justify-content)))
(defn switcher-blur-background []
;; Home Stack
(defn home-stack []
(let [{:keys [width height]} (constants/dimensions)]
{:style {:width width
:height (+ height constants/switcher-container-height-padding)}
:blur-amount 17
:overlay-color colors/neutral-80-opa-80}))
(defn switcher-screen-container []
(let [{:keys [width height]} (constants/dimensions)]
{:width width
:height (+ height constants/switcher-container-height-padding)
:align-items :center
:position :absolute}))
(defn switcher-switch-screen []
{:margin-top 40
:align-items :center})
{:border-bottom-left-radius 20
:border-bottom-right-radius 20
:background-color (colors/theme-colors colors/neutral-5 colors/neutral-95)
:overflow :hidden
:position :absolute
:width width
:height (- height (constants/bottom-tabs-container-height))}))
-80
View File
@@ -1,80 +0,0 @@
(ns status-im.switcher.switcher
(:require [reagent.core :as reagent]
[quo2.reanimated :as reanimated]
[quo2.foundations.colors :as colors]
[status-im.switcher.styles :as styles]
[status-im.ui.components.react :as react]
[status-im.switcher.constants :as constants]
[status-im.switcher.animation :as animation]
[status-im.ui.components.icons.icons :as icons]
[status-im.react-native.resources :as resources]
[status-im.switcher.switcher-container :as switcher-container]
[quo.react-native :as rn]))
(defn switcher-button [view-id toggle-switcher-screen-fn shared-values]
[:f>
(fn []
(let [touchable-original-style (styles/switcher-button-touchable view-id)
close-button-original-style (styles/switcher-close-button)
switcher-button-original-style (styles/switcher-button)
touchable-animated-style (reanimated/apply-animations-to-style
{:transform [{:scale (:button-touchable-scale shared-values)}]}
touchable-original-style)
close-button-animated-style (reanimated/apply-animations-to-style
{:opacity (:close-button-opacity shared-values)}
close-button-original-style)
switcher-button-animated-style (reanimated/apply-animations-to-style
{:opacity (:switcher-button-opacity shared-values)}
switcher-button-original-style)]
[reanimated/touchable-opacity {:active-opacity 1
:on-press-in #(animation/switcher-touchable-on-press-in
(:button-touchable-scale shared-values))
:on-press-out toggle-switcher-screen-fn
:style touchable-animated-style}
[reanimated/view {:style close-button-animated-style}
[icons/icon :main-icons/close {:color colors/neutral-100}]]
[reanimated/image {:source (resources/get-image :switcher)
:style switcher-button-animated-style}]]))])
(defn switcher-screen [toggle-switcher-screen-fn shared-values]
[:f>
(fn []
(let [switcher-screen-original-style (styles/switcher-screen)
switcher-container-original-style (styles/switcher-screen-container)
switcher-screen-animated-style (reanimated/apply-animations-to-style
{:width (:switcher-screen-size shared-values)
:height (:switcher-screen-size shared-values)
:bottom (:switcher-screen-bottom shared-values)
:border-radius (:switcher-screen-radius shared-values)}
switcher-screen-original-style)
switcher-container-animated-style (reanimated/apply-animations-to-style
{:bottom (:switcher-container-bottom shared-values)
:transform [{:scale (:switcher-container-scale shared-values)}]}
switcher-container-original-style)]
[reanimated/view {:style switcher-screen-animated-style}
[react/blur-view (styles/switcher-blur-background)]
[reanimated/view {:style switcher-container-animated-style}
[switcher-container/tabs toggle-switcher-screen-fn]]]))])
(defn switcher [view-id]
[:f>
(fn []
(let [switcher-opened? (reagent/atom false)
switcher-button-opacity (reanimated/use-shared-value 1)
switcher-screen-size (reanimated/use-shared-value constants/switcher-pressed-size)
switcher-screen-radius (animation/switcher-screen-radius switcher-screen-size)
switcher-screen-bottom (animation/switcher-screen-bottom-position switcher-screen-radius view-id)
shared-values {:switcher-button-opacity switcher-button-opacity
:switcher-screen-size switcher-screen-size
:switcher-screen-radius switcher-screen-radius
:switcher-screen-bottom switcher-screen-bottom
:button-touchable-scale (reanimated/use-shared-value 1)
:switcher-container-scale (reanimated/use-shared-value 0.9)
:close-button-opacity (animation/switcher-close-button-opacity switcher-button-opacity)
:switcher-container-bottom (animation/switcher-container-bottom-position switcher-screen-bottom)}
toggle-switcher-screen-fn #(animation/switcher-touchable-on-press-out switcher-opened? view-id shared-values)
{:keys [keyboard-shown]} (rn/use-keyboard)]
(when-not keyboard-shown
[:<>
[switcher-screen toggle-switcher-screen-fn shared-values]
[switcher-button view-id toggle-switcher-screen-fn shared-values]])))])
@@ -1,31 +0,0 @@
(ns status-im.switcher.switcher-container
(:require [quo.react-native :as rn]
[status-im.switcher.cards.messaging-card :as messaging-card]
[status-im.switcher.styles :as styles]
[status-im.utils.handlers :refer [<sub]]))
;; TODO - use something like this to avoid multiple renders etc.
;; (defn switch-screen [toggle-switcher-screen]
;; (let [cards (<sub [:navigation2/switcher-cards])
;; new-cards (reduce (fn [acc card]
;; (conj acc (assoc card :toggle-switcher-screen toggle-switcher-screen)))
;; () cards)]
;; (fn []
;; [rn/view {:style (styles/switcher-switch-screen)}
;; [rn/flat-list {:width 352
;; :data new-cards
;; :render-fn messaging-card/card
;; :num-columns 2
;; :key-fn str}]])))
(defn switch-screen [toggle-switcher-screen]
(let [cards (<sub [:navigation2/switcher-cards toggle-switcher-screen])]
[rn/view {:style (styles/switcher-switch-screen)}
[rn/flat-list {:width 352
:data cards
:render-fn messaging-card/card
:num-columns 2
:key-fn str}]]))
(defn tabs [toggle-switcher-screen]
[switch-screen toggle-switcher-screen])
-6
View File
@@ -1,6 +0,0 @@
(ns status-im.switcher.utils
(:require [re-frame.core :as re-frame]))
(def switcher-container-view-id (atom nil))
(re-frame/reg-fx :switcher-container-view-id #(reset! switcher-container-view-id %))
+17 -2
View File
@@ -6,9 +6,10 @@
prefer to use it for more general purpose concepts, such as the re-frame event
layer."
(:require [re-frame.core :as rf]
[re-frame.registrar :as rf-registrar]
[re-frame.db :as rf-db]
[re-frame.events :as rf-events]
[re-frame.db :as rf-db]))
[re-frame.registrar :as rf-registrar]
[taoensso.timbre :as log]))
(defn db
"A simple wrapper to get the latest value from the app db."
@@ -85,3 +86,17 @@
(original-on-error (on-error fx-map))
(and original-on-success on-success)
(original-on-success (on-success fx-map)))))))
(defn using-log-test-appender
"Rebinds `taoensso.timbre/*config*` to use a custom test appender that persists
all `taoensso.timbre/log` call arguments. `f` is called with the atom
reference so that tests can de-reference it and verify log messages and their
respective levels."
[f]
(let [logs (atom [])]
(binding [log/*config* (assoc-in log/*config*
[:appenders :test]
{:enabled? true
:fn (fn [{:keys [vargs level]}]
(swap! logs conj {:args vargs :level level}))})]
(f logs))))
+1
View File
@@ -41,6 +41,7 @@
(re-frame/dispatch [:mark-all-activity-center-notifications-as-read])
(if config/new-activity-center-enabled?
(re-frame/dispatch [:show-popover {:view :activity-center
:style {:margin 0}
:disable-touchable-overlay? true
:blur-view? true
:blur-view-props {:blur-amount 20
@@ -1,5 +1,6 @@
(ns status-im.ui.screens.activity-center.views
(:require [quo.components.animated.pressable :as animation]
[quo.react :as react]
[quo.react-native :as rn]
[quo2.components.buttons.button :as button]
[quo2.components.markdown.text :as text]
@@ -7,7 +8,6 @@
[quo2.components.tabs.tabs :as tabs]
[quo2.components.tags.context-tags :as context-tags]
[quo2.foundations.colors :as colors]
[reagent.core :as reagent]
[status-im.constants :as constants]
[status-im.i18n.i18n :as i18n]
[status-im.multiaccounts.core :as multiaccounts]
@@ -15,98 +15,114 @@
[status-im.utils.handlers :refer [<sub >evt]]
[quo.components.safe-area :as safe-area]))
(defn activity-title
[{:keys [type]}]
(case type
constants/activity-center-notification-type-contact-request
(i18n/label :t/contact-request)
;;;; Misc
constants/activity-center-notification-type-one-to-one-chat
"Dummy 1:1 chat title"
(defn sender-name
[contact]
(or (get-in contact [:names :nickname])
(get-in contact [:names :three-words-name])))
"Dummy default title"))
(defmulti notification-component :type)
(defn activity-icon
[{:keys [type]}]
(case type
constants/activity-center-notification-type-contact-request
:main-icons2/add-user
:main-icons2/placeholder))
;;;; Contact request notifications
(defn activity-context
[{:keys [message last-message type]}]
(case type
constants/activity-center-notification-type-contact-request
(let [message (or message last-message)
contact (<sub [:contacts/contact-by-identity (:from message)])
sender-name (or (get-in contact [:names :nickname])
(get-in contact [:names :three-words-name]))]
[[context-tags/user-avatar-tag
{:color :purple
:override-theme :dark
:size :small
:style {:background-color colors/white-opa-10}
:text-style {:color colors/white}}
sender-name
(multiaccounts/displayed-photo contact)]
[rn/text {:style {:color colors/white}}
(i18n/label :t/contact-request-sent)]])
nil))
(defmethod notification-component constants/activity-center-notification-type-contact-request
[{:keys [id] :as notification}]
(let [message (or (:message notification) (:last-message notification))
contact (<sub [:contacts/contact-by-identity (:author notification)])
pressable (case (:contact-request-state message)
constants/contact-request-message-state-accepted
;; NOTE [2022-09-21]: We need to dispatch to
;; `:contact.ui/send-message-pressed` instead of
;; `:chat.ui/navigate-to-chat`, otherwise the chat screen looks completely
;; broken if it has never been opened before for the accepted contact.
[animation/pressable {:on-press (fn []
(>evt [:hide-popover])
(>evt [:contact.ui/send-message-pressed {:public-key (:author notification)}]))}]
[:<>])]
(conj pressable
[activity-logs/activity-log
(merge {:title (i18n/label :t/contact-request)
:icon :main-icons2/add-user
:timestamp (datetime/timestamp->relative (:timestamp notification))
:unread? (not (:read notification))
:context [[context-tags/user-avatar-tag
{:color :purple
:override-theme :dark
:size :small
:style {:background-color colors/white-opa-10}
:text-style {:color colors/white}}
(sender-name contact)
(multiaccounts/displayed-photo contact)]
[rn/text {:style {:color colors/white}}
(i18n/label :t/contact-request-sent)]]
:message {:body (get-in message [:content :text])}
:status (case (:contact-request-state message)
constants/contact-request-message-state-accepted
{:type :positive :label (i18n/label :t/accepted)}
constants/contact-request-message-state-declined
{:type :negative :label (i18n/label :t/declined)}
nil)}
(case (:contact-request-state message)
constants/contact-request-state-mutual
{:button-1 {:label (i18n/label :t/decline)
:type :danger
:on-press #(>evt [:contact-requests.ui/decline-request id])}
:button-2 {:label (i18n/label :t/message-reply)
:type :success
:override-background-color colors/success-60
:on-press #(>evt [:contact-requests.ui/accept-request id])}}
nil))])))
(defn activity-message
[{:keys [message last-message]}]
{:body (get-in (or message last-message) [:content :text])})
;;;; Contact verification notifications
(defn activity-status
[notification]
(case (get-in notification [:message :contact-request-state])
constants/contact-request-message-state-accepted
{:type :positive :label (i18n/label :t/accepted)}
constants/contact-request-message-state-declined
{:type :negative :label (i18n/label :t/declined)}
nil))
(defmethod notification-component constants/activity-center-notification-type-contact-verification
[{:keys [id contact-verification-status] :as notification}]
(let [message (or (:message notification) (:last-notification notification))
contact (<sub [:contacts/contact-by-identity (:author notification)])]
[activity-logs/activity-log
(merge {:title (i18n/label :t/identity-verification-request)
:icon :main-icons2/friend
:timestamp (datetime/timestamp->relative (:timestamp notification))
:unread? (not (:read notification))
:context [[context-tags/user-avatar-tag
{:color :purple
:override-theme :dark
:size :small
:style {:background-color colors/white-opa-10}
:text-style {:color colors/white}}
(sender-name contact)
(multiaccounts/displayed-photo contact)]
[rn/text {:style {:color colors/white}}
(str (i18n/label :t/identity-verification-request-sent)
":")]]
:message (case contact-verification-status
(constants/contact-verification-state-pending
constants/contact-verification-state-declined)
{:body (get-in message [:content :text])}
nil)
:status (case contact-verification-status
constants/contact-verification-state-declined
{:type :negative :label (i18n/label :t/declined)}
nil)}
(case contact-verification-status
constants/contact-verification-state-pending
{:button-1 {:label (i18n/label :t/decline)
:type :danger
:on-press #(>evt [:activity-center.contact-verification/decline id])}
:button-2 {:label (i18n/label :t/accept)
:type :primary
;; TODO: The acceptance flow will be implemented in follow-up PRs.
:on-press identity}}
nil))]))
(defn activity-buttons
[{:keys [id type]}]
(case type
constants/activity-center-notification-type-contact-request
{:button-1 {:label (i18n/label :t/decline)
:type :danger
:on-press #(>evt [:contact-requests.ui/decline-request id])}
:button-2 {:label (i18n/label :t/accept)
:type :success
:override-background-color colors/success-60
:on-press #(>evt [:contact-requests.ui/accept-request id])}}
nil))
(defn activity-pressable
[notification activity]
(case (get-in notification [:message :contact-request-state])
constants/contact-request-message-state-accepted
;; NOTE [2022-09-21]: We need to dispatch to
;; `:contact.ui/send-message-pressed` instead of
;; `:chat.ui/navigate-to-chat`, otherwise the chat screen looks completely
;; broken if it has never been opened before for the accepted contact.
[animation/pressable {:on-press (fn []
(>evt [:hide-popover])
(>evt [:contact.ui/send-message-pressed {:public-key (:author notification)}]))}
activity]
activity))
;;;; Type-independent components
(defn render-notification
[notification index]
[rn/view {:margin-top (if (= 0 index) 0 4)
:padding-horizontal 20}
[activity-pressable notification
[activity-logs/activity-log
(merge {:context (activity-context notification)
:icon (activity-icon notification)
:message (activity-message notification)
:status (activity-status notification)
:timestamp (datetime/timestamp->relative (:timestamp notification))
:title (activity-title notification)
:unread? (not (:read notification))}
(activity-buttons notification))]]])
[notification-component notification]])
(defn filter-selector-read-toggle
[]
@@ -164,7 +180,7 @@
:label (i18n/label :t/replies)}
{:id constants/activity-center-notification-type-contact-request
:label (i18n/label :t/contact-requests)}
{:id constants/activity-center-notification-type-identity-verification
{:id constants/activity-center-notification-type-contact-verification
:label (i18n/label :t/identity-verification)}
{:id constants/activity-center-notification-type-tx
:label (i18n/label :t/transactions)}
@@ -181,7 +197,7 @@
:type :blur-bg
:size 32
:override-theme :dark
:style {:margin-vertical 12
:style {:margin-bottom 12
:margin-left screen-padding}
:on-press #(>evt [:hide-popover])}
:main-icons2/close]
@@ -203,19 +219,20 @@
(defn activity-center
[]
(reagent/create-class
{:component-did-mount #(>evt [:activity-center.notifications/fetch-first-page])
:reagent-render
(fn []
(let [notifications (<sub [:activity-center/filtered-notifications])
window-width (<sub [:dimensions/window-width])]
[safe-area/view {:style {:flex 1}}
[rn/view {:style {:width window-width
:flex 1}}
[header]
[rn/flat-list {:content-container-style {:flex-grow 1}
:data notifications
:empty-component [empty-tab]
:key-fn :id
:on-end-reached #(>evt [:activity-center.notifications/fetch-next-page])
:render-fn render-notification}]]]))}))
[:f>
(fn []
(let [notifications (<sub [:activity-center/filtered-notifications])
window-width (<sub [:dimensions/window-width])
{:keys [top bottom]} (safe-area/use-safe-area)]
(react/effect! #(>evt [:activity-center.notifications/fetch-first-page]))
[rn/view {:style {:flex 1
:width window-width
:padding-top (if (pos? top) (+ top 12) 12)
:padding-bottom bottom}}
[header]
[rn/flat-list {:content-container-style {:flex-grow 1}
:data notifications
:empty-component [empty-tab]
:key-fn :id
:on-end-reached #(>evt [:activity-center.notifications/fetch-next-page])
:render-fn render-notification}]]))])
@@ -46,4 +46,4 @@
(let [edit @(re-frame/subscribe [:chats/edit-message])]
(focus-input-on-edit edit had-edit text-input-ref)
(when edit
[edit-message-wrapper])))))
[edit-message-wrapper])))))
@@ -130,4 +130,4 @@
:background-color (colors/get-color :ui-background)
:border-top-width 1
:border-top-color (colors/get-color :ui-01)
:z-index 3})
:z-index 3})
@@ -428,9 +428,7 @@
(if (and (not pinned) (> (count pinned-messages) 2))
(do
(js/setTimeout (fn [] (re-frame/dispatch [:dismiss-keyboard])) 500)
(re-frame/dispatch [:show-popover {:view :pin-limit
:message message
:prevent-closing? true}]))
(re-frame/dispatch [::models.pin-message/show-pin-limit-modal chat-id]))
(re-frame/dispatch [::models.pin-message/send-pin-message (assoc message :pinned (not pinned))]))))
(defn on-long-press-fn [on-long-press {:keys [pinned message-pin-enabled outgoing edit-enabled show-input?] :as message} content]
@@ -540,12 +538,11 @@
(fn [{:keys [content current-public-key outgoing edit-enabled public? pinned in-popover? message-pin-enabled content-type edited-at] :as message}
{:keys [on-long-press modal]
:as reaction-picker}]
;; Makes sure to render a text-messsage and not an emoji-message if it has been edited with text
;; Makes sure to render a text-message and not an emoji-message if it has been edited with text
(if (= content-type constants/content-type-text)
[message-content-wrapper message
[collapsible-text-message message on-long-press modal] reaction-picker]
(let [response-to (:response-to content)]
(println "outgoing" outgoing edit-enabled)
[message-content-wrapper message
[react/touchable-highlight (when-not modal
{:disabled in-popover?
@@ -10,13 +10,17 @@
(defn message-reactions [{:keys [content-type]} reactions timeline on-emoji-press on-open]
(when (seq reactions)
[rn/view {:style (styles/reactions-row timeline (if (= content-type constants/content-type-text) text-reaction-margin-top default-reaction-margin-top))}
[rn/view {:style (styles/reactions-row
timeline
(if (= content-type constants/content-type-text)
text-reaction-margin-top default-reaction-margin-top))}
(for [{:keys [own emoji-id quantity] :as emoji-reaction} reactions]
^{:key (str emoji-reaction)}
[rn/view {:style {:margin-right 6 :margin-top 5}}
[quo2.reaction/reaction {:emoji (get constants/reactions emoji-id)
:neutral? own
:clicks quantity
:on-press #(on-emoji-press emoji-id)}]])
[quo2.reaction/reaction {:emoji (get constants/reactions emoji-id)
:neutral? own
:clicks quantity
:on-press #(on-emoji-press emoji-id)
:accessibility-label (str "emoji-reaction-" emoji-id)}]])
;; on-press won't work until we integrate Message Context Drawer
[quo2.reaction/open-reactions-menu (when @on-open {:on-press @on-open})]]))
@@ -153,18 +153,9 @@
:flex-direction :row-reverse})
(defn message-view
[{:keys [content-type mentioned]}]
[{:keys [content-type]}]
(merge
{:border-radius 10}
(cond
(= content-type constants/content-type-system-text) nil
mentioned {:background-color colors/mentioned-background
:border-color colors/mentioned-border
:border-width 1}
(= content-type constants/content-type-audio) {:background-color colors/blue
:padding-horizontal 12
:padding-top 6})
(when (= content-type constants/content-type-emoji)
{:flex-direction :row})))
+16 -13
View File
@@ -32,17 +32,17 @@
first-name]))))
(defn format-author
([contact] (format-author contact nil))
([{:keys [names] :as contact} {:keys [modal profile? you?]}]
([contact] (format-author contact nil nil))
([{:keys [names] :as contact} {:keys [modal profile? you?]} max-length]
(let [{:keys [nickname ens-name]} names
[first-name second-name] (multiaccounts/contact-two-names contact false)]
(if (or nickname ens-name)
[react/nested-text {:number-of-lines 2
:style {:color (if modal colors/white-persist colors/black)
:font-size (if profile? 15 13)
:line-height (if profile? 22 18)
:style {:color (if modal colors/white-persist colors/black)
:font-size (if profile? 15 13)
:line-height (if profile? 22 18)
:letter-spacing -0.2
:font-weight "600"}}
:font-weight "600"}}
(subs first-name 0 81)
(when you?
[{:style {:color colors/black-light :font-weight "500" :font-size 13}}
@@ -50,12 +50,15 @@
(when nickname
[{:style {:color colors/black-light :font-weight "500"}}
(str " " (subs second-name 0 81))])]
[react/text {:style {:color (if modal colors/white-persist colors/black)
:font-size (if profile? 15 13)
:line-height (if profile? 22 18)
:font-weight "600"
:letter-spacing -0.2}}
first-name]))))
[react/text {:style {:color (if modal colors/white-persist colors/black)
:font-size (if profile? 15 13)
:line-height (if profile? 22 18)
:font-weight "600"
:letter-spacing -0.2}
:number-of-lines 1}
(if (and max-length (> (count first-name) max-length))
(str (subs first-name 0 max-length) "...")
first-name)]))))
(defn format-reply-author [from username current-public-key style outgoing]
(let [contact-name (str reply-symbol username)]
@@ -63,7 +66,7 @@
[react/text {:style (style true)}
(str reply-symbol (i18n/label :t/You))])
(if (or (= (aget contact-name 0) "@")
;; in case of replies
;; in case of replies
(= (aget contact-name 1) "@"))
(let [trimmed-name (subs contact-name 0 81)]
[react/text {:number-of-lines 2
+1
View File
@@ -258,6 +258,7 @@
(re-frame/dispatch [:mark-all-activity-center-notifications-as-read])
(if config/new-activity-center-enabled?
(re-frame/dispatch [:show-popover {:view :activity-center
:style {:margin 0}
:disable-touchable-overlay? true
:blur-view? true
:blur-view-props {:blur-amount 20
@@ -204,7 +204,8 @@
(first @(re-frame/subscribe [:contacts/contact-two-names-by-identity chat-id])))])
(defn home-list-item [home-item opts]
(let [{:keys [chat-id chat-name color group-chat muted emoji highlight edit? public? unviewed-messages-count contacts]} home-item
(let [{:keys [chat-id chat-name color group-chat muted emoji highlight edit? public? unviewed-messages-count contacts last-message]} home-item
last-message-content (get-in last-message [:content :text])
background-color (when highlight (colors/get-color :interactive-02))]
[react/touchable-opacity (merge {:style {:height 64 :background-color background-color}} opts)
[:<>
@@ -230,7 +231,7 @@
:ellipsize-mode :middle
:weight :medium
:style {:color (quo2.colors/theme-colors quo2.colors/neutral-50 quo2.colors/neutral-40)}}
(i18n/label :t/public)]
(or last-message-content (i18n/label :t/public))]
(if group-chat
[react/view {:flex-direction :row
:flex 1
@@ -251,7 +252,7 @@
:style {:color (quo2.colors/theme-colors quo2.colors/neutral-50 quo2.colors/neutral-40)}
:number-of-lines 1
:ellipsize-mode :middle}
(utils.utils/get-shortened-address chat-id)]))]]]))
(or last-message-content (utils.utils/get-shortened-address chat-id))]))]]]))
(defn home-list-item-old [home-item opts]
(let [{:keys [chat-id chat-name color group-chat public? timestamp last-message muted emoji highlight edit?]} home-item
+117
View File
@@ -0,0 +1,117 @@
(ns status-im.ui2.screens.chat.actions
(:require
[status-im.chat.models :as chat.models]
[status-im.chat.models.pin-message :as models.pin-message]
[status-im.i18n.i18n :as i18n]
[status-im.constants :as constants]
[status-im.utils.handlers :refer [<sub >evt]]
[quo2.components.drawers.action-drawers :as drawer]))
(defn- entry [icon label on-press danger?]
{:pre [(keyword? icon)
(string? label)
(fn? on-press)
(boolean? danger?)]}
{:icon icon
:label label
:on-press on-press
:danger? danger?})
(defn hide-sheet-and-dispatch [event]
(>evt [:bottom-sheet/hide])
(>evt event))
(defn show-profile-action [chat-id]
(hide-sheet-and-dispatch [:chat.ui/show-profile chat-id])
(>evt [::models.pin-message/load-pin-messages chat-id]))
(defn mark-all-read-action [chat-id]
(hide-sheet-and-dispatch [:chat/mark-all-as-read chat-id]))
(defn edit-nickname-action [chat-id]
(hide-sheet-and-dispatch [:chat.ui/edit-nickname chat-id]))
(defn mute-chat-action [chat-id]
(hide-sheet-and-dispatch [::chat.models/mute-chat-toggled chat-id true]))
(defn unmute-chat-action [chat-id]
(hide-sheet-and-dispatch [::chat.models/mute-chat-toggled chat-id false]))
(defn clear-history-action [chat-id]
(hide-sheet-and-dispatch [:chat.ui/clear-history-pressed chat-id]))
(defn delete-chat-action [chat-id]
(hide-sheet-and-dispatch [:chat.ui/remove-chat-pressed chat-id]))
(defn mute-chat-entry [muted? chat-id]
(entry :main-icons2/muted
(i18n/label
(if muted?
:unmute-chat
:mute-chat))
(if muted?
#(unmute-chat-action chat-id)
#(mute-chat-action chat-id))
false))
(defn mark-as-read-entry [chat-id]
(entry :main-icons2/check
(i18n/label :mark-as-read)
#(mark-all-read-action chat-id)
false))
(defn clear-history-entry [chat-id]
(entry :main-icons2/delete
(i18n/label :clear-history)
#(clear-history-action chat-id)
true))
(defn delete-chat-entry [chat-id]
(entry :main-icons2/delete
(i18n/label :delete-chat)
#(delete-chat-action chat-id)
true))
(defn view-profile-entry [chat-id]
(entry :main-icons2/friend
(i18n/label :view-profile)
#(show-profile-action chat-id)
false))
(defn edit-nickname-entry [chat-id]
(entry :main-icons2/edit
(i18n/label :edit-nickname)
#(edit-nickname-action chat-id)
false))
(defn destructive-actions [chat-id]
[(clear-history-entry chat-id)
(delete-chat-entry chat-id)])
(defn notification-actions [muted? chat-id]
[(mute-chat-entry muted? chat-id)
(mark-as-read-entry chat-id)])
(defn one-to-one-actions [muted? chat-id]
[drawer/action-drawer [[(view-profile-entry chat-id)
(edit-nickname-entry chat-id)]
(notification-actions muted? chat-id)
(destructive-actions chat-id)]])
(defn public-chat-actions [muted? chat-id]
[drawer/action-drawer [(notification-actions muted? chat-id)
(destructive-actions chat-id)]])
(defn private-group-chat-actions [muted? chat-id]
[drawer/action-drawer [(notification-actions muted? chat-id)
(destructive-actions chat-id)]])
(defn actions [chat-type chat-id]
(let [muted? (<sub [:chats/muted chat-id])]
(case chat-type
constants/one-to-one-chat-type
[one-to-one-actions muted? chat-id]
constants/public-chat-type
[public-chat-actions muted? chat-id]
constants/private-group-chat-type
[private-group-chat-actions muted? chat-id])))
@@ -3,7 +3,7 @@
[re-frame.core :as re-frame]
[status-im.constants :as constants]
[quo2.components.buttons.button :as quo2.button]
[quo2.foundations.colors :as quo2.colors]
[quo2.foundations.colors :as colors]
[quo2.components.list-items.menu-item :as quo2.menu-item]
[quo2.components.separator :as quo2.separator]))
@@ -12,7 +12,7 @@
(let [main-actions (filter #(= (:type %) :main) actions)
danger-actions (filter #(= (:type %) :danger) actions)
admin-actions (filter #(= (:type %) :admin) actions)]
[rn/view {:flex 1}
[rn/view
[rn/view {:style {:width "100%"
:flex-direction :row
:justify-content :space-between
@@ -30,11 +30,11 @@
:type :grey
:icon true
:icon-no-color true
:accessibility-label :reply-cancel-button
:accessibility-label (str "emoji-picker-" id)
:on-press #(do
(send-emoji id)
(re-frame/dispatch [:bottom-sheet/hide]))}
(when active {:style {:background-color quo2.colors/neutral-10}}))
(when active {:style {:background-color colors/neutral-10}}))
icon]))]
[rn/view {:style {:padding-horizontal 8}}
(for [action main-actions]
@@ -73,4 +73,4 @@
:icon (:icon action)
:on-press #(do
(when on-press (on-press))
(re-frame/dispatch [:bottom-sheet/hide]))}]))]])))
(re-frame/dispatch [:bottom-sheet/hide]))}]))]])))
@@ -1,5 +1,5 @@
(ns status-im.ui2.screens.chat.components.reply
(:require [quo2.foundations.colors :as quo2.colors]
(:require [quo2.foundations.colors :as colors]
[status-im.ui.components.icons.icons :as icons]
[quo.react-native :as rn]
[status-im.constants :as constants]
@@ -48,11 +48,13 @@
(let [contact-name (<sub [:contacts/contact-name-by-identity from])
current-public-key (<sub [:multiaccount/public-key])
content-type (or content-type contentType)]
[rn/view {:style {:flex-direction :row :height (when-not pin? 24)}}
[rn/view {:style {:flex-direction :row
:height (when-not pin? 24)
:accessibility-label :reply-message}}
[rn/view {:style (styles/reply-content pin?)}
(when-not pin?
;;TODO quo2 icon should be used
[icons/icon :main-icons/connector {:color (quo2.colors/theme-colors quo2.colors/neutral-40 quo2.colors/neutral-60)
[icons/icon :main-icons/connector {:color (colors/theme-colors colors/neutral-40 colors/neutral-60)
:container-style {:position :absolute :left 10 :bottom -4 :width 16 :height 16}}])
[rn/view {:style (styles/quoted-message pin?)}
[photos/member-photo from identicon 16]
@@ -71,7 +73,7 @@
(when (or (= constants/content-type-image content-type)
(= constants/content-type-sticker content-type)
(= constants/content-type-audio content-type))
{:color (quo2.colors/theme-colors quo2.colors/neutral-50 quo2.colors/neutral-40)}))}
{:color (colors/theme-colors colors/neutral-50 colors/neutral-40)}))}
(case (or content-type contentType)
constants/content-type-image "Image"
constants/content-type-sticker "Sticker"
@@ -86,4 +88,4 @@
;;TODO quo2 icon should be used
[icons/icon :main-icons/close {:width 16
:height 16
:color (quo2.colors/theme-colors quo2.colors/neutral-100 quo2.colors/neutral-40)}]])]))
:color (colors/theme-colors colors/neutral-100 colors/neutral-40)}]])]))
@@ -2,7 +2,7 @@
(:require [quo.react-native :as rn]
[status-im.i18n.i18n :as i18n]
[status-im.utils.handlers :refer [<sub >evt]]
[quo.design-system.colors :as colors]
[quo.design-system.colors :as quo.colors]
[status-im.utils.utils :as utils.utils]
[status-im.utils.platform :as platform]
[clojure.string :as string]
@@ -11,7 +11,7 @@
[status-im.ui2.screens.chat.composer.style :as style]
[re-frame.core :as re-frame]
[status-im.chat.models.mentions :as mentions]
[quo2.foundations.colors :as quo2.colors]
[quo2.foundations.colors :as colors]
[quo.react]))
(defonce input-texts (atom {}))
@@ -156,7 +156,7 @@
:auto-focus false
:on-focus #(set-active-panel nil)
:max-length chat.constants/max-text-size
:placeholder-text-color (:text-02 @colors/theme)
:placeholder-text-color (:text-02 @quo.colors/theme)
:placeholder (if cooldown-enabled?
(i18n/label :cooldown/text-input-disabled)
(i18n/label :t/type-a-message))
@@ -174,6 +174,6 @@
[idx item])
(<sub [:chat/input-with-mentions]))]
^{:key (str idx "_" type "_" text)}
[rn/text (when (= type :mention) {:style {:color quo2.colors/primary-50}})
[rn/text (when (= type :mention) {:style {:color colors/primary-50}})
text])
(get @input-texts chat-id))]))
@@ -1,8 +1,8 @@
(ns status-im.ui2.screens.chat.composer.style
(:require [quo2.foundations.typography :as quo2.typography]
[quo.design-system.colors :as colors]
[quo.design-system.colors :as quo.colors]
[status-im.utils.platform :as platform]
[quo2.foundations.colors :as quo2.colors]))
[quo2.foundations.colors :as colors]))
(defn text-input []
(merge quo2.typography/font-regular
@@ -11,7 +11,7 @@
:min-height 34
:margin 0
:flex-shrink 1
:color (:text-01 @colors/theme)
:color (:text-01 @quo.colors/theme)
:margin-horizontal 20}
(if platform/android?
{:padding-vertical 8
@@ -28,7 +28,7 @@
:bottom (- window-height)
:height window-height
:flex 1
:background-color (quo2.colors/theme-colors quo2.colors/white quo2.colors/neutral-90)
:background-color (colors/theme-colors colors/white colors/neutral-90)
:z-index 2}
(if platform/ios?
{:shadow-radius 16
@@ -40,7 +40,7 @@
(defn bottom-sheet-handle []
{:width 32
:height 4
:background-color (quo2.colors/theme-colors quo2.colors/neutral-100 quo2.colors/white)
:background-color (colors/theme-colors colors/neutral-100 colors/white)
:opacity 0.05
:border-radius 100
:align-self :center
@@ -52,7 +52,7 @@
:elevation 2
:z-index 3
:position :absolute
:background-color (quo2.colors/theme-colors quo2.colors/white quo2.colors/neutral-90)
:background-color (colors/theme-colors colors/white colors/neutral-90)
;these 3 props play together, we need this magic to hide message text in the safe area
:padding-top 10
:padding-bottom (+ 12 (:bottom insets))
@@ -65,7 +65,7 @@
:right 0
:bottom 0
:height window-height
:background-color quo2.colors/neutral-95-opa-70
:background-color colors/neutral-95-opa-70
:z-index 1})
(defn reply-content [pin?]
@@ -79,4 +79,4 @@
:width "45%"}
(when-not pin? {:position :absolute
:left 34
:top 3})))
:top 3})))
@@ -135,7 +135,7 @@
{window-height :height} (rn/use-window-dimensions)
{:keys [keyboard-shown keyboard-height]} (rn/use-keyboard)
max-y (- window-height (if (> keyboard-height 0) keyboard-height 360) (:top insets)) ; 360 - default height
max-height (- max-y 56 (:bottom insets)) ; 56 - top-bar height
max-height (Math/abs (- max-y 56 (:bottom insets))) ; 56 - top-bar height
added-value (if (and (not (seq suggestions)) reply) 38 0) ; increased height of input box needed when reply
min-y (+ min-y (when reply 38))
y (get-y-value context keyboard-shown min-y max-y added-value max-height chat-id suggestions reply)
+205 -95
View File
@@ -8,8 +8,9 @@
[status-im.ui.components.list.views :as list]
[status-im.ui.components.react :as react]
[status-im.ui.screens.home.styles :as styles]
[status-im.ui2.screens.chat.actions :as actions]
[status-im.ui.screens.home.views.inner-item :refer [home-list-item]]
[quo.design-system.colors :as colors]
[quo.design-system.colors :as quo.colors]
[quo.core :as quo]
[quo.platform :as platform]
[status-im.add-new.core :as new-chat]
@@ -19,35 +20,39 @@
[status-im.utils.utils :as utils]
[status-im.ui.components.topbar :as topbar]
[status-im.ui.components.plus-button :as components.plus-button]
[status-im.ui.screens.chat.sheets :as sheets]
[status-im.ui.components.tabbar.core :as tabbar]
[status-im.ui.components.invite.views :as invite]
[status-im.utils.handlers :refer [<sub]]
[status-im.utils.handlers :refer [<sub >evt]]
[status-im.utils.config :as config]
[quo2.components.markdown.text :as quo2.text]
[status-im.qr-scanner.core :as qr-scanner]
[status-im.ui.components.chat-icon.styles :as chat-icon.styles]
[quo.react-native :as rn]
[quo2.foundations.colors :as quo2.colors]
[quo.react]
[quo2.foundations.colors :as colors]
[quo2.foundations.typography :as typography]
[quo2.components.buttons.button :as quo2.button]
[quo2.components.tabs.tabs :as quo2.tabs]
[quo2.components.community.discover-card :as discover-card]
[status-im.multiaccounts.core :as multiaccounts]
[status-im.ui.components.chat-icon.screen :as chat-icon])
[status-im.ui.components.chat-icon.screen :as chat-icon]
[quo2.components.icon :as quo2.icons]
[quo.components.safe-area :as safe-area]
[quo2.components.list-items.received-contact-request :as received-contact-request])
(:require-macros [status-im.utils.views :as views]))
(defn home-tooltip-view []
[rn/view (styles/chat-tooltip)
[rn/view {:style {:width 66 :position :absolute :top -6 :background-color colors/white
[rn/view {:style {:width 66 :position :absolute :top -6 :background-color quo.colors/white
:align-items :center}}
[rn/image {:source (resources/get-image :empty-chats-header)
:style {:width 50 :height 50}}]]
[rn/touchable-highlight
{:style {:position :absolute :right 0 :top 0
:width 44 :height 44 :align-items :center :justify-content :center}
{:style {:position :absolute :right 0 :top 0
:width 44 :height 44 :align-items :center :justify-content :center}
:on-press #(re-frame/dispatch [:multiaccounts.ui/hide-home-tooltip])
:accessibility-label :hide-home-button}
[icons/icon :main-icons/close-circle {:color colors/gray}]]
[icons/icon :main-icons/close-circle {:color quo.colors/gray}]]
[react/i18n-text {:style styles/no-chats-text :key :chat-and-transact}]
[rn/view {:align-items :center
:margin-top 8
@@ -112,7 +117,7 @@
(re-frame/dispatch [:set :public-group-topic nil])
(re-frame/dispatch [:search/home-filter-changed nil]))}])])))
(defn render-fn [{:keys [chat-id] :as home-item}]
(defn render-fn [{:keys [chat-id chat-type] :as home-item}]
[home-list-item
home-item
{:on-press (fn []
@@ -124,31 +129,33 @@
(re-frame/dispatch [:accept-all-activity-center-notifications-from-chat chat-id]))
:on-long-press #(re-frame/dispatch [:bottom-sheet/show-sheet
{:content (fn []
[sheets/actions home-item])}])}])
[actions/actions
chat-type
chat-id])}])}])
(defn- render-contact [{:keys [public-key] :as row}]
(let [[first-name second-name] (multiaccounts/contact-two-names row true)
row (assoc row :chat-id public-key)]
[quo/list-item
{:title first-name
:subtitle second-name
:background-color quo2.colors/neutral-5
:on-press (fn []
(re-frame/dispatch [:dismiss-keyboard])
(if platform/android?
(re-frame/dispatch [:chat.ui/navigate-to-chat-nav2 public-key])
(re-frame/dispatch [:chat.ui/navigate-to-chat public-key]))
(re-frame/dispatch [:search/home-filter-changed nil])
(re-frame/dispatch [:accept-all-activity-center-notifications-from-chat public-key]))
:on-long-press #(re-frame/dispatch [:bottom-sheet/show-sheet
{:content (fn []
[sheets/actions row])}])
:icon [chat-icon/contact-icon-contacts-tab
(multiaccounts/displayed-photo row)]}]))
{:title first-name
:subtitle second-name
:background-color colors/neutral-5
:on-press (fn []
(re-frame/dispatch [:dismiss-keyboard])
(if platform/android?
(re-frame/dispatch [:chat.ui/navigate-to-chat-nav2 public-key])
(re-frame/dispatch [:chat.ui/navigate-to-chat public-key]))
(re-frame/dispatch [:search/home-filter-changed nil])
(re-frame/dispatch [:accept-all-activity-center-notifications-from-chat public-key]))
;:on-long-press #(re-frame/dispatch [:bottom-sheet/show-sheet TODO: new UI yet to be implemented
; {:content (fn []
; [sheets/actions row])}])
:icon [chat-icon/contact-icon-contacts-tab
(multiaccounts/displayed-photo row)]}]))
(defn chat-list-key-fn [item]
(or (:chat-id item) (:public-key item)))
(or (:chat-id item) (:public-key item) (:id item)))
(defn get-item-layout [_ index]
#js {:length 64 :offset (* 64 index) :index index})
@@ -171,17 +178,110 @@
(vals @data)))
(defn contacts-section-header [{:keys [title]}]
[rn/view {:style {:border-top-width 1 :border-top-color quo2.colors/neutral-20 :padding-vertical 8 :padding-horizontal 20 :margin-top 8}}
[rn/text {:style (merge typography/font-medium typography/paragraph-2 {:color quo2.colors/neutral-50})} title]])
[rn/view {:style {:border-top-width 1 :border-top-color colors/neutral-20 :padding-vertical 8 :padding-horizontal 20 :margin-top 8}}
[rn/text {:style (merge typography/font-medium typography/paragraph-2 {:color colors/neutral-50})} title]])
(defn find-contact-requests [notifications]
(let [received-requests (atom [])
has-unread (atom false)]
(doseq [i (range (count notifications))]
(doseq [j (range (count (:data (nth notifications i))))]
(when (= 1 (get-in (nth (:data (nth notifications i)) j) [:message :contact-request-state]))
(swap! received-requests conj (nth (:data (nth notifications i)) j)))
(when (= false (get-in (nth (:data (nth notifications i)) j) [:read]))
(reset! has-unread true))))
{:received-requests @received-requests :has-unread @has-unread}))
(def selected-requests-tab (reagent/atom :received))
(defn contact-requests-sheet []
[:f>
(fn []
(let [{window-height :height} (rn/use-window-dimensions)
safe-area (safe-area/use-safe-area)
notifications (<sub [:activity.center/notifications-grouped-by-date])
{received-requests :received-requests} (find-contact-requests notifications)
sent-requests []]
[rn/view {:style {:margin-left 20
:height (- window-height (:top safe-area))}}
[rn/touchable-opacity
{:on-press #(>evt [:bottom-sheet/hide])
:style
{:background-color (colors/theme-colors colors/neutral-10 colors/neutral-80)
:width 32
:height 32
:border-radius 10
:justify-content :center
:align-items :center
:margin-bottom 24}}
[quo2.icons/icon :main-icons2/close {:color (colors/theme-colors "#000000" "#ffffff")}]]
[rn/text {:style (merge
typography/heading-1
typography/font-semi-bold
{:color (colors/theme-colors "#000000" "#ffffff")})}
(i18n/label :t/pending-requests)]
[quo2.tabs/tabs
{:style {:margin-top 12 :margin-bottom 20}
:size 32
:on-change #(reset! selected-requests-tab %)
:default-active :received
:data [{:id :received
:label (i18n/label :t/received)}
{:id :sent
:label (i18n/label :t/sent)}]}]
[list/flat-list
{:key-fn :first
:data (if (= @selected-requests-tab :received) received-requests sent-requests)
:render-fn received-contact-request/list-item}]]))])
(defn contact-requests [count]
[rn/touchable-opacity
{:active-opacity 1
:on-press #(do
(>evt
[:bottom-sheet/show-sheet
{:content (fn [] [contact-requests-sheet])}])
(>evt [:mark-all-activity-center-notifications-as-read]))
:style {:flex-direction :row
:margin 8
:padding-horizontal 12
:padding-vertical 8
:align-items :center}}
[rn/view {:style {:justify-content :center
:align-items :center
:width 32
:height 32
:border-radius 16
:border-width 1
:border-color (colors/theme-colors colors/neutral-20 colors/neutral-80)}}
[quo2.icons/icon :main-icons2/pending-user {:color (colors/theme-colors colors/neutral-50 colors/neutral-40)}]]
[rn/view {:style {:margin-left 8}}
[rn/text {:style
(merge typography/paragraph-1 typography/font-semi-bold {:color (colors/theme-colors "#000000" "#ffffff")})} (i18n/label :t/pending-requests)]
[rn/text {:style (merge typography/paragraph-2 typography/font-regular {:color (colors/theme-colors colors/neutral-50 colors/neutral-40)})} "Alice, Pedro and 3 others"]]
[rn/view {:style {:width 16
:height 16
:position :absolute
:right 22
:border-radius 6
:background-color (colors/theme-colors colors/primary-50 colors/primary-60)}}
[rn/text {:style (merge typography/font-medium typography/label {:color "#ffffff" :text-align :center})} count]]])
(defn chats []
(let [{:keys [items search-filter]} (<sub [:home-items])
current-active-tab @selected-tab
items (prepare-items current-active-tab items)
contacts (<sub [:contacts/active])
contacts (prepare-contacts contacts)]
items (prepare-items current-active-tab items)
contacts (<sub [:contacts/active])
contacts (prepare-contacts contacts)
notifications (<sub [:activity.center/notifications-grouped-by-date])
{requests :received-requests new-info :has-unread} (find-contact-requests notifications)]
[rn/view {:style {:flex 1}}
[quo2.tabs/tabs {:style {:margin-left 20 :margin-bottom 20} :size 32
[discover-card/discover-card {:title (i18n/label :t/invite-friends-to-status)
:description (i18n/label :t/share-invite-link)}]
[quo2.tabs/tabs {:style {:margin-left 20
:margin-bottom 20
:margin-top 24}
:size 32
:on-change #(reset! selected-tab %)
:default-active selected-tab
:data [{:id :recent
@@ -189,7 +289,8 @@
{:id :groups
:label (i18n/label :t/groups)}
{:id :contacts
:label (i18n/label :t/contacts)}]}]
:label (i18n/label :t/contacts)
:new-info new-info}]}]
(if (and (empty? items)
(empty? search-filter)
(not @search-active?))
@@ -202,12 +303,14 @@
:keyboard-should-persist-taps :always
:data items
:render-fn render-fn}]
[list/section-list
{:key-fn :title
:sticky-section-headers-enabled false
:sections contacts
:render-section-header-fn contacts-section-header
:render-fn render-contact}]))]))
[rn/view {:style {:flex 1}} (when (> (count requests) 0)
[contact-requests (count requests)])
[list/section-list
{:key-fn :title
:sticky-section-headers-enabled false
:sections contacts
:render-section-header-fn contacts-section-header
:render-fn render-contact}]]))]))
(views/defview chats-list []
(views/letsubs [loading? [:chats/loading?]]
@@ -221,81 +324,88 @@
(views/defview plus-button []
(views/letsubs [logging-in? [:multiaccounts/login]]
[components.plus-button/plus-button
{:on-press (when-not logging-in?
#(re-frame/dispatch [:bottom-sheet/show-sheet :add-new {}]))
:loading logging-in?
{:on-press (when-not logging-in?
#(re-frame/dispatch [:bottom-sheet/show-sheet :add-new {}]))
:loading logging-in?
:accessibility-label :new-chat-button}]))
(views/defview notifications-button []
(views/letsubs [notif-count [:activity.center/notifications-count]]
[rn/view
[quo2.button/button {:type :grey
:size 32
:width 32
:style {:margin-left 12}
[quo2.button/button {:type :grey
:size 32
:width 32
:style {:margin-left 12}
:accessibility-label :notifications-button
:on-press #(do
(re-frame/dispatch [:mark-all-activity-center-notifications-as-read])
(if config/new-activity-center-enabled?
(re-frame/dispatch [:navigate-to :activity-center])
(re-frame/dispatch [:navigate-to :notifications-center])))}
[icons/icon :main-icons/notification2 {:color (quo2.colors/theme-colors quo2.colors/neutral-100 quo2.colors/white)}]]
:on-press #(do
(re-frame/dispatch [:mark-all-activity-center-notifications-as-read])
(if config/new-activity-center-enabled?
(re-frame/dispatch [:show-popover {:view :activity-center
:style {:margin 0}
:disable-touchable-overlay? true
:blur-view? true
:blur-view-props {:blur-amount 20
:blur-type :dark}}])
(re-frame/dispatch [:navigate-to :notifications-center])))}
[icons/icon :main-icons/notification2 {:color (colors/theme-colors colors/neutral-100 colors/white)}]]
(when (pos? notif-count)
[rn/view {:style (merge (styles/counter-public-container) {:top 5 :right 5})
[rn/view {:style (merge (styles/counter-public-container) {:top 5 :right 5})
:pointer-events :none}
[rn/view {:style styles/counter-public
:accessibility-label :notifications-unread-badge}]])]))
(defn qr-button []
[quo2.button/button {:type :grey
[quo2.button/button {:type :grey
:accessibility-label "qr-button"
:size 32
:width 32
:style {:margin-left 12}
:on-press #(do
(re-frame/dispatch [::qr-scanner/scan-code
{:handler ::qr-scanner/on-scan-success}]))}
[icons/icon :main-icons/qr2 {:color (quo2.colors/theme-colors quo2.colors/neutral-100 quo2.colors/white)}]])
:size 32
:width 32
:style {:margin-left 12}
:on-press #(do
(re-frame/dispatch [::qr-scanner/scan-code
{:handler ::qr-scanner/on-scan-success}]))}
[icons/icon :main-icons/qr2 {:color (colors/theme-colors colors/neutral-100 colors/white)}]])
(defn scan-button []
[quo2.button/button {:type :grey
:size 32
:width 32
[quo2.button/button {:type :grey
:size 32
:width 32
:accessibility-label "scan-button"
:on-press #(do
(re-frame/dispatch [::qr-scanner/scan-code
{:handler ::qr-scanner/on-scan-success}]))}
[icons/icon :main-icons/scan2 {:color (quo2.colors/theme-colors quo2.colors/neutral-100 quo2.colors/white)}]])
:on-press #(do
(re-frame/dispatch [::qr-scanner/scan-code
{:handler ::qr-scanner/on-scan-success}]))}
[icons/icon :main-icons/scan2 {:color (colors/theme-colors colors/neutral-100 colors/white)}]])
(views/defview profile-button []
(views/letsubs [{:keys [public-key preferred-name emoji]} [:multiaccount]]
[rn/view
[chat-icon/emoji-chat-icon-view public-key false preferred-name emoji
{:size 28
:chat-icon chat-icon.styles/chat-icon-chat-list}]]))
{:size 28
:chat-icon chat-icon.styles/chat-icon-chat-list}]]))
(defn home []
[rn/keyboard-avoiding-view {:style {:flex 1
:background-color (quo2.colors/theme-colors quo2.colors/neutral-5 quo2.colors/neutral-95)}
:ignore-offset true}
[topbar/topbar {:navigation :none
:use-insets true
:background (quo2.colors/theme-colors quo2.colors/neutral-5 quo2.colors/neutral-95)
:left-component [rn/view {:flex-direction :row :margin-left 20}
[profile-button]]
:right-component [rn/view {:flex-direction :row :margin-right 20}
[scan-button]
[qr-button]
[notifications-button]]
:border-bottom false}]
[rn/view {:flex-direction :row
:justify-content :space-between
:align-items :center
:margin-horizontal 20
:margin-top 15
:margin-bottom 8}
[quo2.text/text {:size :heading-1 :weight :semi-bold} (i18n/label :t/messages)]
[plus-button]]
[chats-list]
[tabbar/tabs-counts-subscriptions]])
[:f>
(fn []
(quo.react/effect! #(re-frame/dispatch [:get-activity-center-notifications]))
[rn/keyboard-avoiding-view {:style {:flex 1
:background-color (colors/theme-colors colors/neutral-5 colors/neutral-95)}
:ignore-offset true}
[topbar/topbar {:navigation :none
:use-insets true
:background (colors/theme-colors colors/neutral-5 colors/neutral-95)
:left-component [rn/view {:flex-direction :row :margin-left 20}
[profile-button]]
:right-component [rn/view {:flex-direction :row :margin-right 20}
[scan-button]
[qr-button]
[notifications-button]]
:border-bottom false}]
[rn/view {:flex-direction :row
:justify-content :space-between
:align-items :center
:margin-horizontal 20
:margin-top 15
:margin-bottom 20}
[quo2.text/text {:size :heading-1 :weight :semi-bold} (i18n/label :t/messages)]
[plus-button]]
[chats-list]
[tabbar/tabs-counts-subscriptions]])])
@@ -1,9 +1,9 @@
(ns status-im.ui2.screens.chat.messages.message
(:require [quo.core :as quo]
[quo.design-system.colors :as colors]
[quo.design-system.colors :as quo.colors]
[quo.react-native :as rn]
[quo2.components.messages.system-message :as system-message]
[quo2.foundations.colors :as quo2.colors]
[quo2.foundations.colors :as colors]
[quo2.foundations.typography :as typography]
[re-frame.core :as re-frame]
[reagent.core :as reagent]
@@ -17,7 +17,6 @@
[status-im.ui.components.animation :as animation]
[status-im.ui.components.chat-icon.screen :as chat-icon]
[status-im.ui.components.fast-image :as fast-image]
[status-im.ui.components.icons.icons :as icons]
[status-im.ui.components.list.views :as list]
[status-im.ui.components.react :as react]
[status-im.ui2.screens.chat.components.reaction-drawer :as reaction-drawer]
@@ -37,27 +36,11 @@
[status-im.ui2.screens.chat.components.reply :as components.reply]
[status-im.utils.config :as config]
[status-im.utils.handlers :refer [<sub >evt]]
[status-im.utils.security :as security])
[status-im.utils.security :as security]
[quo2.components.icon :as icons]
[status-im.utils.datetime :as time])
(:require-macros [status-im.utils.views :refer [defview letsubs]]))
(defn message-timestamp-anim
[anim-opacity show-timestamp?]
(animation/start
(animation/anim-sequence
[(animation/timing
anim-opacity
{:toValue 1
:duration 100
:easing (.-ease ^js animation/easing)
:useNativeDriver true})
(animation/timing
anim-opacity
{:toValue 0
:delay 2000
:duration 100
:easing (.-ease ^js animation/easing)
:useNativeDriver true})]) #(reset! show-timestamp? false)))
(defview mention-element [from]
(letsubs [contact-name [:contacts/contact-name-by-identity from]]
contact-name))
@@ -82,16 +65,15 @@
:tiny-icons/tiny-pending)
{:width 16
:height 12
:color (if pinned colors/gray colors/white)
:color (if pinned quo.colors/gray quo.colors/white)
:accessibility-label (name outgoing-status)}])
(when edited-at [rn/text {:style (style/message-status-text)} edited-at-text])]))
(defn message-timestamp
[{:keys [timestamp-str in-popover?]} show-timestamp?]
[{:keys [timestamp-str in-popover?]}]
(when-not in-popover? ;; We keep track if showing this message in a list in pin-limit-popover
(let [anim-opacity (animation/create-value 0)]
[rn/animated-view {:style (style/message-timestamp-wrapper) :opacity anim-opacity}
(when @show-timestamp? (message-timestamp-anim anim-opacity show-timestamp?))
[rn/text
{:style (style/message-timestamp-text)
:accessibility-label :message-timestamp}
@@ -132,7 +114,7 @@
(conj acc
[rn/text
{:style
{:color colors/blue
{:color quo.colors/blue
:text-decoration-line :underline}
:on-press
#(when (and (security/safe-link? destination)
@@ -143,16 +125,16 @@
"mention"
(conj acc
[rn/view {:style {:background-color quo2.colors/primary-50-opa-10 :border-radius 6 :padding-horizontal 3}}
[rn/view {:style {:background-color colors/primary-50-opa-10 :border-radius 6 :padding-horizontal 3}}
[rn/text
{:style (merge {:color (if (system-text? content-type) colors/black quo2.colors/primary-50)}
{:style (merge {:color (if (system-text? content-type) quo.colors/black colors/primary-50)}
(if (system-text? content-type) typography/font-regular typography/font-medium))
:on-press (when-not (system-text? content-type)
#(>evt [:chat.ui/show-profile literal]))}
[mention-element literal]]])
"status-tag"
(conj acc [rn/text
{:style {:color colors/blue
{:style {:color quo.colors/blue
:text-decoration-line :underline}
:on-press
#(re-frame/dispatch
@@ -207,14 +189,14 @@
[{:keys [content-type content] :as message}]
[rn/view (style/message-view message)
[rn/text
{:style {:color colors/white-persist}}
{:style {:color quo.colors/white-persist}}
(if (seq (:text content))
(:text content)
(str "Unhandled content-type " content-type))]])
(defn message-not-sent-text
[chat-id message-id]
[rn/touchable-highlight
[rn/touchable-opacity
{:on-press
(fn []
(re-frame/dispatch
@@ -226,7 +208,7 @@
[rn/text {:style style/not-sent-text}
(i18n/label :t/status-not-sent-tap)]
[rn/view style/not-sent-icon
[icons/icon :main-icons/warning {:color colors/red}]]]])
[icons/icon :main-icons2/warning {:color quo.colors/red}]]]])
(defn pin-author-name [pinned-by]
(let [user-contact @(re-frame/subscribe [:multiaccount/contact])
@@ -235,14 +217,14 @@
(str " " (if (= pinned-by (user-contact :public-key)) (i18n/label :t/You) (first contact-names)))))
(defn pin-icon [color size]
[icons/icon :main-icons/pin16 {:color color
:height size
:width size}])
[icons/icon :main-icons2/pin {:color color
:height size
:width size}])
(defn pinned-by-indicator [pinned-by]
[rn/view {:style (style/pin-indicator)
:accessibility-label :pinned-by}
[pin-icon quo2.colors/primary-50 16]
[pin-icon colors/primary-50 16]
[quo/text {:size :small
:color :main
:style (style/pin-author-text)}
@@ -254,17 +236,17 @@
(= outgoing-status :not-sent))
[message-not-sent-text chat-id message-id]))
(defview message-author-name [from opts]
(defview message-author-name [from opts max-length]
(letsubs [contact-with-names [:contacts/contact-by-identity from]]
(chat.utils/format-author contact-with-names opts)))
(chat.utils/format-author contact-with-names opts max-length)))
(defview message-my-name [opts]
(letsubs [contact-with-names [:multiaccount/contact]]
(chat.utils/format-author contact-with-names opts)))
(chat.utils/format-author contact-with-names opts nil)))
(defview community-content [{:keys [community-id] :as message}]
(letsubs [{:keys [name description verified] :as community} [:communities/community community-id]
communities-enabled? [:communities/enabled?]]
communities-enabled? [:communities/enabled?]]
(when (and communities-enabled? community)
[rn/view {:style (assoc (style/message-wrapper message)
:margin-vertical 10
@@ -273,7 +255,7 @@
(when verified
[rn/view (style/community-verified)
[rn/text {:style {:font-size 13
:color colors/blue}} (i18n/label :t/communities-verified)]])
:color quo.colors/blue}} (i18n/label :t/communities-verified)]])
[rn/view (style/community-message verified)
[rn/view {:width 62
:padding-left 14}
@@ -287,11 +269,11 @@
name]
[rn/text description]]]
[rn/view (style/community-view-button)
[rn/touchable-highlight {:on-press #(re-frame/dispatch [:navigate-to
:community
{:community-id (:id community)}])}
[rn/touchable-opacity {:on-press #(re-frame/dispatch [:navigate-to
:community
{:community-id (:id community)}])}
[rn/text {:style {:text-align :center
:color colors/blue}} (i18n/label :t/view)]]]])))
:color quo.colors/blue}} (i18n/label :t/view)]]]])))
(defn message-content-wrapper
"Author, userpic and delivery wrapper"
@@ -315,8 +297,10 @@
:pointer-events :box-none}
[rn/view (style/message-author-userpic)
(when (or (and (seq response-to) (:quoted-message message)) last-in-group? pinned)
[rn/touchable-highlight {:on-press #(do (when modal (close-modal))
(re-frame/dispatch [:chat.ui/show-profile from]))}
[rn/touchable-opacity {:active-opacity 1
:on-press #(do (when modal (close-modal))
(>evt [:bottom-sheet/hide])
(re-frame/dispatch [:chat.ui/show-profile from]))}
[photos/member-photo from identicon]])]
[rn/view {:style (style/message-author-wrapper)}
@@ -325,6 +309,7 @@
[rn/touchable-opacity {:style style/message-author-touchable
:disabled in-popover?
:on-press #(do (when modal (close-modal))
(>evt [:bottom-sheet/hide])
(re-frame/dispatch [:chat.ui/show-profile from]))}
[message-author-name from {:modal modal}]]
[rn/text
@@ -371,11 +356,11 @@
:visible @visible
:on-close #(do (reset! visible false)
(reagent/flush))}]
[rn/touchable-highlight {:on-press (fn []
(reset! visible true)
(rn/dismiss-keyboard!))
:on-long-press @on-long-press
:disabled in-popover?}
[rn/touchable-opacity {:on-press (fn []
(reset! visible true)
(rn/dismiss-keyboard!))
:on-long-press @on-long-press
:disabled in-popover?}
[rn/view {:style (style/image-message style-opts)
:accessibility-label :image-message}
(when (or (:error @dimensions) (not (:loaded @dimensions)))
@@ -383,7 +368,7 @@
(merge (dissoc style-opts :opacity)
{:flex 1 :align-items :center :justify-content :center :position :absolute})
(if (:error @dimensions)
[icons/icon :main-icons/cancel]
[icons/icon :main-icons2/cancel]
[rn/activity-indicator {:animating true}])])
[fast-image/fast-image {:style (dissoc style-opts :outgoing)
:on-load (image-set-size dimensions)
@@ -401,21 +386,12 @@
[message]
[message.gap/gap message])
(defmethod ->message constants/content-type-system-text [{:keys [content] :as message}]
[rn/view {:accessibility-label :chat-item}
[rn/view (style/system-message-body message)
[rn/view (style/message-view message)
[rn/view (style/message-view-content)
[render-parsed-text message (:parsed-text content)]]]]])
(defn pin-message [{:keys [chat-id pinned] :as message}]
(let [pinned-messages @(re-frame/subscribe [:chats/pinned chat-id])]
(if (and (not pinned) (> (count pinned-messages) 2))
(do
(js/setTimeout (fn [] (re-frame/dispatch [:dismiss-keyboard])) 500)
(re-frame/dispatch [:show-popover {:view :pin-limit
:message message
:prevent-closing? true}]))
(re-frame/dispatch [::models.pin-message/show-pin-limit-modal chat-id]))
(re-frame/dispatch [::models.pin-message/send-pin-message (assoc message :pinned (not pinned))]))))
(defn on-long-press-fn [on-long-press {:keys [pinned message-pin-enabled outgoing edit-enabled show-input? community?] :as message} content]
@@ -447,15 +423,19 @@
:icon :main-icons2/pin
:id (if pinned :unpin :pin)}])
[{:type :danger
:on-press #(re-frame/dispatch
[:chat.ui/delete-message-for-me message
config/delete-message-for-me-undo-time-limit-ms])
:on-press (fn []
(when pinned (pin-message message))
(re-frame/dispatch
[:chat.ui/delete-message-for-me message
config/delete-message-for-me-undo-time-limit-ms]))
:label (i18n/label :t/delete-for-me)
:icon :main-icons2/delete
:id :delete-for-me}]
(when (and outgoing config/delete-message-enabled?)
[{:type :danger
:on-press #(re-frame/dispatch [:chat.ui/soft-delete-message message])
:on-press (fn []
(when pinned (pin-message message))
(re-frame/dispatch [:chat.ui/soft-delete-message message]))
:label (i18n/label :t/delete-for-everyone)
:icon :main-icons2/delete
:id :delete-for-all}]))))
@@ -470,7 +450,7 @@
(js/setTimeout #(on-long-press-fn on-long-press message content) 200))
(on-long-press-fn on-long-press message content)))]
(reset! ref on-long-press)
[rn/touchable-highlight
[rn/touchable-opacity
(when-not modal
{:delay-long-press 100
:on-long-press on-long-press
@@ -513,27 +493,27 @@
[{:type :main
:on-press #(re-frame/dispatch [:chat.ui/reply-to-message message])
:id :reply
:icon :main-icons/reply-context20
:icon :main-icons2/reply-context20
:label (i18n/label :t/message-reply)}
{:type :main
:on-press #(react/copy-to-clipboard (get content :text))
:id :copy
:icon :main-icons/copy-context20
:icon :main-icons2/copy-context20
:label (i18n/label :t/copy-text)}]
(when message-pin-enabled [{:type :main
:on-press #(pin-message message)
:id :pin
:icon :main-icons/pin-context20
:icon :main-icons2/pin-context20
:label (if pinned (i18n/label :t/unpin) (i18n/label :t/pin))}]))))]
(reset! ref on-long-press)
[message-content-wrapper message
[rn/touchable-highlight (when-not modal
{:disabled in-popover?
:on-press (fn []
(rn/dismiss-keyboard!)
(reset! show-timestamp? true))
:delay-long-press 100
:on-long-press on-long-press})
[rn/touchable-opacity (when-not modal
{:disabled in-popover?
:on-press (fn []
(rn/dismiss-keyboard!)
(reset! show-timestamp? true))
:delay-long-press 100
:on-long-press on-long-press})
[rn/view style/message-view-wrapper
[message-timestamp message show-timestamp?]
[rn/view (style/message-view message)
@@ -554,21 +534,21 @@
(on-long-press
(when-not outgoing
[{:type :main
:icon :main-icons/stickers-context20
:icon :main-icons2/stickers-context20
:on-press #(when pack
(re-frame/dispatch [:chat.ui/show-profile from]))
:label (i18n/label :t/see-sticker-set)}])))]
(reset! ref on-long-press)
[message-content-wrapper message
[rn/touchable-highlight (when-not modal
{:disabled in-popover?
:accessibility-label :sticker-message
:on-press (fn [_]
(when pack
(re-frame/dispatch [:stickers/open-sticker-pack (str pack)]))
(rn/dismiss-keyboard!))
:delay-long-press 100
:on-long-press on-long-press})
[rn/touchable-opacity (when-not modal
{:disabled in-popover?
:accessibility-label :sticker-message
:on-press (fn [_]
(when pack
(re-frame/dispatch [:stickers/open-sticker-pack (str pack)]))
(rn/dismiss-keyboard!))
:delay-long-press 100
:on-long-press on-long-press})
[fast-image/fast-image {:style {:margin 10 :width 140 :height 140}
:source {:uri (str (-> content :sticker :url) "&download=true")}}]]
reaction-picker]))
@@ -582,31 +562,31 @@
(concat [{:type :main
:on-press #(re-frame/dispatch [:chat.ui/reply-to-message message])
:id :reply
:icon :main-icons/reply-context20
:icon :main-icons2/reply-context20
:label (i18n/label :t/message-reply)}
{:type :main
:on-press #(re-frame/dispatch [:chat.ui/save-image-to-gallery (:image content)])
:id :save
:icon :main-icons/save-context20
:icon :main-icons2/save-context20
:label (i18n/label :t/save-image-library)}
{:type :main
:on-press #(images/download-image-http
(get-in message [:content :image]) preview/share)
:id :share
:icon :main-icons/share-context20
:icon :main-icons2/share-context20
:label (i18n/label :t/share-image)}]
[{:type :danger
:on-press #(re-frame/dispatch
[:chat.ui/delete-message-for-me message
config/delete-message-for-me-undo-time-limit-ms])
:label (i18n/label :t/delete-for-me)
:icon :main-icons/delete-context20
:icon :main-icons2/delete-context20
:id :delete-for-me}]
(when (and outgoing config/delete-message-enabled?)
[{:type :danger
:on-press #(re-frame/dispatch [:chat.ui/soft-delete-message message])
:label (i18n/label :t/delete-for-everyone)
:icon :main-icons/delete-context20
:icon :main-icons2/delete-context20
:id :delete}]))))]
(reset! ref on-long-press)
[message-content-wrapper message
@@ -625,29 +605,29 @@
(let [on-long-press (fn [] (on-long-press [{:type :main
:on-press #(re-frame/dispatch [:chat.ui/reply-to-message message])
:label (i18n/label :t/message-reply)
:icon :main-icons/reply-context20
:icon :main-icons2/reply
:id :reply}
{:type :main
:on-press #(pin-message message)
:label (i18n/label (if pinned :t/unpin-from-chat :t/pin-to-chat))
:icon :main-icons/pin-context20
:icon :main-icons2/pin-context20
:id (if pinned :unpin :pin)}
{:type :danger
:on-press #(re-frame/dispatch
[:chat.ui/delete-message-for-me message
config/delete-message-for-me-undo-time-limit-ms])
:label (i18n/label :t/delete-for-me)
:icon :main-icons/delete-context20
:icon :main-icons2/delete-context20
:id :delete-for-me}
(when (and outgoing config/delete-message-enabled?)
{:type :danger
:on-press #(re-frame/dispatch [:chat.ui/soft-delete-message message])
:label (i18n/label :t/delete-for-everyone)
:icon :main-icons/delete-context20
:icon :main-icons2/delete-context20
:id :delete})]))]
(reset! ref on-long-press)
[message-content-wrapper message
[rn/touchable-highlight
[rn/touchable-opacity
(when-not modal
{:on-long-press on-long-press
:on-press (fn []
@@ -667,15 +647,15 @@
(i18n/label :t/contact-request-pending)]
[rn/activity-indicator {:animating true
:size :small
:color colors/gray}]])
:color quo.colors/gray}]])
(defn contact-request-status-accepted []
[quo/text {:style {:color colors/green}
[quo/text {:style {:color quo.colors/green}
:weight :medium}
(i18n/label :t/contact-request-accepted)])
(defn contact-request-status-declined []
[quo/text {:style {:color colors/red}
[quo/text {:style {:color quo.colors/red}
:weight :medium}
(i18n/label :t/contact-request-declined)])
@@ -734,11 +714,12 @@
#(on-emoji-press %))}]))
on-long-press (atom nil)]
[rn/view
{:style (merge (when (and (not in-pinned-view?) (or mentioned pinned)) {:background-color quo2.colors/primary-50-opa-5
{:style (merge (when (and (not in-pinned-view?) (or mentioned pinned)) {:background-color colors/primary-50-opa-5
:border-radius 16
:margin-bottom 4})
(when (or mentioned pinned last-in-group?) {:margin-top 8})
{:margin-horizontal 8})}
(when pinned
[rn/view {:style (style/pin-indicator-container)}
[pinned-by-indicator pinned-by]])
@@ -748,9 +729,9 @@
[reaction-row/message-reactions message reactions nil on-emoji-press on-long-press]])) ;; TODO: pass on-open-drawer function
(defn message-render-fn
[{:keys [outgoing] :as message}
[{:keys [outgoing whisper-timestamp] :as message}
_
{:keys [group-chat public? community? current-public-key show-input? message-pin-enabled edit-enabled]}]
{:keys [group-chat public? community? current-public-key show-input? edit-enabled]}]
[chat-message
(assoc message
:incoming-group (and group-chat (not outgoing))
@@ -759,9 +740,10 @@
:community? community?
:current-public-key current-public-key
:show-input? show-input?
:message-pin-enabled message-pin-enabled
:message-pin-enabled true
:in-pinned-view? true
:pinned true
:timestamp-str (time/timestamp->time whisper-timestamp)
:edit-enabled edit-enabled)])
(def list-key-fn #(or (:message-id %) (:value %)))
@@ -770,13 +752,13 @@
(let [pinned-messages (vec (vals (<sub [:chats/pinned chat-id])))
current-chat (<sub [:chats/current-chat])
community (<sub [:communities/community (:community-id current-chat)])]
[rn/view
[rn/view {:accessibility-label :pinned-messages-list}
[rn/text {:style (merge typography/heading-1 typography/font-semi-bold {:margin-horizontal 20
:color (quo2.colors/theme-colors quo2.colors/neutral-100 quo2.colors/white)})}
:color (colors/theme-colors colors/neutral-100 colors/white)})}
(i18n/label :t/pinned-messages)]
(when community
[rn/view {:style {:flex-direction :row
:background-color (quo2.colors/theme-colors quo2.colors/neutral-10 quo2.colors/neutral-80)
:background-color (colors/theme-colors colors/neutral-10 colors/neutral-80)
:border-radius 20
:align-items :center
:align-self :flex-start
@@ -784,44 +766,58 @@
:padding 4
:margin-top 8}}
[chat-icon/chat-icon-view-toolbar chat-id (:group-chat current-chat) (:chat-name current-chat) (:color current-chat) (:emoji current-chat) 22]
[rn/text {:style {:margin-left 6 :margin-right 4 :color (quo2.colors/theme-colors quo2.colors/neutral-100 quo2.colors/white)}} (:name community)]
[rn/text {:style {:margin-left 6 :margin-right 4 :color (colors/theme-colors colors/neutral-100 colors/white)}} (:name community)]
[icons/icon
:main-icons/chevron-right
{:color (quo2.colors/theme-colors quo2.colors/neutral-50 quo2.colors/neutral-40)
:main-icons2/chevron-right
{:color (colors/theme-colors colors/neutral-50 colors/neutral-40)
:width 12
:height 12}]
[rn/text {:style {:margin-left 4
:margin-right 8
:color (quo2.colors/theme-colors quo2.colors/neutral-100 quo2.colors/white)}} (str "# " (:chat-name current-chat))]])
[list/flat-list
{:data pinned-messages
:render-fn message-render-fn
:key-fn list-key-fn
:separator [rn/view {:background-color (quo2.colors/theme-colors quo2.colors/neutral-10 quo2.colors/neutral-80) :height 1 :margin-top 8}]}]]))
:color (colors/theme-colors colors/neutral-100 colors/white)}} (str "# " (:chat-name current-chat))]])
(if (> (count pinned-messages) 0)
[list/flat-list
{:data pinned-messages
:render-fn message-render-fn
:key-fn list-key-fn
:separator [rn/view {:background-color (colors/theme-colors colors/neutral-10 colors/neutral-80) :height 1 :margin-top 8}]}]
[rn/view {:style {:justify-content :center
:align-items :center
:flex 1
:margin-top 20}}
[rn/view {:style {:width 120
:height 120
:justify-content :center
:align-items :center
:border-width 1}} [icons/icon :main-icons2/placeholder]]
[rn/text {:style (merge typography/paragraph-1 typography/font-semi-bold {:margin-top 20})} (i18n/label :t/no-pinned-messages)]
[rn/text {:style (merge typography/paragraph-2 typography/font-regular)}
(i18n/label (if community :t/no-pinned-messages-community-desc :t/no-pinned-messages-desc))]])]))
(defmethod ->message constants/content-type-pin [{:keys [from in-popover? timestamp-str chat-id] :as message} {:keys [modal close-modal]}]
(defn pin-system-message [{:keys [from in-popover? timestamp-str chat-id] :as message} {:keys [modal close-modal]}]
(let [response-to (:response-to (:content message))]
[rn/touchable-opacity {:on-press (fn []
(re-frame/dispatch [:bottom-sheet/show-sheet
{:content #(pinned-messages-list chat-id)}]))
(>evt [:bottom-sheet/show-sheet
{:content #(pinned-messages-list chat-id)}]))
:active-opacity 1
:style (merge {:flex-direction :row :margin-vertical 8} (style/message-wrapper message))}
[rn/view {:style {:width photos.style/default-size
:height photos.style/default-size
:margin-right 16
:border-radius photos.style/default-size
:justify-content :center
:align-items :center
:background-color quo2.colors/primary-50-opa-10}}
[pin-icon quo2.colors/primary-50 16]]
[rn/view {:style {:width photos.style/default-size
:height photos.style/default-size
:margin-right 16
:border-radius photos.style/default-size
:justify-content :center
:align-items :center
:background-color colors/primary-50-opa-10}
:accessibility-label :content-type-pin-icon}
[pin-icon colors/primary-50 16]]
[rn/view
[rn/view {:style {:flex-direction :row :align-items :center}}
[rn/touchable-opacity {:style style/message-author-touchable
:disabled in-popover?
:on-press #(do (when modal (close-modal))
(re-frame/dispatch [:chat.ui/show-profile from]))}
[message-author-name from {:modal modal}]]
[rn/text {:style {:font-size 13}} (str " " (i18n/label :pinned-a-message))]
[message-author-name from {:modal modal} 20]]
[rn/text {:style {:font-size 13}} (str " " (i18n/label :t/pinned-a-message))]
[rn/text
{:style (merge
{:padding-left 5
@@ -831,33 +827,45 @@
timestamp-str]]
[quoted-message response-to (:quoted-message message) true]]]))
(defmethod ->message constants/content-type-system-text [{:keys [content quoted-message] :as message}]
(if quoted-message
[pin-system-message message]
[rn/view {:accessibility-label :chat-item}
[rn/view (style/system-message-body message)
[rn/view (style/message-view message)
[rn/view (style/message-view-content)
[render-parsed-text message (:parsed-text content)]]]]]))
(defn pinned-banner [chat-id]
(let [pinned-messages (<sub [:chats/pinned chat-id])
latest-pin-text (get-in (last (vals pinned-messages)) [:content :text])
pins-count (count (seq pinned-messages))]
(when (> pins-count 0)
[rn/touchable-opacity
{:style {:height 50
:background-color quo2.colors/primary-50-opa-20
:flex-direction :row
:align-items :center
:padding-horizontal 20
:padding-vertical 10}
:active-opacity 1
:on-press (fn []
(re-frame/dispatch [:bottom-sheet/show-sheet
{:content #(pinned-messages-list chat-id)}]))}
[pin-icon (quo2.colors/theme-colors quo2.colors/neutral-100 quo2.colors/white) 20]
{:accessibility-label :pinned-banner
:style {:height 50
:background-color colors/primary-50-opa-20
:flex-direction :row
:align-items :center
:padding-horizontal 20
:padding-vertical 10}
:active-opacity 1
:on-press (fn []
(re-frame/dispatch [:bottom-sheet/show-sheet
{:content #(pinned-messages-list chat-id)}]))}
[pin-icon (colors/theme-colors colors/neutral-100 colors/white) 20]
[rn/text {:number-of-lines 1
:style (merge typography/paragraph-2 {:margin-left 10
:margin-right 50
:color (quo2.colors/theme-colors quo2.colors/neutral-100 quo2.colors/white)})} latest-pin-text]
[rn/view {:style {:position :absolute
:right 22
:height 20
:width 20
:border-radius 8
:justify-content :center
:align-items :center
:background-color quo2.colors/neutral-80-opa-5}}
[rn/text {:style (merge typography/label typography/font-medium {:color (quo2.colors/theme-colors quo2.colors/neutral-100 quo2.colors/white)})} pins-count]]])))
:color (colors/theme-colors colors/neutral-100 colors/white)})} latest-pin-text]
[rn/view {:accessibility-label :pins-count
:style {:position :absolute
:right 22
:height 20
:width 20
:border-radius 8
:justify-content :center
:align-items :center
:background-color colors/neutral-80-opa-5}}
[rn/text {:style (merge typography/label typography/font-medium {:color (colors/theme-colors colors/neutral-100 colors/white)})} pins-count]]])))
@@ -0,0 +1,52 @@
(ns status-im.ui2.screens.chat.messages.pinned-message
(:require [status-im.i18n.i18n :as i18n]
[quo.react :as react]
[quo2.reanimated :as reanimated]
[quo.react-native :as rn]
[quo2.foundations.typography :as typography]
[quo2.foundations.colors :as colors]
[status-im.switcher.constants :as constants]
[status-im.chat.models.pin-message :as models.pin-message]
[status-im.utils.handlers :refer [<sub >evt]]
[status-im.ui2.screens.chat.messages.style :as style]
[quo2.components.icon :as icons]))
(defn pin-limit-popover [chat-id pinned-messages-list]
[:f>
(fn []
(let [{:keys [width]} (constants/dimensions)
show-pin-limit-modal? (<sub [:chats/pin-modal chat-id])
opacity-animation (reanimated/use-shared-value 0)
z-index-animation (reanimated/use-shared-value -1)]
(react/effect! #(do
(reanimated/set-shared-value opacity-animation (reanimated/with-timing (if show-pin-limit-modal? 1 0)))
(reanimated/set-shared-value z-index-animation (reanimated/with-timing (if show-pin-limit-modal? 10 -1)))))
[reanimated/view {:style (reanimated/apply-animations-to-style
{:opacity opacity-animation
:z-index z-index-animation}
(style/pin-popover width))
:accessibility-label :pin-limit-popover}
[rn/view {:style (style/pin-alert-container)}
[rn/view {:style (style/pin-alert-circle)}
[rn/text {:style {:color colors/danger-50}} "!"]]]
[rn/view {:style {:margin-left 8}}
[rn/text {:style (merge typography/paragraph-1 typography/font-semi-bold {:color (colors/theme-colors colors/white colors/neutral-100)})} (i18n/label :t/cannot-pin-title)]
[rn/text {:style (merge typography/paragraph-2 typography/font-regular {:color (colors/theme-colors colors/white colors/neutral-100)})} (i18n/label :t/cannot-pin-desc)]
[rn/touchable-opacity
{:accessibility-label :view-pinned-messages
:active-opacity 1
:on-press (fn []
(>evt [::models.pin-message/hide-pin-limit-modal chat-id])
(>evt [:bottom-sheet/show-sheet
{:content #(pinned-messages-list chat-id)}]))
:style (style/view-pinned-messages)}
[rn/text {:style (merge typography/paragraph-2 typography/font-medium {:color colors/white})} (i18n/label :t/view-pinned-messages)]]]
[rn/touchable-opacity {:accessibility-label :close-pin-limit-popover
:active-opacity 1
:on-press #(>evt [::models.pin-message/hide-pin-limit-modal chat-id])
:style {:position :absolute
:top 16
:right 16}}
[icons/icon :main-icons2/close {:color (colors/theme-colors colors/white colors/neutral-100)
:height 8
:width 8}]]]))])
@@ -0,0 +1,38 @@
(ns status-im.ui2.screens.chat.messages.style
(:require [quo2.foundations.colors :as colors]))
(defn pin-popover [width]
{:position :absolute
:width (- width 16)
:left 8
:background-color (colors/theme-colors colors/neutral-80-opa-90 colors/white-opa-90)
:flex-direction :row
:border-radius 16
:padding 12})
(defn pin-alert-container []
{:background-color (colors/theme-colors colors/neutral-80-opa-20 colors/white-opa-40)
:width 36
:height 36
:border-radius 18
:justify-content :center
:align-items :center})
(defn pin-alert-circle []
{:width 18
:height 18
:border-radius 9
:border-color colors/danger-50-opa-40
:border-width 1
:justify-content :center
:align-items :center})
(defn view-pinned-messages []
{:background-color colors/primary-60
:border-radius 8
:justify-content :center
:align-items :center
:padding-horizontal 8
:padding-vertical 4
:align-self :flex-start
:margin-top 10})
@@ -1,7 +1,7 @@
(ns status-im.ui2.screens.chat.messages.view
(:require [reagent.core :as reagent]
[quo.react-native :as rn]
[quo2.foundations.colors :as quo2.colors]
[quo2.foundations.colors :as colors]
[status-im.constants :as constants]
[status-im.utils.handlers :refer [<sub >evt]]
[status-im.ui.components.list.views :as list]
@@ -36,10 +36,10 @@
:align-items :center
:justify-content :center
:border-radius (/ 24 2)
:background-color (quo2.colors/theme-colors quo2.colors/neutral-80-opa-70 quo2.colors/white-opa-70)}}
:background-color (colors/theme-colors colors/neutral-80-opa-70 colors/white-opa-70)}}
;;TODO icon from quo2 should be used instead!
[icons/icon
:main-icons/arrow-down {:color (quo2.colors/theme-colors quo2.colors/white quo2.colors/neutral-100)
:main-icons/arrow-down {:color (colors/theme-colors colors/white colors/neutral-100)
:width 12
:height 12}]]])
@@ -188,4 +188,4 @@
:inverted (when platform/ios? true)
:style (when platform/android? {:scaleY -1})})]
(when @show-floating-scroll-down-button
[floating-scroll-down-button show-input?])]))
[floating-scroll-down-button show-input?])]))
+8 -4
View File
@@ -6,12 +6,13 @@
[status-im.utils.debounce :as debounce]
[quo.react-native :as rn]
[quo2.components.buttons.button :as quo2.button]
[quo2.foundations.colors :as quo2.colors]
[quo2.foundations.colors :as colors]
[status-im.ui.components.react :as react]
[status-im.navigation.state :as navigation.state]
[status-im.ui2.screens.chat.messages.view :as messages]
[status-im.utils.handlers :refer [<sub >evt]]
[status-im.ui.components.icons.icons :as icons]
[status-im.ui2.screens.chat.messages.pinned-message :as pinned-message]
[re-frame.db]
[status-im.ui2.screens.chat.messages.message :as message]))
@@ -30,15 +31,17 @@
:size 32
:width 32
:accessibility-label "back-button"
:on-press #(>evt [:navigate-back])}
[icons/icon :main-icons/arrow-left {:color (quo2.colors/theme-colors quo2.colors/neutral-100 quo2.colors/white)}]])
:on-press #(do
(>evt [:close-chat])
(>evt [:navigate-back]))}
[icons/icon :main-icons/arrow-left {:color (colors/theme-colors colors/neutral-100 colors/white)}]])
(defn search-button []
[quo2.button/button {:type :grey
:size 32
:width 32
:accessibility-label "search-button"}
[icons/icon :main-icons/search {:color (quo2.colors/theme-colors quo2.colors/neutral-100 quo2.colors/white)}]])
[icons/icon :main-icons/search {:color (colors/theme-colors colors/neutral-100 colors/white)}]])
(defn navigate-back-handler []
(when (and (not @navigation.state/curr-modal) (= (get @re-frame.db/app-db :view-id) :chat))
@@ -68,6 +71,7 @@
(if group-chat
[invitation-requests chat-id admins]
(when-not mutual-contact-requests-enabled? [add-contact-bar chat-id])))
[pinned-message/pin-limit-popover chat-id message/pinned-messages-list]
[message/pinned-banner chat-id]
;;MESSAGES LIST
[messages/messages-view
+1 -1
View File
@@ -82,7 +82,7 @@
and adds unicode ellipsis in between"
[address]
(when address
(str (subs address 0 6) "\u2026" (subs address (- (count address) 4) (count address)))))
(str (subs address 0 6) "\u2026" (subs address (- (count address) 3) (count address)))))
(defn get-shortened-checksum-address [address]
(when address
+3 -3
View File
@@ -3,7 +3,7 @@
"_comment": "Instead use: scripts/update-status-go.sh <rev>",
"owner": "status-im",
"repo": "status-go",
"version": "v0.111.6",
"commit-sha1": "5c3435c12fe430740e2b5d2a05066d423c61acec",
"src-sha256": "0v1scizr5lsbm4w3bysc2cl1v9wsd9b27bzjn80p4rw6gca7s2l1"
"version": "659ab120b66dca9521c93cbd8516582634059074",
"commit-sha1": "659ab120b66dca9521c93cbd8516582634059074",
"src-sha256": "1x5pasyf4ivrw92pil4ljl6cfw3g6wn4drgxxszcdzih0pwzggx4"
}
+42 -36
View File
@@ -22,7 +22,8 @@ class TestrailReport(BaseTestReport):
self.user = environ.get('TESTRAIL_USER')
self.run_id = None
self.suite_id = 48
#self.suite_id = 48
self.suite_id = 5274
self.project_id = 14
self.outcomes = {
@@ -104,43 +105,47 @@ class TestrailReport(BaseTestReport):
test_cases['pr'] = dict()
test_cases['nightly'] = dict()
test_cases['upgrade'] = dict()
## PR e2e
test_cases['pr']['critical'] = 730
test_cases['pr']['contacts'] = 50831
test_cases['pr']['public_chat'] = 50654
test_cases['pr']['one_to_one_chat'] = 50655
test_cases['pr']['group_chat'] = 50656
test_cases['pr']['onboarding'] = 50659
test_cases['pr']['recovery'] = 50660
test_cases['pr']['wallet'] = 50661
test_cases['pr']['send_tx'] = 50662
test_cases['pr']['keycard_tx'] = 50663
test_cases['pr']['1_1_chat_commands'] = 50825
test_cases['pr']['ens'] = 50827
test_cases['pr']['sync'] = 50834
test_cases['pr']['browser'] = 50812
## PR e2e old UI
# test_cases['pr']['critical'] = 730
# test_cases['pr']['contacts'] = 50831
# test_cases['pr']['public_chat'] = 50654
# test_cases['pr']['one_to_one_chat'] = 50655
# test_cases['pr']['group_chat'] = 50656
# test_cases['pr']['onboarding'] = 50659
# test_cases['pr']['recovery'] = 50660
# test_cases['pr']['wallet'] = 50661
# test_cases['pr']['send_tx'] = 50662
# test_cases['pr']['keycard_tx'] = 50663
# test_cases['pr']['1_1_chat_commands'] = 50825
# test_cases['pr']['ens'] = 50827
# test_cases['pr']['sync'] = 50834
# test_cases['pr']['browser'] = 50812
test_cases['pr']['critical'] = 50955
test_cases['pr']['one_to_one_chat'] = 50956
## Nightly e2e
test_cases['nightly']['medium'] = 736
test_cases['nightly']['chat'] = 50811
test_cases['nightly']['browser'] = 50826
test_cases['nightly']['profile'] = 50828
test_cases['nightly']['deep_link'] = 50836
test_cases['nightly']['share_profile'] = 50837
test_cases['nightly']['chat_2'] = 50838
test_cases['nightly']['group_chat'] = 50839
test_cases['nightly']['pairing'] = 50840
test_cases['nightly']['activity_center'] = 50833
test_cases['nightly']['timeline'] = 50842
test_cases['nightly']['community'] = 50841
test_cases['nightly']['permissions'] = 50843
test_cases['nightly']['scan qr'] = 50844
test_cases['nightly']['mentions'] = 50845
test_cases['nightly']['mutual_contact_requests'] = 50857
test_cases['nightly']['keycard'] = 50850
test_cases['nightly']['wallet'] = 50851
# test_cases['nightly']['medium'] = 736
# test_cases['nightly']['chat'] = 50811
# test_cases['nightly']['browser'] = 50826
# test_cases['nightly']['profile'] = 50828
# test_cases['nightly']['deep_link'] = 50836
# test_cases['nightly']['share_profile'] = 50837
# test_cases['nightly']['chat_2'] = 50838
# test_cases['nightly']['group_chat'] = 50839
# test_cases['nightly']['pairing'] = 50840
# test_cases['nightly']['activity_center'] = 50833
# test_cases['nightly']['timeline'] = 50842
# test_cases['nightly']['community'] = 50841
# test_cases['nightly']['permissions'] = 50843
# test_cases['nightly']['scan qr'] = 50844
# test_cases['nightly']['mentions'] = 50845
# test_cases['nightly']['mutual_contact_requests'] = 50857
# test_cases['nightly']['keycard'] = 50850
# test_cases['nightly']['wallet'] = 50851
## Upgrade e2e
test_cases['upgrade']['general'] = 881
# test_cases['upgrade']['general'] = 881
case_ids = list()
for arg in argv:
@@ -148,7 +153,8 @@ class TestrailReport(BaseTestReport):
key, value = arg.split('=')
case_ids = value.split(',')
if len(case_ids) == 0:
if 'critical' in argv:
# if 'critical' in argv:
if 'new_ui_critical' in argv:
for category in test_cases['pr']:
for case in self.get_cases([test_cases['pr'][category]]):
case_ids.append(case['id'])
+1 -1
View File
@@ -60,7 +60,7 @@ test_dapp_web_url = "status-im.github.io/dapp"
test_dapp_url = 'https://simpledapp.status.im/'
test_dapp_name = 'simpledapp.status.im'
emojis = {'thumbs-up': 2, 'thumbs-down': 3, 'love': 1, 'laugh': 4, 'angry': 6, 'sad': 5}
emojis = {'thumbs-up': 5, 'thumbs-down': 6, 'love': 1, 'laugh': 4, 'angry': 2, 'sad': 3}
with open(os.sep.join(__file__.split(os.sep)[:-1]) + '/../../../translations/en.json') as json_file:
@@ -227,6 +227,7 @@ class TestOneToOneChatMultipleSharedDevices(MultipleSharedDeviceTestCase):
self.chat_2 = self.home_2.get_chat(self.default_username_1).click()
@marks.testrail_id(6315)
# moved
def test_1_1_chat_message_reaction(self):
message_from_sender = "Message sender"
self.device_1.just_fyi("Sender start 1-1 chat, set emoji and check counter")
@@ -378,6 +379,7 @@ class TestOneToOneChatMultipleSharedDevices(MultipleSharedDeviceTestCase):
self.errors.verify_no_errors()
@marks.testrail_id(5315)
# moved
def test_1_1_chat_non_latin_message_to_newly_added_contact_with_profile_picture_on_different_networks(self):
self.home_1.get_app_from_background()
self.home_2.get_app_from_background()
@@ -1212,3 +1214,103 @@ class TestEnsStickersMultipleDevicesMerged(MultipleSharedDeviceTestCase):
and account.qr_code_image.is_element_displayed()):
self.errors.append('No self profile pop-up data displayed after My_profile button tap')
self.errors.verify_no_errors()
@pytest.mark.xdist_group(name="one_2")
@marks.new_ui_critical
class TestOneToOneChatMultipleSharedDevices(MultipleSharedDeviceTestCase):
def prepare_devices(self):
self.drivers, self.loop = create_shared_drivers(2)
self.device_1, self.device_2 = SignInView(self.drivers[0]), SignInView(self.drivers[1])
self.home_1 = self.device_1.create_user(enable_notifications=True)
self.home_2 = self.device_2.create_user(enable_notifications=True)
self.profile_1 = self.home_1.get_profile_view()
self.public_key_1, self.default_username_1 = self.home_1.get_public_key_and_username(return_username=True)
self.public_key_2, self.default_username_2 = self.home_2.get_public_key_and_username(return_username=True)
self.profile_1.chats_tab.click()
self.chat_1 = self.home_1.add_contact(self.public_key_2)
self.chat_1.send_message('hey')
self.home_2.click_system_back_button_until_element_is_shown()
self.home_2.chats_tab.click()
self.chat_2 = self.home_2.get_chat(self.default_username_1).click()
@marks.testrail_id(702730)
def test_1_1_chat_message_reaction(self):
message_from_sender = "Message sender"
self.device_1.just_fyi("Sender start 1-1 chat, set emoji and check counter")
self.chat_1.send_message(message_from_sender)
self.chat_1.set_reaction(message_from_sender)
message_sender = self.chat_1.chat_element_by_text(message_from_sender)
if message_sender.emojis_below_message() != 1:
self.errors.append("Counter of reaction is not updated on your own message!")
self.device_2.just_fyi("Receiver sets own emoji and verifies counter on received message in 1-1 chat")
message_receiver = self.chat_2.chat_element_by_text(message_from_sender)
if message_receiver.emojis_below_message() != 1:
self.errors.append("Counter of reaction is not updated on received message!")
self.chat_2.set_reaction(message_from_sender)
for counter in message_sender.emojis_below_message(), message_receiver.emojis_below_message():
if counter != 2:
self.errors.append('Counter is not updated after setting emoji from receiver!')
self.device_2.just_fyi("Receiver pick the same emoji and verify that counter will decrease for both users")
self.chat_2.set_reaction(message_from_sender)
for counter in message_sender.emojis_below_message(), message_receiver.emojis_below_message():
if counter != 1:
self.errors.append('Counter is not decreased after re-tapping emoji from receiver!')
self.errors.verify_no_errors()
@marks.testrail_id(702745)
def test_1_1_chat_non_latin_messages_stack_update_profile_photo(self):
self.home_1.click_system_back_button_until_element_is_shown()
self.home_1.browser_tab.click() #temp, until profile is on browser tab
self.profile_1.edit_profile_picture('sauce_logo.png')
self.profile_1.chats_tab.click()
self.chat_2.just_fyi("Send messages with non-latin symbols")
messages = ['hello', '¿Cómo estás tu año?', 'ё, доброго вечерочка', '® æ ç ♥']
for message in messages:
self.chat_2.send_message(message)
if not self.chat_1.chat_message_input.is_element_displayed():
self.chat_1.click_system_back_button_until_element_is_shown()
self.home_1.get_chat(self.default_username_2).click()
for message in messages:
if not self.chat_1.chat_element_by_text(message).is_element_displayed():
self.errors.append("Message with test '%s' was not received" % message)
self.chat_2.just_fyi("Checking updated member photo, timestamp and username on message")
timestamp = self.chat_2.chat_element_by_text(messages[0]).timestamp
sent_time_variants = self.chat_2.convert_device_time_to_chat_timestamp()
if timestamp not in sent_time_variants:
self.errors.append('Timestamp on message %s does not correspond expected [%s]' % (timestamp, *sent_time_variants))
for message in [messages[1], messages[2]]:
if self.chat_2.chat_element_by_text(message).member_photo.is_element_displayed():
self.errors.append('%s is not stack to 1st(they are sent in less than 5 minutes)!' % message)
self.chat_1.just_fyi("Sending message while user is still not in contacts")
message = 'profile_photo'
self.chat_1.send_message(message)
self.chat_2.chat_element_by_text(message).wait_for_visibility_of_element(30)
if not self.chat_2.chat_element_by_text(message).member_photo.is_element_differs_from_template("member.png",
diff=5):
self.errors.append("Image of user in 1-1 chat is updated even when user is not added to contacts!")
self.chat_1.just_fyi("Users add to contacts each other")
[home.click_system_back_button_until_element_is_shown() for home in (self.home_1, self.home_2)]
[home.browser_tab.click() for home in (self.home_1, self.home_2)]
self.profile_1.add_contact_via_contacts_list(self.public_key_2)
self.profile_2 = self.home_2.get_profile_view()
self.profile_2.add_contact_via_contacts_list(self.public_key_1)
self.chat_1.just_fyi("Go back to chat view and checking that profile photo is updated")
[home.chats_tab.click() for home in (self.home_1, self.home_2)]
if not self.chat_2.chat_message_input.is_element_displayed():
self.home_2.get_chat(self.default_username_1).click()
if self.chat_2.chat_element_by_text(message).member_photo.is_element_differs_from_template("member.png", diff=5):
self.errors.append("Image of user in 1-1 chat is too different from template!")
self.errors.verify_no_errors()
+2
View File
@@ -4,6 +4,8 @@ testrail_case_id = pytest.mark.testrail_case_id
testrail_id = pytest.mark.testrail_id # atomic tests
critical = pytest.mark.critical
medium = pytest.mark.medium
# new ui
new_ui_critical = pytest.mark.new_ui_critical
flaky = pytest.mark.flaky
upgrade = pytest.mark.upgrade
+33 -3
View File
@@ -79,8 +79,28 @@ class HomeButton(TabButton):
self.click_until_presence_of_element(element)
return self.navigate()
class CommunitiesTab(TabButton):
def __init__(self, driver):
super().__init__(driver, accessibility_id="communities-stack-tab")
class ChatsTab(TabButton):
def __init__(self, driver):
super().__init__(driver, accessibility_id="chats-stack-tab")
def navigate(self):
from views.home_view import HomeView
return HomeView(self.driver)
class WalletTab(TabButton):
def __init__(self, driver):
super().__init__(driver, accessibility_id="wallet-stack-tab")
class BrowserTab(TabButton):
def __init__(self, driver):
super().__init__(driver, accessibility_id="browser-stack-tab")
class DappTabButton(TabButton):
def __init__(self, driver):
super().__init__(driver, xpath="//*[contains(@content-desc,'tab, 2 out of 5')]")
@@ -224,13 +244,19 @@ class BaseView(object):
self.send_message_button = SendMessageButton(self.driver)
self.send_contact_request_button = Button(self.driver, translation_id="send-request")
# Tabs
# Old UI Tabs
self.home_button = HomeButton(self.driver)
self.wallet_button = WalletButton(self.driver)
self.profile_button = ProfileButton(self.driver)
self.dapp_tab_button = DappTabButton(self.driver)
self.status_button = StatusButton(self.driver)
# New UI Tabs
self.communities_tab = CommunitiesTab(self.driver)
self.chats_tab = ChatsTab(self.driver)
self.browser_tab = BrowserTab(self.driver)
self.wallet_tab = WalletTab(self.driver)
self.yes_button = Button(self.driver, xpath="//*[@text='YES' or @text='GOT IT']")
self.no_button = Button(self.driver, translation_id="no")
self.back_button = BackButton(self.driver)
@@ -351,7 +377,9 @@ class BaseView(object):
def click_system_back_button_until_element_is_shown(self, attempts=3, element='home'):
counter = 0
if element == 'home':
element = self.home_button
element = self.chats_tab
# Old UI
# element = self.home_button
while not element.is_element_displayed(1) and counter <= attempts:
self.driver.press_keycode(4)
try:
@@ -573,7 +601,9 @@ class BaseView(object):
def get_public_key_and_username(self, return_username=False):
self.driver.info("Get public key and username")
profile_view = self.profile_button.click()
# profile_view = self.profile_button.click()
self.browser_tab.click() # temp, until profile is on browser tab
profile_view = self.get_profile_view()
default_username = profile_view.default_username_text.text
profile_view.share_my_profile_button.click()
profile_view.public_key_text.wait_for_visibility_of_element(20)
+32 -15
View File
@@ -151,14 +151,11 @@ class ChatElementByText(Text):
return TimeStampText(self.driver, self.locator)
@property
def timestamp_on_tap(self):
timestamp_element = Text(self.driver, xpath="//*[@content-desc='message-timestamp']")
try:
self.sent_status_checkmark.wait_for_element(30)
self.sent_status_checkmark.click_until_presence_of_element(timestamp_element)
return timestamp_element.text
except (NoSuchElementException, TimeoutException):
return None
def timestamp(self):
class TimeStampText(Button):
def __init__(self, driver, parent_locator: str):
super().__init__(driver, xpath="%s//*[@content-desc='message-timestamp']" % parent_locator)
return TimeStampText(self.driver, self.locator).text
@property
def member_photo(self):
@@ -228,21 +225,39 @@ class ChatElementByText(Text):
return RepliedToUsernameText(self.driver, self.message_locator).text
except NoSuchElementException:
return ''
# Old UI
# def emojis_below_message(self, emoji: str = 'thumbs-up', own=True):
# class EmojisNumber(Text):
# def __init__(self, driver, parent_locator: str):
# self.own = own
# self.emoji = emoji
# self.emojis_id = 'emoji-' + str(emojis[self.emoji]) + '-is-own-' + str(self.own).lower()
# super().__init__(driver, prefix=parent_locator, xpath="/../..//*[@content-desc='%s']" % self.emojis_id)
#
# @property
# def text(self):
# try:
# text = self.find_element().text
# self.driver.info("%s is '%s' for '%s' where my reaction is set on message is '%s'" % (self.name, text, self.emoji, str(self.own)))
# return text
# except NoSuchElementException:
# return 0
#
# return int(EmojisNumber(self.driver, self.locator).text)
def emojis_below_message(self, emoji: str = 'thumbs-up', own=True):
def emojis_below_message(self, emoji: str = 'thumbs-up'):
class EmojisNumber(Text):
def __init__(self, driver, parent_locator: str):
self.own = own
self.emoji = emoji
self.emojis_id = 'emoji-' + str(emojis[self.emoji]) + '-is-own-' + str(self.own).lower()
super().__init__(driver, prefix=parent_locator, xpath="/../..//*[@content-desc='%s']" % self.emojis_id)
self.emojis_id = 'emoji-reaction-%s' % str(emojis[self.emoji])
super().__init__(driver, prefix=parent_locator, xpath="/../..//*[@content-desc='%s']/android.widget.TextView" % self.emojis_id)
@property
def text(self):
try:
text = self.find_element().text
self.driver.info("%s is '%s' for '%s' where my reaction is set on message is '%s'" % (self.name, text, self.emoji, str(self.own)))
return text
self.driver.info("%s is '%s' for '%s'" % (self.name, text, self.emoji))
return int(text.strip())
except NoSuchElementException:
return 0
@@ -858,7 +873,9 @@ class ChatView(BaseView):
self.chat_element_by_text(message).long_press_element()
else:
self.element_by_text_part(message).long_press_element()
element = Button(self.driver, accessibility_id='pick-emoji-%s' % key)
# old UI
# element = Button(self.driver, accessibility_id='pick-emoji-%s' % key)
element = Button(self.driver, accessibility_id='emoji-picker-%s' % key)
element.click()
element.wait_for_invisibility_of_element()
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

+2 -1
View File
@@ -290,7 +290,8 @@ class HomeView(BaseView):
chat.public_key_edit_box.click()
chat.public_key_edit_box.send_keys(public_key)
one_to_one_chat = self.get_chat_view()
chat.confirm_until_presence_of_element(one_to_one_chat.chat_message_input)
chat.confirm()
# chat.confirm_until_presence_of_element(one_to_one_chat.chat_message_input)
if add_in_contacts and one_to_one_chat.add_to_contacts.is_element_displayed():
one_to_one_chat.add_to_contacts.click()
if nickname:
+11
View File
@@ -362,6 +362,17 @@ class ProfileView(BaseView):
from views.chat_view import ChatView
return ChatView(self.driver)
def add_contact_via_contacts_list(self, public_key):
self.driver.info("Adding user to Contacts via Profile > Contacts")
self.contacts_button.wait_and_click(30)
self.add_new_contact_button.wait_and_click()
chat = self.get_chat_view()
chat.public_key_edit_box.click()
chat.public_key_edit_box.send_keys(public_key)
chat.confirm_until_presence_of_element(self.add_new_contact_button)
self.click_system_back_button_until_element_is_shown()
def add_custom_network(self, rpc_url: str, name: str, symbol: str, netwrok_id:str):
self.driver.info("## Add custom network", device=False)
self.advanced_button.click()
+12 -7
View File
@@ -191,13 +191,18 @@ class SignInView(BaseView):
self.create_password_input.set_value(password)
self.confirm_your_password_input.set_value(password)
self.next_button.click()
self.maybe_later_button.wait_for_visibility_of_element(30)
if enable_notifications:
self.enable_notifications_button.click()
else:
self.maybe_later_button.click_until_presence_of_element(self.lets_go_button)
self.lets_go_button.click_until_absense_of_element(self.lets_go_button)
self.profile_button.wait_for_visibility_of_element(30)
# Old UI
# self.maybe_later_button.wait_for_visibility_of_element(30)
# if enable_notifications:
# self.enable_notifications_button.click()
# else:
# self.maybe_later_button.click_until_presence_of_element(self.lets_go_button)
# self.lets_go_button.click_until_absense_of_element(self.lets_go_button)
# self.profile_button.wait_for_visibility_of_element(30)
self.chats_tab.wait_for_visibility_of_element(30)
self.driver.info("## New multiaccount is created successfully!", device=False)
return self.get_home_view()
+20 -3
View File
@@ -1480,6 +1480,7 @@
"page-camera-request-blocked": "camera requests blocked. To enable camera requests go to Settings",
"nickname": "Nickname",
"add-nickname": "Add a nickname (optional)",
"edit-nickname": "Edit nickname",
"nickname-description": "Nicknames help you identify others in Status.\nOnly you can see the nicknames youve added",
"accept": "Accept",
"group-invite": "Group invite",
@@ -1510,6 +1511,8 @@
"name-optional": "Name (optional)",
"mute": "Mute",
"unmute": "Unmute",
"mute-chat": "Mute chat",
"unmute-chat": "Unmute chat",
"mute-community": "Mute community",
"unmute-community": "Unmute community",
"scan-tokens": "Scan tokens",
@@ -1573,7 +1576,7 @@
"master-account": "Master account",
"back-up": "Back up",
"key-on-device": "Private key is saved on this device",
"whats-trending": "See what`s trending",
"whats-trending": "See what's trending",
"seed-key-uid-mismatch": "Seed doesn't match",
"seed-key-uid-mismatch-desc-1": "The seed phrase you entered does not match {{multiaccount-name}}",
"seed-key-uid-mismatch-desc-2": "To manage keys for this account verify your seed phrase and try again.",
@@ -1775,7 +1778,7 @@
"new-ui": "New UI",
"send-contact-request-message": "To start a chat you need to become contacts",
"contact-request": "Contact request",
"contact-requests": "Contact Requests",
"contact-requests": "Contact requests",
"say-hi": "Say hi",
"opened": "Opened",
"accepted": "Accepted",
@@ -1810,6 +1813,9 @@
"pin-to-channel": "Pin to the channel",
"unpin-from-chat": "Unpin from the chat",
"unpin-from-channel": "Unpin from the channel",
"cannot-pin-title": "You can't pin this message!",
"cannot-pin-desc": "You can only pin a max of 3 messages.\nUnpin at least one to pin a new one.",
"view-pinned-messages": "View pinned messages",
"copy-text": "Copy text",
"edit-message": "Edit message",
"save-image-library": "Save image to library",
@@ -1819,8 +1825,19 @@
"admin": "Admin",
"replies": "Replies",
"identity-verification": "Identity verification",
"identity-verification-request": "Identity verification request",
"identity-verification-request-sent": "asked you",
"membership": "Membership",
"jump-to": "Jump to",
"blank-messages-text": "Your messages will be here",
"groups": "Groups"
"groups": "Groups",
"shell-placeholder-title": "Your apps will run here",
"shell-placeholder-subtitle": "Open tabs of your communities, messages,\nwallet account and browser windows",
"invite-friends-to-status": "Invite friends to status",
"share-invite-link": "Share an invite link",
"pending-requests": "Pending requests",
"received": "Received",
"sent": "Sent",
"no-pinned-messages-desc": "This chat doesn't have any pinned messages.",
"no-pinned-messages-community-desc": "This channel doesn't have any pinned messages."
}