Compare commits

..
Author SHA1 Message Date
pavloburykh 584be05f3e e2e: fix 702745 2022-11-22 18:42:12 +02:00
jakub 7dccbf2395 ci: separate buckets for mobile and desktop builds
Possible fix for slow upload speeds and failures caused by most probably
hitting per-bucket rate limits of DigitalOcean:

>- 500 total operations per second to any individual bucket.
>- 300 combined PUT, POST, COPY, DELETE, and LIST operations per second to any individual Space. We may further limit LIST operations if necessary under periods of high load.y

https://docs.digitalocean.com/products/spaces/details/limits/#rate-limits

Depends on: https://github.com/status-im/status-jenkins-lib/pull/52

Signed-off-by: Jakub Sokołowski <jakub@status.im>
2022-11-22 16:55:59 +01:00
Brian Sztamfater 4a87122336 feat: author component
Signed-off-by: Brian Sztamfater <brian@status.im>
2022-11-21 15:46:56 -03:00
Roman Volosovskyi d18c3ad102 [#14381] Wrap texts with view to ensure separate accessibility labels 2022-11-21 15:40:29 +01:00
Icaro Motta e53f58d8c0 Fix various Activity Log layout issues (#14405) 2022-11-21 11:19:50 -03:00
Omar Basem 5492fb472f Group Details Screen (#14377)
* feat: group details screen
2022-11-21 16:03:49 +04:00
flexsurfer c08d58577d move quo preview (#14409) 2022-11-21 11:03:07 +01:00
Yevheniia Berdnyk 946640339a e2e: pin messages in 1-1 and group chats 2022-11-21 06:49:53 +02:00
Jamie CapraniandErik Seppanen 72d43ba745 Visual tests setup (#14329)
feat: configuration setup for visual tests

Co-authored-by: Erik Seppanen <esep@protonmail.com>
2022-11-20 15:46:04 -08:00
Icaro Motta f4d9162ff8 Fix reconciliation for new filter type :all (#14401) 2022-11-18 06:15:39 -03:00
Omar Basem 73bbfa7d31 Fix Metro Warnings ⚠️ (#14389)
* fix: metro warnings ⚠️
2022-11-17 21:19:54 +04:00
Mohamed Javid 84da3128bf [Fix] Activity Centre UI bug (#14398)
* [Fix] Activity Centre UI bug

* [Fix] Namespace update for config

* [Chore] Moved navigation to Activity Center as reframe event

* [Chore] Code Format
2022-11-18 00:15:49 +08:00
flexsurfer 3777ebc36e move communities 2 (#14394) 2022-11-17 16:43:02 +01:00
Roman Volosovskyi 4c6b4e2d02 [#14388] Re-enable bug reporting 2022-11-17 15:01:45 +01:00
Roman Volosovskyi e469d97702 [#14372] Fix message content type on editing 2022-11-17 08:23:53 +01:00
Roman Volosovskyi cf753a0892 [#14381] Add accessibility lables for chat key and timestamp in message 2022-11-17 07:16:30 +01:00
Icaro Motta 0f7ccce3df Implement identity verification flow (#14365) 2022-11-16 16:46:04 -03:00
Mohamed Javid 509ffc2a01 [Feature] Added messages skeleton (#14072)
* [Feature][#14025] Added Messages Skeleton while loading messages

* [Improvements][#14025] Added animation to message skeleton

* [Improvements][#14025] Changed to Reanimated2 for message skeleton animation

* [Chores][#14025] Removed unused code

* [Improvements][#14025] Added preview screen for messages skeleton

* [Improvements][#14025] Style update for messages skeleton

* [Chore][#14025] Indentation for message skeleton

* [Improvements][#14025] On Layout calculation cleanup

* [Fix][#14025] Added Skeleton on New UI only

* [Chore] Moved Message Skeleton to new UI

* [Feature][#14025] Added Message Skeleton on message gap

* [TEMP][#14025] Added delay of preloading messages for testing

* [Lint][#14025] Lint fixes for Message Gap component

* [Chore][#14025] Reanimated namespace update

* [Chore][#14025] React native namespace update

* [Chore][#14025] Rollback preload messages
2022-11-17 00:43:58 +08:00
John Ngei e44d4c83c0 communites home screen context actions (#14371) 2022-11-16 17:48:18 +03:00
flexsurfer cc733a379c move communities (#14384) 2022-11-16 14:09:16 +01:00
flexsurfer 803328dc4c move switcher (#14378)
* move switcher
2022-11-16 12:00:16 +01:00
Roman Volosovskyi 255049c312 Fix uniq :key warning on opening chat 2022-11-16 10:46:32 +01:00
flexsurfer 31b6e076be new structure continue, move utils, move fx macro to re-frame utils n… (#14373)
* new structure continue, move utils, move fx macro to re-frame utils namespace
2022-11-16 09:09:25 +01:00
209 changed files with 4968 additions and 4473 deletions
+2 -1
View File
@@ -2,12 +2,13 @@
status-im.utils.views/letsubs clojure.core/let
reagent.core/with-let clojure.core/let
status-im.utils.fx/defn clj-kondo.lint-as/def-catch-all
utils.re-frame/defn clj-kondo.lint-as/def-catch-all
quo.react/with-deps-check clojure.core/fn
quo.previews.preview/list-comp clojure.core/for
status-im.utils.styles/def clojure.core/def
status-im.utils.styles/defn clojure.core/defn
taoensso.tufte/defnp clojure.core/defn}
:linters {:invalid-arity {:skip-args [status-im.utils.fx/defn]}
:linters {:invalid-arity {:skip-args [status-im.utils.fx/defn utils.re-frame/defn]}
;;TODO remove number when this is fixed
;;https://github.com/borkdude/clj-kondo/issues/867
:unresolved-symbol {:exclude [PersistentPriorityMap.EMPTY number]}}}
+37
View File
@@ -0,0 +1,37 @@
module.exports = {
"testRunner": "jest",
"testRegex": "\\.visual\\.js$",
"runner-config": "visual-test/config.json",
"devices": {
"simulator": {
"type": "ios.simulator",
"device": {
"type": "iPhone 11 Pro"
}
}
},
"apps": {
"ios.release": {
"name": "StatusIm",
"type": "ios.app",
"binaryPath": "ios/build/Build/Products/Release-iphonesimulator/StatusIm.app",
"build": "make release-ios"
},
"ios.debug": {
"name": "StatusIm",
"type": "ios.app",
"binaryPath": process.env.TEST_BINARY_PATH,
"build": "make run-ios SIMULATOR='iPhone 11 Pro'"
}
},
"configurations": {
"ios.sim.release": {
"device": "simulator",
"app": "ios.release"
},
"ios.sim.debug": {
"device": "simulator",
"app": "ios.debug"
}
}
}
+3
View File
@@ -175,3 +175,6 @@ test/appium/tests/users.py
##node bindings
/bin/
/lib/
## visual tests
/artifacts
+11 -1
View File
@@ -48,7 +48,7 @@ export _NIX_GCROOTS = /nix/var/nix/gcroots/per-user/$(USER)/status-mobile
# Defines which variables will be kept for Nix pure shell, use semicolon as divider
export _NIX_KEEP ?= TMPDIR,BUILD_ENV,STATUS_GO_SRC_OVERRIDE
# Useful for Andoird release builds
# Useful for Android release builds
TMP_BUILD_NUMBER := $(shell ./scripts/version/gen_build_no.sh | cut -c1-10)
# MacOS root is read-only, read nix/README.md for details
@@ -313,6 +313,16 @@ test: ##@test Run tests once in NodeJS
yarn shadow-cljs compile test && \
node --require ./test-resources/override.js target/test/test.js
run-visual-test-ios: export TARGET := clojure
run-visual-test-ios: XCODE_DERIVED_DATA := $(HOME)/Library/Developer/Xcode/DerivedData
run-visual-test-ios: APPLICATION_NAME := $(shell ls $(XCODE_DERIVED_DATA) | grep -E '\bStatusIm-')
run-visual-test-ios: export TEST_BINARY_PATH := $(XCODE_DERIVED_DATA)/$(APPLICATION_NAME)/Build/Products/Debug-iphonesimulator/StatusIm.app
run-visual-test-ios: ##@test Run tests once in NodeJS
yarn install
detox build --configuration ios.sim.debug && \
detox test --configuration ios.sim.debug
#--------------
# Other
#--------------
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.6.1'
library 'status-jenkins-lib@v1.6.2'
/* Options section can't access functions in objects. */
def isPRBuild = utils.isPRBuild()
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.6.1'
library 'status-jenkins-lib@v1.6.2'
pipeline {
agent { label 'linux' }
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.6.1'
library 'status-jenkins-lib@v1.6.2'
/* Options section can't access functions in objects. */
def isPRBuild = utils.isPRBuild()
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.6.1'
library 'status-jenkins-lib@v1.6.2'
pipeline {
agent { label params.AGENT_LABEL }
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.6.1'
library 'status-jenkins-lib@v1.6.2'
/* Options section can't access functions in objects. */
def isPRBuild = utils.isPRBuild()
+1 -1
View File
@@ -62,7 +62,7 @@ pipeline {
--rerun_count=2 \
--testrail_report=True \
-m testrail_id \
-m \"new_ui_critical\" \
-m \"new_ui_critical or new_ui_medium\" \
-k \"${params.KEYWORD_EXPRESSION}\" \
--apk=${params.APK_NAME}
"""
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.6.1'
library 'status-jenkins-lib@v1.6.2'
pipeline {
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.6.1'
library 'status-jenkins-lib@v1.6.2'
pipeline {
agent { label 'macos' }
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.6.1'
library 'status-jenkins-lib@v1.6.2'
pipeline {
agent { label 'linux' }
+4 -1
View File
@@ -84,7 +84,10 @@
"@babel/preset-env": "7.1.0",
"@babel/register": "7.0.0",
"@mapbox/node-pre-gyp": "^1.0.9",
"jest": "^25.1.0",
"@types/jest": "^28.1.6",
"detox": "^19.9.1",
"jest-image-snapshot": "^5.1.0",
"jest": "^28.1.3",
"nodemon": "^2.0.16",
"nyc": "^14.1.1",
"process": "0.11.10",
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

+2 -2
View File
@@ -2,10 +2,10 @@
CHANGES=$(git diff --no-ext-diff --diff-filter=d --cached --unified=0 --no-prefix --minimal --exit-code src/quo src/quo2 | grep '^+' || echo '')
INVALID_CHANGES=$(echo "$CHANGES" | grep -E '(re-frame/dispatch|rf/dispatch|re-frame/subscribe|rf/subscribe|rf/sub|<sub|>evt)')
INVALID_CHANGES=$(echo "$CHANGES" | grep -E '(re-frame/dispatch|rf/dispatch|re-frame/subscribe|rf/subscribe|rf/sub|<sub|>evt|status-im)')
if test -n "$INVALID_CHANGES"; then
echo "re-frame dispatch/subscribe is not allowed in quo/quo2 components"
echo "re-frame dispatch/subscribe and status-im are not allowed in quo/quo2 components"
echo ''
echo "$INVALID_CHANGES"
exit 1
+2 -2
View File
@@ -56,7 +56,7 @@
(defn button [{:keys [on-press disabled type theme before after
haptic-feedback haptic-type on-long-press on-press-start
accessibility-label loading border-radius style]
accessibility-label loading border-radius style test-ID]
:or {theme :main
type :primary
haptic-feedback true
@@ -92,7 +92,7 @@
{:on-press-start (fn []
(optional-haptic)
(on-press-start))}))
[rn/view {:style (merge (style-container type) style)}
[rn/view {:test-ID test-ID :style (merge (style-container type) style)}
(when before
[rn/view
[icons/icon before {:color icon-color}]])
+44
View File
@@ -0,0 +1,44 @@
const waitToNavigate = duration => new Promise(resolve => setTimeout(() => resolve(), duration));
const SUPER_SECRET_PASSWORD = 'password'
const loginToHomePage = async () => {
if (device.getPlatform() === 'ios') {
await device.setStatusBar({ time: '12:34', dataNetwork: 'wifi', wifiBars: '3', batteryState: 'charging', batteryLevel: '100' });
}
await device.reloadReactNative();
await element(by.id('terms-of-service')).tap();
await waitToNavigate(400);
await element(by.id('get-started')).tap();
await waitToNavigate(300);
await element(by.id('generate-keys')).tap();
await waitToNavigate(200);
await element(by.text('Next')).tap();
await waitToNavigate(700);
await element(by.text('Next')).tap();
await waitToNavigate(700);
await element(by.id('password-placeholder')).typeText(SUPER_SECRET_PASSWORD);
await element(by.id('confirm-password-placeholder')).typeText(SUPER_SECRET_PASSWORD);
await element(by.text('Next')).tap();
await waitToNavigate(200);
await element(by.id("browser-stack")).tap();
await waitToNavigate(400);
await element(by.text("Quo2.0 Preview")).tap();
await waitToNavigate(400);
}
describe('Default Renders', () => {
beforeAll(async () => loginToHomePage())
beforeEach(async () => {
});
afterEach(async () => {
await element(by.id("back-button")).tap();
await waitToNavigate(200);
});
it(`button page should match image render`, async () => {
await element(by.id(`quo2-:button`)).tap();
await waitToNavigate(200);
const res = await jestExpect(`button`).toMatchImageSnapshot();
})
});
@@ -15,18 +15,12 @@
(defn group-avatar [_]
(fn [{:keys [color size]}]
(let [container-size (get-in sizes [:container size])
icon-size (get-in sizes [:icon size])
;; theme (or override-theme (if (colors/dark?) :dark :light))
]
icon-size (get-in sizes [:icon size])]
[rn/view {:width container-size
:height container-size
:align-items :center
:justify-content :center
:border-radius (/ container-size 2)
:background-color color
;; :background-color (if (= theme :light) TODO: colors/custom-color method here is not working for some reason.
;; (colors/custom-color color 50)
;; (colors/custom-color color 60))
}
:background-color (colors/theme-alpha color 0.5 0.6)}
[icon/icon :i/group {:size icon-size ; TODO: group icon sizes 12 and 20 (small and large) are missing
:color colors/white-opa-70}]])))
+7 -7
View File
@@ -122,20 +122,20 @@
first-initial-letter
initials)
"")]
[rn/view {:style {:width outer-dimensions
:height outer-dimensions
:border-radius outer-dimensions}}
[rn/view {:accessibility-label :user-avatar
:style {:width outer-dimensions
:height outer-dimensions
:border-radius outer-dimensions}}
(when (and ring? identicon?)
[icons/icon :main-icons/identicon-ring {:width outer-dimensions
:height outer-dimensions
:size outer-dimensions
:no-color true}])
[icons/icon :i/identicon-ring {:size outer-dimensions
:no-color true}])
(if profile-picture
;; display image
[fast-image/fast-image {:source {:uri profile-picture}
:style (container-styling inner-dimensions outer-dimensions)}]
;; else display initials
[container inner-dimensions outer-dimensions
^{:key :icon-text}
[text/text {:weight :semi-bold
:size font-size
:style {:color colors/white-opa-70}}
+3 -2
View File
@@ -186,7 +186,7 @@
(let [pressed (reagent/atom false)]
(fn [{:keys [on-press disabled type size before after above width
override-theme override-background-color
on-long-press accessibility-label icon icon-no-color style]
on-long-press accessibility-label icon icon-no-color style test-ID]
:or {type :primary
size 40}}
children]
@@ -197,7 +197,8 @@
state (cond disabled :disabled @pressed :pressed :else :default)
icon-size (when (= 24 size) 12)
icon-secondary-color (or icon-secondary-color icon-color)]
[rn/touchable-without-feedback (merge {:disabled disabled
[rn/touchable-without-feedback (merge {:test-ID test-ID
:disabled disabled
:accessibility-label accessibility-label}
(when on-press
{:on-press (fn []
+55 -53
View File
@@ -9,54 +9,6 @@
(colors/theme-colors colors/danger-50 colors/danger-60)
(colors/theme-colors colors/neutral-50 colors/neutral-40)))
(defn action [{:keys [icon
label
sub-label
right-icon
danger?
on-press] :as action-props}]
(when action-props
[rn/touchable-opacity {:on-press on-press}
[rn/view {:style
{:height (if sub-label 56 47)
:margin-horizontal 20
:flex-direction :row}}
[rn/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}]]
[rn/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-2
:style {:color
(colors/theme-colors colors/neutral-50 colors/neutral-40)}}
sub-label])]
(when right-icon
[rn/view {:style
{:height 20
:margin-top :auto
:margin-bottom :auto
:width 20}}
[icon/icon right-icon
{:color (get-icon-color danger?)
:size 20}]])]]))
(defn divider []
[rn/view {:style {:border-top-width 1
:border-top-color (colors/theme-colors colors/neutral-10 colors/neutral-80)
@@ -65,10 +17,60 @@
:align-items :center
:flex-direction :row}}])
(defn action [{:keys [icon
label
sub-label
right-icon
danger?
on-press
add-divider?] :as action-props}]
(when action-props
[:<> {:key label}
(when add-divider? [divider])
[rn/touchable-opacity {:on-press on-press}
[rn/view {:style
{:height (if sub-label 56 47)
:margin-horizontal 20
:flex-direction :row}}
[rn/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}]]
[rn/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-2
:style {:color
(colors/theme-colors colors/neutral-50 colors/neutral-40)}}
sub-label])]
(when right-icon
[rn/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 [sections]
[:<> {:style
{:flex 1}}
(interpose
[divider]
[:<>
(doall
(for [actions sections]
(map action actions)))])
(doall
(map action actions))))])
+1 -2
View File
@@ -138,14 +138,13 @@
(fn [{:keys [left-accessories left-component border-bottom
right-accessories right-component insets get-layout
title subtitle title-component style title-align
background header-style]
background]
:or {title-align :center
border-bottom false}}]
(let [status-bar-height (get insets :top 0)
height (+ header-height status-bar-height)]
[reanimated/view {:style (header-wrapper-style {:height height
:background background
:header-style header-style
:border-bottom border-bottom})}
[rn/view {:pointer-events :box-none
:height status-bar-height}]
+2 -1
View File
@@ -30,4 +30,5 @@
(get-icons 12)
(get-icons 16)
(get-icons 20)
(get-icons 24)))
(get-icons 24)
(get-icons 32)))
-15
View File
@@ -1,15 +0,0 @@
(ns quo2.components.list-items.index
(:require [quo.react-native :as rn]
[quo2.components.markdown.text :as text]
[quo2.foundations.colors :as colors]))
(defn index [{:keys [title]}]
[rn/view
[rn/view {:style {:border-top-width 1
:border-color (colors/theme-colors colors/neutral-10 colors/neutral-80)
:padding-vertical 8
:padding-horizontal 16}}
[text/text {:weight :medium
:size :paragraph-2
:secondary-color (colors/theme-colors colors/neutral-50 colors/neutral-40)}
title]]])
@@ -1,276 +0,0 @@
(ns quo2.components.list-items.list-item
(:require [quo.react-native :as rn]
[quo.platform :as platform]
[quo.haptic :as haptic]
[quo.gesture-handler :as gh]
[quo.design-system.spacing :as spacing]
[quo.design-system.colors :as quo.colors]
[quo.components.controls.view :as controls]
[quo.components.tooltip :as tooltip]
[quo2.components.markdown.text :as text]
[quo2.foundations.colors :as colors]
[quo2.components.icon :as quo2.icons]
[quo.components.animated.pressable :as animated]))
(defn themes [theme]
(case 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
:small 36
40))
(defn size->container-size [size]
(case size
:small 52
64))
(defn size->single-title-size [size]
(case size
:small :paragraph-1
:paragraph-1))
(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 (or container-padding-vertical 8)
:flex-direction :row
:align-items :center
:justify-content :space-between} container-style)}]
children))
(defn icon-column
[{:keys [icon icon-bg-color icon-color size icon-container-style]}]
(when icon
(let [icon-size (size->icon-size size)]
[rn/view {:style (or icon-container-style (:tiny spacing/padding-horizontal))}
(cond
(vector? icon)
icon
(keyword? icon)
[rn/view {:style {:width icon-size
:height icon-size
:align-items :center
:justify-content :center
:border-radius (/ icon-size 2)
:background-color icon-bg-color}}
(quo2.icons/icon 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? 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})))}
(cond
(and title subtitle)
[:<>
;; FIXME(Ferossgp): ReactNative 63 will support view inside text on andrid, remove thess if when migrating
(if (string? title)
[text/text {:weight (or title-text-weight :medium)
:style {:color text-color}
:accessibility-label title-accessibility-label
:ellipsize-mode :tail
:number-of-lines 1
:size text-size}
title]
title)
(if (string? subtitle-secondary)
[rn/view {:flex-direction :row}
[text/text {:style {:max-width "56.5%"}
:weight :regular
:color :secondary
:ellipsize-mode :tail
:secondary-color colors/neutral-50
:number-of-lines subtitle-max-lines
:size text-size}
subtitle]
[text/text {:style {:width "7%" :text-align :center}
:weight :regular
:color :secondary
:ellipsize-mode :middle
:secondary-color colors/neutral-50
:number-of-lines subtitle-max-lines
:size text-size}
"•"]
[text/text {:style {:max-width "36.5%"}
:weight :regular
:color :secondary
:ellipsize-mode :middle
:secondary-color colors/neutral-50
:number-of-lines subtitle-max-lines
:size text-size}
subtitle-secondary]]
(if (string? subtitle)
[text/text {:weight :regular
:color :secondary
:ellipsize-mode :tail
:secondary-color colors/neutral-50
:number-of-lines subtitle-max-lines
:size text-size}
subtitle]
subtitle))]
title
(if (string? title)
[text/text {:weight (or title-text-weight :regular)
:number-of-lines 1
:style {:color text-color}
:title-accessibility-label title-accessibility-label
:ellipsize-mode :tail
:size (or text-size (size->single-title-size size))}
title]
title))])
(defn left-side [props]
[rn/view {:style {:flex-direction :row
;; Occupy only content width, never grow, but shrink if need be
:flex-grow 0
:flex-shrink 1
:padding-right 16
:align-items (or (:left-side-alignment props) :center)}}
[icon-column props]
[title-column props]])
(defn right-side [{:keys [chevron active disabled accessory accessory-text accessory-style animated-accessory?]}]
(when (or chevron accessory)
[rn/view {:style (merge {:align-items :center
:justify-content :flex-end
:flex-direction :row
;; Grow to occupy full space, shrink when need be, but always maitaining 16px left gutter
:flex-grow 1
:flex-shrink 0
:margin-left 16
;; When the left-side leaves no room for right-side, the rendered element is pushed out. A flex-basis ensures that there is some room reserved.
;; The number 80px was determined by trial and error.
:flex-basis 80}
accessory-style)}
[rn/view {:style (:tiny spacing/padding-horizontal)}
(case accessory
:radio [controls/radio {:value active :disabled disabled}]
:checkbox [(if animated-accessory?
controls/animated-checkbox
controls/checkbox)
{:value active :disabled disabled}]
:switch [controls/switch {:value active :disabled disabled}]
:text [text/text {:color :secondary
:ellipsize-mode :middle
:number-of-lines 1}
accessory-text]
accessory)]
(when (and chevron platform/ios?)
[rn/view {:style {:padding-right (:tiny spacing/spacing)}}
(quo2.icons/icon :main-icons/next {:container-style {:opacity 0.4
:align-items :center
:justify-content :center}
:resize-mode :center
: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 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 text-color active-background passive-background haptic-type error animated animated-accessory? title-text-weight container-style
active-background-enabled background-color container-padding-horizontal container-padding-vertical]
:or {subtitle-max-lines 1
theme :main
haptic-feedback true
animated platform/ios?
active-background-enabled true
haptic-type :selection}}]
(let [theme (if disabled :disabled theme)
icon-color (or icon-color (:icon-color (themes theme)))
icon-bg-color (or icon-bg-color (:icon-bg-color (themes theme)))
optional-haptic (fn []
(when haptic-feedback
(haptic/trigger haptic-type)))
component (cond
(and (not on-press)
(not on-long-press))
rn/view
animated animated/pressable
:else gh/touchable-highlight)]
[rn/view {:background-color (cond (not= background-color nil)
background-color
(and (= accessory :radio) active)
active-background
:else
passive-background)}
[component
(merge {:type :list-item
:disabled disabled
:bg-color (when active-background-enabled active-background)
:accessibility-label accessibility-label}
(when on-press
{:on-press (fn []
(optional-haptic)
(on-press))})
(when on-long-press
{:on-long-press (fn []
(optional-haptic)
(on-long-press))}))
[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
(:text-color (themes :main)))
:left-side-alignment left-side-alignment
:icon-bg-color icon-bg-color
:title-accessibility-label title-accessibility-label
:icon icon
:icon-container-style icon-container-style
:title title
:title-text-weight title-text-weight
:size size
:text-size text-size
:subtitle subtitle
:subtitle-max-lines subtitle-max-lines
:subtitle-secondary subtitle-secondary
:right-side-present? (or accessory chevron)
:title-column-style title-column-style}]
[right-side {:chevron chevron
:active active
:disabled disabled
:on-press on-press
:accessory-text accessory-text
:animated-accessory? animated-accessory?
:accessory-style accessory-style
:accessory accessory}]]]
(when error
[tooltip/tooltip (merge {:bottom-value 0}
(when accessibility-label
{:accessibility-label (str (name accessibility-label) "-error")}))
[text/text {:color :negative
:size :small}
error]])]))
+3 -3
View File
@@ -1,10 +1,11 @@
(ns quo2.components.markdown.text
(:require [react-native.core :as rn]
[quo2.theme :as theme]
[quo2.foundations.colors :as colors]
[quo2.foundations.typography :as typography]
[reagent.core :as reagent]))
(defn text-style [{:keys [size align weight style secondary-color]}]
(defn text-style [{:keys [size align weight style]}]
(merge (case (or weight :regular)
:regular typography/font-regular
:medium typography/font-medium
@@ -23,8 +24,7 @@
{:text-align (or align :auto)}
(if (:color style)
style
(assoc style :color (or secondary-color
(colors/theme-colors colors/neutral-100 colors/white))))))
(assoc style :color (if (= (theme/get-theme) :dark) colors/white colors/neutral-100)))))
(defn text []
(let [this (reagent/current-component)
@@ -1,270 +0,0 @@
(ns quo2.components.markdown.text-input
(:require [clojure.spec.alpha :as s]
[reagent.core :as reagent]
[oops.core :refer [ocall]]
[quo.react-native :as rn]
;; TODO(Ferossgp): Move icon component to lib
[status-im.ui.components.icons.icons :as icons]
[quo.components.tooltip :as tooltip]
[quo.platform :as platform]
[quo.design-system.typography :as typography]
[quo.design-system.spacing :as spacing]
[quo.design-system.colors :as colors]
[quo.components.text :as text]))
;; NOTE(Ferossgp): Refactor with hooks when available
;; We track all currently mounted text input refs
;; in a ref-to-defaultValue map
;; so that we can clear them (restore their default values)
;; when global react-navigation's onWillBlur event is invoked
(defonce text-input-refs (atom {}))
(s/def ::multiline boolean?)
(s/def ::secure-text-entry boolean?)
(s/def ::show-cancel boolean?)
(s/def ::label (s/nilable (s/or :string string? :component vector?)))
(s/def ::cancel-label (s/nilable string?))
(s/def ::default-value (s/nilable string?))
(s/def ::placeholder (s/nilable string?))
(s/def ::keyboard-type (s/nilable (s/or :string string? :keyword keyword?))) ; TODO: make set
(s/def ::accessibility-label (s/nilable (s/or :string string? :keyword keyword?)))
(s/def ::on-focus fn?)
(s/def ::on-blur fn?)
(s/def ::on-press fn?)
(s/def ::accessory (s/keys :opt-un [::on-press
::icon
::component]))
(s/def ::after (s/nilable ::accessory))
(s/def ::before (s/nilable ::accessory))
(s/def ::style (s/nilable map?))
(s/def ::input-style ::style)
(s/def ::container-style ::style)
(s/def ::text-input (s/keys :opt-un
[::label
::multiline
::error
::style
::input-style
::keyboard-type
::before
::after
::cancel-label
::on-focus
::on-blur
::container-style
::show-cancel
::accessibility-label
::bottom-value
::secure-text-entry]))
(defn check-spec [spec prop]
(if (s/valid? spec prop)
true
(do
(s/explain spec prop)
false)))
(def height 44) ; 22 line-height + 11*2 vertical padding
(def multiline-height 88) ; 3 * 22 three line-height + 11* vertical padding
(defn label-style []
{:margin-bottom (:tiny spacing/spacing)})
(defn text-input-row-style []
{:flex-direction :row
:align-items :center})
(defn text-input-view-style [style]
(merge {:border-radius 8
:flex-direction :row
:flex 1
:align-items :center
:background-color (:ui-01 @colors/theme)}
style))
(defn text-input-style [multiline input-style monospace before after]
(merge (if monospace
typography/monospace
typography/font-regular)
{:padding-top 11
:padding-bottom 11
:font-size 15
:margin 0
:text-align-vertical :center
:flex 1
:color (:text-01 @colors/theme)
:height height}
(when-not before
{:padding-left (:base spacing/spacing)})
(when-not after
{:padding-right (:base spacing/spacing)})
(when multiline
{:text-align-vertical :top
:line-height 22
:height multiline-height})
input-style))
(defn cancel-style []
{:margin-left (:tiny spacing/spacing)
:padding-left (:tiny spacing/spacing)
:justify-content :center
:align-self :stretch})
(defn accessory-style []
(merge (:base spacing/padding-horizontal)
{:flex 1
:justify-content :center}))
(defn accessory-element [{:keys [icon component icon-opts style accessibility-label on-press]}]
(let [el (if on-press
rn/touchable-opacity
rn/view)]
[el (merge {:style {:align-self :stretch}}
(when on-press
{:on-press on-press}))
[rn/view (merge {:style (merge (accessory-style)
style)}
(when accessibility-label
{:accessibility-label accessibility-label}))
(cond
icon
[icons/icon icon (merge {:color (:icon-01 @colors/theme)}
icon-opts)]
component
component
:else
nil)]]))
(defn text-input-raw []
(let [focused (reagent/atom nil)
visible (reagent/atom false)
ref (atom nil)
blur (fn []
(some-> @ref (ocall "blur")))]
(fn [{:keys [label multiline error style input-style keyboard-type before after
cancel-label on-focus on-blur show-cancel accessibility-label
bottom-value secure-text-entry container-style get-ref on-cancel
monospace auto-complete-type auto-correct]
:or {cancel-label "Cancel"}
:as props}]
{:pre [(check-spec ::text-input props)]}
(let [show-cancel (if (nil? show-cancel)
;; Enabled by default on iOs and disabled on Android
platform/ios?
show-cancel)
after (cond
(and secure-text-entry @visible)
{:icon :main-icons/hide
:on-press #(reset! visible false)}
(and secure-text-entry (not @visible))
{:icon :main-icons/show
:on-press #(reset! visible true)}
:else after)
secure (boolean (and secure-text-entry (not @visible))) ; must be a boolean to work on iOS
auto-complete (cond
(= keyboard-type :visible-password)
:off
secure-text-entry
:password
:else
auto-complete-type)
auto-correct (and (not= keyboard-type :visible-password) (not secure-text-entry) auto-correct)
on-cancel (fn []
(when on-cancel
(on-cancel))
(blur))
keyboard-type (cond
(and platform/ios? (= keyboard-type :visible-password))
:default
; the correct approach on Android would be keep secure-text-entry on set keyboard type
; to visible-password. But until https://github.com/facebook/react-native/issues/27946
; is solved that's the second best way.
(and platform/android? secure-text-entry)
:default
:else
keyboard-type)]
[rn/view {:style container-style}
(when label
[text/text {:style (label-style)}
label])
[rn/view {:style (text-input-row-style)}
[rn/view {:style (text-input-view-style style)
:important-for-accessibility (if secure-text-entry
:no-hide-descendants
:auto)}
(when before
[accessory-element before])
[rn/text-input
(merge {:style (text-input-style multiline input-style monospace before after)
:ref (fn [r]
(reset! ref r)
(when get-ref (get-ref r)))
:placeholder-text-color (:text-02 @colors/theme)
:underline-color-android :transparent
:auto-capitalize :none
:secure-text-entry secure
:auto-correct auto-correct
:auto-complete-type auto-complete
:on-focus (fn [evt]
(when on-focus (on-focus evt))
(when show-cancel
(rn/configure-next (:ease-in-ease-out rn/layout-animation-presets)))
(reset! focused true))
:on-blur (fn [evt]
(when on-blur (on-blur evt))
(when show-cancel
(rn/configure-next (:ease-in-ease-out rn/layout-animation-presets)))
(reset! focused false))
:keyboard-type keyboard-type}
(when (and platform/ios? (not after))
{:clear-button-mode :while-editing})
(dissoc props
:style :keyboard-type :on-focus :on-blur
:secure-text-entry :ref :get-ref :auto-correct :auto-complete-type))]
(when after
[accessory-element after])]
(when (and show-cancel
(not multiline)
@focused)
[rn/touchable-opacity {:style (cancel-style)
:on-press on-cancel}
[text/text {:color :link} cancel-label]])
(when error
[tooltip/tooltip (merge {:bottom-value (if bottom-value bottom-value 0)}
(when accessibility-label
{:accessibility-label (str (name accessibility-label) "-error")}))
[text/text {:color :negative
:align :center
:size :small}
error]])]]))))
;; TODO(Ferossgp): Refactor me when hooks available
(defn text-input [{:keys [preserve-input?]
:as props}]
(if preserve-input?
[text-input-raw props]
(let [id (random-uuid)]
(reagent/create-class
{:component-will-unmount
(fn []
(swap! text-input-refs dissoc id))
:reagent-render
(fn [{:keys [get-ref default-value]
:as props}]
[text-input-raw (merge props
{:get-ref (fn [r]
;; Store input and its defaultValue
;; one we receive a non-nil ref
(when r
(swap! text-input-refs assoc id {:ref r :value default-value}))
(when get-ref (get-ref r)))})])}))))
@@ -0,0 +1,39 @@
(ns quo2.components.messages.author.style
(:require [quo2.foundations.colors :as colors]))
(def container
{:flex 1
:flex-wrap :wrap
:height 18
:flex-direction :row
:align-items :center})
(defn ens-text []
{:color (colors/theme-colors colors/neutral-100 colors/white)})
(defn nickname-text []
{:color (colors/theme-colors colors/neutral-100 colors/white)})
(def middle-dot-nickname
{:color colors/neutral-50
:margin-horizontal 4})
(def chat-key-text
{:color colors/neutral-50
:margin-left 8})
(def middle-dot-chat-key
{:color colors/neutral-50
:margin-left 4})
(defn profile-name-text [nickname?]
{:color (if nickname?
(colors/theme-colors colors/neutral-60 colors/neutral-40)
(colors/theme-colors colors/neutral-100 colors/white))})
(def icon-container
{:margin-left 4})
(defn time-text [ens?]
{:color colors/neutral-50
:margin-left (if ens? 8 4)})
@@ -0,0 +1,66 @@
(ns quo2.components.messages.author.view
(:require
[quo2.components.messages.author.style :as style]
[react-native.core :as rn]
[quo2.components.markdown.text :as text]
[quo2.components.icon :as icons]
[status-im.utils.utils :as utils]
[clojure.string :as string]))
(def middle-dot "·")
(defn author [{:keys [profile-name nickname chat-key ens-name time-str contact? verified? untrustworthy?]}]
[:f>
(fn []
(let [ens? (-> ens-name string/blank? not)
nickname? (-> nickname string/blank? not)]
[rn/view {:style style/container}
(if ens?
[text/text {:weight :semi-bold
:size :paragraph-2
:style (style/ens-text)}
ens-name]
[:<>
(when nickname?
[:<>
[text/text {:weight :semi-bold
:size :paragraph-2
:style (style/nickname-text)}
nickname]
[text/text {:size :paragraph-2
:style style/middle-dot-nickname}
middle-dot]])
[text/text {:weight (if nickname? :medium :semi-bold)
:size :paragraph-2
:style (style/profile-name-text nickname?)}
profile-name]])
(when contact?
[icons/icon :main-icons2/contact
{:size 12
:no-color true
:container-style style/icon-container}])
(cond
verified?
[icons/icon :main-icons2/verified
{:size 12
:no-color true
:container-style style/icon-container}]
untrustworthy?
[icons/icon :main-icons2/untrustworthy
{:size 12
:no-color true
:container-style style/icon-container}])
(when-not ens?
[text/text {:monospace true
:size :paragraph-2
:style style/chat-key-text}
(utils/get-shortened-address chat-key)])
(when-not ens?
[text/text {:monospace true
:size :paragraph-2
:style style/middle-dot-chat-key}
middle-dot])
[text/text {:monospace true
:size :paragraph-2
:style (style/time-text ens?)}
time-str]]))])
@@ -6,7 +6,7 @@
[quo2.components.avatars.user-avatar :as user-avatar]
[quo2.components.markdown.text :as text]
[quo2.foundations.colors :as colors]
[utils :as utils]))
[utils.string :as utils]))
(def themes-landed {:pinned colors/primary-50-opa-5
:added colors/primary-50-opa-5
@@ -26,7 +26,7 @@
:icon-color-anim reanimated shared value
"
[{:keys [icon new-notifications? notification-indicator counter-label
on-press pass-through? icon-color-anim accessibility-label]}]
on-press pass-through? icon-color-anim accessibility-label test-ID]}]
[:f>
(fn []
(let [icon-animated-style (reanimated/apply-animations-to-style
@@ -40,7 +40,8 @@
:height 40
:border-radius 10})]
[rn/touchable-without-feedback
{:on-press on-press
{:test-ID test-ID
: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}
@@ -63,6 +63,7 @@
:right 20
:top 12
:flex-direction :row}}
;; TODO component shouldn't know anything about parent system, we should pass buttons as parameters
[base-button :i/search open-search :open-search-button button-common-props]
[base-button :i/scan open-scanner :open-scanner-button button-common-props]
[base-button :i/qr-code show-qr :show-qr-button button-common-props]
@@ -0,0 +1,76 @@
(ns quo2.components.notifications.activity-log.style
(:require [quo2.foundations.colors :as colors]))
(def container
{:flex-direction :row
:flex-grow 1
:align-items :flex-start
:padding-top 8
:padding-horizontal 12
:padding-bottom 12})
(def icon
{:height 32
:width 32
:border-radius 100
:margin-top 10
:border-width 1
:border-color colors/white-opa-5
:align-items :center
:justify-content :center})
(def message-title
{:color colors/white-opa-40
:margin-bottom 2})
(def message-body
{:color colors/white})
(def message-container
{:border-radius 12
:margin-top 12
:padding-horizontal 12
:padding-vertical 8
:background-color colors/white-opa-5})
(def buttons-container
{:margin-top 12
:flex-direction :row
:align-items :flex-start})
(def status
{:margin-top 12
:align-items :flex-start
:flex 1})
(defn title [replying?]
{:color colors/white
:flex-shrink 1
:max-width (when-not replying? "60%")})
(def timestamp
{:text-transform :none
:flex-grow 1
:margin-left 8
:color colors/neutral-40})
(def unread-dot
{:background-color colors/primary-50
:border-radius 4
:width 8
:height 8})
(def unread-dot-container
{:margin-left 8
:padding-horizontal 12
:padding-vertical 7})
(def context-container
{:flex-direction :row
:align-items :center
:justify-content :flex-start
:flex-wrap :wrap})
(def top-section-container
{:align-items :center
:flex-direction :row})
@@ -0,0 +1,178 @@
(ns quo2.components.notifications.activity-log.view
(:require [clojure.string :as string]
[quo.core :as quo]
[quo2.components.buttons.button :as button]
[quo2.components.icon :as icon]
[quo2.components.markdown.text :as text]
[quo2.components.tags.status-tags :as status-tags]
[quo2.foundations.colors :as colors]
[quo2.components.notifications.activity-log.style :as style]
[react-native.core :as rn]
[reagent.core :as reagent]
[status-im.i18n.i18n :as i18n]))
(def ^:private max-reply-length
280)
(defn- valid-reply?
[reply]
(<= (count reply) max-reply-length))
(defn- activity-reply-text-input
[reply-input on-update-reply]
[rn/view
[rn/view {:style {:margin-top 16
:margin-bottom 8
:flex-direction :row}}
[text/text {:weight :medium
:style {:flex-grow 1
:color colors/neutral-40}}
(i18n/label :t/your-answer)]
[text/text {:style {:flex-shrink 1
:color (if (valid-reply? @reply-input)
colors/neutral-40
colors/danger-60)}}
(str (count @reply-input) "/" max-reply-length)]]
[rn/view
;; TODO(@ilmotta): Replace with quo2 component when available.
;; https://github.com/status-im/status-mobile/issues/14364
[quo/text-input
{:on-change-text #(do (reset! reply-input %)
(when on-update-reply
(on-update-reply %)))
:auto-capitalize :none
:auto-focus true
:accessibility-label :identity-verification-reply-text-input
:placeholder (i18n/label :t/type-something)
:return-key-type :none
:multiline false
:auto-correct false}]]])
(defn- activity-icon
[icon]
[rn/view {:style style/icon}
[icon/icon icon {:color colors/white}]])
(defn- activity-context
[context replying?]
(let [first-line-offset (if replying? 4 0)
gap-between-lines 4]
(into [rn/view {:style (assoc style/context-container :margin-top first-line-offset)}]
(mapcat (fn [detail]
^{:key (hash detail)}
(if (string? detail)
(map (fn [s]
[rn/view {:style {:margin-right 4
:margin-top 0}}
[text/text {:size :paragraph-2}
s]])
(string/split detail #"\s+"))
[[rn/view {:margin-right 4
:margin-top gap-between-lines}
detail]]))
context))))
(defn- activity-message
[{:keys [title body]}]
[rn/view {:style style/message-container}
(when title
[text/text {:size :paragraph-2
:accessibility-label :activity-message-title
:style style/message-title}
title])
(if (string? body)
[text/text {:style style/message-body
:accessibility-label :activity-message-body
:size :paragraph-1}
body]
body)])
(defn- activity-buttons
[button-1 button-2 replying? reply-input]
(let [size (if replying? 40 24)
common-style (when replying?
{:padding-vertical 9
:flex-grow 1
:flex-basis 0})]
[rn/view style/buttons-container
(when button-1
[button/button (-> button-1
(assoc :size size)
(update :style merge common-style {:margin-right 8}))
(:label button-1)])
(when button-2
[button/button (-> button-2
(assoc :size size)
(assoc :disabled (and replying? (not (valid-reply? @reply-input))))
(update :style merge common-style))
(:label button-2)])]))
(defn- activity-status
[status]
[rn/view {:style style/status
:accessibility-label :activity-status}
[status-tags/status-tag {:size :small
:label (:label status)
:status status}]])
(defn- activity-title
[title replying?]
[text/text {:weight :semi-bold
:accessibility-label :activity-title
:style (style/title replying?)
:size (if replying? :heading-2 :paragraph-1)}
title])
(defn- activity-timestamp
[timestamp]
[text/text {:size :label
:accessibility-label :activity-timestamp
:style style/timestamp}
timestamp])
(defn- activity-unread-dot
[]
[rn/view {:accessibility-label :activity-unread-indicator
:style style/unread-dot-container}
[rn/view {:style style/unread-dot}]])
(defn- footer
[_]
(let [reply-input (reagent/atom "")]
(fn [{:keys [replying? on-update-reply status button-1 button-2]}]
[:<>
(when replying?
[activity-reply-text-input reply-input on-update-reply])
(cond (some? status)
[activity-status status]
(or button-1 button-2)
[activity-buttons button-1 button-2 replying? reply-input])])))
(defn view
[{:keys [icon
message
context
timestamp
title
replying?
unread?]
:as props}]
[rn/view {:accessibility-label :activity
:style style/container}
(when-not replying?
[activity-icon icon])
[rn/view {:style {:padding-left (when-not replying? 8)
:flex 1}}
[rn/view
[rn/view {:style style/top-section-container}
[activity-title title replying?]
(when-not replying?
[activity-timestamp timestamp])
(when (and unread? (not replying?))
[activity-unread-dot])]
(when context
[activity-context context replying?])]
(when message
[activity-message message])
[footer props]]])
@@ -1,157 +0,0 @@
(ns quo2.components.notifications.activity-logs
(:require [react-native.core :as rn]
[quo2.components.buttons.button :as button]
[quo2.components.icon :as icon]
[quo2.components.markdown.text :as text]
[quo2.components.tags.status-tags :as status-tags]
[quo2.foundations.colors :as colors]))
(defn- activity-icon
[icon]
[rn/view {:height 32
:width 32
:border-radius 100
:margin-top 10
:border-width 1
:border-color colors/white-opa-5
:flex-direction :column
:align-items :center
:justify-content :center}
[icon/icon icon {:color colors/white}]])
(defn- activity-unread-dot
[]
[rn/view {:margin-left 14
:margin-right 6
:background-color colors/primary-50
:width 8
:height 8
:border-radius 4}])
(defn- activity-context
[context]
(let [margin-top 4]
(into [rn/view {:flex 1
:flex-direction :row
:align-items :center
:flex-wrap :wrap
:margin-top (+ 4 (- margin-top))}]
(map-indexed (fn [index detail]
^{:key index}
[rn/view {:margin-right 4
:margin-top margin-top}
(if (string? detail)
[text/text {:size :paragraph-2}
detail]
detail)])
context))))
(defn- activity-message
[{:keys [title body]}]
[rn/view {:border-radius 12
:margin-top 12
:padding-horizontal 12
:padding-vertical 8
:background-color colors/white-opa-5
:flex 1
:flex-direction :column}
(when title
[text/text {:size :paragraph-2
:style {:color colors/white-opa-40}}
title])
(if (string? body)
[text/text {:style {:color colors/white}
:size :paragraph-1}
body]
body)])
(defn- activity-buttons
[button-1 button-2]
(let [size 24
common-style {:padding-top 3
:padding-right 8
:padding-bottom 4
:padding-left 8}]
[rn/view {:margin-top 12
:flex 1
:flex-direction :row
:align-items :flex-start}
(when button-1
[button/button (-> button-1
(assoc :size size)
(assoc-in [:style :margin-right] 8)
(update :style merge common-style))
(:label button-1)])
(when button-2
[button/button (-> button-2
(assoc :size size)
(update :style merge common-style))
(:label button-2)])]))
(defn- activity-status
[status]
[rn/view {:margin-top 12
:align-items :flex-start
:flex 1}
[status-tags/status-tag {:size :small
:label (:label status)
:status status}]])
(defn- activity-title
[title]
[text/text {:weight :semi-bold
:style {:color colors/white}
:size :paragraph-1}
title])
(defn- activity-timestamp
[timestamp]
[rn/view {:margin-left 8}
[text/text {:size :label
:style {:text-transform :none
:color colors/neutral-40}}
timestamp]])
(defn activity-log
[{:keys [button-1
button-2
icon
message
status
context
timestamp
title
unread?]}]
[rn/view {:flex-direction :row
:flex 1
:border-radius 16
:padding-top 8
:padding-horizontal 12
:padding-bottom 12
:background-color (when unread?
colors/primary-50-opa-10)}
[activity-icon icon]
[rn/view {:flex-direction :column
:padding-left 8
:flex 1}
[rn/view {:flex 1
:align-items :center
:flex-direction :row}
[rn/view {:flex 1
:align-items :center
:flex-direction :row}
[rn/view {:flex-shrink 1}
[activity-title title]]
[activity-timestamp timestamp]]
(when unread?
[activity-unread-dot])]
(when context
[activity-context context])
(when message
[activity-message message])
(cond
(some? status)
[activity-status status]
(or button-1 button-2)
[activity-buttons button-1 button-2])]])
+4 -3
View File
@@ -3,7 +3,8 @@
[react-native.core :as rn]
[quo2.components.tabs.tab :as tab]
[reagent.core :as reagent]
[utils :as utils]
[utils.number :as utils.number]
[utils.collection :as utils.collection]
[quo2.foundations.colors :as colors]
[quo2.components.notifications.notification-dot :refer [notification-dot]]
[react-native.masked-view :as masked-view]
@@ -50,7 +51,7 @@
;; Truncate to avoid unnecessary rendering.
(if (> fade-percentage 0.99)
0.99
(utils/naive-round fade-percentage 2))))
(utils.number/naive-round fade-percentage 2))))
(defn scrollable-tabs
"Just like the component `tabs`, displays horizontally scrollable tabs with
@@ -120,7 +121,7 @@
:scroll-on-press?
:size)
(when scroll-on-press?
{:initial-scroll-index (utils/first-index #(= @active-tab-id (:id %)) data)})
{:initial-scroll-index (utils.collection/first-index #(= @active-tab-id (:id %)) data)})
{:ref #(reset! flat-list-ref %)
:extra-data (str @active-tab-id)
:horizontal true
+17 -7
View File
@@ -28,14 +28,18 @@
quo2.components.messages.gap
quo2.components.messages.system-message
quo2.components.reactions.reaction
quo2.components.notifications.activity-logs
quo2.components.notifications.activity-log.view
quo2.components.notifications.info-count
quo2.components.notifications.notification-dot
quo2.components.tags.tags
quo2.components.tags.context-tags
quo2.components.tabs.tabs
quo2.components.tabs.account-selector
quo2.components.list-items.index
quo2.components.list-items.list-item))
quo2.components.navigation.top-nav
quo2.components.navigation.floating-shell-button
quo2.components.tags.status-tags
quo2.components.navigation.page-nav
quo2.components.selectors.disclaimer))
(def button quo2.components.buttons.button/button)
(def dynamic-button quo2.components.buttons.dynamic-button/dynamic-button)
@@ -52,9 +56,16 @@
(def system-message quo2.components.messages.system-message/system-message)
(def reaction quo2.components.reactions.reaction/reaction)
(def tags quo2.components.tags.tags/tags)
(def user-avatar-tag quo2.components.tags.context-tags/user-avatar-tag)
(def context-tag quo2.components.tags.context-tags/context-tag)
(def tabs quo2.components.tabs.tabs/tabs)
(def scrollable-tabs quo2.components.tabs.tabs/scrollable-tabs)
(def account-selector quo2.components.tabs.account-selector/account-selector)
(def top-nav quo2.components.navigation.top-nav/top-nav)
(def floating-shell-button quo2.components.navigation.floating-shell-button/floating-shell-button)
(def status-tag quo2.components.tags.status-tags/status-tag)
(def page-nav quo2.components.navigation.page-nav/page-nav)
(def disclaimer quo2.components.selectors.disclaimer/disclaimer)
;;;; AVATAR
(def account-avatar quo2.components.avatars.account-avatar/account-avatar)
@@ -72,6 +83,7 @@
(def community-stats quo2.components.community.community-view/community-stats)
(def community-tags quo2.components.community.community-view/community-tags)
(def community-title quo2.components.community.community-view/community-title)
(def permission-tag-container quo2.components.community.community-view/permission-tag-container)
(def discover-card quo2.components.community.discover-card/discover-card)
;;;; DIVIDERS
@@ -82,10 +94,8 @@
(def channel-list-item quo2.components.list-items.channel/list-item)
(def menu-item quo2.components.list-items.menu-item/menu-item)
(def preview-list quo2.components.list-items.preview-list/preview-list)
(def index quo2.components.list-items.index/index)
(def list-item quo2.components.list-items.list-item/list-item)
;;;; NOTIFICATIONS
(def activity-log quo2.components.notifications.activity-logs/activity-log)
(def activity-log quo2.components.notifications.activity-log.view/view)
(def info-count quo2.components.notifications.info-count/info-count)
(def notification-dot quo2.components.notifications.notification-dot/notification-dot)
(def notification-dot quo2.components.notifications.notification-dot/notification-dot)
-5
View File
@@ -91,11 +91,6 @@
(def white-opa-90 (alpha white 0.9))
(def white-opa-95 (alpha white 0.95))
;;;;Black
;;Solid
(def black "#000000")
;;;;Primary
;;Solid
+7 -2
View File
@@ -1,7 +1,8 @@
(ns react-native.core
(:require [reagent.core :as reagent]
["react-native" :as react-native]
[react-native.flat-list :as flat-list]))
[react-native.flat-list :as flat-list]
[react-native.section-list :as section-list]))
(def app-state ^js (.-AppState ^js react-native))
@@ -9,7 +10,7 @@
(def scroll-view (reagent/adapt-react-class (.-ScrollView ^js react-native)))
(def image (reagent/adapt-react-class (.-Image ^js react-native)))
(def text (reagent/adapt-react-class (.-Text ^js react-native)))
(def text-input (reagent/adapt-react-class (.-TextInput ^js react-native)))
(def text-input (reagent/adapt-react-class (.-TextInput ^js react-native)))
(def touchable-opacity (reagent/adapt-react-class (.-TouchableOpacity ^js react-native)))
(def touchable-highlight (reagent/adapt-react-class (.-TouchableHighlight ^js react-native)))
@@ -17,6 +18,8 @@
(def flat-list flat-list/flat-list)
(def section-list section-list/section-list)
(def activity-indicator (reagent/adapt-react-class (.-ActivityIndicator ^js react-native)))
(def modal (reagent/adapt-react-class (.-Modal ^js react-native)))
@@ -48,3 +51,5 @@
(defn get-window []
(js->clj (.get (.-Dimensions ^js react-native) "window") :keywordize-keys true))
(def status-bar (.-StatusBar ^js react-native))
+3
View File
@@ -27,6 +27,9 @@
(defn show-overlay [comp]
(.showOverlay Navigation (clj->js comp)))
(defn pop-to-root [tab]
(.popToRoot Navigation (clj->js tab)))
(defn dissmiss-overlay [comp]
(.catch (.dismissOverlay Navigation comp) #()))
+9 -1
View File
@@ -2,8 +2,9 @@
(:require ["react-native" :as rn]
[reagent.core :as reagent]
[clojure.string :as string]
["react-native-linear-gradient" :default LinearGradient]
["react-native-reanimated" :default reanimated
:refer (useSharedValue useAnimatedStyle withTiming withDelay withSpring Easing Keyframe)]))
:refer (useSharedValue useAnimatedStyle withTiming withDelay withSpring withRepeat Easing Keyframe)]))
;; Animated Components
(def create-animated-component (comp reagent/adapt-react-class (.-createAnimatedComponent reanimated)))
@@ -12,6 +13,8 @@
(def image (reagent/adapt-react-class (.-Image reanimated)))
(def touchable-opacity (create-animated-component (.-TouchableOpacity ^js rn)))
(def linear-gradient (create-animated-component LinearGradient))
;; Hooks
(def use-shared-value useSharedValue)
(def use-animated-style useAnimatedStyle)
@@ -21,6 +24,7 @@
(def with-delay withDelay)
(def with-spring withSpring)
(def key-frame Keyframe)
(def with-repeat withRepeat)
;; Easings
(def bezier (.-bezier ^js Easing))
@@ -68,3 +72,7 @@
(defn animate-shared-value-with-delay [anim val duration easing delay]
(set-shared-value anim (with-delay delay (with-timing val (js-obj "duration" duration
"easing" (get easings easing))))))
(defn animate-shared-value-with-repeat [anim val duration easing number-of-repetitions reverse?]
(set-shared-value anim (with-repeat (with-timing val (js-obj "duration" duration
"easing" (get easings easing))) number-of-repetitions reverse?)))
+40
View File
@@ -0,0 +1,40 @@
(ns react-native.section-list
(:require [reagent.core :as reagent]
[react-native.flat-list :as flat-list]
["react-native" :as react-native]))
(def section-list-class (reagent/adapt-react-class (.-SectionList react-native)))
(def memo-wrap-render-fn
(memoize
(fn [f render-data]
(fn [^js data]
(reagent/as-element [f (.-item data) (.-index data) (.-separators data) render-data])))))
(defn- wrap-render-section-header-fn [f]
(fn [^js data]
(let [^js section (.-section data)]
(reagent/as-element [f {:title (.-title section)
:data (.-data section)}]))))
(defn- wrap-per-section-render-fn [props]
(update
(if-let [f (:render-fn props)]
(assoc (dissoc props :render-fn :render-data) :renderItem (memo-wrap-render-fn f (:render-data props)))
props)
:data to-array))
(defn section-list
"A wrapper for SectionList.
To render something on empty sections, use renderSectionFooter and conditionaly
render on empty data
See https://facebook.github.io/react-native/docs/sectionlist.html"
[{:keys [sections render-section-header-fn render-section-footer-fn style] :as props}]
[section-list-class
(merge (flat-list/base-list-props props)
props
(when render-section-footer-fn
{:renderSectionFooter (wrap-render-section-header-fn render-section-footer-fn)})
{:sections (clj->js (map wrap-per-section-render-fn sections))
:renderSectionHeader (wrap-render-section-header-fn render-section-header-fn)
:style style})])
+80 -29
View File
@@ -6,6 +6,26 @@
[status-im.utils.fx :as fx]
[taoensso.timbre :as log]))
;;;; Navigation
(fx/defn open-activity-center
{:events [:activity-center/open]}
[_]
(rf/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}}]))
;;;; Misc
(fx/defn process-notification-failure
{:events [:activity-center/process-notification-failure]}
[_ notification-id action error]
(log/warn (str "Failed to " action)
{:notification-id notification-id :error error}))
;;;; Notification reconciliation
(defn- update-notifications
@@ -18,8 +38,7 @@
~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)
remove-notification (fn [data]
(let [remove-notification (fn [data]
(remove #(= id (:id %)) data))
insert-and-sort (fn [data]
(->> notification
@@ -27,14 +46,17 @@
(sort-by (juxt :timestamp :id))
reverse))]
(as-> acc $
(update-in $ [type :read :data] remove-notification)
(update-in $ [type :all :data] remove-notification)
(update-in $ [types/no-type :all :data] remove-notification)
(update-in $ [type :unread :data] remove-notification)
(update-in $ [types/no-type :read :data] remove-notification)
(update-in $ [types/no-type :unread :data] remove-notification)
(if (or (:dismissed notification) (:accepted notification))
$
(-> $ (update-in [type filter-status :data] insert-and-sort)
(update-in [types/no-type filter-status :data] insert-and-sort))))))
(cond-> (-> $
(update-in [type :all :data] insert-and-sort)
(update-in [types/no-type :all :data] insert-and-sort))
(not read) (update-in [type :unread :data] insert-and-sort)
(not read) (update-in [types/no-type :unread :data] insert-and-sort))))))
db-notifications
new-notifications))
@@ -45,38 +67,69 @@
{: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]}
(fx/defn notifications-reconcile-from-response
{:events [:activity-center/reconcile-notifications-from-response]}
[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)
;;;; Contact verification
(fx/defn contact-verification-decline
{:events [:activity-center.contact-verification/decline]}
[_ notification-id]
{::json-rpc/call [{:method "wakuext_declineContactVerificationRequest"
:params [notification-id]
:on-success #(rf/dispatch [:activity-center/reconcile-notifications-from-response %])
:on-error #(rf/dispatch [:activity-center/process-notification-failure
notification-id
:contact-verification/decline
%])}]})
(fx/defn contact-verification-reply
{:events [:activity-center.contact-verification/reply]}
[_ notification-id reply]
{::json-rpc/call [{:method "wakuext_acceptContactVerificationRequest"
:params [notification-id reply]
:on-success #(rf/dispatch [:activity-center/reconcile-notifications-from-response %])
:on-error #(rf/dispatch [:activity-center/process-notification-failure
notification-id
:contact-verification/reply
%])}]})
(fx/defn contact-verification-mark-as-trusted
{:events [:activity-center.contact-verification/mark-as-trusted]}
[_ notification-id]
{::json-rpc/call [{:method "wakuext_verifiedTrusted"
:params [{:id notification-id}]
:on-success #(rf/dispatch [:activity-center/reconcile-notifications-from-response %])
:on-error #(rf/dispatch [:activity-center/process-notification-failure
notification-id
:contact-verification/mark-as-trusted
%])}]})
(fx/defn contact-verification-mark-as-untrustworthy
{:events [:activity-center.contact-verification/mark-as-untrustworthy]}
[_ notification-id]
{::json-rpc/call [{:method "wakuext_verifiedUntrustworthy"
:params [{:id notification-id}]
:on-success #(rf/dispatch [:activity-center/reconcile-notifications-from-response %])
:on-error #(rf/dispatch [:activity-center/process-notification-failure
notification-id
:contact-verification/mark-as-untrustworthy
%])}]})
;;;; Notifications fetching and pagination
(def defaults
{:filter-status :unread
:filter-type types/no-type
:notifications-per-page 10})
;; Choose the maximum number of notifications that *usually/safely* fit on
;; most screens, so that the UI doesn't have to needlessly render
;; notifications.
:notifications-per-page 8})
(def start-or-end-cursor
"")
@@ -86,15 +139,13 @@
(and (some? cursor)
(not= cursor start-or-end-cursor)))
(def ^:const status-read 1)
(def ^:const status-unread 2)
(def ^:const status-all 3)
(defn status [filter-status]
(case filter-status
:read status-read
:unread status-unread
:all status-all
:all status-all
99))
(fx/defn notifications-fetch
+219 -269
View File
@@ -3,10 +3,11 @@
[day8.re-frame.test :as rf-test]
[re-frame.core :as rf]
[status-im.activity-center.notification-types :as types]
[status-im.constants :as constants]
[status-im.ethereum.json-rpc :as json-rpc]
status-im.events
[status-im.test-helpers :as h]
[status-im.utils.config :as config]))
[status-im2.setup.config :as config]))
(defn setup []
(h/register-helper-events)
@@ -14,87 +15,140 @@
;;;; Contact verification
(def notification-id 24)
(def contact-verification-rpc-response
{:activityCenterNotifications
[{:accepted false
:author "0x04d03f"
:chatId "0x04d03f"
:contactVerificationStatus constants/contact-verification-status-pending
:dismissed false
:id notification-id
:message {}
:name "0x04d03f"
:read true
:timestamp 1666647286000
:type types/contact-verification}]})
(def contact-verification-expected-notification
{:accepted false
:author "0x04d03f"
:chat-id "0x04d03f"
:contact-verification-status constants/contact-verification-status-pending
:dismissed false
:id notification-id
: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 types/contact-verification})
(defn test-log-on-failure
[{:keys [notification-id event action]}]
(rf-test/run-test-sync
(setup)
(h/using-log-test-appender
(fn [logs]
(h/stub-fx-with-callbacks ::json-rpc/call :on-error (constantly :fake-error))
(rf/dispatch event)
(is (= {:args [(str "Failed to " action)
{:notification-id notification-id
:error :fake-error}]
:level :warn}
(last @logs)))))))
(defn test-contact-verification-event
[{:keys [event expected-rpc-call]}]
(rf-test/run-test-sync
(setup)
(let [spy-queue (atom [])]
(h/stub-fx-with-callbacks ::json-rpc/call :on-success (constantly contact-verification-rpc-response))
(h/spy-fx spy-queue ::json-rpc/call)
(rf/dispatch event)
(is (= {types/no-type
{:all {:data [contact-verification-expected-notification]}
:unread {:data []}}
types/contact-verification
{:all {:data [contact-verification-expected-notification]}
:unread {:data []}}}
(get-in (h/db) [:activity-center :notifications])))
(is (= expected-rpc-call
(-> @spy-queue
(get-in [0 :args 0])
(select-keys [:method :params])))))))
(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 types/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 types/contact-verification}]}))
(testing "declines notification and reconciles"
(test-contact-verification-event
{:event [:activity-center.contact-verification/decline notification-id]
:expected-rpc-call {:method "wakuext_declineContactVerificationRequest"
:params [notification-id]}}))
(testing "logs on failure"
(test-log-on-failure
{:notification-id notification-id
:event [:activity-center.contact-verification/decline notification-id]
:action :contact-verification/decline}))))
(h/spy-fx spy-queue ::json-rpc/call)
(deftest contact-verification-reply-test
(with-redefs [config/new-activity-center-enabled? true]
(testing "sends reply and reconciles"
(let [reply "any answer"]
(test-contact-verification-event
{:event [:activity-center.contact-verification/reply notification-id reply]
:expected-rpc-call {:method "wakuext_acceptContactVerificationRequest"
:params [notification-id reply]}})))
(testing "logs on failure"
(test-log-on-failure
{:notification-id notification-id
:event [:activity-center.contact-verification/reply notification-id "any answer"]
:action :contact-verification/reply}))))
(rf/dispatch [:activity-center.contact-verification/decline contact-verification-id])
(deftest contact-verification-mark-as-trusted-test
(with-redefs [config/new-activity-center-enabled? true]
(testing "marks notification as trusted and reconciles"
(test-contact-verification-event
{:event [:activity-center.contact-verification/mark-as-trusted notification-id]
:expected-rpc-call {:method "wakuext_verifiedTrusted"
:params [{:id notification-id}]}}))
(testing "logs on failure"
(test-log-on-failure
{:notification-id notification-id
:event [:activity-center.contact-verification/mark-as-trusted notification-id]
:action :contact-verification/mark-as-trusted}))))
(is (= {:method "wakuext_declineContactVerificationRequest"
:params [contact-verification-id]}
(-> @spy-queue
(get-in [0 :args 0])
(select-keys [:method :params]))))
(is (= {types/no-type
{:read {:data [expected-notification]}
:unread {:data []}}
types/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))))))))))
(deftest contact-verification-mark-as-untrustworthy-test
(with-redefs [config/new-activity-center-enabled? true]
(testing "marks notification as untrustworthy and reconciles"
(test-contact-verification-event
{:event [:activity-center.contact-verification/mark-as-untrustworthy notification-id]
:expected-rpc-call {:method "wakuext_verifiedUntrustworthy"
:params [{:id notification-id}]}}))
(testing "logs on failure"
(test-log-on-failure
{:notification-id notification-id
:event [:activity-center.contact-verification/mark-as-untrustworthy notification-id]
:action :contact-verification/mark-as-untrustworthy}))))
;;;; Notification reconciliation
@@ -104,12 +158,12 @@
(rf-test/run-test-sync
(setup)
(let [notifications {types/one-to-one-chat
{:read {:cursor ""
{:all {:cursor ""
:data [{:id "0x1"
:read true
:type types/one-to-one-chat}
{:id "0x2"
:read true
:read false
:type types/one-to-one-chat}]}
:unread {:cursor ""
:data [{:id "0x3"
@@ -129,216 +183,112 @@
(testing "removes dismissed or accepted notifications"
(rf-test/run-test-sync
(setup)
(rf/dispatch [:test/assoc-in [:activity-center :notifications]
{types/one-to-one-chat
{:read {:cursor ""
:data [{:id "0x1" :read true :type types/one-to-one-chat}
{:id "0x2" :read true :type types/one-to-one-chat}]}
:unread {:cursor ""
:data [{:id "0x3" :read false :type types/one-to-one-chat}]}}
types/private-group-chat
{:unread {:cursor ""
:data [{:id "0x4" :read false :type types/private-group-chat}
{:id "0x6" :read false :type types/private-group-chat}]}}}])
(let [notif-1 {:id "0x1" :read true :type types/one-to-one-chat}
notif-2 {:id "0x2" :read false :type types/one-to-one-chat}
notif-3 {:id "0x3" :read false :type types/one-to-one-chat}
notif-4 {:id "0x4" :read false :type types/private-group-chat}
notif-5 {:id "0x5" :read true :type types/private-group-chat}
notif-6 {:id "0x6" :read false :type types/private-group-chat}]
(rf/dispatch [:test/assoc-in [:activity-center :notifications]
{types/one-to-one-chat
{:all {:cursor "" :data [notif-1 notif-2]}
:unread {:cursor "" :data [notif-3]}}
types/private-group-chat
{:unread {:cursor "" :data [notif-4 notif-6]}}}])
(rf/dispatch [:activity-center.notifications/reconcile
[{:id "0x1"
:read true
:type types/one-to-one-chat
:dismissed true}
{:id "0x3"
:read false
:type types/one-to-one-chat
:accepted true}
{:id "0x4"
:read false
:type types/private-group-chat
:dismissed true}
{:id "0x5"
:read false
:type types/private-group-chat
:accepted true}]])
(rf/dispatch [:activity-center.notifications/reconcile
[(assoc notif-1 :dismissed true)
(assoc notif-3 :accepted true)
(assoc notif-4 :dismissed true)
notif-5]])
(is (= {types/no-type
{:read {:data []}
:unread {:data []}}
types/one-to-one-chat
{:read {:cursor ""
:data [{:id "0x2"
:read true
:type types/one-to-one-chat}]}
:unread {:cursor ""
:data []}}
types/private-group-chat
{:read {:data []}
:unread {:cursor ""
:data [{:id "0x6"
:read false
:type types/private-group-chat}]}}}
(get-in (h/db) [:activity-center :notifications])))))
(is (= {types/no-type
{:all {:data [notif-5]}
:unread {:data []}}
types/one-to-one-chat
{:all {:cursor "" :data [notif-2]}
:unread {:cursor "" :data []}}
types/private-group-chat
{:all {:data [notif-5]}
:unread {:cursor "" :data [notif-6]}}}
(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]
{types/no-type
{:read {:cursor ""
:data [{:id "0x1"
:read true
:type types/one-to-one-chat}]}
:unread {:cursor ""
:data [{:id "0x4"
:read false
:type types/private-group-chat}
{:id "0x6"
:read false
:type types/private-group-chat}]}}
types/one-to-one-chat
{:read {:cursor ""
:data [{:id "0x1"
:read true
:type types/one-to-one-chat}]}}
types/private-group-chat
{:unread {:cursor ""
:data [{:id "0x4"
:read false
:type types/private-group-chat}
{:id "0x6"
:read false
:type types/private-group-chat}]}}}])
(let [notif-1 {:id "0x1" :read true :type types/one-to-one-chat}
notif-4 {:id "0x4" :read false :type types/private-group-chat}
notif-6 {:id "0x6" :read false :type types/private-group-chat}
new-notif-1 (assoc notif-1 :last-message {})
new-notif-4 (assoc notif-4 :author "0xabc")]
(rf/dispatch [:test/assoc-in [:activity-center :notifications]
{types/no-type
{:all {:cursor "" :data [notif-1]}
:unread {:cursor "" :data [notif-4 notif-6]}}
types/one-to-one-chat
{:all {:cursor "" :data [notif-1]}}
types/private-group-chat
{:unread {:cursor "" :data [notif-4 notif-6]}}}])
(rf/dispatch [:activity-center.notifications/reconcile
[{:id "0x1"
:read true
:type types/one-to-one-chat
:last-message {}}
{:id "0x4"
:read false
:type types/private-group-chat
:author "0xabc"}
{:id "0x6"
:read false
:type types/private-group-chat}]])
(rf/dispatch [:activity-center.notifications/reconcile [new-notif-1 new-notif-4 notif-6]])
(is (= {types/no-type
{:read {:cursor ""
:data [{:id "0x1"
:read true
:type types/one-to-one-chat
:last-message {}}]}
:unread {:cursor ""
:data [{:id "0x6"
:read false
:type types/private-group-chat}
{:id "0x4"
:read false
:type types/private-group-chat
:author "0xabc"}]}}
types/one-to-one-chat
{:read {:cursor ""
:data [{:id "0x1"
:read true
:type types/one-to-one-chat
:last-message {}}]}
:unread {:data []}}
types/private-group-chat
{:read {:data []}
:unread {:cursor ""
:data [{:id "0x6"
:read false
:type types/private-group-chat}
{:id "0x4"
:read false
:type types/private-group-chat
:author "0xabc"}]}}}
(get-in (h/db) [:activity-center :notifications])))))
(is (= {types/no-type
{:all {:cursor "" :data [notif-6 new-notif-4 new-notif-1]}
:unread {:cursor "" :data [notif-6 new-notif-4]}}
types/one-to-one-chat
{:all {:cursor "" :data [new-notif-1]}
:unread {:data []}}
types/private-group-chat
{:all {:data [notif-6 new-notif-4]}
:unread {:cursor "" :data [notif-6 new-notif-4]}}}
(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]
{types/one-to-one-chat
{:read {:cursor ""
:data [{:id "0x1"
:read true
:type types/one-to-one-chat}]}}}])
(let [notif-1 {:id "0x1" :read true :type types/one-to-one-chat}
new-notif-1 (assoc notif-1 :read false)]
(rf/dispatch [:test/assoc-in [:activity-center :notifications]
{types/one-to-one-chat
{:all {:cursor "" :data [notif-1]}}}])
(rf/dispatch [:activity-center.notifications/reconcile
[{:id "0x1"
:read false
:type types/one-to-one-chat}]])
(rf/dispatch [:activity-center.notifications/reconcile [new-notif-1]])
(is (= {types/no-type
{:read {:data []}
:unread {:data [{:id "0x1"
:read false
:type types/one-to-one-chat}]}}
(is (= {types/no-type
{:all {:data [new-notif-1]}
:unread {:data [new-notif-1]}}
types/one-to-one-chat
{:read {:cursor ""
:data []}
:unread {:data [{:id "0x1"
:read false
:type types/one-to-one-chat}]}}}
(get-in (h/db) [:activity-center :notifications])))))
types/one-to-one-chat
{:all {:cursor "" :data [new-notif-1]}
:unread {:data [new-notif-1]}}}
(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]
{types/one-to-one-chat
{:read {:cursor ""
:data [{:id "0x1" :read true :type types/one-to-one-chat :timestamp 1}
{:id "0x2" :read true :type types/one-to-one-chat :timestamp 1}]}
:unread {:cursor ""
:data [{:id "0x3" :read false :type types/one-to-one-chat :timestamp 50}
{:id "0x4" :read false :type types/one-to-one-chat :timestamp 100}
{:id "0x5" :read false :type types/one-to-one-chat :timestamp 100}]}}}])
(let [notif-1 {:id "0x1" :read true :type types/one-to-one-chat :timestamp 1}
notif-2 {:id "0x2" :read true :type types/one-to-one-chat :timestamp 1}
notif-3 {:id "0x3" :read false :type types/one-to-one-chat :timestamp 50}
notif-4 {:id "0x4" :read false :type types/one-to-one-chat :timestamp 100}
notif-5 {:id "0x5" :read false :type types/one-to-one-chat :timestamp 100}
new-notif-1 (assoc notif-1 :last-message {})
new-notif-4 (assoc notif-4 :last-message {})]
(rf/dispatch [:test/assoc-in [:activity-center :notifications]
{types/one-to-one-chat
{:all {:cursor "" :data [notif-1 notif-2]}
:unread {:cursor "" :data [notif-3 notif-4 notif-5]}}}])
(rf/dispatch [:activity-center.notifications/reconcile
[{:id "0x1" :read true :type types/one-to-one-chat :timestamp 1 :last-message {}}
{:id "0x4" :read false :type types/one-to-one-chat :timestamp 100 :last-message {}}]])
(rf/dispatch [:activity-center.notifications/reconcile [new-notif-1 new-notif-4]])
(is (= {types/no-type
{:read {:data [{:id "0x1"
:read true
:type types/one-to-one-chat
:timestamp 1
:last-message {}}]}
:unread {:data [{:id "0x4"
:read false
:type types/one-to-one-chat
:timestamp 100
:last-message {}}]}}
types/one-to-one-chat
{:read {:cursor ""
:data [{:id "0x2"
:read true
:type types/one-to-one-chat
:timestamp 1}
{:id "0x1"
:read true
:type types/one-to-one-chat
:timestamp 1
:last-message {}}]}
:unread {:cursor ""
:data [{:id "0x5"
:read false
:type types/one-to-one-chat
:timestamp 100}
{:id "0x4"
:read false
:type types/one-to-one-chat
:timestamp 100
:last-message {}}
{:id "0x3"
:read false
:type types/one-to-one-chat
:timestamp 50}]}}}
(get-in (h/db) [:activity-center :notifications])))))))
(is (= {types/no-type
{:all {:data [new-notif-4 new-notif-1]}
:unread {:data [new-notif-4]}}
types/one-to-one-chat
{:all {:cursor "" :data [new-notif-4 notif-2 new-notif-1]}
:unread {:cursor "" :data [notif-5 new-notif-4 notif-3]}}}
(get-in (h/db) [:activity-center :notifications]))))))))
;;;; Notifications fetching and pagination
@@ -456,12 +406,12 @@
(let [spy-queue (atom [])]
(h/spy-fx spy-queue ::json-rpc/call)
(rf/dispatch [:test/assoc-in [:activity-center :filter :status]
:read])
:all])
(rf/dispatch [:test/assoc-in [:activity-center :filter :type]
types/one-to-one-chat])
(rf/dispatch [:test/assoc-in [:activity-center :notifications types/one-to-one-chat :read :cursor]
(rf/dispatch [:test/assoc-in [:activity-center :notifications types/one-to-one-chat :all :cursor]
"10"])
(rf/dispatch [:test/assoc-in [:activity-center :notifications types/one-to-one-chat :read :loading?]
(rf/dispatch [:test/assoc-in [:activity-center :notifications types/one-to-one-chat :all :loading?]
true])
(rf/dispatch [:activity-center.notifications/fetch-next-page])
@@ -6,7 +6,7 @@
(def ^:const mention 3)
(def ^:const reply 4)
(def ^:const contact-request 5)
(def ^:const contact-verification 6)
(def ^:const contact-verification 10)
;; TODO: Remove this constant once the old Notification Center code is removed.
;; Its value clashes with the new constant `-contact-verification`
+5 -5
View File
@@ -134,7 +134,7 @@
(re-frame/dispatch [::failed-to-import %]))}]})
(fx/defn join
{:events [::join]}
{:events [:communities/join]}
[cofx community-id]
{::json-rpc/call [{:method "wakuext_joinCommunity"
:params [community-id]
@@ -156,8 +156,8 @@
(re-frame/dispatch [::failed-to-request-to-join %]))}]})
(fx/defn leave
{:events [::leave]}
[{:keys [db] :as cofx} community-id]
{:events [:communities/leave]}
[{:keys [db]} community-id]
(let [community-chat-ids (map #(str community-id %)
(keys (get-in db [:communities community-id :chats])))]
{:clear-message-notifications [community-chat-ids
@@ -325,7 +325,7 @@
{:db (assoc db :communities/create-channel {})})
(fx/defn invite-people-pressed
{:events [::invite-people-pressed]}
{:events [:communities/invite-people-pressed]}
[cofx id]
(fx/merge cofx
(reset-community-id-input id)
@@ -333,7 +333,7 @@
(navigation/open-modal :invite-people-community {:invite? true})))
(fx/defn share-community-pressed
{:events [::share-community-pressed]}
{:events [:communities/share-community-pressed]}
[cofx id]
(fx/merge cofx
(reset-community-id-input id)
+7 -6
View File
@@ -21,12 +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 contact-verification-status-unknown 0)
(def ^:const contact-verification-status-pending 1)
(def ^:const contact-verification-status-accepted 2)
(def ^:const contact-verification-status-declined 3)
(def ^:const contact-verification-status-cancelled 4)
(def ^:const contact-verification-status-trusted 5)
(def ^:const contact-verification-status-untrustworthy 6)
(def ^:const emoji-reaction-love 1)
(def ^:const emoji-reaction-thumbs-up 2)
+1 -1
View File
@@ -4,7 +4,7 @@
[status-im.constants :as constants]
[status-im.activity-center.notification-types :as notification-types]
[status-im.data-store.messages :as messages]
[status-im.utils.config :as config]))
[status-im2.setup.config :as config]))
(defn- rpc->type [{:keys [type name] :as chat}]
(case type
@@ -3,7 +3,7 @@
[status-im.constants :as constants]
[status-im.activity-center.notification-types :as notification-types]
[status-im.data-store.activities :as store]
[status-im.utils.config :as config]))
[status-im2.setup.config :as config]))
(def chat-id
"0x04c66155")
@@ -13,7 +13,7 @@
(def raw-notification
{:chatId chat-id
:contactVerificationStatus constants/contact-verification-state-pending
:contactVerificationStatus constants/contact-verification-status-pending
:lastMessage {}
:name chat-name
:replyMessage {}})
@@ -23,7 +23,7 @@
(testing "renames keys"
(is (= {:name chat-name
:chat-id chat-id
:contact-verification-status constants/contact-verification-state-pending}
:contact-verification-status constants/contact-verification-status-pending}
(-> raw-notification
store/<-rpc
(dissoc :last-message :message :reply-message)))))
+1
View File
@@ -46,6 +46,7 @@
status-im.ui.screens.privacy-and-security-settings.events
[status-im.utils.dimensions :as dimensions]
[status-im.utils.fx :as fx]
status-im.utils.logging.core
[status-im.utils.universal-links.core :as universal-links]
[status-im.utils.utils :as utils]
status-im.visibility-status-updates.core
+1 -1
View File
@@ -110,7 +110,7 @@
:blank-keycard-title
:block
:block-contact
:block-user-confirmation
:block-contact-details
:blocked-users
:bootnode-address
:bootnode-details
-54
View File
@@ -1,54 +0,0 @@
(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)]]])])
-24
View File
@@ -1,24 +0,0 @@
(ns status-im.switcher.shell-stack
(:require [status-im.i18n.i18n :as i18n]
[status-im.switcher.shell :as shell]
[status-im.switcher.constants :as constants]
[status-im.switcher.animation :as animation]
[status-im.switcher.home-stack :as home-stack]
[status-im.switcher.bottom-tabs :as bottom-tabs]
[quo2.components.navigation.floating-shell-button :as floating-shell-button]))
(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]
[floating-shell-button/floating-shell-button
{:jump-to {:on-press #(animation/close-home-stack shared-values)
:label (i18n/label :t/jump-to)}}
{:position :absolute
:bottom (+ (constants/bottom-tabs-container-height) 7)} ;; bottom offset is 12 = 7 + 5(padding on button)
(:home-stack-opacity shared-values)
(:home-stack-pointer shared-values)]]))])
+1 -1
View File
@@ -5,7 +5,7 @@
[status-im.chat.models.pin-message :as models.pin-message]
[status-im.chat.models :as models.chat]
[status-im.chat.models.reactions :as models.reactions]
[status-im.utils.config :as config]
[status-im2.setup.config :as config]
[status-im.contact.core :as models.contact]
[status-im.communities.core :as models.communities]
[status-im.pairing.core :as models.pairing]
+1 -29
View File
@@ -2,22 +2,7 @@
(:require [quo.design-system.colors :as colors]
[quo.core :as quo]
[status-im.ui.components.react :as react]
[status-im.ui.components.icons.icons :as icons]
[quo2.components.buttons.button :as quo2.button]))
(def action-button-container
{:align-items :center
:justify-content :center
:height 32
:border-radius 16
:overflow :hidden})
(defn action-button []
{:width 32
:height 32
:border-radius 18
:align-items :center
:justify-content :center})
[status-im.ui.components.icons.icons :as icons]))
(def action-button-container-old
{:position :absolute
@@ -44,19 +29,6 @@
"rgba(0, 12, 63, 0.2)")
:elevation 2})
(defn plus-button [{:keys [on-press loading accessibility-label]}]
[react/view action-button-container
[quo2.button/button {:type :primary
:size 32
:width 32
:accessibility-label (or accessibility-label :plus-button)
:on-press on-press}
[react/view (action-button)
(if loading
[react/activity-indicator {:color colors/white-persist
:animating true}]
[icons/icon :main-icons/add {:color colors/white-persist}])]]])
(defn plus-button-old [{:keys [on-press loading accessibility-label]}]
[react/view action-button-container-old
[quo/button {:type :scale
+1 -1
View File
@@ -8,7 +8,7 @@
(defn default-navigation [modal? {:keys [on-press label icon]}]
(cond-> {:icon (if modal? :main-icons/close :main-icons/arrow-left)
:accessibility-label :back-button
:on-press #(re-frame/dispatch [:bottom-sheet/hide :navigate-back])}
:on-press #(re-frame/dispatch [:navigate-back])}
on-press
(assoc :on-press on-press)
@@ -0,0 +1,11 @@
(ns status-im.ui.screens.activity-center.notification.contact-request.style
(:require [quo2.foundations.colors :as colors]))
(def context-tag-text
{:color colors/white})
(def user-avatar-tag-text
{:color colors/white})
(def user-avatar-tag
{:background-color colors/white-opa-10})
@@ -0,0 +1,59 @@
(ns status-im.ui.screens.activity-center.notification.contact-request.view
(:require [quo.components.animated.pressable :as animation]
[quo2.core :as quo2]
[status-im.constants :as constants]
[status-im.i18n.i18n :as i18n]
[status-im.multiaccounts.core :as multiaccounts]
[status-im.ui.screens.activity-center.notification.contact-request.style :as style]
[status-im.ui.screens.activity-center.utils :as activity-center.utils]
[status-im.utils.datetime :as datetime]
[utils.re-frame :as rf]))
(defn view
[{:keys [id author message last-message] :as notification}]
(let [message (or message last-message)
contact (rf/sub [:contacts/contact-by-identity author])
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 []
(rf/dispatch [:hide-popover])
(rf/dispatch [:contact.ui/send-message-pressed {:public-key author}]))}]
[:<>])]
(conj pressable
[quo2/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 [[quo2/user-avatar-tag
{:color :purple
:override-theme :dark
:size :small
:style style/user-avatar-tag
:text-style style/user-avatar-tag-text}
(activity-center.utils/contact-name contact)
(multiaccounts/displayed-photo contact)]
(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)
:accessibility-label :decline-contact-request
:type :danger
:on-press #(rf/dispatch [:contact-requests.ui/decline-request id])}
:button-2 {:label (i18n/label :t/accept)
:accessibility-label :accept-contact-request
:type :positive
:on-press #(rf/dispatch [:contact-requests.ui/accept-request id])}}
nil))])))
@@ -0,0 +1,11 @@
(ns status-im.ui.screens.activity-center.notification.contact-verification.style
(:require [quo2.foundations.colors :as colors]))
(def context-tag-text
{:color colors/white})
(def user-avatar-tag-text
{:color colors/white})
(def user-avatar-tag
{:background-color colors/white-opa-10})
@@ -0,0 +1,109 @@
(ns status-im.ui.screens.activity-center.notification.contact-verification.view
(:require [clojure.string :as str]
[quo2.core :as quo2]
[status-im.constants :as constants]
[status-im.i18n.i18n :as i18n]
[status-im.multiaccounts.core :as multiaccounts]
[status-im.ui.screens.activity-center.notification.contact-verification.style :as style]
[status-im.ui.screens.activity-center.utils :as activity-center.utils]
[status-im.utils.datetime :as datetime]
[utils.re-frame :as rf]))
(defn- hide-bottom-sheet-and-dispatch
[event]
(rf/dispatch [:bottom-sheet/hide])
(rf/dispatch [:dismiss-keyboard])
(rf/dispatch event))
(defn- context-tags
[challenger? {:keys [author contact-verification-status]}]
(let [contact (rf/sub [:contacts/contact-by-identity author])]
[[quo2/user-avatar-tag
{:color :purple
:override-theme :dark
:size :small
:style style/user-avatar-tag
:text-style style/user-avatar-tag-text}
(activity-center.utils/contact-name contact)
(multiaccounts/displayed-photo contact)]
(if challenger?
(when (or (= contact-verification-status constants/contact-verification-status-accepted)
(= contact-verification-status constants/contact-verification-status-trusted)
(= contact-verification-status constants/contact-verification-status-untrustworthy))
(str (str/lower-case (i18n/label :t/replied)) ":"))
(when (or (= contact-verification-status constants/contact-verification-status-accepted)
(= contact-verification-status constants/contact-verification-status-pending)
(= contact-verification-status constants/contact-verification-status-declined))
(str (i18n/label :t/identity-verification-request-sent) ":")))]))
(defn- activity-message
[challenger? {:keys [contact-verification-status message reply-message]}]
(if challenger?
(when (or (= contact-verification-status constants/contact-verification-status-accepted)
(= contact-verification-status constants/contact-verification-status-trusted)
(= contact-verification-status constants/contact-verification-status-untrustworthy))
{:title (get-in message [:content :text])
:body (get-in reply-message [:content :text])})
(when (or (= contact-verification-status constants/contact-verification-status-accepted)
(= contact-verification-status constants/contact-verification-status-pending)
(= contact-verification-status constants/contact-verification-status-declined))
{:body (get-in message [:content :text])})))
(defn- activity-status
[challenger? contact-verification-status]
(if challenger?
(cond (= contact-verification-status constants/contact-verification-status-trusted)
{:type :positive :label (i18n/label :t/status-confirmed)}
(= contact-verification-status constants/contact-verification-status-untrustworthy)
{:type :negative :label (i18n/label :t/untrustworthy)})
(cond (= contact-verification-status constants/contact-verification-status-accepted)
{:type :positive :label (i18n/label :t/replied)}
(= contact-verification-status constants/contact-verification-status-declined)
{:type :negative :label (i18n/label :t/declined)})))
(defn view
[_ _]
(let [reply (atom "")]
(fn [{:keys [id message contact-verification-status] :as notification} {:keys [replying?]}]
(let [challenger? (:outgoing message)]
;; TODO(@ilmotta): Declined challenges should only be displayed for the
;; challengee, not the challenger.
;; https://github.com/status-im/status-mobile/issues/14354
(when-not (and challenger? (= contact-verification-status constants/contact-verification-status-declined))
[quo2/activity-log
(merge {:title (i18n/label :t/identity-verification-request)
:icon :i/friend
:timestamp (datetime/timestamp->relative (:timestamp notification))
:unread? (not (:read notification))
:on-update-reply #(reset! reply %)
:replying? replying?
:context (context-tags challenger? notification)
:message (activity-message challenger? notification)
:status (activity-status challenger? contact-verification-status)}
(if challenger?
(when (= contact-verification-status constants/contact-verification-status-accepted)
{:button-1 {:label (i18n/label :t/untrustworthy)
:accessibility-label :mark-contact-verification-as-untrustworthy
:type :danger
:on-press #(rf/dispatch [:activity-center.contact-verification/mark-as-untrustworthy id])}
:button-2 {:label (i18n/label :t/accept)
:accessibility-label :mark-contact-verification-as-trusted
:type :positive
:on-press #(rf/dispatch [:activity-center.contact-verification/mark-as-trusted id])}})
(when (= contact-verification-status constants/contact-verification-status-pending)
{:button-1 {:label (i18n/label :t/decline)
:accessibility-label :decline-contact-verification
:type :danger
:on-press #(hide-bottom-sheet-and-dispatch [:activity-center.contact-verification/decline id])}
:button-2 (if replying?
{:label (i18n/label :t/send-reply)
:accessibility-label :reply-to-contact-verification
:type :primary
:on-press #(hide-bottom-sheet-and-dispatch [:activity-center.contact-verification/reply id @reply])}
{:label (i18n/label :t/message-reply)
:accessibility-label :send-reply-to-contact-verification
:type :primary
:on-press #(rf/dispatch [:bottom-sheet/show-sheet
:activity-center.contact-verification/reply
{:notification notification
:replying? true}])})})))])))))
@@ -0,0 +1,9 @@
(ns status-im.ui.screens.activity-center.sheet.contact-verification
(:require [status-im.ui.screens.activity-center.notification.contact-verification.view :as contact-verification]))
(defn- reply-view
[{:keys [notification replying?]}]
[contact-verification/view notification {:replying? replying?}])
(def reply
{:content reply-view})
@@ -0,0 +1,28 @@
(ns status-im.ui.screens.activity-center.style
(:require [quo2.foundations.colors :as colors]))
(def screen-padding 20)
(def header-button
{:margin-bottom 12
:margin-left screen-padding})
(def header-heading
{:padding-horizontal screen-padding
:padding-vertical 12
:color colors/white})
(defn screen-container
[window-width top bottom]
{:flex 1
:width window-width
:padding-top (if (pos? top) (+ top 12) 12)
:padding-bottom bottom})
(defn notification-container
[index]
{:margin-top (if (zero? index) 0 4)
:padding-horizontal 8})
(def tabs
{:padding-left screen-padding})
@@ -0,0 +1,6 @@
(ns status-im.ui.screens.activity-center.utils)
(defn contact-name
[contact]
(or (get-in contact [:names :nickname])
(get-in contact [:names :three-words-name])))
@@ -1,154 +1,32 @@
(ns status-im.ui.screens.activity-center.views
(:require [quo.components.animated.pressable :as animation]
(:require [quo.components.safe-area :as safe-area]
[quo.react :as react]
[quo.react-native :as rn]
[quo2.components.buttons.button :as button]
[quo2.components.markdown.text :as text]
[quo2.components.notifications.activity-logs :as activity-logs]
[quo2.components.tabs.tabs :as tabs]
[quo2.components.tags.context-tags :as context-tags]
[quo2.core :as quo2]
[quo2.foundations.colors :as colors]
[status-im.constants :as constants]
[status-im.activity-center.notification-types :as types]
[status-im.i18n.i18n :as i18n]
[status-im.multiaccounts.core :as multiaccounts]
[status-im.utils.datetime :as datetime]
[goog.string :as gstring]
[status-im.utils.handlers :refer [<sub >evt]]
[quo.components.safe-area :as safe-area]))
;;;; Misc
(defn sender-name
[contact]
(or (get-in contact [:names :nickname])
(get-in contact [:names :three-words-name])))
(defmulti notification-component :type)
;; TODO(rasom): should be removed as soon as all notifications types covered
(defmethod notification-component :default
[{:keys [type]}]
[rn/view {:style {:width 300, :height 100}}
[rn/text
(gstring/format
"I exist just to avoid crashing for no reason. I'm sorry. Type %d" type)]])
;;;; Contact request notifications
(defmethod notification-component types/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 :i/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))])))
;;;; Contact verification notifications
(defmethod notification-component types/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 :i/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))]))
;;;; Type-independent components
(defn render-notification
[notification index]
[rn/view {:margin-top (if (= 0 index) 0 4)
:padding-horizontal 20}
[notification-component notification]])
[status-im.ui.screens.activity-center.notification.contact-request.view :as contact-request]
[status-im.ui.screens.activity-center.notification.contact-verification.view :as contact-verification]
[status-im.ui.screens.activity-center.style :as style]
[utils.re-frame :as rf]))
(defn filter-selector-read-toggle
[]
(let [unread-filter-enabled? (<sub [:activity-center/filter-status-unread-enabled?])]
;; TODO: Replace the button by a Filter Selector component once available for use.
[button/button {:icon true
:type (if unread-filter-enabled? :primary :blur-bg-outline)
:size 32
:override-theme :dark
:on-press #(>evt [:activity-center.notifications/fetch-first-page
{:filter-status (if unread-filter-enabled?
:all
:unread)}])}
(let [unread-filter-enabled? (rf/sub [:activity-center/filter-status-unread-enabled?])]
;; TODO(@ilmotta): Replace the button by a Filter Selector.
;; https://github.com/status-im/status-mobile/issues/14355
[quo2/button {:icon true
:type (if unread-filter-enabled? :primary :blur-bg-outline)
:size 32
:override-theme :dark
:on-press #(rf/dispatch [:activity-center.notifications/fetch-first-page
{:filter-status (if unread-filter-enabled?
:all
:unread)}])}
:i/unread]))
;; TODO(2022-10-07): The empty state is still under design analysis, so we
;; TODO(@ilmotta,2022-10-07): The empty state is still under design analysis, so we
;; shouldn't even care about translations at this point. A placeholder box is
;; used instead of an image.
(defn empty-tab
@@ -161,24 +39,24 @@
:height 120
:margin-bottom 20
:width 120}}]
[text/text {:size :paragraph-1
[quo2/text {:size :paragraph-1
:style {:padding-bottom 2}
:weight :semi-bold}
"No notifications"]
[text/text {:size :paragraph-2}
[quo2/text {:size :paragraph-2}
"Your notifications will be here"]])
(defn tabs
[]
(let [filter-type (<sub [:activity-center/filter-type])]
[tabs/scrollable-tabs {:size 32
(let [filter-type (rf/sub [:activity-center/filter-type])]
[quo2/scrollable-tabs {:size 32
:blur? true
:override-theme :dark
:style {:padding-left 20}
:style style/tabs
:fade-end-percentage 0.79
:scroll-on-press? true
:fade-end? true
:on-change #(>evt [:activity-center.notifications/fetch-first-page {:filter-type %}])
:on-change #(rf/dispatch [:activity-center.notifications/fetch-first-page {:filter-type %}])
:default-active filter-type
:data [{:id types/no-type
:label (i18n/label :t/all)}
@@ -203,19 +81,16 @@
[]
(let [screen-padding 20]
[rn/view
[button/button {:icon true
:type :blur-bg
:size 32
:override-theme :dark
:style {:margin-bottom 12
:margin-left screen-padding}
:on-press #(>evt [:hide-popover])}
[quo2/button {:icon true
:type :blur-bg
:size 32
:override-theme :dark
:style style/header-button
:on-press #(rf/dispatch [:hide-popover])}
:i/close]
[text/text {:size :heading-1
[quo2/text {:size :heading-1
:weight :semi-bold
:style {:padding-horizontal screen-padding
:padding-vertical 12
:color colors/white}}
:style style/header-heading}
(i18n/label :t/notifications)]
[rn/view {:flex-direction :row
:padding-vertical 12}
@@ -227,22 +102,30 @@
:padding-right screen-padding}
[filter-selector-read-toggle]]]]))
(defn render-notification
[notification index]
[rn/view {:style (style/notification-container index)}
(case (:type notification)
types/contact-verification
[contact-verification/view notification {}]
types/contact-request
[contact-request/view notification]
nil)])
(defn activity-center
[]
[: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}}
(let [notifications (rf/sub [:activity-center/filtered-notifications])
window-width (rf/sub [:dimensions/window-width])
{:keys [top bottom]} (safe-area/use-safe-area)]
(react/effect! #(rf/dispatch [:activity-center.notifications/fetch-first-page]))
[rn/view {:style (style/screen-container window-width top 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}]]))])
[rn/flat-list {:data notifications
:empty-component [empty-tab]
:key-fn :id
:on-end-reached #(rf/dispatch [:activity-center.notifications/fetch-next-page])
:render-fn render-notification}]]))])
@@ -1,12 +1,13 @@
(ns status-im.ui.screens.bottom-sheets.views
(:require [status-im.ui.screens.mobile-network-settings.view :as mobile-network-settings]
(:require [quo.core :as quo]
[re-frame.core :as re-frame]
[status-im.ui.screens.about-app.views :as about-app]
[status-im.ui.screens.activity-center.sheet.contact-verification :as contact-verification.sheet]
[status-im.ui.screens.home.sheet.views :as home.sheet]
[status-im.ui.screens.keycard.views :as keycard]
[status-im.ui.screens.mobile-network-settings.view :as mobile-network-settings]
[status-im.ui.screens.multiaccounts.key-storage.views :as key-storage]
[status-im.ui.screens.about-app.views :as about-app]
[status-im.ui.screens.multiaccounts.recover.views :as recover.views]
[quo.core :as quo]))
[status-im.ui.screens.multiaccounts.recover.views :as recover.views]))
(defn bottom-sheet []
(let [{:keys [show? view options]} @(re-frame/subscribe [:bottom-sheet])
@@ -27,11 +28,8 @@
(= view :add-new)
(merge home.sheet/add-new)
(= view :add-new-sheet-view)
(merge home.sheet/add-new-sheet)
(= view :start-a-new-chat)
(merge home.sheet/start-a-new-chat)
(= view :activity-center.contact-verification/reply)
(merge contact-verification.sheet/reply)
(= view :keycard.login/more)
(merge keycard/more-sheet)
@@ -46,4 +44,4 @@
(merge key-storage/migrate-account-password))]
[quo/bottom-sheet opts
(when content
[content (when options options)])]))
[content (when options options)])]))
@@ -0,0 +1,77 @@
(ns status-im.ui.screens.chat.components.messages-skeleton
(:require [status-im.ui.components.react :as react]
[react-native.core :as rn]
[reagent.core :as reagent]
[react-native.reanimated :as reanimated]
[quo2.foundations.colors :as colors]))
(def message-skeleton-height 54)
(def avatar-skeleton-size 32)
(def message-content-width [{:author 80
:message 249}
{:author 124
:message 156}
{:author 96
:message 212}
{:author 112
:message 144}])
;; Standlone message skeleton
(defn message-skeleton []
[:f>
(fn []
(let [color (colors/theme-colors colors/neutral-5 colors/neutral-70)
loading-color (colors/theme-colors colors/neutral-10 colors/neutral-60)
content-width (rand-nth message-content-width)
author-width (content-width :author)
message-width (content-width :message)
{window-width :width} (rn/use-window-dimensions)
translate-x (reanimated/use-shared-value (- window-width))
animated-gradient-style (reanimated/apply-animations-to-style
{:transform [{:translateX translate-x}]}
{:width window-width
:height "100%"})]
(reanimated/animate-shared-value-with-repeat translate-x window-width 1000 :linear (- 1) false)
[react/masked-view
{:style {:height message-skeleton-height}
:maskElement (reagent/as-element
[rn/view {:style {:height message-skeleton-height
:flex-direction :row
:padding-vertical 11
:background-color :transparent
:padding-left 21}}
[rn/view {:style {:height avatar-skeleton-size
:width avatar-skeleton-size
:border-radius (/ avatar-skeleton-size 2)
:background-color color
:overflow :hidden}}]
[rn/view {:style {:padding-left 8
:background-color :transparent}}
[rn/view {:style {:height 8
:width author-width
:border-radius 6
:background-color color
:margin-bottom 8
:overflow :hidden}}]
[rn/view {:style {:height 16
:width message-width
:border-radius 6
:overflow :hidden
:background-color color}}]]])}
[rn/view {:style {:flex 1
:background-color color}}
[reanimated/linear-gradient {:colors [color color loading-color color color]
:start {:x 0 :y 0}
:end {:x 1 :y 0}
:style animated-gradient-style}]]]))])
(defn messages-skeleton [parent-height]
(let [number-of-skeletons (int (Math/floor (/ parent-height message-skeleton-height)))]
[rn/view {:style {:background-color (colors/theme-colors
colors/white
colors/neutral-90)
:flex 1}}
(for [n (range number-of-skeletons)]
[message-skeleton {:key n}])]))
@@ -4,6 +4,7 @@
[re-frame.core :as re-frame]
[status-im.i18n.i18n :as i18n]
[status-im.utils.datetime :as datetime]
[status-im.ui.screens.chat.components.messages-skeleton :as messages-skeleton]
[status-im.ui.screens.chat.styles.input.gap :as style]))
(defn on-press
@@ -18,16 +19,17 @@
chat-id]
connected? [:mailserver/connected?]
use-status-nodes? [:mailserver/use-status-nodes?]
first-gap? (= gap-ids #{:first-gap})]
first-gap? (= gap-ids #{:first-gap})
window-height [:dimensions/window-height]]
(when (or (not first-gap?) public? community?)
[react/view {:style (style/gap-container)}
[react/view {:style (when-not in-progress? style/gap-container)}
[react/touchable-highlight
{:on-press (when (and (not in-progress?) use-status-nodes? connected?)
(on-press chat-id gap-ids))
:style style/touchable}
:style {:height (if in-progress? window-height 48)}}
[react/view {:style style/label-container}
(if in-progress?
[react/activity-indicator]
[messages-skeleton/messages-skeleton window-height]
[react/nested-text
{:style (style/gap-text (and connected? use-status-nodes?))}
(i18n/label (if first-gap? :t/load-more-messages :t/fetch-messages))
@@ -1,28 +1,29 @@
(ns status-im.ui.screens.communities.community
(:require [status-im.ui.components.topbar :as topbar]
[quo.react-native :as rn]
(:require [i18n.i18n :as i18n]
[utils.re-frame :as rf]
[react-native.core :as rn]
[quo2.components.community.style :as styles]
;; TODO reimplement with quo2 library and new designs
[status-im.ui.components.topbar :as topbar]
[status-im.ui.components.toolbar :as toolbar]
[quo.core :as quo]
[status-im.constants :as constants]
[status-im.chat.models.link-preview :as link-preview]
[status-im.utils.handlers :refer [>evt <sub]]
[status-im.i18n.i18n :as i18n]
[status-im.utils.datetime :as datetime]
[status-im.communities.core :as communities]
[status-im.ui.components.list.views :as list]
[status-im.ui.components.react :as components.react]
[status-im.ui.screens.home.views.inner-item :as inner-item]
[status-im.ui.screens.chat.photos :as photos]
[status-im.react-native.resources :as resources]
[status-im.ui.components.chat-icon.screen :as chat-icon.screen]
[quo.design-system.colors :as colors]
[status-im.ui.components.icons.icons :as icons]
[status-im.utils.core :as utils]
[status-im.ui.components.plus-button :as components.plus-button]
[re-frame.core :as re-frame]
[status-im.ui.screens.chat.sheets :as sheets]
[status-im.ui.components.accordion :as accordion]
[quo2.components.community.style :as styles]))
[status-im.ui.components.list.views :as list]
[status-im.ui.components.react :as react]
[quo.core :as quo]
[quo.design-system.colors :as colors]))
(def request-cooldown-ms (* 24 60 60 1000))
@@ -61,8 +62,8 @@
(i18n/label :t/open-membership))]]]))
(defn hide-sheet-and-dispatch [event]
(>evt [:bottom-sheet/hide])
(>evt event))
(rf/dispatch [:bottom-sheet/hide])
(rf/dispatch event))
(defn community-plus-actions [{:keys [id permissions can-manage-users?]}]
(let [can-invite? (and can-manage-users? (not= (:access permissions) constants/community-no-membership-access))
@@ -87,14 +88,14 @@
:title (i18n/label :t/invite-people)
:icon :main-icons/share
:accessibility-label :community-invite-people
:on-press #(>evt [::communities/invite-people-pressed id])}])
:on-press #(rf/dispatch [:communities/invite-people-pressed id])}])
(when (and can-share? (not can-invite?))
[quo/list-item
{:theme :accent
:title (i18n/label :t/invite-people)
:icon :main-icons/share
:accessibility-label :community-share
:on-press #(>evt [::communities/share-community-pressed id])}])]))
:on-press #(rf/dispatch [:communities/share-community-pressed id])}])]))
(defn community-actions [{:keys [id name images color can-manage-users?]}]
(let [thumbnail-image (get-in images [:thumbnail :uri])]
@@ -153,13 +154,13 @@
;; unread indicator
(assoc home-item :public? true)
{:on-press (fn []
(re-frame/dispatch [:dismiss-keyboard])
(re-frame/dispatch [:chat.ui/navigate-to-chat chat-id])
(re-frame/dispatch [:search/home-filter-changed nil])
(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])}])}])
(rf/dispatch [:dismiss-keyboard])
(rf/dispatch [:chat.ui/navigate-to-chat chat-id])
(rf/dispatch [:search/home-filter-changed nil])
(rf/dispatch [:accept-all-activity-center-notifications-from-chat chat-id]))
:on-long-press #(rf/dispatch [:bottom-sheet/show-sheet
{:content (fn []
[sheets/actions home-item])}])}])
(defn categories-accordion [community-id chats categories data]
[:<>
@@ -167,8 +168,8 @@
^{:key (str "cat" name id)}
[:<>
[accordion/section
{:on-open #(>evt [::communities/store-category-state community-id id true])
:on-close #(>evt [::communities/store-category-state community-id id false])
{:on-open #(rf/dispatch [::communities/store-category-state community-id id true])
:on-close #(rf/dispatch [::communities/store-category-state community-id id false])
:default state
:title [rn/view styles/category-item
[icons/icon :main-icons/channel-category {:color colors/gray}]
@@ -180,7 +181,7 @@
[quo/separator]])])
(defn community-chat-list [community-id categories from-chat]
(let [chats (<sub [:chats/sorted-categories-by-community-id community-id])]
(let [chats (rf/sub [:chats/sorted-categories-by-community-id community-id])]
(if (and (empty? categories) (empty? chats))
[blank-page (i18n/label :t/welcome-community-blank-message)]
[list/flat-list
@@ -230,25 +231,25 @@
:render-fn channel-preview-item}]))
(defn unknown-community [community-id]
(let [fetching (<sub [:communities/fetching-community community-id])]
(let [fetching (rf/sub [:communities/fetching-community community-id])]
[:<> {:style {:flex 1}}
[topbar/topbar {:title (if fetching (i18n/label :t/fetching-community) (i18n/label :t/not-found))}]
[rn/view {:style {:padding 16 :flex 1 :flex-direction :row :align-items :center :justify-content :center}}
[quo/button {:on-press (when-not fetching #(>evt [::link-preview/resolve-community-info community-id]))
[quo/button {:on-press (when-not fetching #(rf/dispatch [::link-preview/resolve-community-info community-id]))
:disabled fetching
:color :secondary}
:color :secondary}
(if fetching
[components.react/small-loading-indicator]
[react/small-loading-indicator]
(i18n/label :t/fetch-community))]]]))
(defn community []
(let [{:keys [community-id from-chat]} (<sub [:get-screen-params])]
(let [{:keys [community-id from-chat]} (rf/sub [:get-screen-params])]
(fn []
(let [{:keys [id chats name images members permissions color joined
can-request-access? can-join? requested-to-join-at admin]
:as community} (<sub [:communities/community community-id])
categories (<sub [:communities/sorted-categories community-id])]
:as community} (rf/sub [:communities/community community-id])
categories (rf/sub [:communities/sorted-categories community-id])]
(if community
[rn/view {:style {:flex 1}}
[topbar/topbar
@@ -264,24 +265,24 @@
(when (or admin joined)
[{:icon :main-icons/more
:accessibility-label :community-menu-button
:on-press #(>evt [:bottom-sheet/show-sheet
{:content (fn []
[community-actions community])}])}])}]
:on-press #(rf/dispatch [:bottom-sheet/show-sheet
{:content (fn []
[community-actions community])}])}])}]
(if joined
[community-chat-list id categories false from-chat]
[community-channel-preview-list id chats])
(when admin
[components.plus-button/plus-button-old
{:on-press #(>evt [:bottom-sheet/show-sheet
{:content (fn []
[community-plus-actions community])}])
{:on-press #(rf/dispatch [:bottom-sheet/show-sheet
{:content (fn []
[community-plus-actions community])}])
:accessibility-label :new-chat-button}])
(when-not joined
(cond
can-join?
[toolbar/toolbar
{:show-border? true
:center [quo/button {:on-press #(>evt [::communities/join id])
:center [quo/button {:on-press #(rf/dispatch [:communities/join id])
:type :secondary}
(i18n/label :t/join)]}]
can-request-access?
@@ -292,13 +293,13 @@
:left [quo/text {:color :secondary} (i18n/label :t/membership-request-pending)]}]
[toolbar/toolbar
{:show-border? true
:center [quo/button {:on-press #(>evt [::communities/request-to-join id])
:center [quo/button {:on-press #(rf/dispatch [::communities/request-to-join id])
:type :secondary}
(i18n/label :t/request-access)]}])
:else
[toolbar/toolbar
{:show-border? true
:center [quo/button {:on-press #(>evt [::communities/join id])
:center [quo/button {:on-press #(rf/dispatch [:communities/join id])
:type :secondary}
(i18n/label :t/follow)]}]))]
[unknown-community community-id])))))
@@ -1,89 +0,0 @@
(ns status-im.ui.screens.communities.community-options-bottom-sheet
(:require [status-im.i18n.i18n :as i18n]
[quo.react-native :as rn] [quo2.components.markdown.text :as text]
[quo2.components.buttons.button :as button]
[quo2.components.drawers.action-drawers :as action-drawers]
[quo2.components.tags.context-tags :as context-tags]
[status-im.react-native.resources :as resources]
[status-im.utils.handlers :refer [<sub >evt]]
[status-im.communities.core :as communities]))
(def not-joined-options [{:icon :i/members
:label (i18n/label :t/view-members)}
{:icon :i/bullet-list
:right-icon :i/chevron-right
:label (i18n/label :t/view-community-rules)}
{:icon :i/add-user
:label (i18n/label :t/invite-contacts)}
{:icon :i/qr-code
:label (i18n/label :t/show-qr)}
{:icon :i/share
:label (i18n/label :t/share-community)}])
(def joined-options [{:icon :i/members
:label (i18n/label :t/view-members)}
{:icon :i/bullet-list
:right-icon :i/chevron-right
:label (i18n/label :t/view-community-rules)}
{:icon :i/up-to-date
:label (i18n/label :t/mark-as-read)}
{:icon :i/muted
:label (i18n/label :t/mute-community)
:right-icon :i/chevron-right}
{:icon :i/notifications
:label (i18n/label :t/community-notification-settings)
:right-icon :i/chevron-right}
{:icon :i/add-user
:label (i18n/label :t/invite-contacts)}
{:icon :i/qr-code
:label (i18n/label :t/show-qr)}
{:icon :i/share
:label (i18n/label :t/share-community)}])
(defn leave-sheet [community]
[rn/view {:style {:flex 1 :margin-left 20 :margin-right 20 :margin-bottom 20}}
[rn/view {:style {:flex 1 :flex-direction :row :align-items :center :justify-content :space-between}}
[text/text {:accessibility-label :communities-join-community
:weight :semi-bold
:size :heading-1}
(i18n/label :t/leave-community?)]]
;; TODO get tag image from community data
[context-tags/context-tag
{:style
{:margin-right :auto
:margin-top 8}}
(resources/get-image :status-logo) (:name community)]
[text/text {:accessibility-label :communities-join-community
:size :paragraph-1
:style {:margin-top 16}}
(i18n/label :t/leave-community-message)]
[rn/view {:style {:width "100%"
:margin-top 16 :margin-bottom 16
:flex 1
:flex-direction :row
:align-items :center
:justify-content :space-evenly}}
[button/button {:on-press #(>evt [:bottom-sheet/hide])
:type :grey :style {:flex 1 :margin-right 12}} (i18n/label :t/cancel)]
[button/button
{:on-press (fn []
(>evt [::communities/leave (:id community)])
(>evt [:bottom-sheet/hide]))
:style {:flex 1}} (i18n/label :t/leave-community)]]])
(defn options-menu []
(let [community-mock (<sub [:get-screen-params :community-overview]) ;;TODO stop using mock data and only pass community id
community (<sub [:communities/community (:id community-mock)])]
[action-drawers/action-drawer {:actions (if (:joined community)
joined-options
not-joined-options)
:actions-with-consequence
(when (:joined community)
[{:icon :i/log-out
:label (i18n/label :t/leave-community)
:on-press #(>evt [:bottom-sheet/show-sheet
{:content (constantly [leave-sheet community])
:content-height 300}])}])}]))
@@ -1,171 +0,0 @@
(ns status-im.ui.screens.communities.community-overview-redesign
(:require [status-im.i18n.i18n :as i18n]
[quo.react-native :as rn]
[quo2.components.markdown.text :as text]
[quo2.components.buttons.button :as button]
[quo2.components.list-items.preview-list :as preview-list]
[quo2.components.list-items.channel :as channel]
[quo2.components.dividers.divider-label :as divider-label]
[quo2.components.community.community-view :as community-view]
[quo2.components.tags.status-tags :as status-tags]
[quo2.components.community.style :as styles]
[quo2.foundations.colors :as colors]
[quo2.components.navigation.page-nav :as page-nav]
[status-im.ui.screens.communities.request-to-join-bottom-sheet-redesign :as request-to-join]
[status-im.ui.screens.communities.community-options-bottom-sheet :as options-menu]
[status-im.ui.components.list.views :as list]
[status-im.utils.handlers :refer [<sub >evt]]
[status-im.ui.screens.communities.icon :as communities.icon]))
;; Mocked list items
(def user-list
[{:full-name "Alicia K"}
{:full-name "Marcus C"}
{:full-name "MNO PQR"}
{:full-name "STU VWX"}])
(defn preview-user-list []
[rn/view {:style {:flex-direction :row
:align-items :center
:margin-top 20}}
[preview-list/preview-list {:type :user
:more-than-99-label (i18n/label :counter-99-plus)
:user user-list :list-size 4 :size 24}]
[text/text {:accessibility-label :communities-screen-title
:style {:margin-left 8}
:size :label}
"Join Alicia, Marcus and 2 more"]]) ;; TODO remove mocked data and use from contacts list/communities members
(def list-of-channels {:Welcome [{:name "welcome"
:emoji "🤝"}
{:name "onboarding"
:emoji "🍑"}
{:name "intro"
:emoji "🦄"}]
:General [{:name "general"
:emoji "🐷"}
{:name "people-ops"
:emoji "🌏"}
{:name "announcements"
:emoji "🎺"}]
:Mobile [{:name "mobile"
:emoji "👽"}]})
(defn channel-list-component []
[rn/scroll-view {:style {:margin-top 20}}
[:<> {:style {:flex 1}}
(map (fn [category]
^{:key (get category 0)}
[rn/view {:flex 1}
[divider-label/divider-label
{:label (first category)
:chevron-position :left}]
[rn/view
{:margin-left 8
:margin-top 10
:margin-bottom 8}
[list/flat-list
{:shows-horizontal-scroll-indicator false
:separator [rn/view {:margin-top 4}]
:data ((first category) list-of-channels)
:render-fn channel/list-item}]]])
list-of-channels)]])
(defn icon-color []
(colors/theme-colors
colors/white-opa-40
colors/neutral-80-opa-40))
(defn community-card-page-view [{:keys [name description locked joined
status tokens cover tags community-color] :as community}]
[rn/view
{:style
{:flex 1
:border-radius 20}}
[rn/view (styles/community-cover-container 148)
[rn/image
{:source cover
:style {:position :absolute
:flex 1}}]
[rn/view {:style {:margin-top 26}}
[page-nav/page-nav
{:horizontal-description? true
:one-icon-align-left? true
:align-mid? false
:page-nav-color :transparent
:page-nav-background-uri ""
:mid-section {:type :text-with-description}
:right-section-buttons [{:icon :i/search
:background-color (icon-color)}
{:icon :i/options
:background-color (icon-color)
:on-press #(>evt [:bottom-sheet/show-sheet
{:content (constantly [options-menu/options-menu community])
:content-height 400}])}]
:left-section {:icon :i/close
:icon-background-color (icon-color)
:on-press #(>evt [:navigate-back])}}]]]
[rn/view {:flex 1
:height 20
:border-radius 16
:background-color (colors/theme-colors
colors/white
colors/neutral-90)}
[rn/view {:padding-horizontal 20}
[rn/view {:border-radius 40
:border-width 1
:border-color colors/white
:position :absolute
:top (- (/ 80 2))
:left (/ 70 4)
:padding 2
:background-color (colors/theme-colors
colors/white
colors/neutral-90)}
[communities.icon/community-icon-redesign community 80]]
(when (and (not joined)
(= status :gated))
[rn/view (styles/permission-tag-styles)
[community-view/permission-tag-container
{:locked locked
:status status
:tokens tokens}]])
(when joined
[rn/view {:position :absolute
:top 12
:right 12}
[status-tags/status-tag {:status {:type :positive} :label (i18n/label :joined)}]])
[community-view/community-title
{:title name
:size :large
:description description}]
[rn/view {:margin-top 12}
[community-view/community-stats-column :card-view]]
[rn/view {:margin-top 16}
[community-view/community-tags tags]]
[preview-user-list]
(when (not joined)
[button/button
{:on-press #(>evt [:bottom-sheet/show-sheet
{:content (constantly [request-to-join/request-to-join community])
:content-height 300}])
:override-background-color community-color
:style
{:width "100%"
:margin-top 20
:margin-left :auto
:margin-right :auto}
:before :i/communities}
(i18n/label :join-open-community)])]
[channel-list-component]]])
(defn overview []
(let [community-mock (<sub [:get-screen-params :community-overview]) ;;TODO stop using mock data and only pass community id
community (<sub [:communities/community (:id community-mock)])]
[rn/view {:style
{:height "100%"}}
[community-card-page-view
(merge community-mock {:joined (:joined community)})]]))
@@ -21,6 +21,7 @@
[chat-icon.screen/chat-icon-view-chat-list
id true name color false false])))
;; TODO (flexsurfer) reimplement with new design, its still old design, photos and chat-icon components from old design
(defn community-icon-redesign [{:keys [id name images color]} size]
(let [color (or color (rand-nth colors/chat-colors))
thumbnail-image (get-in images [:thumbnail :uri])]
@@ -73,6 +73,7 @@
:center
[quo/button {:disabled (and (str/blank? @user-pk)
(zero? (count selected)))
:accessibility-label :share-community-link
:type :secondary
:on-press #(>evt-once
[(if can-invite?
@@ -70,7 +70,7 @@
:title (i18n/label :t/invite-people)
:accessibility-label :community-invite-people
:theme :accent
:on-press #(>evt [::communities/invite-people-pressed community-id])}]
:on-press #(>evt [:communities/invite-people-pressed community-id])}]
[quo/separator {:style {:margin-vertical 8}}]])
(defn requests-to-join [community-id]
@@ -31,7 +31,7 @@
:on-press #(>evt [:navigate-back])}]
:right-accessories [{:icon :main-icons/share
:accessibility-label :invite-button
:on-press #(>evt [::communities/share-community-pressed community-id])}]
:on-press #(>evt [:communities/share-community-pressed community-id])}]
:extended-header (profile-header/extended-header
{:title name
:color (or color (rand-nth colors/chat-colors))
@@ -95,7 +95,7 @@
[quo/list-item {:theme :accent
:icon :main-icons/arrow-left
:title (i18n/label :t/leave-community)
:on-press #(>evt [::communities/leave community-id])}]
:on-press #(>evt [:communities/leave community-id])}]
;; Disable as not implemented yet
(when false
[quo/list-item {:theme :negative
@@ -1,132 +0,0 @@
(ns status-im.ui.screens.communities.request-to-join-bottom-sheet-redesign
(:require [status-im.i18n.i18n :as i18n]
[reagent.core :as reagent]
[status-im.react-native.resources :as resources]
[status-im.ui.components.list.views :as list]
[status-im.ui.components.react :as react]
[quo2.components.markdown.text :as text]
[quo2.components.buttons.button :as button]
[quo2.components.selectors.disclaimer :as disclaimer]
[quo2.components.icon :as icon]
[status-im.utils.handlers :refer [>evt]]
[quo2.components.tags.context-tags :as context-tags]
[status-im.communities.core :as communities]
[quo2.foundations.colors :as colors]))
;; TODO: update with real data
(def community-rules [{:index 1
:title "Be respectful"
:content "You must respect all users, regardless of your liking towards them. Treat others the way you want to be treated."}
{:index 2
:title "No Inappropriate Language"
:content "The use of profanity should be kept to a minimum. However, any derogatory language towards any user is prohibited."}
{:index 3
:title "No spamming"
:content "Don't send a lot of small messages right after each other. Do not disrupt chat by spamming."}
{:index 4
:title "No pornographic, adult or NSFW material"
:content "This is a community server and not meant to share this kind of material."}
{:index 5
:title "No advertisements"
:content "We do not tolerate any kind of advertisements, whether it be for other communities or streams."}
{:index 6
:title "No offensive names and profile pictures"
:content "You will be asked to change your name or picture if the staff deems them inappropriate."}])
(defn community-rule-item [{:keys [title content index]}]
[react/view
{:style {:flex 1 :margin-top 16}}
[react/view
{:style
{:flex 1
:flex-direction :row
:align-items :center}}
[react/view
{:style
{:height 18
:width 18
:margin-left 1
:margin-right 9
:background-color colors/white
:border-color colors/neutral-20
:border-width 1
:border-radius 6}}
[text/text {:style
{:margin-left :auto
:margin-right :auto
:margin-top :auto
:margin-bottom :auto}
:accessibility-label :communities-rule-index
:weight :medium
:size :label}
(str index)]]
[text/text
{:accessibility-label :communities-rule-title
:weight :semi-bold
:size :paragraph-2}
title]]
[text/text
{:style {:margin-left 28 :margin-top 1}
:accessibility-label :communities-rule-content
:size :paragraph-2}
content]])
(defn community-rules-list [rules]
[list/flat-list
{:shows-horizontal-scroll-indicator false
:data rules
:separator [react/view {:margin-top 1}]
:render-fn community-rule-item}])
(defn request-to-join [community]
(let [agreed-to-rules? (reagent/atom false)]
(fn []
[react/view {:style {:flex 1 :margin-left 20 :margin-right 20 :margin-bottom 20}}
[react/view {:style {:flex 1 :flex-direction :row :align-items :center :justify-content :space-between}}
[text/text {:accessibility-label :communities-join-community
:weight :semi-bold
:size :heading-1}
(i18n/label :t/join-open-community)]
[react/view {:style {:height 32
:width 32
:align-items :center
:background-color colors/white
:border-color colors/neutral-20
:border-width 1
:border-radius 8
:display :flex
:justify-content :center}}
[icon/icon :i/info]]]
;; TODO get tag image from community data
[context-tags/context-tag
{:style
{:margin-right :auto
:margin-top 8}}
(resources/get-image :status-logo) (:name community)]
[text/text {:style {:margin-top 24}
:accessibility-label :communities-rules-title
:weight :semi-bold
:size :paragraph-1}
(i18n/label :t/community-rules)]
[community-rules-list community-rules]
[disclaimer/disclaimer
{:container-style {:margin-top 20}
:on-change #(swap! agreed-to-rules? not)}
(i18n/label :t/accept-community-rules)]
[react/view {:style {:width "100%"
:margin-top 32 :margin-bottom 16
:flex 1
:flex-direction :row
:align-items :center
:justify-content :space-evenly}}
[button/button {:on-press #(>evt [:bottom-sheet/hide])
:type :grey :style {:flex 1 :margin-right 12}} (i18n/label :t/cancel)]
[button/button
{:on-press (fn []
(>evt [::communities/join (:id community)])
(>evt [:bottom-sheet/hide]))
:disabled (not @agreed-to-rules?) :style {:flex 1}} (i18n/label :t/join-open-community)]]])))
@@ -159,15 +159,3 @@
:accessibility-label :chat-key
:monospace true}
community-key]]]))
(defn render-featured-community [{:keys [name id]}]
[react/touchable-highlight {:on-press #(>evt [:navigate-to :community {:community-id id}])
:accessibility-label :chat-item}
[react/view {:padding-right 8
:padding-vertical 8}
[react/view {:border-color quo.colors/gray-lighter
:border-radius 36
:border-width 1
:padding-horizontal 8
:padding-vertical 5}
[quo/text {:color :link} name]]]])
+14 -52
View File
@@ -1,26 +1,23 @@
(ns status-im.ui.screens.home.sheet.views
(:require [quo.core :as quo]
[quo2.foundations.colors :as colors]
[status-im.utils.re-frame :as rf]
[status-im.i18n.i18n :as i18n]
(:require [re-frame.core :as re-frame]
[status-im.ui.components.react :as react]
[status-im.qr-scanner.core :as qr-scanner]
[status-im.ui.components.invite.views :as invite]
[status-im.ui.components.react :as rn]
[status-im.ui2.screens.chat.components.new-chat :as new-chat-aio]
[quo.core :as quo]
[status-im.i18n.i18n :as i18n]
[status-im.utils.config :as config]
[quo2.components.list-items.list-item :as quo2.list-item]))
[status-im.ui.components.invite.views :as invite]))
(defn hide-sheet-and-dispatch [event]
(rf/dispatch [:bottom-sheet/hide])
(rf/dispatch event))
(re-frame/dispatch [:bottom-sheet/hide])
(re-frame/dispatch event))
(defn add-new-view []
[rn/view
[rn/view {:style {:flex-direction :row
:padding-left 16
:padding-right 8
:justify-content :space-between
:align-items :center}}
[react/view
[react/view {:style {:flex-direction :row
:padding-left 16
:padding-right 8
:justify-content :space-between
:align-items :center}}
[quo/text {:size :large
:weight :bold}
(i18n/label :t/open-home)]
@@ -50,7 +47,7 @@
:accessibility-label :join-public-chat-button
:icon :main-icons/public-chat
:on-press #(hide-sheet-and-dispatch [:open-modal :new-public-chat])}]
(when (rf/sub [:communities/enabled?])
(when @(re-frame/subscribe [:communities/enabled?])
[quo/list-item
{:theme :accent
:title (i18n/label :t/communities-alpha)
@@ -60,40 +57,5 @@
[invite/list-item
{:accessibility-label :chats-menu-invite-friends-button}]])
(defn add-new-sheet-view []
[rn/view
[quo2.list-item/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 :i/new-message
:on-press #(hide-sheet-and-dispatch [:bottom-sheet/show-sheet :start-a-new-chat])}]
[quo2.list-item/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 :i/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})
(def start-a-new-chat
{:content (fn [] [new-chat-aio/contact-toggle-list])})
+3 -8
View File
@@ -22,7 +22,7 @@
[status-im.ui.components.plus-button :as components.plus-button]
[status-im.ui.screens.chat.sheets :as sheets]
[status-im.ui.components.invite.views :as invite]
[status-im.utils.config :as config]
[status-im2.setup.config :as config]
[quo2.components.markdown.text :as quo2.text]
[status-im.qr-scanner.core :as qr-scanner]
[status-im.ui.components.chat-icon.screen :as chat-icon.screen]
@@ -234,7 +234,7 @@
(views/defview plus-button []
(views/letsubs [logging-in? [:multiaccounts/login]]
[components.plus-button/plus-button
[components.plus-button/plus-button-old
{:on-press (when-not logging-in?
#(re-frame/dispatch [:bottom-sheet/show-sheet :add-new {}]))
:loading logging-in?
@@ -259,12 +259,7 @@
: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 [:activity-center/open])
(re-frame/dispatch [:navigate-to :notifications-center])))}
[icons/icon :main-icons/notification2 {:color (quo2.colors/theme-colors quo2.colors/neutral-100 quo2.colors/white)}]]
(when (pos? notif-count)
@@ -234,7 +234,8 @@
:height 110}}])]]]
[react/view {:margin-bottom 50}
[quo/button
{:on-press #(re-frame/dispatch [:keycard.recovery.no-key.ui/generate-key-pressed])}
{:test-id :generate-new-key
:on-press #(re-frame/dispatch [:keycard.recovery.no-key.ui/generate-key-pressed])}
(i18n/label :t/generate-new-key)]
[quo/button
{:type :secondary
@@ -157,7 +157,7 @@
:margin-bottom 24}
[quo/checkbox {:value @tos-accepted
:on-change #(swap! tos-accepted not)}]
[rn/touchable-opacity {:on-press #(swap! tos-accepted not)}
[rn/touchable-opacity {:test-ID :terms-of-service :on-press #(swap! tos-accepted not)}
[react/nested-text {:style {:margin-left 12}}
(i18n/label :t/accept-status-tos-prefix)
[{:style (merge {:color colors/blue}
@@ -167,7 +167,8 @@
" "
(i18n/label :t/terms-of-service)]]]]
[react/view {:style {:margin-bottom 24}}
[quo/button {:disabled (not @tos-accepted)
[quo/button {:test-ID :get-started
:disabled (not @tos-accepted)
:on-press #(do (re-frame/dispatch [:init-root :onboarding])
;; clear atom state for next use
(reset! tos-accepted false)
@@ -83,7 +83,8 @@
[react/view {:style {:align-items :center}}
[react/view {:style (assoc styles/bottom-button :margin-bottom 16)}
[quo/button
{;:disabled existing-account?
{:test-ID :generate-keys
;:disabled existing-account?
:on-press #(re-frame/dispatch [:generate-and-derive-addresses])
:accessibility-label :onboarding-next-button}
(i18n/label :t/generate-a-key)]]
@@ -44,7 +44,8 @@
(i18n/label :intro-wizard-title-alt4)]
[rn/view
[rn/view {:style {:padding 16}}
[quo/text-input {:secure-text-entry true
[quo/text-input {:test-ID :password-placeholder
:secure-text-entry true
:auto-capitalize :none
:auto-focus true
:show-cancel false
@@ -56,7 +57,8 @@
(some-> ^js @confirm-ref .focus))}]]
[rn/view {:style {:padding 16
:opacity (if-not valid-password 0.33 1)}}
[quo/text-input {:secure-text-entry true
[quo/text-input {:test-ID :confirm-password-placeholder
:secure-text-entry true
:get-ref #(reset! confirm-ref %)
:auto-capitalize :none
:show-cancel false
@@ -12,7 +12,7 @@
in-progress? (reagent/atom false)]
[react/view {:style {:padding-top 16 :padding-horizontal 24 :padding-bottom 8}}
[react/text {:style styles/sheet-text}
(i18n/label :t/block-user-confirmation)]
(i18n/label :t/block-contact-details)]
[react/view {:align-items :center :margin-top 16}
[quo/button {:theme :negative
:disabled @in-progress?
+7 -17
View File
@@ -18,7 +18,6 @@
[status-im.ui.screens.browser.views :as browser]
[status-im.ui.screens.bug-report :as bug-report]
[status-im.ui.screens.chat.pinned-messages :as pin-messages]
[status-im.ui.screens.communities.community-overview-redesign :as community-overview]
[status-im.ui.screens.communities.channel-details :as communities.channel-details]
[status-im.ui.screens.communities.community :as community]
[status-im.ui.screens.communities.community-emoji-thumbnail-picker :as community-emoji-thumbnail-picker]
@@ -116,7 +115,7 @@
[status-im.ui.screens.wallet.recipient.views :as recipient]
[status-im.ui.screens.wallet.send.views :as wallet.send]
[status-im.ui.screens.wallet.manage-connections.views :as manage-all-connections]
[status-im.ui2.screens.chat.components.new-chat :as new-chat-aio]))
[status-im.ui2.screens.chat.group-details.view :as group-details]))
(defn right-button-options [id icon]
{:id id
@@ -213,10 +212,9 @@
:component pin-messages/pinned-messages}
{:name :group-chat-profile
:insets {:top false}
;;TODO animated-header
:options {:topBar {:visible false}}
:component profile.group-chat/group-chat-profile}
:component group-details/group-details}
{:name :group-chat-invite
;;TODO parameter in the event
:options {:topBar {:visible false}}
@@ -308,9 +306,7 @@
{:name :community-membership
:options {:topBar {:title {:text (i18n/label :t/membership-title)}}}
:component membership/membership}
{:name :community-overview
:options {:topBar {:visible false}}
:component community-overview/overview}
;;BROWSER
{:name :empty-tab
@@ -596,17 +592,11 @@
:options {:topBar {:visible false}}
:component contact/nickname}
{:name :new-chat-aio
:on-focus [::new-chat.events/new-chat-focus]
;;TODO accessories
:options {:topBar {:visible false}}
:component new-chat-aio/contact-toggle-list}
;[Chat] New Public chat
{:name :new-public-chat
;[Group chat] Edit group chat name
{:name :edit-group-chat-name
:insets {:bottom true}
:options {:topBar {:title {:text (i18n/label :t/new-public-group-chat)}}}
:component new-public-chat/new-public-chat}
:options {:topBar {:title {:text (i18n/label :t/edit-group)}}}
:component group-chat/edit-group-chat-name}
;[Group chat] Add participants
{:name :add-participants-toggle-list
@@ -1,50 +0,0 @@
(ns status-im.ui2.components.search-input
(:require [reagent.core :as reagent]
[status-im.i18n.i18n :as i18n]
[quo.design-system.colors :as colors]
[quo2.foundations.colors :as quo2.colors]
[quo.components.text-input :as text-input]))
(defn search-input [{:keys [search-active?]}]
(let [input-ref (reagent/atom nil)
placeholder-text (reagent/atom :t/search-contacts)
search-active? (or search-active? (reagent/atom nil))]
(fn [{:keys [on-focus on-change search-border-width show-cancel? search-border-radius on-blur on-cancel search-filter auto-focus] :or {show-cancel? false}}]
[text-input/text-input {:placeholder (i18n/label @placeholder-text)
:placeholder-text-color (if (= @placeholder-text :t/search-contacts)
(quo2.colors/theme-colors quo2.colors/neutral-30 quo2.colors/neutral-50)
(quo2.colors/theme-colors quo2.colors/neutral-30 quo2.colors/neutral-60))
:accessibility-label :search-input
:text-padding-left 0
:blur-on-submit true
:multiline false
:get-ref #(reset! input-ref %)
:default-value search-filter
:auto-focus auto-focus
:on-cancel on-cancel
:show-cancel show-cancel?
:auto-correct false
:auto-capitalize :none
:container-style {:border-radius (or search-border-radius 10)
:border-width (or search-border-width 1)
:border-color (:ui-01 @colors/theme)
:background-color (quo2.colors/theme-colors quo2.colors/white quo2.colors/neutral-90)
:overflow :hidden}
:input-style {:height 32
:padding-vertical 2
:background-color (quo2.colors/theme-colors quo2.colors/white quo2.colors/neutral-90)}
:on-focus #(do
(when on-focus
(on-focus search-filter))
(swap! search-active? not)
(reset! placeholder-text :t/who-are-you-looking-for))
:on-blur #(do
(when on-blur
(on-blur))
(swap! search-active? not)
(reset! placeholder-text :t/search-contacts))
:on-change (fn [e]
(let [^js native-event (.-nativeEvent ^js e)
text (.-text native-event)]
(when on-change
(on-change text))))}])))
-40
View File
@@ -1,40 +0,0 @@
(ns status-im.ui2.components.toolbar
(:require [status-im.ui.components.react :as react]
[quo.design-system.colors :as colors]))
(defn toolbar-container [{:keys [show-border? size center? margin-bottom container-styling]
:or {size :default}}]
(merge {:align-items :center
:padding-horizontal 8
:margin-bottom (or margin-bottom 0)
:width "100%"
:flex-direction :row
:justify-content :space-between}
container-styling
(when center?
{:justify-content :center})
(when show-border?
{:border-top-width 1
:border-top-color colors/gray-lighter})
(case size
:large {:height 60}
{:height 52})))
;; TODO(Ferossgp): Allow components when moving to Quo
(defn toolbar [{:keys [center left right show-border? size margin-bottom container-styling]}]
(if center
[react/view {:style (toolbar-container {:show-border? show-border?
:center? true
:margin-bottom margin-bottom
:size size
:container-styling container-styling})}
center]
[react/view {:style (toolbar-container {:show-border? show-border?
:margin-bottom margin-bottom
:center? false
:size size
:container-styling container-styling})}
[react/view {:flex-shrink 1}
(when left left)]
[react/view
(when right right)]]))
+39 -34
View File
@@ -3,23 +3,24 @@
[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.utils.re-frame :as rf]
[utils.re-frame :as rf]
[status-im.ui2.screens.common.core :as common]
[status-im.constants :as constants]
[quo2.components.drawers.action-drawers :as drawer]))
(defn- entry [{:keys [icon label on-press danger? sub-label chevron?]}]
(defn- entry [{:keys [icon label on-press danger? sub-label chevron? add-divider?]}]
{:pre [(keyword? icon)
(string? label)
(fn? on-press)
(boolean? danger?)
(boolean? chevron?)]}
{:icon icon
:label label
:on-press on-press
:danger? danger?
:sub-label sub-label
:right-icon (when chevron? :i/chevron-right)})
{:icon icon
:label label
:on-press on-press
:danger? danger?
:sub-label sub-label
:right-icon (when chevron? :i/chevron-right)
:add-divider? add-divider?})
(defn hide-sheet-and-dispatch [event]
(rf/dispatch [:bottom-sheet/hide])
@@ -71,7 +72,7 @@
(defn block-user-action [{:keys [public-key] :as item}]
(hide-sheet-and-dispatch [:bottom-sheet/show-sheet
{:content (fn [] (common/alert {:title (i18n/label :t/block-user?)
:description (i18n/label :t/block-user-confirmation)
:description (i18n/label :t/block-contact-details)
:context item
:button-text (i18n/label :t/block-user)
:on-press #(hide-sheet-and-dispatch [:contact.ui/block-contact-confirmed public-key])}))}]))
@@ -91,20 +92,22 @@
:chevron? true})))
(defn mark-as-read-entry [chat-id]
(entry {:icon :i/correct
:label (i18n/label :t/mark-as-read)
:on-press #(mark-all-read-action chat-id)
:danger? false
:sub-label nil
:chevron? false}))
(entry {:icon :i/correct
:label (i18n/label :t/mark-as-read)
:on-press #(mark-all-read-action chat-id)
:danger? false
:sub-label nil
:chevron? false
:add-divider? true}))
(defn clear-history-entry [chat-id]
(entry {:icon :i/delete
:label (i18n/label :t/clear-history)
:on-press #(clear-history-action chat-id)
:danger? true
:sub-label nil
:chevron? false}))
(entry {:icon :i/delete
:label (i18n/label :t/clear-history)
:on-press #(clear-history-action chat-id)
:danger? true
:sub-label nil
:chevron? false
:add-divider? true}))
(defn delete-chat-entry [item]
(entry {:icon :i/delete
@@ -203,12 +206,13 @@
:chevron? false}))
(defn mark-untrustworthy-entry []
(entry {:icon :i/alert
:label (i18n/label :t/mark-untrustworthy)
:on-press #(js/alert "TODO: to be implemented, requires status-go impl.")
:danger? true
:sub-label nil
:chevron? false}))
(entry {:icon :i/alert
:label (i18n/label :t/mark-untrustworthy)
:on-press #(js/alert "TODO: to be implemented, requires status-go impl.")
:danger? true
:sub-label nil
:chevron? false
:add-divider? true}))
(defn block-user-entry [item]
(entry {:icon :i/block
@@ -258,8 +262,8 @@
:sub-label nil
:chevron? false}))
(defn destructive-actions [{:keys [chat-id group-chat] :as item}]
[(clear-history-entry chat-id)
(defn destructive-actions [{:keys [group-chat] :as item}]
[(clear-history-entry item)
(if group-chat
(leave-group-entry item)
(delete-chat-entry item))])
@@ -307,12 +311,13 @@
(notification-actions item inside-chat?)
(destructive-actions item)]])
(defn contact-actions [{:keys [public-key] :as contact}]
(defn contact-actions [{:keys [public-key added] :as contact}]
[drawer/action-drawer [[(view-profile-entry public-key)
(remove-from-contacts-entry contact)
(rename-entry)
(show-qr-entry)
(share-profile-entry)]
(when added
(remove-from-contacts-entry contact)
(rename-entry)
(show-qr-entry)
(share-profile-entry))]
[(mark-untrustworthy-entry)
(block-user-entry contact)]]])
@@ -8,7 +8,7 @@
[quo.platform :as platform]
[quo2.components.markdown.text :as text]
[status-im.ui2.screens.chat.components.message-home-item.style :as style]
[status-im.utils.re-frame :as rf]
[utils.re-frame :as rf]
[status-im.ui2.screens.chat.actions :as actions]))
(defn open-chat [chat-id]
@@ -22,11 +22,13 @@
(defn contact-item [item]
(let [{:keys [public-key ens-verified added? images]} item
display-name (first (rf/sub [:contacts/contact-two-names-by-identity public-key]))
photo-path (when (seq images) (rf/sub [:chats/photo-path public-key]))]
photo-path (when (seq images) (rf/sub [:chats/photo-path public-key]))
current-pk (rf/sub [:multiaccount/public-key])]
[rn/touchable-opacity (merge {:style (style/container)
:on-press #(open-chat public-key)
:on-long-press #(rf/dispatch [:bottom-sheet/show-sheet
{:content (fn [] [actions/actions item])}])})
:on-long-press #(when-not (= current-pk public-key)
(rf/dispatch [:bottom-sheet/show-sheet
{:content (fn [] [actions/actions item])}]))})
[user-avatar/user-avatar {:full-name display-name
:profile-picture photo-path
:status-indicator? true
@@ -44,11 +46,14 @@
(when added?
[rn/view {:style {:margin-left 5 :margin-top 4}}
[icons/icon :i/contact {:no-color true :size 12 :color (colors/theme-colors colors/primary-50 colors/primary-60)}]]))]
[text/text {:size :paragraph-1
[text/text {:size :paragraph-1
:style {:color (colors/theme-colors colors/neutral-50 colors/neutral-40)}}
(utils/get-shortened-address public-key)]]
[rn/touchable-opacity {:style {:position :absolute
:right 20}
:active-opacity 1} ; TODO: on-long-press to be added when contact bottom sheet is implemented
[icons/icon :i/options {:size 20 :color (colors/theme-colors colors/primary-50 colors/primary-60)}]]]))
(when-not (= current-pk public-key)
[rn/touchable-opacity {:style {:position :absolute
:right 20}
:active-opacity 1
:on-press #(rf/dispatch [:bottom-sheet/show-sheet
{:content (fn [] [actions/actions item])}])}
[icons/icon :i/options {:size 20 :color (colors/theme-colors colors/neutral-50 colors/neutral-40)}]])]))
@@ -1,6 +1,6 @@
(ns status-im.ui2.screens.chat.components.message-home-item.view
(:require [clojure.string :as string]
[status-im.utils.re-frame :as rf]
[utils.re-frame :as rf]
[status-im.utils.datetime :as time]
[quo2.foundations.typography :as typography]
[quo2.components.icon :as icons]
@@ -91,9 +91,9 @@
(if group-chat
[quo2/group-avatar {:color color
:size :medium}]
[quo2/user-avatar {:full-name display-name
:profile-picture photo-path
:size :small}]))
[quo2/user-avatar {:full-name display-name
:profile-picture photo-path
:size :small}]))
(defn messages-home-item [item]
(let [{:keys [chat-id
@@ -1,283 +0,0 @@
(ns status-im.ui2.screens.chat.components.new-chat
(:require [cljs.spec.alpha :as spec]
[clojure.string :as string]
[re-frame.core :as re-frame]
[reagent.core :as reagent]
[status-im.constants :as constants]
[status-im.i18n.i18n :as i18n]
[status-im.ui.components.chat-icon.screen :as chat-icon]
[status-im.multiaccounts.core :as multiaccounts]
[status-im.ui.components.keyboard-avoid-presentation :as kb-presentation]
[status-im.ui.components.invite.views :as invite]
[status-im.ui.components.list.views :as list]
[status-im.ui.components.react :as react]
[status-im.ui2.components.search-input :as search]
[status-im.ui2.components.toolbar :as toolbar]
[status-im.ui.components.topbar :as topbar]
[status-im.ui.screens.group.styles :as styles]
[quo.core :as quo]
[quo2.core :as quo2]
[quo2.components.buttons.button :as button]
[status-im.utils.re-frame :as rf]
[status-im.utils.debounce :as debounce]
[quo2.foundations.colors :as quo2.colors]
[status-im.ui2.screens.chat.actions :refer [hide-sheet-and-dispatch]])
(:require-macros [status-im.utils.views :as views]))
(defn- render-contact [row]
(let [[first-name second-name] (multiaccounts/contact-two-names row false)]
[quo2/list-item
{:title first-name
:subtitle second-name
:icon [chat-icon/contact-icon-contacts-tab
(multiaccounts/displayed-photo row)]}]))
(defn- on-toggle [allow-new-users? checked? public-key]
(cond
checked?
(rf/dispatch [:deselect-contact public-key allow-new-users?])
;; Only allow new users if not reached the maximum
(and (not checked?)
allow-new-users?)
(rf/dispatch [:select-contact public-key allow-new-users?])))
(defn- on-toggle-participant [allow-new-users? checked? public-key]
(cond
checked?
(rf/dispatch [:deselect-participant public-key allow-new-users?])
;; Only allow new users if not reached the maximum
(and (not checked?)
allow-new-users?)
(rf/dispatch [:select-participant public-key allow-new-users?])))
(defn- toggle-item []
(fn [allow-new-users? subs-name {:keys [public-key] :as contact} on-toggle]
(let [contact-selected? @(re-frame/subscribe [subs-name public-key])
[first-name second-name] (multiaccounts/contact-two-names contact true)]
[quo2/list-item
{:title first-name
:text-color (quo2.colors/theme-colors quo2.colors/neutral-100 quo2.colors/white)
:subtitle second-name
:icon [chat-icon/contact-icon-contacts-tab
(multiaccounts/displayed-photo contact)]
:on-press #(on-toggle allow-new-users? contact-selected? public-key)
:active contact-selected?
:accessory :checkbox
:background-color (quo2.colors/theme-colors quo2.colors/white quo2.colors/neutral-90)}])))
(defn- group-toggle-contact [{:keys [:allow-new-users?] :as contact} _ _]
[toggle-item allow-new-users? :is-contact-selected? contact on-toggle])
(defn- group-toggle-participant [{:keys [:allow-new-users?] :as contact} _ _]
[toggle-item allow-new-users? :is-participant-selected? contact on-toggle-participant])
(defn toggle-list [{:keys [contacts render-fn]}]
[list/section-list
{:content-container-style {:padding-vertical 8}
:key-fn :id
:keyboard-should-persist-taps :always
:sticky-section-headers-enabled false
:sections contacts
:render-section-header-fn quo2/index
:render-fn render-fn}])
(defn no-contacts [{:keys [no-contacts]}]
[react/view {:style styles/no-contacts}
[react/text
{:style (styles/no-contact-text)}
no-contacts]
[invite/button]])
(defn filter-contacts [filter-text contacts]
(let [lower-filter-text (string/lower-case (str filter-text))
filter-fn (fn [{:keys [data]}]
(let [{:keys [name alias nickname]} (first data)]
(or
(string/includes? (string/lower-case (str name)) lower-filter-text)
(string/includes? (string/lower-case (str alias)) lower-filter-text)
(when nickname
(string/includes? (string/lower-case (str nickname)) lower-filter-text)))))]
(if filter-text
(filter filter-fn contacts)
contacts)))
;; Set name of new group-chat
(defn new-group []
(let [contacts (rf/sub [:selected-group-contacts])
group-name (rf/sub [:new-chat-name])
group-name-empty? (not (spec/valid? :global/not-empty-string group-name))]
[react/keyboard-avoiding-view {:style styles/group-container
:ignore-offset true}
[react/view {:flex 1}
[topbar/topbar {:use-insets false
:title (i18n/label :t/new-group-chat)
:subtitle (i18n/label :t/group-chat-members-count
{:selected (inc (count contacts))
:max constants/max-group-chat-participants})}]
[react/view {:style {:padding-top 16
:flex 1}}
[react/view {:style {:padding-horizontal 16}}
[quo/text-input
{:auto-focus true
:on-change-text #(rf/dispatch [:set :new-chat-name %])
:default-value group-name
:placeholder (i18n/label :t/set-a-topic)
:accessibility-label :chat-name-input}]
[react/text {:style (styles/members-title)}
(i18n/label :t/members-title)]]
[react/view {:style {:margin-top 8
:flex 1}}
[list/flat-list {:data contacts
:key-fn :address
:render-fn render-contact
:bounces false
:keyboard-should-persist-taps :always
:enable-empty-sections true}]]]
[toolbar/toolbar
{:show-border? true
:left [quo/button {:type :secondary
:before :main-icon/back
:accessibility-label :previous-button
:on-press #(rf/dispatch [:navigate-back])}
(i18n/label :t/back)]
:right [quo/button {:type :secondary
:accessibility-label :create-group-chat-button
:disabled group-name-empty?
:on-press #(debounce/dispatch-and-chill [:group-chats.ui/create-pressed group-name]
300)}
(i18n/label :t/create-group-chat)]}]]]))
(defn searchable-contact-list []
(let [search-value (reagent/atom nil)]
(fn [{:keys [contacts no-contacts-label toggle-fn allow-new-users? show-cancel?]}]
[react/view {:style {:flex 1}}
[react/view {:style (styles/search-container)}
[search/search-input {:on-cancel #(reset! search-value nil)
:search-border-radius 0
:show-cancel show-cancel?
:search-border-width 0
:on-change #(reset! search-value %)}]]
[react/view {:style {:flex 1
:padding-vertical 8
:background-color (quo2.colors/theme-colors quo2.colors/white quo2.colors/neutral-90)}}
(if (seq contacts)
[toggle-list {:contacts (filter-contacts @search-value contacts)
:render-data allow-new-users?
:render-fn toggle-fn}]
[no-contacts {:no-contacts no-contacts-label}])]])))
;; Start group chat
(defn contact-toggle-list []
(let [contacts (rf/sub [:contacts/sorted-and-grouped-by-first-letter])
selected-contacts-count (rf/sub [:selected-contacts-count])
window-height (rf/sub [:dimensions/window-height])
one-contact-selected? (= selected-contacts-count 1)
no-contacts-selected? (zero? selected-contacts-count)
{:keys [alias public-key]} (-> contacts first :data first)]
[react/view {:style {:height (* window-height 0.95)}}
[topbar/topbar {:use-insets false
:border-bottom false
:style {:top -15}
:close-icon-container-props {:style {:width 32
:height 32
:border-radius 10
:justify-content :center
:align-items :center
:background-color (quo2.colors/theme-colors quo2.colors/neutral-10 quo2.colors/neutral-80)}}
:close-icon-props {:size 20
:color (quo2.colors/theme-colors quo2.colors/black quo2.colors/white)}
:navigation {:sheet? true}
:modal? true
:background (quo2.colors/theme-colors quo2.colors/white quo2.colors/neutral-90)}]
[react/view {:style {:flex-direction :row
:justify-content :space-between
:align-items :flex-end
:padding-horizontal 20}}
[quo2/text {:weight :semi-bold
:size :heading-1
:color (quo2.colors/theme-colors quo2.colors/neutral-40 quo2.colors/neutral-50)}
(i18n/label :t/new-chat)]
[quo2/text {:size :paragraph-2
:weight :regular
:secondary-color (quo2.colors/theme-colors quo2.colors/neutral-40 quo2.colors/neutral-50)}
(i18n/label :t/selected-count-from-max
{:selected (inc selected-contacts-count)
:max constants/max-group-chat-participants})]]
[react/view {:style {:height 430}}
[searchable-contact-list
{:contacts contacts
:show-cancel? false
:no-contacts-label (i18n/label :t/group-chat-no-contacts)
:toggle-fn group-toggle-contact}]]
(when-not no-contacts-selected?
[toolbar/toolbar
{:show-border? false
:margin-bottom 20
:center [button/button {:type :primary
:accessibility-label :next-button
:on-press #(do
(if one-contact-selected?
(hide-sheet-and-dispatch [:chat.ui/start-chat public-key])
(hide-sheet-and-dispatch [:navigate-to :new-group])))}
(if one-contact-selected?
(i18n/label :t/chat-with {:selected-user alias})
(i18n/label :t/setup-group-chat))]}])]))
;; Add participants to existing group chat
(defn add-participants-toggle-list []
(let [contacts (rf/sub [:contacts/all-contacts-not-in-current-chat])
current-chat (rf/sub [:chats/current-chat])
selected-contacts-count (rf/sub [:selected-participants-count])
current-participants-count (count (:contacts current-chat))]
[kb-presentation/keyboard-avoiding-view {:style styles/group-container}
[topbar/topbar {:use-insets false
:border-bottom false
:title (i18n/label :t/add-members)
:subtitle (i18n/label :t/group-chat-members-count
{:selected (+ current-participants-count selected-contacts-count)
:max constants/max-group-chat-participants})}]
[searchable-contact-list
{:contacts contacts
:no-contacts-label (i18n/label :t/group-chat-all-contacts-invited)
:toggle-fn group-toggle-participant
:allow-new-users? (< (+ current-participants-count
selected-contacts-count)
constants/max-group-chat-participants)}]
[toolbar/toolbar
{:show-border? true
:center [quo/button {:type :secondary
:accessibility-label :next-button
:disabled (zero? selected-contacts-count)
:on-press #(rf/dispatch [:group-chats.ui/add-members-pressed])}
(i18n/label :t/add)]}]]))
(defn edit-group-chat-name []
(let [{:keys [name chat-id]} (rf/sub [:chats/current-chat])
new-group-chat-name (reagent/atom nil)]
[kb-presentation/keyboard-avoiding-view {:style styles/group-container}
[react/scroll-view {:style {:padding 16
:flex 1}}
[quo/text-input
{:on-change-text #(reset! new-group-chat-name %)
:default-value name
:on-submit-editing #(when (seq @new-group-chat-name)
(rf/dispatch [:group-chats.ui/name-changed chat-id @new-group-chat-name]))
:placeholder (i18n/label :t/enter-contact-code)
:accessibility-label :new-chat-name
:return-key-type :go}]]
[react/view {:style {:flex 1}}]
[toolbar/toolbar
{:show-border? true
:center
[quo/button {:type :secondary
:accessibility-label :done
:disabled (and (<= (count @new-group-chat-name) 1)
(not (nil? @new-group-chat-name)))
:on-press #(cond
(< 1 (count @new-group-chat-name))
(rf/dispatch [:group-chats.ui/name-changed chat-id @new-group-chat-name])
(nil? @new-group-chat-name)
(rf/dispatch [:navigate-back]))}
(i18n/label :t/done)]}]]))
@@ -68,11 +68,11 @@
:size :label
:weight :regular
:accessibility-label :quoted-message
:ellipsize-mode :tail
:style (merge
{:ellipsize-mode :tail
:text-transform :none
:margin-left 4
:margin-top 2}
{:text-transform :none
:margin-left 4
:margin-top 2}
(when (or (= constants/content-type-image content-type)
(= constants/content-type-sticker content-type)
(= constants/content-type-audio content-type))
@@ -0,0 +1,20 @@
(ns status-im.ui2.screens.chat.group-details.style
(:require [quo2.foundations.colors :as colors]))
(defn actions-view []
{:flex-direction :row
:justify-content :space-between
:margin-vertical 20
:padding-horizontal 20})
(defn action-container [color]
{:background-color (colors/theme-alpha color 0.1 0.1)
:flex 0.29
:border-radius 16
:padding 12})
(defn count-container []
{:width 16
:height 16
:border-radius 6
:background-color (colors/theme-colors colors/neutral-80-opa-5 colors/white-opa-5)})
@@ -0,0 +1,90 @@
(ns status-im.ui2.screens.chat.group-details.view
(:require [react-native.core :as rn]
[quo2.foundations.colors :as colors]
[status-im.ui2.screens.chat.group-details.style :as style]
[quo2.core :as quo2]
[utils.re-frame :as rf]
[i18n.i18n :as i18n]
[status-im.ui2.screens.chat.components.contact-item.view :as contact-item]))
(defn back-button []
[quo2/button {:type :grey
:size 32
:width 32
:style {:margin-left 20}
:accessibility-label :back-button
:on-press #(rf/dispatch [:navigate-back])}
[quo2/icon :i/arrow-left {:color (colors/theme-colors colors/neutral-100 colors/white)}]])
(defn options-button []
[quo2/button {:type :grey
:size 32
:width 32
:style {:margin-right 20}
:accessibility-label :options-button}
[quo2/icon :i/options {:color (colors/theme-colors colors/neutral-100 colors/white)}]])
(defn count-container [count]
[rn/view {:style (style/count-container)}
[quo2/text {:size :label
:weight :medium
:style {:text-align :center}} count]])
(defn prepare-members [members]
(let [admins (filter :admin? members)
online (filter #(and (not (:admin? %)) (:online? %)) members)
offline (filter #(and (not (:admin? %)) (not (:online? %))) members)]
(vals (cond-> {}
(seq admins) (assoc :owner {:title "Owner" :data admins})
(seq online) (assoc :online {:title "Online" :data online})
(seq offline) (assoc :offline {:title "Offline" :data offline})))))
(defn contacts-section-header [{:keys [title]}]
[rn/view {:style {:padding-horizontal 20 :border-top-width 1 :border-top-color colors/neutral-20 :padding-vertical 8 :margin-top 8}}
[quo2/text {:size :paragraph-2
:weight :medium
:style {:color (colors/theme-colors colors/neutral-50 colors/neutral-40)}} title]])
(defn group-details []
(let [{:keys [admins chat-id chat-name color public?]} (rf/sub [:chats/current-chat])
members (rf/sub [:contacts/current-chat-contacts])
sectioned-members (prepare-members members)
pinned-messages (rf/sub [:chats/pinned chat-id])
current-pk (rf/sub [:multiaccount/public-key])
admin? (get admins current-pk)]
[rn/view {:style {:flex 1
:background-color (colors/theme-colors colors/white colors/neutral-95)}}
[quo2/header {:left-component [back-button]
:right-component [options-button]
:background (colors/theme-colors colors/white colors/neutral-95)}]
[rn/view {:style {:flex-direction :row
:margin-top 12
:padding-horizontal 20}}
[quo2/group-avatar {:color color
:size :medium}]
[quo2/text {:weight :semi-bold
:size :heading-1
:style {:margin-horizontal 8}} chat-name]
[rn/view {:style {:margin-top 8}}
[quo2/icon (if public? :i/world :i/privacy) {:size 20 :color (colors/theme-colors colors/neutral-50 colors/neutral-40)}]]]
[rn/view {:style (style/actions-view)}
[rn/touchable-opacity {:style (style/action-container color)}
[rn/view {:style {:flex-direction :row
:justify-content :space-between}}
[quo2/icon :i/pin {:size 20 :color (colors/theme-colors colors/neutral-100 colors/white)}]
[count-container (count pinned-messages)]]
[quo2/text {:style {:margin-top 16} :size :paragraph-1 :weight :medium} (i18n/label :t/pinned-messages-2)]]
[rn/touchable-opacity {:style (style/action-container color)}
[quo2/icon :i/activity-center {:size 20 :color (colors/theme-colors colors/neutral-100 colors/white)}]
[quo2/text {:style {:margin-top 16} :size :paragraph-1 :weight :medium} (i18n/label :t/mute-group)]]
[rn/touchable-opacity {:style (style/action-container color)}
[rn/view {:style {:flex-direction :row
:justify-content :space-between}}
[quo2/icon :i/add-user {:size 20 :color (colors/theme-colors colors/neutral-100 colors/white)}]
[count-container (count members)]]
[quo2/text {:style {:margin-top 16} :size :paragraph-1 :weight :medium} (i18n/label (if admin? :t/manage-members :t/add-members))]]]
[rn/section-list {:key-fn :title
:sticky-section-headers-enabled false
:sections sectioned-members
:render-section-header-fn contacts-section-header
:render-fn contact-item/contact-item}]]))
+72 -98
View File
@@ -15,16 +15,14 @@
[status-im.add-new.db :as db]
[status-im.utils.debounce :as debounce]
[status-im.utils.utils :as utils]
[status-im.utils.re-frame :as rf]
[status-im.ui.components.topbar :as topbar]
[status-im.ui.components.plus-button :as components.plus-button]
[status-im.ui.components.invite.views :as invite]
[status-im.utils.handlers :refer [>evt]]
[status-im.utils.config :as config]
[status-im.utils.handlers :refer [<sub >evt]]
[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]
[react-native.core :as rn]
[quo.react-native :as quo.rn]
[quo.react]
[quo2.foundations.colors :as colors]
[quo2.foundations.typography :as typography]
@@ -38,43 +36,32 @@
[status-im.ui2.screens.chat.components.received-cr-item :as received-cr-item]
[status-im.ui2.screens.chat.components.message-home-item.view :refer [messages-home-item]]
[status-im.ui2.screens.chat.components.contact-item.view :refer [contact-item]]
[clojure.string :as str])
[clojure.string :as str]
[status-im2.common.plus-button.view :as components.plus-button]
[status-im2.setup.config :as config])
(: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 quo.colors/white
:align-items :center}}
[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}}]]
: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}
:on-press #(rf/dispatch [:multiaccounts.ui/hide-home-tooltip])
{: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 quo.colors/gray}]]
[react/i18n-text {:style styles/no-chats-text
:key :chat-and-transact}]
[react/i18n-text {:style styles/no-chats-text :key :chat-and-transact}]
[rn/view {:align-items :center
:margin-top 8
:margin-bottom 12}
[invite/button]]])
(defn welcome-blank-chats []
[rn/view {:style {:flex 1
:align-items :center
:justify-content :center}}
[icons/icon :main-icons/placeholder20 {:width 120
:height 120}]
[rn/view {:style {:flex 1 :align-items :center :justify-content :center}}
[icons/icon :main-icons/placeholder20 {:width 120 :height 120}]
[rn/text {:style (merge typography/font-semi-bold typography/paragraph-1)} (i18n/label :t/no-messages)]
[rn/text {:style (merge typography/font-regular typography/paragraph-2)} (i18n/label :t/blank-messages-text)]])
@@ -83,8 +70,7 @@
:flex-direction :row
:align-items :center
:justify-content :center}}
[react/i18n-text {:style styles/welcome-blank-text
:key :welcome-blank-message}]])
[react/i18n-text {:style styles/welcome-blank-text :key :welcome-blank-message}]])
(defonce search-active? (reagent/atom false))
@@ -94,24 +80,25 @@
[search-input/search-input
{:search-active? search-active?
:search-filter search-filter
:on-cancel #(rf/dispatch [:search/home-filter-changed nil])
:on-cancel #(re-frame/dispatch [:search/home-filter-changed nil])
:on-blur (fn []
(when chats-empty
(rf/dispatch [:search/home-filter-changed nil]))
(rf/dispatch [::new-chat/clear-new-identity]))
(re-frame/dispatch [:search/home-filter-changed nil]))
(re-frame/dispatch [::new-chat/clear-new-identity]))
:on-focus (fn [search-filter]
(when-not search-filter
(rf/dispatch [:search/home-filter-changed ""])
(rf/dispatch [::new-chat/clear-new-identity])))
(re-frame/dispatch [:search/home-filter-changed ""])
(re-frame/dispatch [::new-chat/clear-new-identity])))
:on-change (fn [text]
(rf/dispatch [:search/home-filter-changed text])
(rf/dispatch [:set-in [:contacts/new-identity :state] :searching])
(re-frame/dispatch [:search/home-filter-changed text])
(re-frame/dispatch [:set-in [:contacts/new-identity :state] :searching])
(debounce/debounce-and-dispatch [:new-chat/set-new-identity text] 300))}]])
(defn start-suggestion [search-value]
(let [{:keys [state ens-name public-key]} (rf/sub [:contacts/new-identity])
valid-private? (= state :valid)
valid-public? (db/valid-topic? search-value)]
(let [{:keys [state ens-name public-key]}
@(re-frame/subscribe [:contacts/new-identity])
valid-private? (= state :valid)
valid-public? (db/valid-topic? search-value)]
(when (or valid-public? valid-private?)
[rn/view
[quo/list-header (i18n/label :t/search-no-chat-found)]
@@ -122,16 +109,16 @@
:subtitle (i18n/label :t/join-new-private-chat)
:on-press (fn []
(debounce/dispatch-and-chill [:contact.ui/contact-code-submitted false] 3000)
(rf/dispatch [:search/home-filter-changed nil]))}])
(re-frame/dispatch [:search/home-filter-changed nil]))}])
(when valid-public?
[quo/list-item {:theme :accent
:icon :main-icons/public-chat
:title (str "#" search-value)
:subtitle (i18n/label :t/join-new-public-chat)
:on-press (fn []
(rf/dispatch [:chat.ui/start-public-chat search-value])
(rf/dispatch [:set :public-group-topic nil])
(rf/dispatch [:search/home-filter-changed nil]))}])])))
(re-frame/dispatch [:chat.ui/start-public-chat search-value])
(re-frame/dispatch [:set :public-group-topic nil])
(re-frame/dispatch [:search/home-filter-changed nil]))}])])))
(defn chat-list-key-fn [item]
(or (:chat-id item) (:public-key item) (:id item)))
@@ -157,11 +144,7 @@
(vals @data)))
(defn contacts-section-header [{:keys [title]}]
[rn/view {:style {:border-top-width 1
:border-top-color colors/neutral-20
:padding-vertical 8
:padding-horizontal 20
:margin-top 8}}
[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]
@@ -180,22 +163,23 @@
(defn contact-requests-sheet []
[:f>
(fn []
(let [{window-height :height} (rn/use-window-dimensions)
safe-area (safe-area/use-safe-area)
notifications (rf/sub [:activity.center/notifications-grouped-by-date])
(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 []]
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}}
: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 :i/close {:color (colors/theme-colors "#000000" "#ffffff")}]]
[rn/text {:style (merge
typography/heading-1
@@ -203,8 +187,7 @@
{:color (colors/theme-colors "#000000" "#ffffff")})}
(i18n/label :t/pending-requests)]
[quo2.tabs/tabs
{:style {:margin-top 12
:margin-bottom 20}
{:style {:margin-top 12 :margin-bottom 20}
:size 32
:on-change #(reset! selected-requests-tab %)
:default-active :received
@@ -213,12 +196,12 @@
{:id :sent
:label (i18n/label :t/sent)}]}]
[list/flat-list
{:key-fn :first
{:key-fn :chat-id
:data (if (= @selected-requests-tab :received) received-requests sent-requests)
:render-fn received-cr-item/received-cr-item}]]))])
(defn get-display-name [{:keys [chat-id message]}]
(let [name (first (rf/sub [:contacts/contact-two-names-by-identity chat-id]))
(let [name (first (<sub [:contacts/contact-two-names-by-identity chat-id]))
no-ens-name (str/blank? (get-in message [:content :ens-name]))]
(if no-ens-name
(first (str/split name " "))
@@ -260,12 +243,12 @@
[info-count (count requests)]])
(defn chats []
(let [{:keys [items search-filter]} (rf/sub [:home-items])
(let [{:keys [items search-filter]} (<sub [:home-items])
current-active-tab @selected-tab
items (prepare-items current-active-tab items)
contacts (rf/sub [:contacts/active])
contacts (<sub [:contacts/active])
contacts (prepare-contacts contacts)
notifications (rf/sub [:activity.center/notifications-grouped-by-date])
notifications (<sub [:activity.center/notifications-grouped-by-date])
{requests :received-requests new-info :has-unread?} (find-contact-requests notifications)]
[rn/view {:style {:flex 1}}
[discover-card/discover-card {:title (i18n/label :t/invite-friends-to-status)
@@ -291,21 +274,21 @@
[list/flat-list
{:key-fn chat-list-key-fn
:getItemLayout get-item-layout
:on-end-reached #(rf/dispatch [:chat.ui/show-more-chats])
:on-end-reached #(re-frame/dispatch [:chat.ui/show-more-chats])
:keyboard-should-persist-taps :always
:data items
:render-fn messages-home-item}]
[rn/view {:style {:flex 1}} (when (> (count requests) 0)
[contact-requests requests])
[list/section-list
[rn/section-list
{:key-fn :title
:sticky-section-headers-enabled false
:sections contacts
:render-section-header-fn contacts-section-header
:render-fn contact-item}]]))]))
(defn chats-list []
(let [loading? (rf/sub [:chats/loading?])]
(views/defview chats-list []
(views/letsubs [loading? [:chats/loading?]]
[:<>
[connectivity/loading-indicator]
(if loading?
@@ -313,24 +296,16 @@
[rn/activity-indicator {:animating true}]]
[chats])]))
(defn plus-button []
(let [logging-in? (rf/sub [:multiaccounts/login])]
(views/defview plus-button []
(views/letsubs [logging-in? [:multiaccounts/login]]
[components.plus-button/plus-button
{:on-press (when-not logging-in?
#(rf/dispatch [:bottom-sheet/show-sheet :add-new {}]))
#(re-frame/dispatch [:bottom-sheet/show-sheet :add-new {}]))
:loading logging-in?
:accessibility-label :new-chat-button}]))
(defn plus-button-new-messages []
(let [logging-in? (rf/sub [:multiaccounts/login])]
[components.plus-button/plus-button
{:on-press (when-not logging-in?
#(rf/dispatch [:bottom-sheet/show-sheet :add-new-sheet-view {}]))
:loading logging-in?
:accessibility-label :new-chat-button}]))
(defn notifications-button []
(let [notif-count (rf/sub [:activity.center/notifications-count])]
(views/defview notifications-button []
(views/letsubs [notif-count [:activity.center/notifications-count]]
[rn/view
[quo2.button/button {:type :grey
:size 32
@@ -338,10 +313,10 @@
:style {:margin-left 12}
:accessibility-label :notifications-button
:on-press #(do
(rf/dispatch [:mark-all-activity-center-notifications-as-read])
(re-frame/dispatch [:mark-all-activity-center-notifications-as-read])
(if config/new-activity-center-enabled?
(rf/dispatch [:navigate-to :activity-center])
(rf/dispatch [:navigate-to :notifications-center])))}
(re-frame/dispatch [:activity-center/open])
(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})
@@ -356,7 +331,7 @@
:width 32
:style {:margin-left 12}
:on-press #(do
(rf/dispatch [::qr-scanner/scan-code
(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)}]])
@@ -366,12 +341,12 @@
:width 32
:accessibility-label "scan-button"
:on-press #(do
(rf/dispatch [::qr-scanner/scan-code
{:handler ::qr-scanner/on-scan-success}]))}
(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)}]])
(defn profile-button []
(let [{:keys [public-key preferred-name emoji]} (rf/sub [:multiaccount])]
(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
@@ -380,10 +355,10 @@
(defn home []
[:f>
(fn []
(quo.react/effect! #(rf/dispatch [:get-activity-center-notifications]))
[rn/keyboard-avoiding-view {:style {:flex 1
:background-color (colors/theme-colors colors/neutral-5 colors/neutral-95)}
:ignore-offset true}
(quo.react/effect! #(re-frame/dispatch [:get-activity-center-notifications]))
[quo.rn/keyboard-avoiding-view {:style {:flex 1
:background-color (colors/theme-colors colors/neutral-5 colors/neutral-95)}
:ignore-offset true}
[topbar/topbar {:navigation :none
:use-insets true
:background (colors/theme-colors colors/neutral-5 colors/neutral-95)
@@ -401,6 +376,5 @@
:margin-top 15
:margin-bottom 20}
[quo2.text/text {:size :heading-1 :weight :semi-bold} (i18n/label :t/messages)]
[plus-button-new-messages]]
[plus-button]]
[chats-list]])])
@@ -34,7 +34,7 @@
[status-im.ui.screens.communities.icon :as communities.icon]
[status-im.ui2.screens.chat.components.reply :as components.reply]
[status-im.utils.config :as config]
[status-im.utils.re-frame :as rf]
[utils.re-frame :as rf]
[status-im.utils.security :as security]
[quo2.components.icon :as icons]
[status-im.utils.datetime :as time]
@@ -288,11 +288,18 @@
display-name]
[message-home-item/verified-or-contact-icon contact]
(when show-key?
[text/text {:size :label
:style {:color (colors/theme-colors colors/neutral-50 colors/neutral-40)
:margin-left 8
:margin-top 2}}
(str (utils/get-shortened-address (:public-key contact)) " • " (time/to-short-str timestamp))])])
(let [props {:size :label
:style {:color (colors/theme-colors colors/neutral-50 colors/neutral-40)}}]
[rn/view {:style {:margin-left 8
:margin-top 2
:flex-direction :row}}
[text/text
(assoc props :accessibility-label :message-chat-key)
(utils/get-shortened-address (:public-key contact))]
[text/text props " • "]
[text/text
(assoc props :accessibility-label :message-timestamp)
(time/to-short-str timestamp)]]))])
(defn message-content-wrapper
"Author, userpic and delivery wrapper"
@@ -5,7 +5,6 @@
[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]
@@ -14,7 +13,7 @@
(defn pin-limit-popover [chat-id pinned-messages-list]
[:f>
(fn []
(let [{:keys [width]} (constants/dimensions)
(let [width (<sub [:dimensions/window-width])
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)]
@@ -9,6 +9,7 @@
[status-im.ui.screens.chat.group :as chat.group]
[status-im.ui.screens.chat.message.datemark :as message-datemark]
[status-im.ui.screens.chat.message.gap :as gap]
[status-im.ui.screens.chat.components.messages-skeleton :as messages-skeleton]
[status-im.utils.utils :as utils]
[status-im.utils.platform :as platform]
[status-im.ui.screens.chat.state :as state]
@@ -19,6 +20,11 @@
(defonce show-floating-scroll-down-button (reagent/atom false))
(defonce messages-list-ref (atom nil))
(def messages-view-height (reagent/atom 0))
(defn on-messages-view-layout [^js ev]
(reset! messages-view-height (-> ev .-nativeEvent .-layout .-height)))
(def list-key-fn #(or (:message-id %) (:value %)))
(def list-ref #(reset! messages-list-ref %))
@@ -63,8 +69,7 @@
all-loaded? (<sub [:chats/all-loaded? chat-id])]
[rn/view {:style (when platform/android? {:scaleY -1})}
(if (or loading-messages? (not chat-id) (not all-loaded?))
[rn/view {:height 324 :align-items :center :justify-content :center}
[rn/activity-indicator {:animating true}]]
[messages-skeleton/messages-skeleton @messages-view-height]
[chat-intro-header-container chat no-messages?])]))
(defn list-header [{:keys [chat-id chat-type invitation-admin]}]
@@ -186,6 +191,7 @@
:on-scroll on-scroll
;;TODO https://github.com/facebook/react-native/issues/30034
:inverted (when platform/ios? true)
:style (when platform/android? {:scaleY -1})})]
:style (when platform/android? {:scaleY -1})
:on-layout on-messages-view-layout})]
(when @show-floating-scroll-down-button
[floating-scroll-down-button show-input?])]))

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