Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f0ce0d7bd | ||
|
|
bc51a59774 | ||
|
|
4639660943 | ||
|
|
b58e7d6fff | ||
|
|
3b593bedac | ||
|
|
944cb6c822 | ||
|
|
8700886ae4 | ||
|
|
c5cc18e9e0 | ||
|
|
2b6f354fb6 | ||
|
|
717a225fd4 |
@@ -2,13 +2,12 @@
|
||||
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 utils.re-frame/defn]}
|
||||
:linters {:invalid-arity {:skip-args [status-im.utils.fx/defn]}
|
||||
;;TODO remove number when this is fixed
|
||||
;;https://github.com/borkdude/clj-kondo/issues/867
|
||||
:unresolved-symbol {:exclude [PersistentPriorityMap.EMPTY number]}}}
|
||||
|
||||
-37
@@ -1,37 +0,0 @@
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -175,6 +175,3 @@ test/appium/tests/users.py
|
||||
##node bindings
|
||||
/bin/
|
||||
/lib/
|
||||
|
||||
## visual tests
|
||||
/artifacts
|
||||
@@ -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 Android release builds
|
||||
# Useful for Andoird 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,16 +313,6 @@ 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,4 +1,4 @@
|
||||
library 'status-jenkins-lib@v1.6.2'
|
||||
library 'status-jenkins-lib@v1.6.1'
|
||||
|
||||
/* Options section can't access functions in objects. */
|
||||
def isPRBuild = utils.isPRBuild()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
library 'status-jenkins-lib@v1.6.2'
|
||||
library 'status-jenkins-lib@v1.6.1'
|
||||
|
||||
pipeline {
|
||||
agent { label 'linux' }
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
library 'status-jenkins-lib@v1.6.2'
|
||||
library 'status-jenkins-lib@v1.6.1'
|
||||
|
||||
/* Options section can't access functions in objects. */
|
||||
def isPRBuild = utils.isPRBuild()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
library 'status-jenkins-lib@v1.6.2'
|
||||
library 'status-jenkins-lib@v1.6.1'
|
||||
|
||||
pipeline {
|
||||
agent { label params.AGENT_LABEL }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
library 'status-jenkins-lib@v1.6.2'
|
||||
library 'status-jenkins-lib@v1.6.1'
|
||||
|
||||
/* Options section can't access functions in objects. */
|
||||
def isPRBuild = utils.isPRBuild()
|
||||
|
||||
@@ -62,7 +62,7 @@ pipeline {
|
||||
--rerun_count=2 \
|
||||
--testrail_report=True \
|
||||
-m testrail_id \
|
||||
-m \"new_ui_critical or new_ui_medium\" \
|
||||
-m \"new_ui_critical\" \
|
||||
-k \"${params.KEYWORD_EXPRESSION}\" \
|
||||
--apk=${params.APK_NAME}
|
||||
"""
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
library 'status-jenkins-lib@v1.6.2'
|
||||
library 'status-jenkins-lib@v1.6.1'
|
||||
|
||||
pipeline {
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
library 'status-jenkins-lib@v1.6.2'
|
||||
library 'status-jenkins-lib@v1.6.1'
|
||||
|
||||
pipeline {
|
||||
agent { label 'macos' }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
library 'status-jenkins-lib@v1.6.2'
|
||||
library 'status-jenkins-lib@v1.6.1'
|
||||
|
||||
pipeline {
|
||||
agent { label 'linux' }
|
||||
|
||||
+1
-4
@@ -84,10 +84,7 @@
|
||||
"@babel/preset-env": "7.1.0",
|
||||
"@babel/register": "7.0.0",
|
||||
"@mapbox/node-pre-gyp": "^1.0.9",
|
||||
"@types/jest": "^28.1.6",
|
||||
"detox": "^19.9.1",
|
||||
"jest-image-snapshot": "^5.1.0",
|
||||
"jest": "^28.1.3",
|
||||
"jest": "^25.1.0",
|
||||
"nodemon": "^2.0.16",
|
||||
"nyc": "^14.1.1",
|
||||
"process": "0.11.10",
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.7 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.4 KiB |
@@ -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|status-im)')
|
||||
INVALID_CHANGES=$(echo "$CHANGES" | grep -E '(re-frame/dispatch|rf/dispatch|re-frame/subscribe|rf/subscribe|rf/sub|<sub|>evt)')
|
||||
|
||||
if test -n "$INVALID_CHANGES"; then
|
||||
echo "re-frame dispatch/subscribe and status-im are not allowed in quo/quo2 components"
|
||||
echo "re-frame dispatch/subscribe is not allowed in quo/quo2 components"
|
||||
echo ''
|
||||
echo "$INVALID_CHANGES"
|
||||
exit 1
|
||||
|
||||
@@ -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 test-ID]
|
||||
accessibility-label loading border-radius style]
|
||||
:or {theme :main
|
||||
type :primary
|
||||
haptic-feedback true
|
||||
@@ -92,7 +92,7 @@
|
||||
{:on-press-start (fn []
|
||||
(optional-haptic)
|
||||
(on-press-start))}))
|
||||
[rn/view {:test-ID test-ID :style (merge (style-container type) style)}
|
||||
[rn/view {:style (merge (style-container type) style)}
|
||||
(when before
|
||||
[rn/view
|
||||
[icons/icon before {:color icon-color}]])
|
||||
|
||||
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 142 KiB |
@@ -1,44 +0,0 @@
|
||||
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,12 +15,18 @@
|
||||
(defn group-avatar [_]
|
||||
(fn [{:keys [color size]}]
|
||||
(let [container-size (get-in sizes [:container size])
|
||||
icon-size (get-in sizes [:icon size])]
|
||||
icon-size (get-in sizes [:icon size])
|
||||
;; theme (or override-theme (if (colors/dark?) :dark :light))
|
||||
]
|
||||
[rn/view {:width container-size
|
||||
:height container-size
|
||||
:align-items :center
|
||||
:justify-content :center
|
||||
:border-radius (/ container-size 2)
|
||||
:background-color (colors/theme-alpha color 0.5 0.6)}
|
||||
: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))
|
||||
}
|
||||
[icon/icon :i/group {:size icon-size ; TODO: group icon sizes 12 and 20 (small and large) are missing
|
||||
:color colors/white-opa-70}]])))
|
||||
|
||||
@@ -122,20 +122,20 @@
|
||||
first-initial-letter
|
||||
initials)
|
||||
"")]
|
||||
[rn/view {:accessibility-label :user-avatar
|
||||
:style {:width outer-dimensions
|
||||
:height outer-dimensions
|
||||
:border-radius outer-dimensions}}
|
||||
[rn/view {:style {:width outer-dimensions
|
||||
:height outer-dimensions
|
||||
:border-radius outer-dimensions}}
|
||||
(when (and ring? identicon?)
|
||||
[icons/icon :i/identicon-ring {:size outer-dimensions
|
||||
:no-color true}])
|
||||
[icons/icon :main-icons/identicon-ring {:width outer-dimensions
|
||||
:height outer-dimensions
|
||||
: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}}
|
||||
|
||||
@@ -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 test-ID]
|
||||
on-long-press accessibility-label icon icon-no-color style]
|
||||
:or {type :primary
|
||||
size 40}}
|
||||
children]
|
||||
@@ -197,8 +197,7 @@
|
||||
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 {:test-ID test-ID
|
||||
:disabled disabled
|
||||
[rn/touchable-without-feedback (merge {:disabled disabled
|
||||
:accessibility-label accessibility-label}
|
||||
(when on-press
|
||||
{:on-press (fn []
|
||||
|
||||
@@ -9,6 +9,54 @@
|
||||
(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)
|
||||
@@ -17,60 +65,10 @@
|
||||
: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]
|
||||
[:<>
|
||||
(doall
|
||||
[:<> {:style
|
||||
{:flex 1}}
|
||||
(interpose
|
||||
[divider]
|
||||
(for [actions sections]
|
||||
(doall
|
||||
(map action actions))))])
|
||||
(map action actions)))])
|
||||
|
||||
@@ -138,13 +138,14 @@
|
||||
(fn [{:keys [left-accessories left-component border-bottom
|
||||
right-accessories right-component insets get-layout
|
||||
title subtitle title-component style title-align
|
||||
background]
|
||||
background header-style]
|
||||
: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}]
|
||||
|
||||
@@ -30,5 +30,4 @@
|
||||
(get-icons 12)
|
||||
(get-icons 16)
|
||||
(get-icons 20)
|
||||
(get-icons 24)
|
||||
(get-icons 32)))
|
||||
(get-icons 24)))
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
(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]]])
|
||||
@@ -0,0 +1,276 @@
|
||||
(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]])]))
|
||||
@@ -1,11 +1,10 @@
|
||||
(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]}]
|
||||
(defn text-style [{:keys [size align weight style secondary-color]}]
|
||||
(merge (case (or weight :regular)
|
||||
:regular typography/font-regular
|
||||
:medium typography/font-medium
|
||||
@@ -24,7 +23,8 @@
|
||||
{:text-align (or align :auto)}
|
||||
(if (:color style)
|
||||
style
|
||||
(assoc style :color (if (= (theme/get-theme) :dark) colors/white colors/neutral-100)))))
|
||||
(assoc style :color (or secondary-color
|
||||
(colors/theme-colors colors/neutral-100 colors/white))))))
|
||||
|
||||
(defn text []
|
||||
(let [this (reagent/current-component)
|
||||
|
||||
@@ -0,0 +1,270 @@
|
||||
(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)))})])}))))
|
||||
@@ -1,39 +0,0 @@
|
||||
(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)})
|
||||
@@ -1,66 +0,0 @@
|
||||
(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.string :as utils]))
|
||||
[utils :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 test-ID]}]
|
||||
on-press pass-through? icon-color-anim accessibility-label]}]
|
||||
[:f>
|
||||
(fn []
|
||||
(let [icon-animated-style (reanimated/apply-animations-to-style
|
||||
@@ -40,8 +40,7 @@
|
||||
:height 40
|
||||
:border-radius 10})]
|
||||
[rn/touchable-without-feedback
|
||||
{:test-ID test-ID
|
||||
:on-press on-press
|
||||
{: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,7 +63,6 @@
|
||||
: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]
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
(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})
|
||||
@@ -1,178 +0,0 @@
|
||||
(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]]])
|
||||
@@ -0,0 +1,157 @@
|
||||
(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])]])
|
||||
@@ -3,8 +3,7 @@
|
||||
[react-native.core :as rn]
|
||||
[quo2.components.tabs.tab :as tab]
|
||||
[reagent.core :as reagent]
|
||||
[utils.number :as utils.number]
|
||||
[utils.collection :as utils.collection]
|
||||
[utils :as utils]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[quo2.components.notifications.notification-dot :refer [notification-dot]]
|
||||
[react-native.masked-view :as masked-view]
|
||||
@@ -51,7 +50,7 @@
|
||||
;; Truncate to avoid unnecessary rendering.
|
||||
(if (> fade-percentage 0.99)
|
||||
0.99
|
||||
(utils.number/naive-round fade-percentage 2))))
|
||||
(utils/naive-round fade-percentage 2))))
|
||||
|
||||
(defn scrollable-tabs
|
||||
"Just like the component `tabs`, displays horizontally scrollable tabs with
|
||||
@@ -121,7 +120,7 @@
|
||||
:scroll-on-press?
|
||||
:size)
|
||||
(when scroll-on-press?
|
||||
{:initial-scroll-index (utils.collection/first-index #(= @active-tab-id (:id %)) data)})
|
||||
{:initial-scroll-index (utils/first-index #(= @active-tab-id (:id %)) data)})
|
||||
{:ref #(reset! flat-list-ref %)
|
||||
:extra-data (str @active-tab-id)
|
||||
:horizontal true
|
||||
|
||||
+7
-17
@@ -28,18 +28,14 @@
|
||||
quo2.components.messages.gap
|
||||
quo2.components.messages.system-message
|
||||
quo2.components.reactions.reaction
|
||||
quo2.components.notifications.activity-log.view
|
||||
quo2.components.notifications.activity-logs
|
||||
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.navigation.top-nav
|
||||
quo2.components.navigation.floating-shell-button
|
||||
quo2.components.tags.status-tags
|
||||
quo2.components.navigation.page-nav
|
||||
quo2.components.selectors.disclaimer))
|
||||
quo2.components.list-items.index
|
||||
quo2.components.list-items.list-item))
|
||||
|
||||
(def button quo2.components.buttons.button/button)
|
||||
(def dynamic-button quo2.components.buttons.dynamic-button/dynamic-button)
|
||||
@@ -56,16 +52,9 @@
|
||||
(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)
|
||||
@@ -83,7 +72,6 @@
|
||||
(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
|
||||
@@ -94,8 +82,10 @@
|
||||
(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-log.view/view)
|
||||
(def activity-log quo2.components.notifications.activity-logs/activity-log)
|
||||
(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)
|
||||
@@ -91,6 +91,11 @@
|
||||
(def white-opa-90 (alpha white 0.9))
|
||||
(def white-opa-95 (alpha white 0.95))
|
||||
|
||||
;;;;Black
|
||||
|
||||
;;Solid
|
||||
(def black "#000000")
|
||||
|
||||
;;;;Primary
|
||||
|
||||
;;Solid
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
(ns react-native.core
|
||||
(:require [reagent.core :as reagent]
|
||||
["react-native" :as react-native]
|
||||
[react-native.flat-list :as flat-list]
|
||||
[react-native.section-list :as section-list]))
|
||||
[react-native.flat-list :as flat-list]))
|
||||
|
||||
(def app-state ^js (.-AppState ^js react-native))
|
||||
|
||||
@@ -10,7 +9,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)))
|
||||
@@ -18,8 +17,6 @@
|
||||
|
||||
(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)))
|
||||
@@ -51,5 +48,3 @@
|
||||
|
||||
(defn get-window []
|
||||
(js->clj (.get (.-Dimensions ^js react-native) "window") :keywordize-keys true))
|
||||
|
||||
(def status-bar (.-StatusBar ^js react-native))
|
||||
|
||||
@@ -27,9 +27,6 @@
|
||||
(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) #()))
|
||||
|
||||
|
||||
@@ -2,9 +2,8 @@
|
||||
(: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 withRepeat Easing Keyframe)]))
|
||||
:refer (useSharedValue useAnimatedStyle withTiming withDelay withSpring Easing Keyframe)]))
|
||||
|
||||
;; Animated Components
|
||||
(def create-animated-component (comp reagent/adapt-react-class (.-createAnimatedComponent reanimated)))
|
||||
@@ -13,8 +12,6 @@
|
||||
(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)
|
||||
@@ -24,7 +21,6 @@
|
||||
(def with-delay withDelay)
|
||||
(def with-spring withSpring)
|
||||
(def key-frame Keyframe)
|
||||
(def with-repeat withRepeat)
|
||||
|
||||
;; Easings
|
||||
(def bezier (.-bezier ^js Easing))
|
||||
@@ -72,7 +68,3 @@
|
||||
(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?)))
|
||||
@@ -1,40 +0,0 @@
|
||||
(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})])
|
||||
@@ -6,26 +6,6 @@
|
||||
[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
|
||||
@@ -38,7 +18,8 @@
|
||||
~excessively~ big, this implementation will probably need to be revisited."
|
||||
[db-notifications new-notifications]
|
||||
(reduce (fn [acc {:keys [id type read] :as notification}]
|
||||
(let [remove-notification (fn [data]
|
||||
(let [filter-status (if read :read :unread)
|
||||
remove-notification (fn [data]
|
||||
(remove #(= id (:id %)) data))
|
||||
insert-and-sort (fn [data]
|
||||
(->> notification
|
||||
@@ -46,17 +27,14 @@
|
||||
(sort-by (juxt :timestamp :id))
|
||||
reverse))]
|
||||
(as-> acc $
|
||||
(update-in $ [type :all :data] remove-notification)
|
||||
(update-in $ [types/no-type :all :data] remove-notification)
|
||||
(update-in $ [type :read :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))
|
||||
$
|
||||
(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))))))
|
||||
(-> $ (update-in [type filter-status :data] insert-and-sort)
|
||||
(update-in [types/no-type filter-status :data] insert-and-sort))))))
|
||||
db-notifications
|
||||
new-notifications))
|
||||
|
||||
@@ -67,69 +45,38 @@
|
||||
{:db (update-in db [:activity-center :notifications]
|
||||
update-notifications new-notifications)}))
|
||||
|
||||
(fx/defn notifications-reconcile-from-response
|
||||
{:events [:activity-center/reconcile-notifications-from-response]}
|
||||
;;;; Contact verification
|
||||
|
||||
(fx/defn contact-verification-decline
|
||||
{:events [:activity-center.contact-verification/decline]}
|
||||
[_ contact-verification-id]
|
||||
{::json-rpc/call [{:method "wakuext_declineContactVerificationRequest"
|
||||
:params [contact-verification-id]
|
||||
:on-success #(rf/dispatch [:activity-center.contact-verification/decline-success %])
|
||||
:on-error #(rf/dispatch [:activity-center.contact-verification/decline-error contact-verification-id %])}]})
|
||||
|
||||
(fx/defn contact-verification-decline-success
|
||||
{:events [:activity-center.contact-verification/decline-success]}
|
||||
[cofx response]
|
||||
(->> response
|
||||
:activityCenterNotifications
|
||||
(map data-store.activities/<-rpc)
|
||||
(notifications-reconcile cofx)))
|
||||
|
||||
;;;; 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
|
||||
%])}]})
|
||||
(fx/defn contact-verification-decline-error
|
||||
{:events [:activity-center.contact-verification/decline-error]}
|
||||
[_ contact-verification-id error]
|
||||
(log/warn "Failed to decline contact verification"
|
||||
{:contact-verification-id contact-verification-id
|
||||
:error error})
|
||||
nil)
|
||||
|
||||
;;;; Notifications fetching and pagination
|
||||
|
||||
(def defaults
|
||||
{:filter-status :unread
|
||||
:filter-type types/no-type
|
||||
;; 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})
|
||||
:notifications-per-page 10})
|
||||
|
||||
(def start-or-end-cursor
|
||||
"")
|
||||
@@ -139,13 +86,15 @@
|
||||
(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
|
||||
|
||||
@@ -3,11 +3,10 @@
|
||||
[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-im2.setup.config :as config]))
|
||||
[status-im.utils.config :as config]))
|
||||
|
||||
(defn setup []
|
||||
(h/register-helper-events)
|
||||
@@ -15,140 +14,87 @@
|
||||
|
||||
;;;; 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 "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}))))
|
||||
(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}]}))
|
||||
|
||||
(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}))))
|
||||
(h/spy-fx spy-queue ::json-rpc/call)
|
||||
|
||||
(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}))))
|
||||
(rf/dispatch [:activity-center.contact-verification/decline contact-verification-id])
|
||||
|
||||
(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}))))
|
||||
(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))))))))))
|
||||
|
||||
;;;; Notification reconciliation
|
||||
|
||||
@@ -158,12 +104,12 @@
|
||||
(rf-test/run-test-sync
|
||||
(setup)
|
||||
(let [notifications {types/one-to-one-chat
|
||||
{:all {:cursor ""
|
||||
{:read {:cursor ""
|
||||
:data [{:id "0x1"
|
||||
:read true
|
||||
:type types/one-to-one-chat}
|
||||
{:id "0x2"
|
||||
:read false
|
||||
:read true
|
||||
:type types/one-to-one-chat}]}
|
||||
:unread {:cursor ""
|
||||
:data [{:id "0x3"
|
||||
@@ -183,112 +129,216 @@
|
||||
(testing "removes dismissed or accepted notifications"
|
||||
(rf-test/run-test-sync
|
||||
(setup)
|
||||
(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 [: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}]}}}])
|
||||
|
||||
(rf/dispatch [:activity-center.notifications/reconcile
|
||||
[(assoc notif-1 :dismissed true)
|
||||
(assoc notif-3 :accepted true)
|
||||
(assoc notif-4 :dismissed true)
|
||||
notif-5]])
|
||||
(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}]])
|
||||
|
||||
(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]))))))
|
||||
(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])))))
|
||||
|
||||
(testing "replaces old notifications with newly arrived ones"
|
||||
(rf-test/run-test-sync
|
||||
(setup)
|
||||
(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 [: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}]}}}])
|
||||
|
||||
(rf/dispatch [:activity-center.notifications/reconcile [new-notif-1 new-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}]])
|
||||
|
||||
(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]))))))
|
||||
(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])))))
|
||||
|
||||
(testing "reconciles notifications that switched their read/unread status"
|
||||
(rf-test/run-test-sync
|
||||
(setup)
|
||||
(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 [:test/assoc-in [:activity-center :notifications]
|
||||
{types/one-to-one-chat
|
||||
{:read {:cursor ""
|
||||
:data [{:id "0x1"
|
||||
:read true
|
||||
:type types/one-to-one-chat}]}}}])
|
||||
|
||||
(rf/dispatch [:activity-center.notifications/reconcile [new-notif-1]])
|
||||
(rf/dispatch [:activity-center.notifications/reconcile
|
||||
[{:id "0x1"
|
||||
:read false
|
||||
:type types/one-to-one-chat}]])
|
||||
|
||||
(is (= {types/no-type
|
||||
{:all {:data [new-notif-1]}
|
||||
:unread {:data [new-notif-1]}}
|
||||
(is (= {types/no-type
|
||||
{:read {:data []}
|
||||
:unread {:data [{:id "0x1"
|
||||
:read false
|
||||
:type types/one-to-one-chat}]}}
|
||||
|
||||
types/one-to-one-chat
|
||||
{:all {:cursor "" :data [new-notif-1]}
|
||||
:unread {:data [new-notif-1]}}}
|
||||
(get-in (h/db) [:activity-center :notifications]))))))
|
||||
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])))))
|
||||
|
||||
;; 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)
|
||||
(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 [: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}]}}}])
|
||||
|
||||
(rf/dispatch [:activity-center.notifications/reconcile [new-notif-1 new-notif-4]])
|
||||
(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 {}}]])
|
||||
|
||||
(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]))))))))
|
||||
(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])))))))
|
||||
|
||||
;;;; Notifications fetching and pagination
|
||||
|
||||
@@ -406,12 +456,12 @@
|
||||
(let [spy-queue (atom [])]
|
||||
(h/spy-fx spy-queue ::json-rpc/call)
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :filter :status]
|
||||
:all])
|
||||
:read])
|
||||
(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 :all :cursor]
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :notifications types/one-to-one-chat :read :cursor]
|
||||
"10"])
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :notifications types/one-to-one-chat :all :loading?]
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :notifications types/one-to-one-chat :read :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 10)
|
||||
(def ^:const contact-verification 6)
|
||||
|
||||
;; TODO: Remove this constant once the old Notification Center code is removed.
|
||||
;; Its value clashes with the new constant `-contact-verification`
|
||||
|
||||
@@ -134,7 +134,7 @@
|
||||
(re-frame/dispatch [::failed-to-import %]))}]})
|
||||
|
||||
(fx/defn join
|
||||
{:events [:communities/join]}
|
||||
{:events [::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 [:communities/leave]}
|
||||
[{:keys [db]} community-id]
|
||||
{:events [::leave]}
|
||||
[{:keys [db] :as cofx} 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 [:communities/invite-people-pressed]}
|
||||
{:events [::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 [:communities/share-community-pressed]}
|
||||
{:events [::share-community-pressed]}
|
||||
[cofx id]
|
||||
(fx/merge cofx
|
||||
(reset-community-id-input id)
|
||||
|
||||
@@ -21,13 +21,12 @@
|
||||
(def ^:const contact-request-state-received 3)
|
||||
(def ^:const contact-request-state-dismissed 4)
|
||||
|
||||
(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 contact-verification-state-unknown 0)
|
||||
(def ^:const contact-verification-state-pending 1)
|
||||
(def ^:const contact-verification-state-accepted 2)
|
||||
(def ^:const contact-verification-state-declined 3)
|
||||
(def ^:const contact-verification-state-cancelled 4)
|
||||
(def ^:const contact-verification-state-trusted 5)
|
||||
|
||||
(def ^:const emoji-reaction-love 1)
|
||||
(def ^:const emoji-reaction-thumbs-up 2)
|
||||
|
||||
@@ -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-im2.setup.config :as config]))
|
||||
[status-im.utils.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-im2.setup.config :as config]))
|
||||
[status-im.utils.config :as config]))
|
||||
|
||||
(def chat-id
|
||||
"0x04c66155")
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
(def raw-notification
|
||||
{:chatId chat-id
|
||||
:contactVerificationStatus constants/contact-verification-status-pending
|
||||
:contactVerificationStatus constants/contact-verification-state-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-status-pending}
|
||||
:contact-verification-status constants/contact-verification-state-pending}
|
||||
(-> raw-notification
|
||||
store/<-rpc
|
||||
(dissoc :last-message :message :reply-message)))))
|
||||
|
||||
@@ -46,7 +46,6 @@
|
||||
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
|
||||
|
||||
@@ -110,7 +110,7 @@
|
||||
:blank-keycard-title
|
||||
:block
|
||||
:block-contact
|
||||
:block-contact-details
|
||||
:block-user-confirmation
|
||||
:blocked-users
|
||||
:bootnode-address
|
||||
:bootnode-details
|
||||
|
||||
+3
-5
@@ -1,11 +1,9 @@
|
||||
(ns status-im2.contexts.shell.animation
|
||||
(ns status-im.switcher.animation
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[react-native.reanimated :as reanimated]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[status-im2.contexts.shell.constants :as constants]
|
||||
|
||||
;;TODO remove when not used anymore
|
||||
[status-im.async-storage.core :as async-storage]))
|
||||
[status-im.async-storage.core :as async-storage]
|
||||
[status-im.switcher.constants :as constants]))
|
||||
|
||||
;;;; Bottom Tabs & Home Stack Animations
|
||||
|
||||
+7
-8
@@ -1,10 +1,10 @@
|
||||
(ns status-im2.contexts.shell.bottom-tabs
|
||||
(:require [reagent.core :as reagent]
|
||||
(ns status-im.switcher.bottom-tabs
|
||||
(:require [quo.react-native :as rn]
|
||||
[reagent.core :as reagent]
|
||||
[re-frame.core :as re-frame]
|
||||
[react-native.core :as rn]
|
||||
[status-im2.contexts.shell.style :as styles]
|
||||
[status-im2.contexts.shell.constants :as constants]
|
||||
[status-im2.contexts.shell.animation :as animation]
|
||||
[status-im.switcher.styles :as styles]
|
||||
[status-im.switcher.constants :as constants]
|
||||
[status-im.switcher.animation :as animation]
|
||||
[quo2.components.navigation.bottom-nav-tab :as bottom-nav-tab]))
|
||||
|
||||
;; Reagent atoms used for lazily loading home screen tabs
|
||||
@@ -38,8 +38,7 @@
|
||||
|
||||
(defn bottom-tab [icon stack-id shared-values]
|
||||
[bottom-nav-tab/bottom-nav-tab
|
||||
{:test-ID stack-id
|
||||
:icon icon
|
||||
{:icon icon
|
||||
:icon-color-anim (get
|
||||
shared-values
|
||||
(get constants/tabs-icon-color-keywords stack-id))
|
||||
+11
-5
@@ -1,20 +1,24 @@
|
||||
(ns status-im2.contexts.shell.constants
|
||||
(:require [react-native.platform :as platform]
|
||||
[utils.re-frame :as rf]
|
||||
[react-native.core :as rn]))
|
||||
(ns status-im.switcher.constants
|
||||
(:require [quo.react-native :as rn]
|
||||
[reagent.core :as reagent]
|
||||
[status-im.utils.handlers :refer [<sub]]
|
||||
[status-im.utils.platform :as platform]))
|
||||
|
||||
(def shell-animation-time 200)
|
||||
|
||||
(defn bottom-tabs-container-height []
|
||||
(if platform/android? 57 82))
|
||||
|
||||
(defn bottom-tabs-extended-container-height []
|
||||
(if platform/android? 90 120))
|
||||
|
||||
(defn status-bar-offset []
|
||||
(if platform/android? (.-currentHeight ^js rn/status-bar) 0))
|
||||
|
||||
;; status bar height is not included in : the dimensions/window for devices with a notch
|
||||
;; https://github.com/facebook/react-native/issues/23693#issuecomment-662860819
|
||||
(defn dimensions []
|
||||
(let [{:keys [width height]} (rf/sub [:dimensions/window])]
|
||||
(let [{:keys [width height]} (<sub [:dimensions/window])]
|
||||
{:width width
|
||||
:height (if (> (status-bar-offset) 28)
|
||||
(+ height (status-bar-offset))
|
||||
@@ -39,3 +43,5 @@
|
||||
:chats-stack :chats-tab-icon-opacity
|
||||
:wallet-stack :wallet-tab-icon-opacity
|
||||
:browser-stack :browser-tab-icon-opacity})
|
||||
|
||||
(def pass-through? (reagent/atom false))
|
||||
+8
-10
@@ -1,14 +1,12 @@
|
||||
(ns status-im2.contexts.shell.home-stack
|
||||
(:require [react-native.reanimated :as reanimated]
|
||||
[status-im2.contexts.shell.style :as styles]
|
||||
[status-im2.contexts.shell.constants :as constants]
|
||||
[status-im2.contexts.shell.bottom-tabs :as bottom-tabs]
|
||||
[status-im2.contexts.communities.home.view :as communities]
|
||||
|
||||
;; TODO move to status-im2
|
||||
(ns status-im.switcher.home-stack
|
||||
(:require [status-im.switcher.styles :as styles]
|
||||
[react-native.reanimated :as reanimated]
|
||||
[status-im.switcher.constants :as constants]
|
||||
[status-im.ui2.screens.chat.home :as chat.home]
|
||||
[status-im.switcher.bottom-tabs :as bottom-tabs]
|
||||
[status-im.ui.screens.profile.user.views :as profile.user]
|
||||
[status-im.ui.screens.wallet.accounts.views :as wallet.accounts]
|
||||
[status-im.ui2.screens.chat.home :as chat.home]))
|
||||
[status-im.ui2.screens.communities.communities-home :as communities-home]))
|
||||
|
||||
(defn load-stack? [stack-id]
|
||||
(case stack-id
|
||||
@@ -32,7 +30,7 @@
|
||||
:right 0
|
||||
:accessibility-label stack-id})}
|
||||
(case stack-id
|
||||
:communities-stack [communities/home]
|
||||
:communities-stack [communities-home/views]
|
||||
:chats-stack [chat.home/home]
|
||||
:wallet-stack [wallet.accounts/accounts-overview]
|
||||
:browser-stack [profile.user/my-profile])])]))
|
||||
@@ -0,0 +1,54 @@
|
||||
(ns status-im.switcher.shell
|
||||
(:require [quo.react-native :as rn]
|
||||
[status-im.i18n.i18n :as i18n]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[quo2.components.markdown.text :as text]
|
||||
[quo.components.safe-area :as safe-area]
|
||||
[quo2.components.navigation.top-nav :as top-nav]))
|
||||
|
||||
(defn placeholder []
|
||||
[rn/view {:style {:position :absolute
|
||||
:top 0
|
||||
:left 0
|
||||
:right 0
|
||||
:bottom -1
|
||||
:justify-content :center
|
||||
:align-items :center
|
||||
:accessibility-label :shell-placeholder-view}}
|
||||
[rn/view {:style {:margin-top 12
|
||||
:width 80
|
||||
:height 80
|
||||
:border-radius 16
|
||||
:background-color colors/neutral-90}}]
|
||||
[text/text {:size :heading-2
|
||||
:weight :semi-bold
|
||||
:style {:margin-top 20
|
||||
:color colors/white}}
|
||||
(i18n/label :t/shell-placeholder-title)]
|
||||
[text/text {:size :paragraph-1
|
||||
:weight :regular
|
||||
:align :center
|
||||
:style {:margin-top 8
|
||||
:color colors/white}}
|
||||
(i18n/label :t/shell-placeholder-subtitle)]])
|
||||
|
||||
(defn shell []
|
||||
[safe-area/consumer
|
||||
(fn [insets]
|
||||
[rn/view {:style {:top 0
|
||||
:left 0
|
||||
:right 0
|
||||
:bottom -1
|
||||
:position :absolute
|
||||
:background-color colors/neutral-100}}
|
||||
[top-nav/top-nav {:type :shell
|
||||
:style {:margin-top (:top insets)}}]
|
||||
[placeholder]
|
||||
[rn/scroll-view {:style {:padding-horizontal 20
|
||||
:flex-direction :row}}
|
||||
[text/text {:size :heading-1
|
||||
:weight :semi-bold
|
||||
:style {:color colors/white
|
||||
:margin-top 12}}
|
||||
(i18n/label :t/jump-to)]]])])
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
(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,7 +1,7 @@
|
||||
(ns status-im2.contexts.shell.style
|
||||
(ns status-im.switcher.styles
|
||||
(:require [quo2.foundations.colors :as colors]
|
||||
[status-im2.contexts.shell.constants :as constants]
|
||||
[react-native.platform :as platform]))
|
||||
[status-im.utils.platform :as platform]
|
||||
[status-im.switcher.constants :as constants]))
|
||||
|
||||
;; Bottom Tabs
|
||||
(defn bottom-tabs-container [pass-through?]
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
(ns status-im2.contexts.shell.cards.style
|
||||
(ns status-im.switcher.switcher-cards.styles
|
||||
(:require [quo2.foundations.colors :as colors]))
|
||||
|
||||
(def colors-map
|
||||
+18
-18
@@ -1,6 +1,5 @@
|
||||
(ns status-im2.contexts.shell.cards.view
|
||||
(:require [i18n.i18n :as i18n]
|
||||
[react-native.core :as rn]
|
||||
(ns status-im.switcher.switcher-cards.switcher-cards
|
||||
(:require [react-native.core :as rn]
|
||||
[react-native.fast-image :as fast-image]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[quo2.components.markdown.text :as text]
|
||||
@@ -11,26 +10,27 @@
|
||||
[quo2.components.avatars.group-avatar :as group-avatar]
|
||||
[quo2.components.list-items.preview-list :as preview-list]
|
||||
[quo2.components.avatars.channel-avatar :as channel-avatar]
|
||||
[status-im2.contexts.shell.cards.style :as style]))
|
||||
[status-im.switcher.switcher-cards.styles :as styles]
|
||||
[status-im.i18n.i18n :as i18n]))
|
||||
|
||||
(defn content-container [{:keys [content-type data new-notifications? color-50]}]
|
||||
[rn/view {:style (style/content-container new-notifications?)}
|
||||
[rn/view {:style (styles/content-container new-notifications?)}
|
||||
(case content-type
|
||||
:text [text/text (style/last-message-text-props) data]
|
||||
:text [text/text (styles/last-message-text-props) data]
|
||||
:photo [preview-list/preview-list {:type :photo
|
||||
:more-than-99-label (i18n/label :counter-99-plus)
|
||||
:size 24
|
||||
:override-theme :dark} data]
|
||||
:sticker [fast-image/fast-image {:source (:source data)
|
||||
:style (style/sticker)}]
|
||||
:style (styles/sticker)}]
|
||||
:gif [fast-image/fast-image {:source (:source data)
|
||||
:style (style/gif)}]
|
||||
:style (styles/gif)}]
|
||||
:channel [rn/view {:style {:flex-direction :row
|
||||
:align-items :center}}
|
||||
[channel-avatar/channel-avatar
|
||||
{:emoji (:emoji data)
|
||||
:emoji-background-color (colors/alpha color-50 0.1)}]
|
||||
[text/text (style/community-channel-props) (:channel-name data)]]
|
||||
[text/text (styles/community-channel-props) (:channel-name data)]]
|
||||
:community-info (case (:type data)
|
||||
:pending [status-tags/status-tag
|
||||
{:status :pending
|
||||
@@ -47,12 +47,12 @@
|
||||
[:<>])])
|
||||
|
||||
(defn notification-container [{:keys [notification-indicator counter-label color-60]}]
|
||||
[rn/view {:style (style/notification-container)}
|
||||
[rn/view {:style (styles/notification-container)}
|
||||
(if (= notification-indicator :counter)
|
||||
[counter/counter {:outline false
|
||||
:override-text-color colors/white
|
||||
:override-bg-color color-60} counter-label]
|
||||
[rn/view {:style (style/unread-dot color-60)}])])
|
||||
[rn/view {:style (styles/unread-dot color-60)}])])
|
||||
|
||||
(defn bottom-container [{:keys [new-notifications?] :as content}]
|
||||
[:<>
|
||||
@@ -71,7 +71,7 @@
|
||||
:size :large
|
||||
:override-theme :dark}]
|
||||
:community-card [fast-image/fast-image {:source (:source avatar-params)
|
||||
:style (style/community-avatar)}]))
|
||||
:style (styles/community-avatar)}]))
|
||||
|
||||
(defn subtitle [{:keys [content-type data]}]
|
||||
(case content-type
|
||||
@@ -92,18 +92,18 @@
|
||||
(let [color-50 (colors/custom-color customization-color 50)
|
||||
color-60 (colors/custom-color customization-color 60)]
|
||||
[rn/touchable-without-feedback {:on-press on-press}
|
||||
[rn/view {:style (style/base-container color-50)}
|
||||
[rn/view {:style (styles/base-container color-50)}
|
||||
(when banner
|
||||
[rn/image {:source (:source banner)
|
||||
:style {:width 160}}])
|
||||
[rn/view {:style (style/secondary-container)}
|
||||
[text/text (style/title-props) title]
|
||||
[text/text (style/subtitle-props) (subtitle content)]
|
||||
[rn/view {:style (styles/secondary-container)}
|
||||
[text/text (styles/title-props) title]
|
||||
[text/text (styles/subtitle-props) (subtitle content)]
|
||||
[bottom-container (merge {:color-50 color-50 :color-60 color-60} content)]]
|
||||
(when avatar
|
||||
[rn/view {:style (style/avatar-container)}
|
||||
[rn/view {:style (styles/avatar-container)}
|
||||
[avatar avatar-params type customization-color]])
|
||||
[button/button (style/close-button-props on-close) :i/close]]]))
|
||||
[button/button (styles/close-button-props on-close) :i/close]]]))
|
||||
|
||||
;; browser Card
|
||||
(defn browser-card [_]
|
||||
@@ -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-im2.setup.config :as config]
|
||||
[status-im.utils.config :as config]
|
||||
[status-im.contact.core :as models.contact]
|
||||
[status-im.communities.core :as models.communities]
|
||||
[status-im.pairing.core :as models.pairing]
|
||||
|
||||
@@ -2,7 +2,22 @@
|
||||
(: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]))
|
||||
[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})
|
||||
|
||||
(def action-button-container-old
|
||||
{:position :absolute
|
||||
@@ -29,6 +44,19 @@
|
||||
"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
|
||||
|
||||
@@ -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 [:navigate-back])}
|
||||
:on-press #(re-frame/dispatch [:bottom-sheet/hide :navigate-back])}
|
||||
on-press
|
||||
(assoc :on-press on-press)
|
||||
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
(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})
|
||||
@@ -1,59 +0,0 @@
|
||||
(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))])))
|
||||
@@ -1,11 +0,0 @@
|
||||
(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})
|
||||
@@ -1,109 +0,0 @@
|
||||
(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}])})})))])))))
|
||||
@@ -1,9 +0,0 @@
|
||||
(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})
|
||||
@@ -1,28 +0,0 @@
|
||||
(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})
|
||||
@@ -1,6 +0,0 @@
|
||||
(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,32 +1,154 @@
|
||||
(ns status-im.ui.screens.activity-center.views
|
||||
(:require [quo.components.safe-area :as safe-area]
|
||||
(:require [quo.components.animated.pressable :as animation]
|
||||
[quo.react :as react]
|
||||
[quo.react-native :as rn]
|
||||
[quo2.core :as quo2]
|
||||
[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.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.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]))
|
||||
[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]])
|
||||
|
||||
(defn filter-selector-read-toggle
|
||||
[]
|
||||
(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)}])}
|
||||
(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)}])}
|
||||
:i/unread]))
|
||||
|
||||
;; TODO(@ilmotta,2022-10-07): The empty state is still under design analysis, so we
|
||||
;; TODO(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
|
||||
@@ -39,24 +161,24 @@
|
||||
:height 120
|
||||
:margin-bottom 20
|
||||
:width 120}}]
|
||||
[quo2/text {:size :paragraph-1
|
||||
[text/text {:size :paragraph-1
|
||||
:style {:padding-bottom 2}
|
||||
:weight :semi-bold}
|
||||
"No notifications"]
|
||||
[quo2/text {:size :paragraph-2}
|
||||
[text/text {:size :paragraph-2}
|
||||
"Your notifications will be here"]])
|
||||
|
||||
(defn tabs
|
||||
[]
|
||||
(let [filter-type (rf/sub [:activity-center/filter-type])]
|
||||
[quo2/scrollable-tabs {:size 32
|
||||
(let [filter-type (<sub [:activity-center/filter-type])]
|
||||
[tabs/scrollable-tabs {:size 32
|
||||
:blur? true
|
||||
:override-theme :dark
|
||||
:style style/tabs
|
||||
:style {:padding-left 20}
|
||||
:fade-end-percentage 0.79
|
||||
:scroll-on-press? true
|
||||
:fade-end? true
|
||||
:on-change #(rf/dispatch [:activity-center.notifications/fetch-first-page {:filter-type %}])
|
||||
:on-change #(>evt [:activity-center.notifications/fetch-first-page {:filter-type %}])
|
||||
:default-active filter-type
|
||||
:data [{:id types/no-type
|
||||
:label (i18n/label :t/all)}
|
||||
@@ -81,16 +203,19 @@
|
||||
[]
|
||||
(let [screen-padding 20]
|
||||
[rn/view
|
||||
[quo2/button {:icon true
|
||||
:type :blur-bg
|
||||
:size 32
|
||||
:override-theme :dark
|
||||
:style style/header-button
|
||||
:on-press #(rf/dispatch [:hide-popover])}
|
||||
[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])}
|
||||
:i/close]
|
||||
[quo2/text {:size :heading-1
|
||||
[text/text {:size :heading-1
|
||||
:weight :semi-bold
|
||||
:style style/header-heading}
|
||||
:style {:padding-horizontal screen-padding
|
||||
:padding-vertical 12
|
||||
:color colors/white}}
|
||||
(i18n/label :t/notifications)]
|
||||
[rn/view {:flex-direction :row
|
||||
:padding-vertical 12}
|
||||
@@ -102,30 +227,22 @@
|
||||
: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 (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)}
|
||||
(let [notifications (<sub [:activity-center/filtered-notifications])
|
||||
window-width (<sub [:dimensions/window-width])
|
||||
{:keys [top bottom]} (safe-area/use-safe-area)]
|
||||
(react/effect! #(>evt [:activity-center.notifications/fetch-first-page]))
|
||||
[rn/view {:style {:flex 1
|
||||
:width window-width
|
||||
:padding-top (if (pos? top) (+ top 12) 12)
|
||||
:padding-bottom bottom}}
|
||||
[header]
|
||||
[rn/flat-list {:data notifications
|
||||
:empty-component [empty-tab]
|
||||
:key-fn :id
|
||||
:on-end-reached #(rf/dispatch [:activity-center.notifications/fetch-next-page])
|
||||
:render-fn render-notification}]]))])
|
||||
[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}]]))])
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
(ns status-im.ui.screens.bottom-sheets.views
|
||||
(:require [quo.core :as quo]
|
||||
(:require [status-im.ui.screens.mobile-network-settings.view :as mobile-network-settings]
|
||||
[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.multiaccounts.recover.views :as recover.views]))
|
||||
[status-im.ui.screens.about-app.views :as about-app]
|
||||
[status-im.ui.screens.multiaccounts.recover.views :as recover.views]
|
||||
[quo.core :as quo]))
|
||||
|
||||
(defn bottom-sheet []
|
||||
(let [{:keys [show? view options]} @(re-frame/subscribe [:bottom-sheet])
|
||||
@@ -28,8 +27,11 @@
|
||||
(= view :add-new)
|
||||
(merge home.sheet/add-new)
|
||||
|
||||
(= view :activity-center.contact-verification/reply)
|
||||
(merge contact-verification.sheet/reply)
|
||||
(= view :add-new-sheet-view)
|
||||
(merge home.sheet/add-new-sheet)
|
||||
|
||||
(= view :start-a-new-chat)
|
||||
(merge home.sheet/start-a-new-chat)
|
||||
|
||||
(= view :keycard.login/more)
|
||||
(merge keycard/more-sheet)
|
||||
@@ -44,4 +46,4 @@
|
||||
(merge key-storage/migrate-account-password))]
|
||||
[quo/bottom-sheet opts
|
||||
(when content
|
||||
[content (when options options)])]))
|
||||
[content (when options options)])]))
|
||||
@@ -1,77 +0,0 @@
|
||||
(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,7 +4,6 @@
|
||||
[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
|
||||
@@ -19,17 +18,16 @@
|
||||
chat-id]
|
||||
connected? [:mailserver/connected?]
|
||||
use-status-nodes? [:mailserver/use-status-nodes?]
|
||||
first-gap? (= gap-ids #{:first-gap})
|
||||
window-height [:dimensions/window-height]]
|
||||
first-gap? (= gap-ids #{:first-gap})]
|
||||
(when (or (not first-gap?) public? community?)
|
||||
[react/view {:style (when-not in-progress? style/gap-container)}
|
||||
[react/view {:style (style/gap-container)}
|
||||
[react/touchable-highlight
|
||||
{:on-press (when (and (not in-progress?) use-status-nodes? connected?)
|
||||
(on-press chat-id gap-ids))
|
||||
:style {:height (if in-progress? window-height 48)}}
|
||||
:style style/touchable}
|
||||
[react/view {:style style/label-container}
|
||||
(if in-progress?
|
||||
[messages-skeleton/messages-skeleton window-height]
|
||||
[react/activity-indicator]
|
||||
[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,29 +1,28 @@
|
||||
(ns status-im.ui.screens.communities.community
|
||||
(: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]
|
||||
(:require [status-im.ui.components.topbar :as topbar]
|
||||
[quo.react-native :as rn]
|
||||
[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]
|
||||
[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]))
|
||||
[quo2.components.community.style :as styles]))
|
||||
|
||||
(def request-cooldown-ms (* 24 60 60 1000))
|
||||
|
||||
@@ -62,8 +61,8 @@
|
||||
(i18n/label :t/open-membership))]]]))
|
||||
|
||||
(defn hide-sheet-and-dispatch [event]
|
||||
(rf/dispatch [:bottom-sheet/hide])
|
||||
(rf/dispatch event))
|
||||
(>evt [:bottom-sheet/hide])
|
||||
(>evt 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))
|
||||
@@ -88,14 +87,14 @@
|
||||
:title (i18n/label :t/invite-people)
|
||||
:icon :main-icons/share
|
||||
:accessibility-label :community-invite-people
|
||||
:on-press #(rf/dispatch [:communities/invite-people-pressed id])}])
|
||||
:on-press #(>evt [::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 #(rf/dispatch [:communities/share-community-pressed id])}])]))
|
||||
:on-press #(>evt [::communities/share-community-pressed id])}])]))
|
||||
|
||||
(defn community-actions [{:keys [id name images color can-manage-users?]}]
|
||||
(let [thumbnail-image (get-in images [:thumbnail :uri])]
|
||||
@@ -154,13 +153,13 @@
|
||||
;; unread indicator
|
||||
(assoc home-item :public? true)
|
||||
{:on-press (fn []
|
||||
(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])}])}])
|
||||
(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])}])}])
|
||||
|
||||
(defn categories-accordion [community-id chats categories data]
|
||||
[:<>
|
||||
@@ -168,8 +167,8 @@
|
||||
^{:key (str "cat" name id)}
|
||||
[:<>
|
||||
[accordion/section
|
||||
{:on-open #(rf/dispatch [::communities/store-category-state community-id id true])
|
||||
:on-close #(rf/dispatch [::communities/store-category-state community-id id false])
|
||||
{:on-open #(>evt [::communities/store-category-state community-id id true])
|
||||
:on-close #(>evt [::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}]
|
||||
@@ -181,7 +180,7 @@
|
||||
[quo/separator]])])
|
||||
|
||||
(defn community-chat-list [community-id categories from-chat]
|
||||
(let [chats (rf/sub [:chats/sorted-categories-by-community-id community-id])]
|
||||
(let [chats (<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
|
||||
@@ -231,25 +230,25 @@
|
||||
:render-fn channel-preview-item}]))
|
||||
|
||||
(defn unknown-community [community-id]
|
||||
(let [fetching (rf/sub [:communities/fetching-community community-id])]
|
||||
(let [fetching (<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 #(rf/dispatch [::link-preview/resolve-community-info community-id]))
|
||||
[quo/button {:on-press (when-not fetching #(>evt [::link-preview/resolve-community-info community-id]))
|
||||
:disabled fetching
|
||||
:color :secondary}
|
||||
:color :secondary}
|
||||
(if fetching
|
||||
[react/small-loading-indicator]
|
||||
[components.react/small-loading-indicator]
|
||||
(i18n/label :t/fetch-community))]]]))
|
||||
|
||||
(defn community []
|
||||
(let [{:keys [community-id from-chat]} (rf/sub [:get-screen-params])]
|
||||
(let [{:keys [community-id from-chat]} (<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} (rf/sub [:communities/community community-id])
|
||||
categories (rf/sub [:communities/sorted-categories community-id])]
|
||||
:as community} (<sub [:communities/community community-id])
|
||||
categories (<sub [:communities/sorted-categories community-id])]
|
||||
(if community
|
||||
[rn/view {:style {:flex 1}}
|
||||
[topbar/topbar
|
||||
@@ -265,24 +264,24 @@
|
||||
(when (or admin joined)
|
||||
[{:icon :main-icons/more
|
||||
:accessibility-label :community-menu-button
|
||||
:on-press #(rf/dispatch [:bottom-sheet/show-sheet
|
||||
{:content (fn []
|
||||
[community-actions community])}])}])}]
|
||||
:on-press #(>evt [: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 #(rf/dispatch [:bottom-sheet/show-sheet
|
||||
{:content (fn []
|
||||
[community-plus-actions community])}])
|
||||
{:on-press #(>evt [: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 #(rf/dispatch [:communities/join id])
|
||||
:center [quo/button {:on-press #(>evt [::communities/join id])
|
||||
:type :secondary}
|
||||
(i18n/label :t/join)]}]
|
||||
can-request-access?
|
||||
@@ -293,13 +292,13 @@
|
||||
:left [quo/text {:color :secondary} (i18n/label :t/membership-request-pending)]}]
|
||||
[toolbar/toolbar
|
||||
{:show-border? true
|
||||
:center [quo/button {:on-press #(rf/dispatch [::communities/request-to-join id])
|
||||
:center [quo/button {:on-press #(>evt [::communities/request-to-join id])
|
||||
:type :secondary}
|
||||
(i18n/label :t/request-access)]}])
|
||||
:else
|
||||
[toolbar/toolbar
|
||||
{:show-border? true
|
||||
:center [quo/button {:on-press #(rf/dispatch [:communities/join id])
|
||||
:center [quo/button {:on-press #(>evt [::communities/join id])
|
||||
:type :secondary}
|
||||
(i18n/label :t/follow)]}]))]
|
||||
[unknown-community community-id])))))
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
(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}])}])}]))
|
||||
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
(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,7 +21,6 @@
|
||||
[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,7 +73,6 @@
|
||||
: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
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
(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,3 +159,15 @@
|
||||
: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]]]])
|
||||
|
||||
@@ -1,23 +1,26 @@
|
||||
(ns status-im.ui.screens.home.sheet.views
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[status-im.ui.components.react :as react]
|
||||
[status-im.qr-scanner.core :as qr-scanner]
|
||||
[quo.core :as quo]
|
||||
(:require [quo.core :as quo]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[status-im.utils.re-frame :as rf]
|
||||
[status-im.i18n.i18n :as i18n]
|
||||
[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]
|
||||
[status-im.utils.config :as config]
|
||||
[status-im.ui.components.invite.views :as invite]))
|
||||
[quo2.components.list-items.list-item :as quo2.list-item]))
|
||||
|
||||
(defn hide-sheet-and-dispatch [event]
|
||||
(re-frame/dispatch [:bottom-sheet/hide])
|
||||
(re-frame/dispatch event))
|
||||
(rf/dispatch [:bottom-sheet/hide])
|
||||
(rf/dispatch event))
|
||||
|
||||
(defn add-new-view []
|
||||
[react/view
|
||||
[react/view {:style {:flex-direction :row
|
||||
:padding-left 16
|
||||
:padding-right 8
|
||||
:justify-content :space-between
|
||||
:align-items :center}}
|
||||
[rn/view
|
||||
[rn/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)]
|
||||
@@ -47,7 +50,7 @@
|
||||
:accessibility-label :join-public-chat-button
|
||||
:icon :main-icons/public-chat
|
||||
:on-press #(hide-sheet-and-dispatch [:open-modal :new-public-chat])}]
|
||||
(when @(re-frame/subscribe [:communities/enabled?])
|
||||
(when (rf/sub [:communities/enabled?])
|
||||
[quo/list-item
|
||||
{:theme :accent
|
||||
:title (i18n/label :t/communities-alpha)
|
||||
@@ -57,5 +60,40 @@
|
||||
[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])})
|
||||
|
||||
@@ -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-im2.setup.config :as config]
|
||||
[status-im.utils.config :as config]
|
||||
[quo2.components.markdown.text :as quo2.text]
|
||||
[status-im.qr-scanner.core :as qr-scanner]
|
||||
[status-im.ui.components.chat-icon.screen :as chat-icon.screen]
|
||||
@@ -234,7 +234,7 @@
|
||||
|
||||
(views/defview plus-button []
|
||||
(views/letsubs [logging-in? [:multiaccounts/login]]
|
||||
[components.plus-button/plus-button-old
|
||||
[components.plus-button/plus-button
|
||||
{:on-press (when-not logging-in?
|
||||
#(re-frame/dispatch [:bottom-sheet/show-sheet :add-new {}]))
|
||||
:loading logging-in?
|
||||
@@ -259,7 +259,12 @@
|
||||
:on-press #(do
|
||||
(re-frame/dispatch [:mark-all-activity-center-notifications-as-read])
|
||||
(if config/new-activity-center-enabled?
|
||||
(re-frame/dispatch [:activity-center/open])
|
||||
(re-frame/dispatch [:show-popover {:view :activity-center
|
||||
:style {:margin 0}
|
||||
:disable-touchable-overlay? true
|
||||
:blur-view? true
|
||||
:blur-view-props {:blur-amount 20
|
||||
:blur-type :dark}}])
|
||||
(re-frame/dispatch [:navigate-to :notifications-center])))}
|
||||
[icons/icon :main-icons/notification2 {:color (quo2.colors/theme-colors quo2.colors/neutral-100 quo2.colors/white)}]]
|
||||
(when (pos? notif-count)
|
||||
|
||||
@@ -234,8 +234,7 @@
|
||||
:height 110}}])]]]
|
||||
[react/view {:margin-bottom 50}
|
||||
[quo/button
|
||||
{:test-id :generate-new-key
|
||||
:on-press #(re-frame/dispatch [:keycard.recovery.no-key.ui/generate-key-pressed])}
|
||||
{: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 {:test-ID :terms-of-service :on-press #(swap! tos-accepted not)}
|
||||
[rn/touchable-opacity {: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,8 +167,7 @@
|
||||
" "
|
||||
(i18n/label :t/terms-of-service)]]]]
|
||||
[react/view {:style {:margin-bottom 24}}
|
||||
[quo/button {:test-ID :get-started
|
||||
:disabled (not @tos-accepted)
|
||||
[quo/button {:disabled (not @tos-accepted)
|
||||
:on-press #(do (re-frame/dispatch [:init-root :onboarding])
|
||||
;; clear atom state for next use
|
||||
(reset! tos-accepted false)
|
||||
|
||||
@@ -83,8 +83,7 @@
|
||||
[react/view {:style {:align-items :center}}
|
||||
[react/view {:style (assoc styles/bottom-button :margin-bottom 16)}
|
||||
[quo/button
|
||||
{:test-ID :generate-keys
|
||||
;:disabled existing-account?
|
||||
{;:disabled existing-account?
|
||||
:on-press #(re-frame/dispatch [:generate-and-derive-addresses])
|
||||
:accessibility-label :onboarding-next-button}
|
||||
(i18n/label :t/generate-a-key)]]
|
||||
|
||||
@@ -44,8 +44,7 @@
|
||||
(i18n/label :intro-wizard-title-alt4)]
|
||||
[rn/view
|
||||
[rn/view {:style {:padding 16}}
|
||||
[quo/text-input {:test-ID :password-placeholder
|
||||
:secure-text-entry true
|
||||
[quo/text-input {:secure-text-entry true
|
||||
:auto-capitalize :none
|
||||
:auto-focus true
|
||||
:show-cancel false
|
||||
@@ -57,8 +56,7 @@
|
||||
(some-> ^js @confirm-ref .focus))}]]
|
||||
[rn/view {:style {:padding 16
|
||||
:opacity (if-not valid-password 0.33 1)}}
|
||||
[quo/text-input {:test-ID :confirm-password-placeholder
|
||||
:secure-text-entry true
|
||||
[quo/text-input {: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-contact-details)]
|
||||
(i18n/label :t/block-user-confirmation)]
|
||||
[react/view {:align-items :center :margin-top 16}
|
||||
[quo/button {:theme :negative
|
||||
:disabled @in-progress?
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
[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]
|
||||
@@ -115,7 +116,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.group-details.view :as group-details]))
|
||||
[status-im.ui2.screens.chat.components.new-chat :as new-chat-aio]))
|
||||
|
||||
(defn right-button-options [id icon]
|
||||
{:id id
|
||||
@@ -212,9 +213,10 @@
|
||||
:component pin-messages/pinned-messages}
|
||||
|
||||
{:name :group-chat-profile
|
||||
:insets {:top false}
|
||||
;;TODO animated-header
|
||||
:options {:topBar {:visible false}}
|
||||
:component group-details/group-details}
|
||||
:component profile.group-chat/group-chat-profile}
|
||||
{:name :group-chat-invite
|
||||
;;TODO parameter in the event
|
||||
:options {:topBar {:visible false}}
|
||||
@@ -306,7 +308,9 @@
|
||||
{: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
|
||||
@@ -592,11 +596,17 @@
|
||||
:options {:topBar {:visible false}}
|
||||
:component contact/nickname}
|
||||
|
||||
;[Group chat] Edit group chat name
|
||||
{:name :edit-group-chat-name
|
||||
{: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
|
||||
:insets {:bottom true}
|
||||
:options {:topBar {:title {:text (i18n/label :t/edit-group)}}}
|
||||
:component group-chat/edit-group-chat-name}
|
||||
:options {:topBar {:title {:text (i18n/label :t/new-public-group-chat)}}}
|
||||
:component new-public-chat/new-public-chat}
|
||||
|
||||
;[Group chat] Add participants
|
||||
{:name :add-participants-toggle-list
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
(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))))}])))
|
||||
@@ -0,0 +1,40 @@
|
||||
(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)]]))
|
||||
@@ -3,24 +3,23 @@
|
||||
[status-im.chat.models :as chat.models]
|
||||
[status-im.chat.models.pin-message :as models.pin-message]
|
||||
[status-im.i18n.i18n :as i18n]
|
||||
[utils.re-frame :as rf]
|
||||
[status-im.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? add-divider?]}]
|
||||
(defn- entry [{:keys [icon label on-press danger? sub-label chevron?]}]
|
||||
{: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)
|
||||
:add-divider? add-divider?})
|
||||
{:icon icon
|
||||
:label label
|
||||
:on-press on-press
|
||||
:danger? danger?
|
||||
:sub-label sub-label
|
||||
:right-icon (when chevron? :i/chevron-right)})
|
||||
|
||||
(defn hide-sheet-and-dispatch [event]
|
||||
(rf/dispatch [:bottom-sheet/hide])
|
||||
@@ -72,7 +71,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-contact-details)
|
||||
:description (i18n/label :t/block-user-confirmation)
|
||||
:context item
|
||||
:button-text (i18n/label :t/block-user)
|
||||
:on-press #(hide-sheet-and-dispatch [:contact.ui/block-contact-confirmed public-key])}))}]))
|
||||
@@ -92,22 +91,20 @@
|
||||
: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
|
||||
:add-divider? true}))
|
||||
(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}))
|
||||
|
||||
(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
|
||||
:add-divider? true}))
|
||||
(entry {:icon :i/delete
|
||||
:label (i18n/label :t/clear-history)
|
||||
:on-press #(clear-history-action chat-id)
|
||||
:danger? true
|
||||
:sub-label nil
|
||||
:chevron? false}))
|
||||
|
||||
(defn delete-chat-entry [item]
|
||||
(entry {:icon :i/delete
|
||||
@@ -206,13 +203,12 @@
|
||||
: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
|
||||
:add-divider? true}))
|
||||
(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}))
|
||||
|
||||
(defn block-user-entry [item]
|
||||
(entry {:icon :i/block
|
||||
@@ -262,8 +258,8 @@
|
||||
:sub-label nil
|
||||
:chevron? false}))
|
||||
|
||||
(defn destructive-actions [{:keys [group-chat] :as item}]
|
||||
[(clear-history-entry item)
|
||||
(defn destructive-actions [{:keys [chat-id group-chat] :as item}]
|
||||
[(clear-history-entry chat-id)
|
||||
(if group-chat
|
||||
(leave-group-entry item)
|
||||
(delete-chat-entry item))])
|
||||
@@ -311,13 +307,12 @@
|
||||
(notification-actions item inside-chat?)
|
||||
(destructive-actions item)]])
|
||||
|
||||
(defn contact-actions [{:keys [public-key added] :as contact}]
|
||||
(defn contact-actions [{:keys [public-key] :as contact}]
|
||||
[drawer/action-drawer [[(view-profile-entry public-key)
|
||||
(when added
|
||||
(remove-from-contacts-entry contact)
|
||||
(rename-entry)
|
||||
(show-qr-entry)
|
||||
(share-profile-entry))]
|
||||
(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]
|
||||
[utils.re-frame :as rf]
|
||||
[status-im.utils.re-frame :as rf]
|
||||
[status-im.ui2.screens.chat.actions :as actions]))
|
||||
|
||||
(defn open-chat [chat-id]
|
||||
@@ -22,13 +22,11 @@
|
||||
(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]))
|
||||
current-pk (rf/sub [:multiaccount/public-key])]
|
||||
photo-path (when (seq images) (rf/sub [:chats/photo-path public-key]))]
|
||||
[rn/touchable-opacity (merge {:style (style/container)
|
||||
:on-press #(open-chat public-key)
|
||||
:on-long-press #(when-not (= current-pk public-key)
|
||||
(rf/dispatch [:bottom-sheet/show-sheet
|
||||
{:content (fn [] [actions/actions item])}]))})
|
||||
:on-long-press #(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
|
||||
@@ -46,14 +44,11 @@
|
||||
(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)]]
|
||||
(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)}]])]))
|
||||
[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)}]]]))
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
(ns status-im.ui2.screens.chat.components.message-home-item.view
|
||||
(:require [clojure.string :as string]
|
||||
[utils.re-frame :as rf]
|
||||
[status-im.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
|
||||
|
||||
@@ -0,0 +1,283 @@
|
||||
(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)]}]]))
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user