Compare commits

...
23 Commits
Author SHA1 Message Date
ibrahem a8549c7912 Renaming 2022-11-07 12:23:08 +03:00
ibrahem 8fc7421bef comments 2022-11-07 12:23:08 +03:00
ibrahem 1050b57de8 Comments 2022-11-07 12:23:08 +03:00
ibrahem 936168b6df Conflicts 2022-11-07 12:23:08 +03:00
Ibrahem Khalil 3b69e5873e Add members key to the chat-preview flow to show count of members on chat preview (#14274) 2022-11-03 12:10:01 +03:00
Andrej Česen 5e5ed0a11e Add code-snippet component (#14275) 2022-11-03 10:55:33 +03:00
Ibrahem Khalil bca2258508 Format numbers helper (#14186)
format number to 100000 -> 100k/ 1000000000 -> 1b/ 1000000 -> 1m
2022-11-03 10:33:34 +03:00
flexsurfer 7b739dff45 New icons (icons2) not working in token-tag component #14277 (#14280) 2022-11-01 14:58:33 +01: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
114 changed files with 3266 additions and 1679 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',
+6
View File
@@ -41,6 +41,8 @@
65F6941A25780A4F00A45E76 /* Bridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65F6941825780A4F00A45E76 /* Bridge.swift */; };
65F6941B25780A4F00A45E76 /* Bridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65F6941825780A4F00A45E76 /* Bridge.swift */; };
70ADBB5ECF934DCF8A0E4919 /* Inter-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = 1426DF592BA248FC81D955CB /* Inter-Regular.otf */; };
715D8132290BE850006F5C88 /* UbuntuMono-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 715D8131290BE850006F5C88 /* UbuntuMono-Regular.ttf */; };
715D8133290BE850006F5C88 /* UbuntuMono-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 715D8131290BE850006F5C88 /* UbuntuMono-Regular.ttf */; };
74B758FC20D7C00B003343C3 /* launch-image-universal.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 74B758FB20D7C00B003343C3 /* launch-image-universal.storyboard */; };
8391E8E0E93C41A98AAA6631 /* Inter-SemiBoldItalic.otf in Resources */ = {isa = PBXBuildFile; fileRef = A4F2BBE8D4DD4140A6CCAC39 /* Inter-SemiBoldItalic.otf */; };
A0093AB50DA85E1D01818CCA /* libPods-Status-StatusIm-StatusImTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1E88D313E5D854BE6BC81FC1 /* libPods-Status-StatusIm-StatusImTests.a */; };
@@ -121,6 +123,7 @@
65F6941725780A4E00A45E76 /* StatusImTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "StatusImTests-Bridging-Header.h"; sourceTree = "<group>"; };
65F6941825780A4F00A45E76 /* Bridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bridge.swift; sourceTree = "<group>"; };
693A62DB37BC4CD5A30E5C96 /* Inter-SemiBold.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Inter-SemiBold.otf"; path = "../resources/fonts/Inter-SemiBold.otf"; sourceTree = "<group>"; };
715D8131290BE850006F5C88 /* UbuntuMono-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "UbuntuMono-Regular.ttf"; path = "../resources/fonts/UbuntuMono-Regular.ttf"; sourceTree = "<group>"; };
74B758FB20D7C00B003343C3 /* launch-image-universal.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = "launch-image-universal.storyboard"; sourceTree = "<group>"; };
7AFBE28450C0239119FBDBA2 /* Pods-Status-StatusIm-StatusImTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Status-StatusIm-StatusImTests.debug.xcconfig"; path = "Target Support Files/Pods-Status-StatusIm-StatusImTests/Pods-Status-StatusIm-StatusImTests.debug.xcconfig"; sourceTree = "<group>"; };
8B9A886A2CB448B1ABA0EB62 /* libc++.tbd */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; };
@@ -229,6 +232,7 @@
693A62DB37BC4CD5A30E5C96 /* Inter-SemiBold.otf */,
A4F2BBE8D4DD4140A6CCAC39 /* Inter-SemiBoldItalic.otf */,
9C76AF5A418D4D65A4CAD1D9 /* InterStatus-Regular.otf */,
715D8131290BE850006F5C88 /* UbuntuMono-Regular.ttf */,
);
name = Resources;
sourceTree = "<group>";
@@ -475,6 +479,7 @@
buildActionMask = 2147483647;
files = (
74B758FC20D7C00B003343C3 /* launch-image-universal.storyboard in Resources */,
715D8132290BE850006F5C88 /* UbuntuMono-Regular.ttf in Resources */,
B2F2D1BC1D9D531B00B7B453 /* Images.xcassets in Resources */,
D84616FB563A48EBB1678699 /* Inter-Bold.otf in Resources */,
D99C50E5E18942A39C8DDF61 /* Inter-BoldItalic.otf in Resources */,
@@ -493,6 +498,7 @@
buildActionMask = 2147483647;
files = (
3AAD2ACA24A3A60E0075D594 /* launch-image-universal.storyboard in Resources */,
715D8133290BE850006F5C88 /* UbuntuMono-Regular.ttf in Resources */,
3AAD2ACB24A3A60E0075D594 /* Images.xcassets in Resources */,
3AAD2ACC24A3A60E0075D594 /* Inter-Bold.otf in Resources */,
3AAD2ACD24A3A60E0075D594 /* Inter-BoldItalic.otf in Resources */,
+1
View File
@@ -105,6 +105,7 @@
<string>Inter-Thin-BETA.otf</string>
<string>Inter-ThinItalic-BETA.otf</string>
<string>InterStatus-Regular.otf</string>
<string>UbuntuMono-Regular.ttf</string>
</array>
<key>UIBackgroundModes</key>
<array>
+1
View File
@@ -108,6 +108,7 @@
<string>Inter-Thin-BETA.otf</string>
<string>Inter-ThinItalic-BETA.otf</string>
<string>InterStatus-Regular.otf</string>
<string>UbuntuMono-Regular.ttf</string>
</array>
<key>UIBackgroundModes</key>
<array>
+1
View File
@@ -70,6 +70,7 @@
"react-native-svg": "^9.8.4",
"react-native-touch-id": "^4.4.1",
"react-native-webview": "git+https://github.com/status-im/react-native-webview.git#refs/tags/v11.16.0-status",
"react-syntax-highlighter": "^15.5.0",
"rn-emoji-keyboard": "git+https://github.com/status-im/rn-emoji-keyboard.git#refs/tags/v0.4.3",
"tdigest": "^0.1.1"
},
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 706 B

After

Width:  |  Height:  |  Size: 246 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 972 B

After

Width:  |  Height:  |  Size: 286 B

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)))
+60 -44
View File
@@ -4,40 +4,50 @@
[quo.haptic :as haptic]
[quo.gesture-handler :as gh]
[quo.design-system.spacing :as spacing]
[quo.design-system.colors :as colors]
[quo.components.text :as text]
[quo.design-system.colors :as quo.colors]
[clojure.string :as string]
[quo.components.controls.view :as controls]
[quo.components.tooltip :as tooltip]
[quo2.components.markdown.text :as text]
[status-im.ui.components.icons.icons :as icons]
[quo2.foundations.colors :as colors]
[quo2.components.icon :as quo2.icons]
[quo.components.animated.pressable :as animated]))
(defn- icons-lib [icon-name props]
(if (-> icon-name
namespace
(string/includes? "main-icons2"))
[quo2.icons/icon icon-name props]
[icons/icon icon-name props]))
(defn themes [theme]
(case theme
:main {:icon-color (:icon-04 @colors/theme)
:icon-bg-color (:interactive-02 @colors/theme)
:active-background (:interactive-02 @colors/theme)
:passive-background (:ui-background @colors/theme)
:text-color (:text-01 @colors/theme)}
:accent {:icon-color (:icon-04 @colors/theme)
:icon-bg-color (:interactive-02 @colors/theme)
:active-background (:interactive-02 @colors/theme)
:passive-background (:ui-background @colors/theme)
:text-color (:text-04 @colors/theme)}
:negative {:icon-color (:negative-01 @colors/theme)
:icon-bg-color (:negative-02 @colors/theme)
:active-background (:negative-02 @colors/theme)
:passive-background (:ui-background @colors/theme)
:text-color (:negative-01 @colors/theme)}
:positive {:icon-color (:positive-01 @colors/theme)
:icon-bg-color (:positive-02 @colors/theme)
:active-background (:positive-02 @colors/theme)
:passive-background (:ui-background @colors/theme)
:text-color (:positive-01 @colors/theme)}
:disabled {:icon-color (:icon-02 @colors/theme)
:icon-bg-color (:ui-01 @colors/theme)
:active-background (:ui-01 @colors/theme)
:passive-background (:ui-background @colors/theme)
:text-color (:text-02 @colors/theme)}))
:main {:icon-color (:icon-04 @quo.colors/theme)
:icon-bg-color (:interactive-02 @quo.colors/theme)
:active-background (:interactive-02 @quo.colors/theme)
:passive-background (:ui-background @quo.colors/theme)
:text-color (:text-01 @quo.colors/theme)}
:accent {:icon-color (:icon-04 @quo.colors/theme)
:icon-bg-color (:interactive-02 @quo.colors/theme)
:active-background (:interactive-02 @quo.colors/theme)
:passive-background (:ui-background @quo.colors/theme)
:text-color (:text-04 @quo.colors/theme)}
:negative {:icon-color (:negative-01 @quo.colors/theme)
:icon-bg-color (:negative-02 @quo.colors/theme)
:active-background (:negative-02 @quo.colors/theme)
:passive-background (:ui-background @quo.colors/theme)
:text-color (:negative-01 @quo.colors/theme)}
:positive {:icon-color (:positive-01 @quo.colors/theme)
:icon-bg-color (:positive-02 @quo.colors/theme)
:active-background (:positive-02 @quo.colors/theme)
:passive-background (:ui-background @quo.colors/theme)
:text-color (:positive-01 @quo.colors/theme)}
:disabled {:icon-color (:icon-02 @quo.colors/theme)
:icon-bg-color (:ui-01 @quo.colors/theme)
:active-background (:ui-01 @quo.colors/theme)
:passive-background (:ui-background @quo.colors/theme)
:text-color (:text-02 @quo.colors/theme)}))
(defn size->icon-size [size]
(case size
@@ -51,13 +61,13 @@
(defn size->single-title-size [size]
(case size
:small :base
:large))
:small :paragraph-1
:paragraph-1))
(defn container [{:keys [size container-style]} & children]
(into [rn/view {:style (merge (:tiny spacing/padding-horizontal)
(defn container [{:keys [size container-style container-padding-horizontal container-padding-vertical]} & children]
(into [rn/view {:style (merge (or container-padding-horizontal (:tiny spacing/padding-horizontal))
{:min-height (size->container-size size)
:padding-vertical 8
:padding-vertical (or container-padding-vertical 8)
:flex-direction :row
:align-items :center
:justify-content :space-between} container-style)}]
@@ -78,17 +88,18 @@
:justify-content :center
:border-radius (/ icon-size 2)
:background-color icon-bg-color}}
[icons/icon icon {:color icon-color}]])])))
(icons-lib icon {:color icon-color})])])))
(defn title-column
[{:keys [title text-color subtitle subtitle-max-lines subtitle-secondary
title-accessibility-label size text-size title-text-weight
right-side-present?]}]
[rn/view {:style (merge (:tiny spacing/padding-horizontal)
right-side-present? title-column-style]}]
[rn/view {:style (or title-column-style
(merge (:tiny spacing/padding-horizontal)
;; make left-side title grow if nothing is present on right-side
(when-not right-side-present?
{:flex 1
:justify-content :center}))}
(when-not right-side-present?
{:flex 1
:justify-content :center})))}
(cond
(and title subtitle)
@@ -109,6 +120,7 @@
:weight :regular
:color :secondary
:ellipsize-mode :tail
:secondary-color colors/neutral-50
:number-of-lines subtitle-max-lines
:size text-size}
subtitle]
@@ -116,6 +128,7 @@
:weight :regular
:color :secondary
:ellipsize-mode :middle
:secondary-color colors/neutral-50
:number-of-lines subtitle-max-lines
:size text-size}
"•"]
@@ -123,6 +136,7 @@
:weight :regular
:color :secondary
:ellipsize-mode :middle
:secondary-color colors/neutral-50
:number-of-lines subtitle-max-lines
:size text-size}
subtitle-secondary]]
@@ -130,6 +144,7 @@
[text/text {:weight :regular
:color :secondary
:ellipsize-mode :tail
:secondary-color colors/neutral-50
:number-of-lines subtitle-max-lines
:size text-size}
subtitle]
@@ -184,19 +199,19 @@
accessory)]
(when (and chevron platform/ios?)
[rn/view {:style {:padding-right (:tiny spacing/spacing)}}
[icons/icon :main-icons/next {:container-style {:opacity 0.4
(icons-lib :main-icons/next {:container-style {:opacity 0.4
:align-items :center
:justify-content :center}
:resize-mode :center
:color (:icon-02 @colors/theme)}]])]))
:color (:icon-02 @quo.colors/theme)})])]))
(defn list-item
[{:keys [theme accessory disabled subtitle-max-lines icon icon-container-style
left-side-alignment icon-color icon-bg-color
left-side-alignment icon-color icon-bg-color title-column-style
title subtitle subtitle-secondary active on-press on-long-press chevron size text-size
accessory-text accessibility-label title-accessibility-label accessory-style
haptic-feedback haptic-type error animated animated-accessory? title-text-weight container-style
active-background-enabled background-color]
active-background-enabled background-color container-padding-horizontal container-padding-vertical]
:or {subtitle-max-lines 1
theme :main
haptic-feedback true
@@ -236,7 +251,7 @@
{:on-long-press (fn []
(optional-haptic)
(on-long-press))}))
[container {:size size :container-style container-style}
[container {:size size :container-style container-style :container-padding-vertical container-padding-vertical :container-padding-horizontal container-padding-horizontal }
[left-side {:icon-color icon-color
:text-color (if on-press
text-color
@@ -253,7 +268,8 @@
:subtitle subtitle
:subtitle-max-lines subtitle-max-lines
:subtitle-secondary subtitle-secondary
:right-side-present? (or accessory chevron)}]
:right-side-present? (or accessory chevron)
:title-column-style title-column-style}]
[right-side {:chevron chevron
:active active
:disabled disabled
+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
+154
View File
@@ -0,0 +1,154 @@
(ns quo2.components.code.snippet
(:require ["react-native" :as react-native]
["react-syntax-highlighter" :default Highlighter]
[cljs-bean.core :as bean]
[clojure.string :as str]
[oops.core :as oops]
[quo.react-native :as rn]
[quo.theme :as theme]
[quo2.components.buttons.button :as button]
[quo2.components.markdown.text :as text]
[quo2.foundations.colors :as colors]
[reagent.core :as reagent]
[status-im.ui.components.react :as react]))
;; Example themes:
;; https://github.com/react-syntax-highlighter/react-syntax-highlighter/tree/master/src/styles/hljs
(def ^:private themes
{:light {:hljs-comment {:color colors/neutral-40},
:hljs-title {:color (colors/custom-color :blue 50)},
:hljs-keyword {:color (colors/custom-color :green 50)},
:hljs-string {:color (colors/custom-color :turquoise 50)},
:hljs-literal {:color (colors/custom-color :turquoise 50)}
:hljs-number {:color (colors/custom-color :turquoise 50)}
:line-number {:color colors/neutral-40}}
:dark {:hljs-comment {:color colors/neutral-60},
:hljs-title {:color (colors/custom-color :blue 60)},
:hljs-keyword {:color (colors/custom-color :green 60)},
:hljs-string {:color (colors/custom-color :turquoise 60)},
:hljs-literal {:color (colors/custom-color :turquoise 60)}
:hljs-number {:color (colors/custom-color :turquoise 60)}
:line-number {:color colors/neutral-40}}})
(defn- text-style [class-names]
(->> class-names
(map keyword)
(reduce #(merge %1 (get-in themes [(theme/get-theme) %2]))
{:flex-shrink 1
;; Round to a nearest whole number to achieve consistent
;; spacing (also important for calculating `max-text-height`).
;; Line height seems to be inconsistent between text being
;; wrapped and text being rendered on a newline using Flexbox
;; layout.
:line-height 18})))
(defn- render-nodes [nodes]
(map (fn [{:keys [children value] :as node}]
;; Node can have :children or a :value.
(if children
(into [text/text {:weight :code
:size :paragraph-2
:style (text-style (get-in node [:properties :className]))}]
(render-nodes children))
;; Remove newlines as we already render each line separately.
(-> value str/trim-newline)))
nodes))
(defn- code-block [{:keys [rows line-number-width]}]
[into [:<>]
(->> rows
(render-nodes)
;; Line numbers
(map-indexed (fn [idx row]
(conj [rn/view {:style {:flex-direction :row}}
[rn/view {:style {:width line-number-width
;; 8+12 margin
:margin-right 20}}
[text/text {:weight :code
:size :paragraph-2
:style (text-style ["line-number"])}
(inc idx)]]]
row))))])
(defn- native-renderer []
(let [text-height (reagent/atom nil)]
(fn [{:keys [rows max-lines on-copy-press]}]
(let [background-color (colors/theme-colors
colors/white
colors/neutral-80-opa-40)
background-color-left (colors/theme-colors
colors/neutral-5
colors/neutral-80)
border-color (colors/theme-colors
colors/neutral-20
colors/neutral-80)
rows (bean/->clj rows)
font-scale (:font-scale (rn/use-window-dimensions))
max-rows (or max-lines (count rows)) ;; Cut down on rows to process.
max-line-digits (-> rows count (min max-rows) str count)
;; ~ 9 is char width, 18 is width used in Figma.
line-number-width (* font-scale (max 18 (* 9 max-line-digits)))
max-text-height (some-> max-lines (* font-scale 18)) ;; 18 is font's line height.
truncated? (and max-text-height (< max-text-height @text-height))
maybe-mask-wrapper (if truncated?
[react/masked-view
{:mask-element
(reagent/as-element
[react/linear-gradient {:colors ["black" "transparent"]
:locations [0.75 1]
:style {:flex 1}}])}]
[:<>])]
[rn/view {:style {:overflow :hidden
:padding 8
:background-color background-color
:border-color border-color
:border-width 1
:border-radius 8
;; Hide on intial render to avoid flicker when mask-wrapper is shown.
:opacity (if @text-height 1 0)}}
;; Line number container
[rn/view {:style {:position :absolute
:bottom 0
:top 0
:left 0
:width (+ line-number-width 8 8)
:background-color background-color-left
:border-right-color border-color
:border-right-width 1}}]
(conj maybe-mask-wrapper [rn/view {:max-height max-text-height}
[rn/view {:on-layout (fn [evt]
(let [height (oops/oget evt "nativeEvent.layout.height")]
(reset! text-height height)))}
[code-block {:rows (take max-rows rows)
:line-number-width line-number-width}]]])
;; Copy button
[rn/view {:style {:position :absolute
:bottom 8
:right 8}}
[button/button {:icon true
:type :grey
:size 24
:on-press on-copy-press}
:main-icons/copy]]]))))
(defn- wrap-renderer-fn [f {:keys [max-lines on-copy-press]}]
(fn [^js props]
(reagent/as-element [:f> f {:rows (.-rows props)
:max-lines max-lines
:on-copy-press on-copy-press}])))
(defn snippet [{:keys [language max-lines on-copy-press]} children]
[:> Highlighter {:language language
:renderer (wrap-renderer-fn
native-renderer {:max-lines max-lines
:on-copy-press #(when on-copy-press
(on-copy-press children))})
;; Default props to adapt Highlighter for react-native.
:CodeTag react-native/View
:PreTag react-native/View
:show-line-numbers false
:style #js {}
:custom-style #js {:backgroundColor nil}}
children])
@@ -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)]]]]]))
+4 -2
View File
@@ -5,13 +5,14 @@
[quo2.foundations.typography :as typography]
[reagent.core :as reagent]))
(defn text-style [{:keys [size align weight style]}]
(defn text-style [{:keys [size align weight style secondary-color]}]
(merge (case (or weight :regular)
:regular typography/font-regular
:medium typography/font-medium
:semi-bold typography/font-semi-bold
:bold typography/font-bold
:monospace typography/monospace
:code typography/code
:inherit nil)
(case (or size :paragraph-1)
:label typography/label
@@ -23,7 +24,8 @@
{:text-align (or align :auto)}
(if (:color style)
style
(assoc style :color (if (= (theme/get-theme) :dark) colors/white colors/neutral-100)))))
(assoc style :color (or secondary-color
(colors/theme-colors colors/neutral-100 colors/white))))))
(defn text []
(let [this (reagent/current-component)
@@ -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]])})])))))
+10 -14
View File
@@ -3,7 +3,7 @@
[quo.react-native :as rn]
[quo.theme :as theme]
[quo2.components.markdown.text :as text]
[status-im.ui.components.icons.icons :as icons]))
[quo2.components.icon :as icons]))
(def themes {:light {:background-color colors/neutral-20}
:dark {:background-color colors/neutral-80}})
@@ -76,23 +76,19 @@
:or
{size :small border-color (colors/custom-color-by-theme :purple 50 60)}}]
[tag {:size size
[tag {:size size
:token-img-src token-img-src
:border-color (when is-required border-color)
:border-color (when is-required border-color)
:overlay
(when (or is-required is-purchasable)
[rn/view
{:style (merge icon-container-styles
{:width 15.5
:height 15.5
:background-color border-color
:border-color (if (= (theme/get-theme) :dark) colors/neutral-100 colors/white)
:border-width 1
:right (get-value-from-size size -3.75 -5.75)
:bottom (get-value-from-size size (- 32 7.75 4) (- 24 7.75 2)) ; (- height (icon-height/2) spacing)
})}
[icons/icon (if is-required :main-icons2/required-checkmark12 :main-icons2/purchasable12)
{:background-color border-color
:border-color (if (= (theme/get-theme) :dark) colors/neutral-100 colors/white)
:border-width 1
:right (get-value-from-size size -3.75 -5.75)
:bottom (get-value-from-size size (- 32 7.75 4) (- 24 7.75 2))})}
[icons/icon (if is-required :main-icons2/hold :main-icons2/add)
{:no-color true
:width 13.5
:height 13.5}]])}
:size 12}]])}
(str value " " token)]))
+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
+2
View File
@@ -45,3 +45,5 @@
(def font-bold {:font-family "Inter-Bold"}) ; 700
(def monospace {:font-family "InterStatus-Regular"})
(def code {:font-family "UbuntuMono-Regular"})
+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
+92
View File
@@ -0,0 +1,92 @@
(ns quo2.screens.code.snippet
(:require [quo.previews.preview :as preview]
[quo.react-native :as rn]
[quo2.components.code.snippet :as snippet]
[quo2.foundations.colors :as colors]
[reagent.core :as reagent]))
(def go-example "func (s *Server) listenAndServe() {
cfg := &tls.Config{Certificates: []tls.Certificate{*s.cert}, ServerName: s.hostname, MinVersion: tls.VersionTLS12}
// in case of restart, we should use the same port as the first start in order not to break existing links
listener, err := tls.Listen(\"tcp\", s.getHost(), cfg)
if err != nil {
s.logger.Error(\"failed to start server, retrying\", zap.Error(err))
s.ResetPort()
err = s.Start()
if err != nil {
s.logger.Error(\"server start failed, giving up\", zap.Error(err))
}
return
}
err = s.SetPort(listener.Addr().(*net.TCPAddr).Port)
if err != nil {
s.logger.Error(\"failed to set Server.port\", zap.Error(err))
return
}
}")
(def clojure-example "(defn request->xhrio-options
[{:as request
:keys [on-success on-failure]
:or {on-success [:http-no-on-success]
on-failure [:http-no-on-failure]}}]
; wrap events in cljs-ajax callback
(let [api (new goog.net.XhrIo)]
(-> request
(assoc
:api api
:handler (partial ajax-xhrio-handler
#(dispatch (conj on-success %))
#(dispatch (conj on-failure %))
api))
(dissoc :on-success :on-failure :on-request))))")
(def examples {:clojure {:language :clojure
:text clojure-example}
:go {:language :go
:text go-example}})
(def descriptor [{:label "Language:"
:key :language
:type :select
:options [{:key :clojure
:value :clojure}
{:key :go
:value :go}]}
{:label "Max lines:"
:key :max-lines
:type :text}
{:label "Syntax highlight:"
:key :syntax
:type :boolean}])
(defn cool-preview []
(let [state (reagent/atom {:language :clojure
:max-lines ""
:syntax true})]
(fn []
(let [language (if (:syntax @state) (:language @state) :text)
text (-> (:language @state) examples :text)
max-lines (as-> (:max-lines @state) max-lines
(js/parseInt max-lines)
(when-not (js/Number.isNaN max-lines)
max-lines))]
[rn/touchable-without-feedback {:on-press rn/dismiss-keyboard!}
[rn/view {:style {:padding-bottom 150}}
[preview/customizer state descriptor]
[rn/view {:style {:padding-vertical 60
:padding-horizontal 16}}
[snippet/snippet {:language language
:max-lines max-lines
:on-copy-press #(js/alert %)}
text]]]]))))
(defn preview-code-snippet []
[rn/view {:background-color (colors/theme-colors colors/white colors/neutral-90)
:flex 1}
[rn/flat-list {:flex 1
:keyboardShouldPersistTaps :always
:header [cool-preview]
:key-fn str}]])
+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
+4
View File
@@ -14,6 +14,7 @@
[quo2.screens.avatars.wallet-user-avatar :as wallet-user-avatar]
[quo2.screens.buttons.button :as button]
[quo2.screens.buttons.dynamic-button :as dynamic-button]
[quo2.screens.code.snippet :as code-snippet]
[quo2.screens.counter.counter :as counter]
[quo2.screens.community.community-card-view :as community-card]
[quo2.screens.dividers.divider-label :as divider-label]
@@ -76,6 +77,9 @@
{:name :dynamic-button
:insets {:top false}
:component dynamic-button/preview-dynamic-button}]
:code [{:name :snippet
:insets {:top false}
:component code-snippet/preview-code-snippet}]
:community [{:name :community-cards
:insets {:top false}
:component community-card/preview-community-card}]
+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])))))))
+11 -10
View File
@@ -73,15 +73,15 @@
(dissoc :members)))
(defn <-rpc-js [^js chat]
(-> {:name (.-name chat)
:description (.-description chat)
:color (.-color chat)
:emoji (.-emoji chat)
:timestamp (.-timestamp chat)
:alias (.-alias chat)
:identicon (.-identicon chat)
:muted (.-muted chat)
:joined (.-joined chat)
(-> {:name (.-name chat)
:description (.-description chat)
:color (.-color chat)
:emoji (.-emoji chat)
:timestamp (.-timestamp chat)
:alias (.-alias chat)
:identicon (.-identicon chat)
:muted (.-muted chat)
:joined (.-joined chat)
:chat-id (.-id chat)
:community-id (.-communityId chat)
@@ -98,7 +98,8 @@
:last-clock-value (.-lastClockValue chat)
:profile-public-key (.-profile chat)
:highlight (.-highlight chat)
:active (.-active chat)}
:active (.-active chat)
:members (types/js->clj (.-members chat))}
rpc->type
unmarshal-members))
+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"
+3 -2
View File
@@ -3,8 +3,9 @@
(def members-added-type 3)
(defn member?
[public-key {:keys [contacts]}]
(contains? contacts public-key))
[public-key {:keys [members contacts users]}]
(let [members-list (into #{} (concat (keys users) contacts (map #(:id %) members)))]
(contains? members-list public-key)))
(defn invited?
[my-public-key {:keys [contacts]}]
+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}]]))])
@@ -28,6 +28,9 @@
(= view :add-new)
(merge home.sheet/add-new)
(= view :add-new-sheet-view)
(merge home.sheet/add-new-sheet)
(= view :keycard.login/more)
(merge keycard/more-sheet)
@@ -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
+34 -1
View File
@@ -5,7 +5,9 @@
[quo.core :as quo]
[status-im.i18n.i18n :as i18n]
[status-im.utils.config :as config]
[status-im.ui.components.invite.views :as invite]))
[status-im.ui.components.invite.views :as invite]
[quo.theme :as theme]
[quo2.foundations.colors :as colors]))
(defn hide-sheet-and-dispatch [event]
(re-frame/dispatch [:bottom-sheet/hide])
@@ -57,5 +59,36 @@
[invite/list-item
{:accessibility-label :chats-menu-invite-friends-button}]])
(defn add-new-sheet-view []
[react/view
[quo/list-item
{:theme :main
:title (i18n/label :t/new-chat)
:icon-bg-color :transparent
:icon-container-style {:padding-horizontal 0}
:container-padding-horizontal {:padding-horizontal 4}
:container-padding-vertical 12
:title-column-style {:margin-left 2}
:icon-color (colors/theme-colors colors/neutral-50 colors/neutral-40)
:accessibility-label :start-a-new-chat
:icon :main-icons2/new-message
:on-press #(hide-sheet-and-dispatch [:open-modal :new-chat])}]
[quo/list-item
{:theme :main
:title (i18n/label :t/add-a-contact)
:icon-bg-color :transparent
:icon-container-style {:padding-horizontal 0}
:container-padding-horizontal {:padding-horizontal 4}
:container-padding-vertical 12
:title-column-style {:margin-left 2}
:icon-color (colors/theme-colors colors/neutral-50 colors/neutral-40)
:accessibility-label :add-a-contact
:subtitle (i18n/label :t/enter-a-chat-key)
:icon :main-icons2/add-user
:on-press #(hide-sheet-and-dispatch [:open-modal :new-contact])}]])
(def add-new-sheet
{:content add-new-sheet-view})
(def add-new
{:content add-new-view})
+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,8 +204,11 @@
(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
background-color (when highlight (colors/get-color :interactive-02))]
(let [{:keys [chat-id chat-name color group-chat muted emoji highlight edit? public? unviewed-messages-count contacts users members]} home-item
background-color (when highlight (colors/get-color :interactive-02))
group-members-public-keys (->> (concat (keys users) contacts (map #(:id %) members))
(into #{})
(remove nil?))]
[react/touchable-opacity (merge {:style {:height 64 :background-color background-color}} opts)
[:<>
(when (pos? unviewed-messages-count)
@@ -245,7 +248,7 @@
:margin-right 4}}]
[quo2.text/text {:weight :medium
:style {:color (quo2.colors/theme-colors quo2.colors/neutral-50 quo2.colors/neutral-40)}}
(i18n/label :t/members-count {:count (count contacts)})]]
(i18n/label :t/members-count {:count (count group-members-public-keys)})]]
[quo2.text/text {:monospace true
:weight :medium
:style {:color (quo2.colors/theme-colors quo2.colors/neutral-50 quo2.colors/neutral-40)}
+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)
+192 -76
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?]]
@@ -220,67 +323,80 @@
(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?
:accessibility-label :new-chat-button}]))
[components.plus-button/plus-button
{: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 plus-button-new-messages []
(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-sheet-view {}]))
: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)}
:background-color (colors/theme-colors colors/neutral-5 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)
: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}
@@ -295,7 +411,7 @@
:margin-top 15
:margin-bottom 8}
[quo2.text/text {:size :heading-1 :weight :semi-bold} (i18n/label :t/messages)]
[plus-button]]
[plus-button-new-messages]]
[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
+61 -1
View File
@@ -1,4 +1,6 @@
(ns status-im.utils.number)
(ns status-im.utils.number
(:require [goog.string :as gstring]
[goog.string.format]))
(defn naive-round
"Quickly and naively round number `n` up to `decimal-places`.
@@ -13,3 +15,61 @@
(let [scale (Math/pow 10 decimal-places)]
(/ (Math/round (* n scale))
scale)))
(defn- caught-invalid-number []
(throw (js/Error "Invalid Number")))
(defn- with-precision [precision input-number]
(gstring/format (str "%." precision "f") input-number))
(defn- is-bad-number [number-string]
(nil? (re-find #"^\d+(\.\d{1,9})?$" number-string)))
(defn- with-precision-division [numerator-num denominator-num precision]
(->> (/ numerator-num denominator-num)
(with-precision precision)))
(defn- number->formatted-number [number-string precision]
(let [parsed-number (js/parseInt number-string 10)
numeric-value (if (or (neg? number-string)
(is-bad-number (str number-string)))
(caught-invalid-number)
parsed-number)
million 1000000
billion 1000000000
>=-and-< #(and
(>= %1 %2)
(< %1 %3))
unit (cond
(>=-and-< parsed-number billion js/Number.MAX_SAFE_INTEGER) "b"
(>=-and-< parsed-number million billion) "m"
:else
"k")
denominator (cond
(= unit "m") million
(= unit "b") billion
:else 1000)]
(if (js/isNaN numeric-value)
(caught-invalid-number)
(if (>= numeric-value 1000)
(str (with-precision-division numeric-value denominator precision)
unit)
(str numeric-value)))))
(defn format-number
"Returns the thousands in a number in kilo format
Arguments:
number-string (or string int) The input of value of the number
Examples:
[1000 0] -> 1k
[1001 0] -> 1k
[10000 0] -> 10k
[5000 0] -> 5k
[5000 0] -> 5k
[5000000 0] -> 5m
[5000 0] -> 5k"
[number-string precision]
(number->formatted-number number-string precision))
+27
View File
@@ -0,0 +1,27 @@
(ns status-im.utils.number-test
(:require [cljs.test :refer-macros [deftest testing are]]
[status-im.utils.number :as number]))
(deftest format-number-test
(testing "Positive cases"
(are [input-number precision expected] (= (number/format-number input-number precision) expected)
"100" 0 "100"
"1000" 0 "1k"
"10000" 0 "10k"
"11000" 1 "11.0k"
"11000" 0 "11k"
"11010" 3 "11.010k"
"5200000" 3 "5.200m"
"1000000000" 0 "1b"
"1110000000" 2 "1.11b"
"9000000" 0 "9m"))
(testing "Negative cases"
(are [input-number precision] (thrown-with-msg? js/Error #"Invalid Number" (number/format-number input-number precision))
js/undefined 0
nil 0
"-1" 0
"" 0
js/NaN 0
"1e3" 0
"10,2" 0
"6hello" 0)))

Some files were not shown because too many files have changed in this diff Show More