Compare commits

..
Author SHA1 Message Date
Omar Basem efa8bec582 updates 2023-01-19 13:25:45 +04:00
Omar Basem b6705db744 updates
https://github.com/status-im/status-go/compare/d60c1d00...d60c1d00
2023-01-19 11:26:26 +04:00
173 changed files with 2832 additions and 4256 deletions
+1
View File
@@ -351,6 +351,7 @@ component-test: export COMPONENT_TEST := true
component-test: export BABEL_ENV := test
component-test: ##@test Run tests once in NodeJS
# Here we create the gyp bindings for nodejs
yarn install
yarn shadow-cljs compile component-test && \
jest --config=test/jest/jest.config.js
Binary file not shown.

Before

Width:  |  Height:  |  Size: 370 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 534 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 472 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 696 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 370 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 455 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 443 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 601 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 822 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 KiB

+1 -1
View File
@@ -38,7 +38,7 @@
{:mobile
{:target :react-native
:output-dir "app"
:init-fn status-im2.core/init
:init-fn status-im2.setup.core/init
;; When false, the Shadow-CLJS watcher won't automatically refresh
;; the target files (a.k.a hot reload). When false, you can manually
;; reload by calling `shadow.cljs.devtools.api/watch-compile-all!`.
+12 -48
View File
@@ -2,75 +2,39 @@ import { useDerivedValue, interpolate } from 'react-native-reanimated';
// Generic Worklets
// 1. kebab-case styles are not working for worklets
// so we have to convert kebab case styles into camel case styles
// 2. remove keys with nil value, else useAnimatedStyle will throw an error
// https://github.com/status-im/status-mobile/issues/14756
export function applyAnimationsToStyle(animations, style) {
return function() {
'worklet'
var animatedStyle = {}
// Normal Style
for (var key in style) {
if (key == "transform") {
var transforms = style[key];
var filteredTransforms = []
for (var transform of transforms) {
var transformKey = Object.keys(transform)[0];
var transformValue = transform[transformKey];
if(transformValue !== null) {
filteredTransforms.push(
{[transformKey.replace(/-./g, x=>x[1].toUpperCase())]: transformValue}
);
}
}
animatedStyle[key] = filteredTransforms;
} else {
var value = style[key];
if (value !== null) {
animatedStyle[key.replace(/-./g, x=>x[1].toUpperCase())] = value;
}
}
}
// Animations
for (var key in animations) {
if (key == "transform") {
var transforms = animations[key];
var animatedTransforms = []
for (var transform of transforms) {
var transformKey = Object.keys(transform)[0];
var transformValue = transform[transformKey].value;
if (transformValue !== null) {
animatedTransforms.push(
{[transformKey.replace(/-./g, x=>x[1].toUpperCase())]: transformValue}
);
}
animatedTransforms.push({
[transformKey]: transform[transformKey].value
})
}
animatedStyle[key] = animatedTransforms;
} else {
var animatedValue = animations[key].value;
if (animatedValue !== null) {
animatedStyle[key.replace(/-./g, x=>x[1].toUpperCase())] = animatedValue;
}
animatedStyle[key] = animations[key].value;
}
}
return animatedStyle;
return Object.assign(animatedStyle, style);
};
};
export function interpolateValue(sharedValue, inputRange, outputRange, extrapolation) {
export function interpolateValue(sharedValue, inputRange, outputRange) {
return useDerivedValue(
function () {
'worklet'
return interpolate(sharedValue.value, inputRange, outputRange, extrapolation);
return interpolate(sharedValue.value, inputRange, outputRange);
}
);
}
+4 -3
View File
@@ -1,5 +1,6 @@
(ns quo2.components.code.snippet
(:require [cljs-bean.core :as bean]
(:require ["react-native" :as react-native]
[cljs-bean.core :as bean]
[clojure.string :as string]
[oops.core :as oops]
[quo2.components.buttons.button :as button]
@@ -167,8 +168,8 @@
:on-copy-press #(when on-copy-press
(on-copy-press children))})
;; Default props to adapt Highlighter for react-native.
;;:CodeTag react-native/View
;;:PreTag react-native/View
:CodeTag react-native/View
:PreTag react-native/View
:show-line-numbers false
:style #js {}
:custom-style #js {:backgroundColor nil}}
@@ -1,5 +1,6 @@
(ns quo2.components.community.token-gating
(:require [quo2.components.avatars.channel-avatar :as channel-avatar]
(:require [quo.react-native :as rn]
[quo2.components.avatars.channel-avatar :as channel-avatar]
[quo2.components.buttons.button :as button]
[quo2.components.icon :as icon]
[quo2.components.info.information-box :as information-box]
@@ -7,8 +8,7 @@
[quo2.components.tags.token-tag :as token-tag]
[quo2.foundations.colors :as colors]
[utils.i18n :as i18n]
[react-native.fast-image :as fast-image]
[react-native.core :as rn]))
[react-native.fast-image :as fast-image]))
(def ^:private token-tag-horizontal-spacing 7)
(def token-tag-vertical-spacing 5)
@@ -1,6 +1,6 @@
(ns quo2.components.drawers.action-drawers.component-spec
(ns quo2.components.drawers.--tests--.action-drawers-component-spec
(:require ["@testing-library/react-native" :as rtl]
[quo2.components.drawers.action-drawers.view :as action-drawer]
[quo2.components.drawers.action-drawers :as action-drawer]
[reagent.core :as reagent]))
(defn render-action-drawer
@@ -50,4 +50,4 @@
:add-divider? true
:accessibility-label :first-element}]])
(-> (js/expect (rtl/screen.getAllByLabelText "divider"))
(.toBeTruthy))))
(.toBeTruthy))))
@@ -1,9 +1,8 @@
(ns quo2.components.drawers.action-drawers.view
(ns quo2.components.drawers.action-drawers
(:require [quo2.components.icon :as icon]
[quo2.components.markdown.text :as text]
[quo2.foundations.colors :as colors]
[react-native.core :as rn]
[quo2.components.drawers.action-drawers.style :as style]))
[react-native.core :as rn]))
(defn- get-icon-color
[danger?]
@@ -13,7 +12,14 @@
(def divider
[rn/view
{:style style/divider
{:style {:border-top-width 1
:border-top-color (colors/theme-colors
colors/neutral-10
colors/neutral-90)
:margin-top 8
:margin-bottom 7
:align-items :center
:flex-direction :row}
:accessible true
:accessibility-label :divider}])
@@ -32,20 +38,32 @@
(when add-divider? divider)
[rn/touchable-highlight
{:accessibility-label accessibility-label
:style (style/container sub-label)
:style {:border-radius 12
:height (if sub-label 58 50)
:margin-horizontal 8}
:underlay-color (colors/theme-colors colors/neutral-5 colors/neutral-90)
:on-press on-press}
[rn/view
{:style (style/row-container sub-label)}
{:style
{:height (if sub-label 58 50)
:margin-horizontal 12
:flex-direction :row}}
[rn/view
{:accessibility-label :left-icon-for-action
:accessible true
:style style/left-icon}
: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 style/text-container}
{:style
{:flex 1
:justify-content :center}}
[text/text
{:size :paragraph-1
:weight :medium
@@ -61,7 +79,11 @@
sub-label])]
(when right-icon
[rn/view
{:style style/right-icon
{:style
{:height 20
:margin-top :auto
:margin-bottom :auto
:width 20}
:accessible true
:accessibility-label :right-icon-for-action}
[icon/icon right-icon
@@ -1,41 +0,0 @@
(ns quo2.components.drawers.action-drawers.style
(:require [quo2.foundations.colors :as colors]))
(def divider
{:border-top-width 1
:border-top-color (colors/theme-colors
colors/neutral-10
colors/neutral-90)
:margin-top 8
:margin-bottom 7
:align-items :center
:flex-direction :row})
(defn container
[sub-label]
{:border-radius 12
:height (if sub-label 58 50)
:margin-horizontal 8})
(defn row-container
[sub-label]
{:height (if sub-label 58 50)
:margin-horizontal 12
:flex-direction :row})
(def left-icon
{:height 20
:margin-top :auto
:margin-bottom :auto
:margin-right 12
:width 20})
(def text-container
{:flex 1
:justify-content :center})
(def right-icon
{:height 20
:margin-top :auto
:margin-bottom :auto
:width 20})
@@ -1,10 +1,10 @@
(ns quo2.components.drawers.permission-context.component-spec
(ns quo2.components.drawers.permission-context.--tests--.permission-context-component-spec
(:require [quo2.components.drawers.permission-context.view :as permission-context]
[react-native.core :as rn]
[test-helpers.component :as h]))
(h/describe "permission context"
(h/test "it tests the default render"
(h/test
(h/render [permission-context/view
[rn/text
{:accessibility-label :accessibility-id}
@@ -3,13 +3,9 @@
(def radius 20)
(def container
{:flex-direction :row
:background-color (colors/theme-colors colors/white colors/neutral-90)
:height 82
:padding-top 16
{:padding-top 16
:padding-bottom 48
:justify-content :center
:padding-right :auto
:padding-horizontal 20
:shadow-offset {:width 0
:height 2}
:shadow-radius radius
@@ -1,12 +1,8 @@
(ns quo2.components.drawers.permission-context.view
(:require [react-native.core :as rn]
[quo2.foundations.colors :as colors]
[quo2.components.drawers.permission-context.style :as style]))
(defn view
[children on-press]
[rn/touchable-highlight
{:on-press on-press
:underlay-color (colors/theme-colors :transparent colors/neutral-95-opa-70)
:style style/container}
[children]
[rn/view {:style style/container}
children])
@@ -3,7 +3,8 @@
[quo2.components.icon :as icons]
[quo2.components.markdown.text :as text]
[quo2.components.messages.author.style :as style]
[react-native.core :as rn]))
[react-native.core :as rn]
[status-im.utils.utils :as utils]))
(def middle-dot "·")
@@ -34,7 +35,7 @@
profile-name])])))
(defn author
[{:keys [profile-name nickname short-chat-key ens-name time-str contact? verified? untrustworthy?]}]
[{:keys [profile-name nickname chat-key ens-name time-str contact? verified? untrustworthy?]}]
[:f>
(fn []
(let [ens? (-> ens-name string/blank? not)
@@ -84,7 +85,7 @@
{:monospace true
:size :paragraph-2
:style style/chat-key-text}
short-chat-key])
(utils/get-shortened-address chat-key)])
(when-not ens?
[text/text
{:monospace true
@@ -1,159 +0,0 @@
(ns quo2.components.record-audio.record-audio.--tests--.record-audio-component-spec
(:require [quo2.components.record-audio.record-audio.view :as record-audio]
[status-im.audio.core :as audio]
[test-helpers.component :as h]))
(h/describe "record audio component"
(h/before-each
(fn []
(h/use-fake-timers)))
(h/after-each
(fn []
(h/clear-all-timers)
(h/use-real-timers)))
(h/test "renders record-audio"
(h/render [record-audio/record-audio])
(-> (h/expect (h/get-by-test-id "record-audio"))
(.toBeTruthy)))
(h/test "record-audio on-start-recording works"
(let [event (js/jest.fn)]
(h/render [record-audio/record-audio {:on-start-recording event}])
(h/fire-event
:on-start-should-set-responder
(h/get-by-test-id "record-audio")
{:nativeEvent {:locationX 70
:locationY 70}})
(-> (h/expect event)
(.toHaveBeenCalledTimes 1))))
(h/test "record-audio on-reviewing-audio works"
(let [event (js/jest.fn)]
(h/render [record-audio/record-audio {:on-reviewing-audio event}])
(with-redefs [audio/start-recording (fn [_ on-start _]
(on-start))]
(h/fire-event
:on-start-should-set-responder
(h/get-by-test-id "record-audio")
{:nativeEvent {:locationX 70
:locationY 70}})
(h/advance-timers-by-time 500)
(h/fire-event
:on-responder-release
(h/get-by-test-id "record-audio")
{:nativeEvent {:locationX 70
:locationY 70}})
(-> (h/expect event)
(.toHaveBeenCalledTimes 1)))))
(h/test "record-audio on-send works after reviewing audio"
(let [event (js/jest.fn)]
(h/render [record-audio/record-audio {:on-send event}])
(with-redefs [audio/start-recording (fn [_ on-start _]
(on-start))
audio/get-recorder-file-path (fn [] "audio-file-path")]
(h/fire-event
:on-start-should-set-responder
(h/get-by-test-id "record-audio")
{:nativeEvent {:locationX 70
:locationY 70}})
(h/advance-timers-by-time 500)
(h/fire-event
:on-responder-release
(h/get-by-test-id "record-audio")
{:nativeEvent {:locationX 70
:locationY 70}})
(h/fire-event
:on-responder-release
(h/get-by-test-id "record-audio")
{:nativeEvent {:locationX 80
:locationY 80}})
(-> (js/expect event)
(.toHaveBeenCalledTimes 1))
(-> (js/expect event)
(.toHaveBeenCalledWith "audio-file-path")))))
(h/test "record-audio on-send works after sliding to the send button"
(let [event (js/jest.fn)]
(h/render [record-audio/record-audio {:on-send event}])
(with-redefs [audio/start-recording (fn [_ on-start _]
(on-start))
audio/stop-recording (fn [_ on-stop _]
(on-stop))
audio/get-recorder-file-path (fn [] "audio-file-path")]
(h/fire-event
:on-start-should-set-responder
(h/get-by-test-id "record-audio")
{:nativeEvent {:locationX 70
:locationY 70}})
(h/advance-timers-by-time 500)
(h/fire-event
:on-responder-move
(h/get-by-test-id "record-audio")
{:nativeEvent {:locationX 80
:locationY -30
:pageX 80
:pageY -30}})
(h/fire-event
:on-responder-release
(h/get-by-test-id "record-audio")
{:nativeEvent {:locationX 40
:locationY 80}})
(-> (js/expect event)
(.toHaveBeenCalledTimes 1))
(-> (js/expect event)
(.toHaveBeenCalledWith "audio-file-path")))))
(h/test "record-audio on-cancel works after reviewing audio"
(let [event (js/jest.fn)]
(h/render [record-audio/record-audio {:on-cancel event}])
(with-redefs [audio/start-recording (fn [_ on-start _]
(on-start))]
(h/fire-event
:on-start-should-set-responder
(h/get-by-test-id "record-audio")
{:nativeEvent {:locationX 70
:locationY 70}})
(h/advance-timers-by-time 500)
(h/fire-event
:on-responder-release
(h/get-by-test-id "record-audio")
{:nativeEvent {:locationX 70
:locationY 70}})
(h/fire-event
:on-responder-release
(h/get-by-test-id "record-audio")
{:nativeEvent {:locationX 40
:locationY 80}})
(-> (js/expect event)
(.toHaveBeenCalledTimes 1)))))
(h/test "cord-audio on-cancel works after sliding to the cancel button"
(let [event (js/jest.fn)]
(h/render [record-audio/record-audio {:on-cancel event}])
(with-redefs [audio/start-recording (fn [_ on-start _]
(on-start))
audio/stop-recording (fn [_ on-stop _]
(on-stop))]
(h/fire-event
:on-start-should-set-responder
(h/get-by-test-id "record-audio")
{:nativeEvent {:locationX 70
:locationY 70}})
(h/advance-timers-by-time 500)
(h/fire-event
:on-responder-move
(h/get-by-test-id "record-audio")
{:nativeEvent {:locationX -30
:locationY 80
:pageX -30
:pageY 80}})
(h/fire-event
:on-responder-release
(h/get-by-test-id "record-audio")
{:nativeEvent {:locationX -10
:locationY 70}})
(-> (js/expect event)
(.toHaveBeenCalledTimes 1))))))
@@ -1,88 +0,0 @@
(ns quo2.components.record-audio.record-audio.buttons.delete-button
(:require [quo2.components.icon :as icons]
[quo2.components.record-audio.record-audio.style :as style]
[quo2.foundations.colors :as colors]
[react-native.reanimated :as reanimated]
[react-native.core :refer [use-effect]]
[quo2.components.record-audio.record-audio.helpers :refer
[animate-linear-with-delay
animate-easing-with-delay
animate-linear
set-value]]))
(defn delete-button
[recording? ready-to-delete? reviewing-audio?]
[:f>
(fn []
(let [opacity (reanimated/use-shared-value 0)
translate-x (reanimated/use-shared-value 20)
scale (reanimated/use-shared-value 1)
connector-opacity (reanimated/use-shared-value 0)
connector-width (reanimated/use-shared-value 24)
connector-height (reanimated/use-shared-value 12)
border-radius-first-half (reanimated/use-shared-value 8)
border-radius-second-half (reanimated/use-shared-value 8)
start-x-animation (fn []
(animate-linear-with-delay translate-x 12 50 133.33)
(animate-easing-with-delay connector-opacity 1 0 93.33)
(animate-easing-with-delay connector-width 56 83.33 80)
(animate-easing-with-delay connector-height 56 83.33 80)
(animate-easing-with-delay border-radius-first-half 28 83.33 80)
(animate-easing-with-delay border-radius-second-half 28 83.33 80))
reset-x-animation (fn []
(animate-linear translate-x 0 100)
(set-value connector-opacity 0)
(set-value connector-width 24)
(set-value connector-height 12)
(set-value border-radius-first-half 8)
(set-value border-radius-second-half 16))
fade-in-animation (fn []
(animate-linear translate-x 0 200)
(animate-linear opacity 1 200))
fade-out-animation (fn []
(animate-linear
translate-x
(if @reviewing-audio? 35 20)
200)
(if @reviewing-audio?
(animate-linear scale 0.75 200)
(animate-linear opacity 0 200))
(set-value connector-opacity 0)
(set-value connector-width 24)
(set-value connector-height 12)
(set-value border-radius-first-half 8)
(set-value border-radius-second-half 16))
fade-out-reset-animation (fn []
(animate-linear opacity 0 200)
(animate-linear-with-delay translate-x 20 0 200)
(animate-linear-with-delay scale 1 0 200))]
(use-effect (fn []
(if @recording?
(fade-in-animation)
(fade-out-animation)))
[@recording?])
(use-effect (fn []
(when-not @reviewing-audio?
(fade-out-reset-animation)))
[@reviewing-audio?])
(use-effect (fn []
(cond
@ready-to-delete?
(start-x-animation)
@recording?
(reset-x-animation)))
[@ready-to-delete?])
[:<>
[reanimated/view {:style (style/delete-button-container opacity)}
[reanimated/view
{:style (style/delete-button-connector connector-opacity
connector-width
connector-height
border-radius-first-half
border-radius-second-half)}]]
[reanimated/view
{:style (style/delete-button scale translate-x opacity)
:pointer-events :none}
[icons/icon :i/delete
{:color colors/white
:size 20}]]]))])
@@ -1,85 +0,0 @@
(ns quo2.components.record-audio.record-audio.buttons.lock-button
(:require [quo2.components.icon :as icons]
[quo2.components.record-audio.record-audio.style :as style]
[quo2.foundations.colors :as colors]
[react-native.reanimated :as reanimated]
[react-native.core :refer [use-effect]]
[quo2.components.record-audio.record-audio.helpers :refer
[animate-linear-with-delay
animate-easing-with-delay
animate-linear
set-value]]))
(defn lock-button
[recording? ready-to-lock? locked?]
[:f>
(fn []
(let [translate-x-y (reanimated/use-shared-value 20)
opacity (reanimated/use-shared-value 0)
connector-opacity (reanimated/use-shared-value 0)
width (reanimated/use-shared-value 24)
height (reanimated/use-shared-value 12)
border-radius-first-half (reanimated/use-shared-value 8)
border-radius-second-half (reanimated/use-shared-value 8)
start-x-y-animation (fn []
(animate-linear-with-delay translate-x-y 8 50 116.66)
(animate-easing-with-delay connector-opacity 1 0 80)
(animate-easing-with-delay width 56 83.33 63.33)
(animate-easing-with-delay height 56 83.33 63.33)
(animate-easing-with-delay border-radius-first-half
28
83.33
63.33)
(animate-easing-with-delay border-radius-second-half
28
83.33
63.33))
reset-x-y-animation (fn []
(animate-linear translate-x-y 0 100)
(set-value connector-opacity 0)
(set-value width 24)
(set-value height 12)
(set-value border-radius-first-half 8)
(set-value border-radius-second-half 16))
fade-in-animation (fn []
(animate-linear translate-x-y 0 220)
(animate-linear opacity 1 220))
fade-out-animation (fn []
(animate-linear translate-x-y 20 200)
(animate-linear opacity 0 200)
(set-value connector-opacity 0)
(set-value width 24)
(set-value height 12)
(set-value border-radius-first-half 8)
(set-value border-radius-second-half 16))]
(use-effect (fn []
(if @recording?
(fade-in-animation)
(fade-out-animation)))
[@recording?])
(use-effect (fn []
(cond
@ready-to-lock?
(start-x-y-animation)
(and @recording? (not @locked?))
(reset-x-y-animation)))
[@ready-to-lock?])
(use-effect (fn []
(if @locked?
(fade-out-animation)
(reset-x-y-animation)))
[@locked?])
[:<>
[reanimated/view {:style (style/lock-button-container opacity)}
[reanimated/view
{:style (style/lock-button-connector connector-opacity
width
height
border-radius-first-half
border-radius-second-half)}]]
[reanimated/view
{:style (style/lock-button translate-x-y opacity)
:pointer-events :none}
[icons/icon (if @ready-to-lock? :i/locked :i/unlocked)
{:color (colors/theme-colors colors/black colors/white)
:size 20}]]]))])
@@ -1,28 +0,0 @@
(ns quo2.components.record-audio.record-audio.buttons.record-button
(:require [quo2.components.icon :as icons]
[quo2.components.record-audio.record-audio.style :as style]
[quo2.foundations.colors :as colors]
[react-native.core :as rn :refer [use-effect]]
[react-native.reanimated :as reanimated]
[quo2.components.buttons.button :as button]
[quo2.components.record-audio.record-audio.helpers :refer [set-value]]))
(defn record-button
[recording? reviewing-audio?]
[:f>
(fn []
(let [opacity (reanimated/use-shared-value 1)
show-animation #(set-value opacity 1)
hide-animation #(set-value opacity 0)]
(use-effect (fn []
(if (or @recording? @reviewing-audio?)
(hide-animation)
(show-animation)))
[@recording? @reviewing-audio?])
[reanimated/view {:style (style/record-button-container opacity)}
[button/button
{:type :outline
:size 32
:width 32
:accessibility-label :mic-button}
[icons/icon :i/audio {:color (colors/theme-colors colors/neutral-100 colors/white)}]]]))])
@@ -1,212 +0,0 @@
(ns quo2.components.record-audio.record-audio.buttons.record-button-big
(:require [quo.react :refer [memo]]
[quo2.components.icon :as icons]
[quo2.components.record-audio.record-audio.style :as style]
[quo2.foundations.colors :as colors]
[react-native.core :as rn :refer [use-effect]]
[react-native.reanimated :as reanimated]
[status-im.audio.core :as audio]
[taoensso.timbre :as log]
[cljs-bean.core :as bean]
[reagent.core :as reagent]
[quo2.components.record-audio.record-audio.helpers :refer
[animate-linear
animate-linear-with-delay
animate-linear-with-delay-loop
animate-easing
set-value]]))
(def ^:private scale-to-each 1.8)
(def ^:private scale-to-total 2.6)
(def ^:private scale-padding 0.16)
(def ^:private opacity-from-lock 1)
(def ^:private opacity-from-default 0.5)
(def ^:private signal-anim-duration 3900)
(def ^:private signal-anim-duration-2 1950)
(def ^:private record-audio-worklets (js/require "../src/js/record_audio_worklets.js"))
(defn- ring-scale
[scale substract]
(.ringScale ^js record-audio-worklets
scale
substract))
(def ^:private animated-ring
(reagent/adapt-react-class
(memo
(fn [props]
(let [{:keys [scale opacity color]} (bean/bean props)]
(reagent/as-element
[reanimated/view {:style (style/animated-circle scale opacity color)}]))))))
(defn record-button-big
[recording? ready-to-send? ready-to-lock? ready-to-delete? record-button-is-animating?
record-button-at-initial-position? locked? reviewing-audio? recording-timer recording-length-ms
clear-timeout touch-active? recorder-ref reload-recorder-fn idle? on-send on-cancel]
[:f>
(fn []
(let [scale (reanimated/use-shared-value 1)
opacity (reanimated/use-shared-value 0)
opacity-from (if @ready-to-lock? opacity-from-lock opacity-from-default)
animations (map
(fn [index]
(let [ring-scale (ring-scale scale (* scale-padding index))]
{:scale ring-scale
:opacity (reanimated/interpolate ring-scale
[1 scale-to-each]
[opacity-from 0])}))
(range 0 5))
rings-color (cond
@ready-to-lock? (colors/theme-colors colors/neutral-80-opa-5-opaque
colors/neutral-80)
@ready-to-delete? colors/danger-50
:else colors/primary-50)
translate-y (reanimated/use-shared-value 0)
translate-x (reanimated/use-shared-value 0)
button-color colors/primary-50
icon-color (if (and (not (colors/dark?)) @ready-to-lock?) colors/black colors/white)
icon-opacity (reanimated/use-shared-value 1)
red-overlay-opacity (reanimated/use-shared-value 0)
gray-overlay-opacity (reanimated/use-shared-value 0)
complete-animation (fn []
(cond
(and @ready-to-lock? (not @record-button-is-animating?))
(do
(reset! locked? true)
(reset! ready-to-lock? false))
(and (not @locked?) (not @reviewing-audio?))
(audio/stop-recording
@recorder-ref
(fn []
(cond
@ready-to-send?
(when on-send
(on-send (audio/get-recorder-file-path @recorder-ref)))
@ready-to-delete?
(when on-cancel
(on-cancel)))
(reload-recorder-fn)
(reset! recording? false)
(reset! ready-to-send? false)
(reset! ready-to-delete? false)
(reset! ready-to-lock? false)
(reset! idle? true)
(js/setTimeout #(reset! idle? false) 1000)
(js/clearInterval @recording-timer)
(reset! recording-length-ms 0)
(log/debug "[record-audio] stop recording - success"))
#(log/error "[record-audio] stop recording - error: " %))))
start-animation (fn []
(set-value opacity 1)
(animate-linear scale 2.6 signal-anim-duration)
;; TODO: Research if we can implement this with withSequence method
;; from Reanimated 2
;; GitHub issue [#14561]:
;; https://github.com/status-im/status-mobile/issues/14561
(reset! clear-timeout
(js/setTimeout
(fn []
(set-value scale scale-to-each)
(animate-linear-with-delay-loop scale
scale-to-total
signal-anim-duration-2
0))
signal-anim-duration)))
stop-animation (fn []
(set-value opacity 0)
(reanimated/cancel-animation scale)
(set-value scale 1)
(when @clear-timeout (js/clearTimeout @clear-timeout)))
start-y-animation (fn []
(reset! record-button-at-initial-position? false)
(reset! record-button-is-animating? true)
(animate-easing translate-y -64 250)
(animate-linear-with-delay icon-opacity 0 33.33 76.66)
(js/setTimeout (fn []
(reset! record-button-is-animating? false)
(when-not @touch-active? (complete-animation)))
250))
reset-y-animation (fn []
(animate-easing translate-y 0 300)
(animate-linear icon-opacity 1 500)
(js/setTimeout (fn []
(reset! record-button-at-initial-position? true))
500))
start-x-animation (fn []
(reset! record-button-at-initial-position? false)
(reset! record-button-is-animating? true)
(animate-easing translate-x -64 250)
(animate-linear-with-delay icon-opacity 0 33.33 76.66)
(animate-linear red-overlay-opacity 1 33.33)
(js/setTimeout (fn []
(reset! record-button-is-animating? false)
(when-not @touch-active? (complete-animation)))
250))
reset-x-animation (fn []
(animate-easing translate-x 0 300)
(animate-linear icon-opacity 1 500)
(animate-linear red-overlay-opacity 0 100)
(js/setTimeout (fn []
(reset! record-button-at-initial-position? true))
500))
start-x-y-animation (fn []
(reset! record-button-at-initial-position? false)
(reset! record-button-is-animating? true)
(animate-easing translate-y -44 200)
(animate-easing translate-x -44 200)
(animate-linear-with-delay icon-opacity 0 33.33 33.33)
(animate-linear gray-overlay-opacity 1 33.33)
(js/setTimeout (fn []
(reset! record-button-is-animating? false)
(when-not @touch-active? (complete-animation)))
200))
reset-x-y-animation (fn []
(animate-easing translate-y 0 300)
(animate-easing translate-x 0 300)
(animate-linear icon-opacity 1 500)
(animate-linear gray-overlay-opacity 0 800)
(js/setTimeout (fn []
(reset! record-button-at-initial-position? true))
800))]
(use-effect (fn []
(cond
@recording?
(start-animation)
(not @ready-to-lock?)
(stop-animation)))
[@recording?])
(use-effect (fn []
(if @ready-to-lock?
(start-x-y-animation)
(reset-x-y-animation)))
[@ready-to-lock?])
(use-effect (fn []
(if @ready-to-send?
(start-y-animation)
(reset-y-animation)))
[@ready-to-send?])
(use-effect (fn []
(if @ready-to-delete?
(start-x-animation)
(reset-x-animation)))
[@ready-to-delete?])
[reanimated/view
{:style (style/record-button-big-container translate-x translate-y opacity)
:pointer-events :none}
[:<>
(map-indexed
(fn [id animation]
^{:key id}
[animated-ring
{:scale (:scale animation)
:opacity (:opacity animation)
:color rings-color}])
animations)]
[rn/view {:style (style/record-button-big-body button-color)}
[reanimated/view {:style (style/record-button-big-red-overlay red-overlay-opacity)}]
[reanimated/view {:style (style/record-button-big-gray-overlay gray-overlay-opacity)}]
[reanimated/view {:style (style/record-button-big-icon-container icon-opacity)}
(if @locked?
[rn/view {:style style/stop-icon}]
[icons/icon :i/audio {:color icon-color}])]]]))])
@@ -1,90 +0,0 @@
(ns quo2.components.record-audio.record-audio.buttons.send-button
(:require [quo2.components.icon :as icons]
[quo2.components.record-audio.record-audio.style :as style]
[quo2.foundations.colors :as colors]
[react-native.reanimated :as reanimated]
[react-native.core :refer [use-effect]]
[quo2.components.record-audio.record-audio.helpers :refer
[animate-linear
animate-linear-with-delay
animate-easing-with-delay
set-value]]))
(defn send-button
[recording? ready-to-send? reviewing-audio?]
[:f>
(fn []
(let [opacity (reanimated/use-shared-value 0)
translate-y (reanimated/use-shared-value 20)
connector-opacity (reanimated/use-shared-value 0)
width (reanimated/use-shared-value 12)
height (reanimated/use-shared-value 24)
border-radius-first-half (reanimated/use-shared-value 16)
border-radius-second-half (reanimated/use-shared-value 8)
start-y-animation (fn []
(animate-linear-with-delay translate-y 12 50 133.33)
(animate-easing-with-delay connector-opacity 1 0 93.33)
(animate-easing-with-delay width 56 83.33 80)
(animate-easing-with-delay height 56 83.33 80)
(animate-easing-with-delay border-radius-first-half 28 83.33 80)
(animate-easing-with-delay border-radius-second-half 28 83.33 80))
reset-y-animation (fn []
(animate-linear translate-y 0 100)
(set-value connector-opacity 0)
(set-value width 12)
(set-value height 24)
(set-value border-radius-first-half 16)
(set-value border-radius-second-half 8))
fade-in-animation (fn []
(animate-linear translate-y 0 200)
(animate-linear opacity 1 200))
fade-out-animation (fn []
(animate-linear
translate-y
(if @reviewing-audio? 76 20)
200)
(when-not @reviewing-audio?
(animate-linear opacity 0 200))
(set-value connector-opacity 0)
(set-value width 24)
(set-value height 12)
(set-value border-radius-first-half 8)
(set-value border-radius-second-half 16))
fade-out-reset-animation (fn []
(animate-linear opacity 0 200)
(animate-linear-with-delay translate-y 20 0 200)
(set-value connector-opacity 0)
(set-value width 24)
(set-value height 12)
(set-value border-radius-first-half 8)
(set-value border-radius-second-half 16))]
(use-effect (fn []
(if @recording?
(fade-in-animation)
(fade-out-animation)))
[@recording?])
(use-effect (fn []
(when-not @reviewing-audio?
(fade-out-reset-animation)))
[@reviewing-audio?])
(use-effect (fn []
(cond
@ready-to-send?
(start-y-animation)
@recording? (reset-y-animation)))
[@ready-to-send?])
[:<>
[reanimated/view {:style (style/send-button-container opacity)}
[reanimated/view
{:style (style/send-button-connector connector-opacity
width
height
border-radius-first-half
border-radius-second-half)}]]
[reanimated/view
{:style (style/send-button translate-y opacity)
:pointer-events :none}
[icons/icon :i/arrow-up
{:color colors/white
:size 20
:container-style style/send-icon-container}]]]))])
@@ -1,50 +0,0 @@
(ns quo2.components.record-audio.record-audio.helpers
(:require [react-native.reanimated :as reanimated]))
(defn animate-linear
[shared-value value duration]
(reanimated/animate-shared-value-with-timing
shared-value
value
duration
:linear))
(defn animate-linear-with-delay
[shared-value value duration delay]
(reanimated/animate-shared-value-with-delay
shared-value
value
duration
:linear
delay))
(defn animate-linear-with-delay-loop
[shared-value value duration delay]
(reanimated/animate-shared-value-with-delay-repeat
shared-value
value
duration
:linear
delay
-1))
(defn animate-easing
[shared-value value duration]
(reanimated/animate-shared-value-with-timing
shared-value
value
duration
:easing1))
(defn animate-easing-with-delay
[shared-value value duration delay]
(reanimated/animate-shared-value-with-delay
shared-value
value
duration
:easing1
delay))
(defn set-value
[shared-value value]
(reanimated/set-shared-value shared-value value))
@@ -199,10 +199,9 @@
:z-index 0}))
(defn delete-button
[scale translate-x opacity]
[translate-x opacity]
(reanimated/apply-animations-to-style
{:transform [{:translateX translate-x}
{:scale scale}]
{:transform [{:translateX translate-x}]
:opacity opacity}
{:width 32
:height 32
@@ -222,66 +221,8 @@
{:margin-bottom 32
:margin-right 32}))
(def button-container
(def input-container
{:width 140
:height 140
:align-items :flex-end
:justify-content :flex-end
:position :absolute
:right -10})
(def bar-container
{:flex 1
:height 128})
(defn recording-bar-container
[]
{:height 4
:border-radius 2
:background-color (colors/theme-colors colors/neutral-20 colors/neutral-80)
:overflow :hidden
:position :absolute
:left 80
:right 148
:bottom 34})
(defn recording-bar
[fill-percentage ready-to-delete?]
{:width (str fill-percentage "%")
:height 4
:border-radius 2
:background-color (if ready-to-delete?
(colors/theme-colors colors/danger-50 colors/danger-60)
(colors/theme-colors colors/primary-50 colors/primary-60))})
(defn timer-container
[reviewing-audio?]
{:position :absolute
:left (if reviewing-audio? 67 20)
:bottom 28.5
:flex-direction :row
:align-items :center})
(defn timer-circle
[]
{:width 8
:height 8
:border-radius 4
:margin-right 6
:background-color (colors/theme-colors colors/danger-50 colors/danger-60)})
(defn timer-text
[]
{:color (colors/theme-colors colors/danger-50 colors/danger-60)})
(defn play-button
[]
{:position :absolute
:bottom 20
:left 20
:width 32
:height 32
:border-radius 16
:align-items :center
:justify-content :center
:background-color (colors/theme-colors colors/neutral-10 colors/neutral-90)})
:justify-content :flex-end})
File diff suppressed because it is too large Load Diff
@@ -1,82 +0,0 @@
(ns quo2.components.record-audio.soundtrack.--tests--.soundtrack-component-spec
(:require [quo2.components.record-audio.soundtrack.view :as soundtrack]
[test-helpers.component :as h]
[reagent.core :as reagent]
[status-im.audio.core :as audio]))
(h/describe "soundtrack component"
(h/before-each
(fn []
(h/use-fake-timers)))
(h/after-each
(fn []
(h/clear-all-timers)
(h/use-real-timers)))
(h/test "renders soundtrack"
(with-redefs [audio/get-player-duration (fn [] 2000)]
(let [player-ref (reagent/atom {})
audio-current-time-ms (reagent/atom 0)]
(h/render [soundtrack/soundtrack
{:player-ref player-ref
:audio-current-time-ms audio-current-time-ms}])
(-> (h/expect (h/get-by-test-id "soundtrack"))
(.toBeTruthy)))))
(h/test "soundtrack on-sliding-start works"
(with-redefs [audio/get-player-duration (fn [] 2000)]
(let [seeking-audio? (reagent/atom false)
player-ref (reagent/atom {})
audio-current-time-ms (reagent/atom 0)]
(h/render [soundtrack/soundtrack
{:seeking-audio? seeking-audio?
:player-ref player-ref
:audio-current-time-ms audio-current-time-ms}])
(h/fire-event
:on-sliding-start
(h/get-by-test-id "soundtrack"))
(-> (h/expect @seeking-audio?)
(.toBe true)))))
(h/test "soundtrack on-sliding-complete works"
(with-redefs [audio/get-player-duration (fn [] 2000)
audio/seek-player (js/jest.fn)]
(let [seeking-audio? (reagent/atom false)
player-ref (reagent/atom {})
audio-current-time-ms (reagent/atom 0)]
(h/render [soundtrack/soundtrack
{:seeking-audio? seeking-audio?
:player-ref player-ref
:audio-current-time-ms audio-current-time-ms}])
(h/fire-event
:on-sliding-start
(h/get-by-test-id "soundtrack"))
(h/fire-event
:on-sliding-complete
(h/get-by-test-id "soundtrack")
1000)
(-> (h/expect @seeking-audio?)
(.toBe false))
(-> (h/expect audio/seek-player)
(.toHaveBeenCalledTimes 1)))))
(h/test "soundtrack on-value-change when seeking audio works"
(with-redefs [audio/get-player-duration (fn [] 2000)
audio/seek-player (js/jest.fn)]
(let [seeking-audio? (reagent/atom false)
player-ref (reagent/atom {})
audio-current-time-ms (reagent/atom 0)]
(h/render [soundtrack/soundtrack
{:seeking-audio? seeking-audio?
:player-ref player-ref
:audio-current-time-ms audio-current-time-ms}])
(h/fire-event
:on-sliding-start
(h/get-by-test-id "soundtrack"))
(h/fire-event
:on-value-change
(h/get-by-test-id "soundtrack")
1000)
(-> (h/expect @audio-current-time-ms)
(.toBe 1000))))))
@@ -1,16 +0,0 @@
(ns quo2.components.record-audio.soundtrack.style
(:require [react-native.platform :as platform]))
(defn player-slider-container
[]
(merge
{:position :absolute
:left (if platform/ios? 115 104)
:right (if platform/ios? 108 92)
:bottom (if platform/ios? 16 27)}
(when platform/android?
;; Workaround to increase the thickness of the slider track on Android
;; which is currently not supported by the Slider library and remove
;; the thumb shadow that appears when dragging.
{:transform [{:scaleY 2}]
:background-color :transparent})))
@@ -1,38 +0,0 @@
(ns quo2.components.record-audio.soundtrack.view
(:require [quo2.components.record-audio.soundtrack.style :as style]
[quo2.foundations.colors :as colors]
[status-im.audio.core :as audio]
[taoensso.timbre :as log]
[react-native.platform :as platform]
[react-native.slider :as slider]))
(def ^:private thumb-light (js/require "../resources/images/icons2/12x12/thumb-light.png"))
(def ^:private thumb-dark (js/require "../resources/images/icons2/12x12/thumb-dark.png"))
(defn soundtrack
[{:keys [audio-current-time-ms player-ref seeking-audio?]}]
[:f>
(fn []
(let [audio-duration-ms (audio/get-player-duration @player-ref)]
[:<>
[slider/slider
{:test-ID "soundtrack"
:style (style/player-slider-container)
:minimum-value 0
:maximum-value audio-duration-ms
:value @audio-current-time-ms
:on-sliding-start #(reset! seeking-audio? true)
:on-sliding-complete (fn [seek-time]
(reset! seeking-audio? false)
(audio/seek-player
@player-ref
seek-time
#(log/debug "[record-audio] on seek - seek time: " seek-time)
#(log/error "[record-audio] on seek - error: " %)))
:on-value-change #(when @seeking-audio?
(reset! audio-current-time-ms %))
:thumb-image (if (colors/dark?) thumb-dark thumb-light)
:minimum-track-tint-color (colors/theme-colors colors/primary-50 colors/primary-60)
:maximum-track-tint-color (colors/theme-colors
(if platform/ios? colors/neutral-20 colors/neutral-40)
(if platform/ios? colors/neutral-80 colors/neutral-60))}]]))])
+1 -1
View File
@@ -64,7 +64,7 @@
[rn/image
{:style {:width 20
:border-radius 10
:background-color :white
:background-color :red
:height 20}
:source photo}]
[rn/view
+64 -66
View File
@@ -1,12 +1,11 @@
(ns quo2.components.tags.tags
(:require [reagent.core :as reagent]
[quo.react-native :as rn]
[oops.core :refer [oget]]
[status-im.ui.components.react :as react]
[status-im.utils.core :as utils]
[quo2.components.tags.tag :as tag]
[utils.number :as number-utils]
[react-native.core :as rn]
[react-native.masked-view :as masked-view]
[react-native.linear-gradient :as linear-gradient]
[utils.collection :as utils.collection]))
[utils.number :as number-utils]))
(def default-tab-size 32)
@@ -77,9 +76,9 @@
size default-tab-size}
:as props}]
(let [maybe-mask-wrapper (if fade-end?
[masked-view/masked-view
[react/masked-view
{:mask-element (reagent/as-element
[linear-gradient/linear-gradient
[react/linear-gradient
{:colors [:black :transparent]
:locations [(get @fading :fade-end-percentage)
1]
@@ -93,65 +92,64 @@
(conj
maybe-mask-wrapper
[rn/flat-list
(merge
(dissoc props
:default-active
:fade-end-percentage
:fade-end?
:on-change
:scroll-on-press?
:size)
(when scroll-on-press?
{:initial-scroll-index (utils.collection/first-index #(= @active-tab-id (:id %)) data)})
{:ref #(reset! flat-list-ref %)
:extra-data (str @active-tab-id)
:horizontal true
:scroll-event-throttle scroll-event-throttle
:shows-horizontal-scroll-indicator false
:data data
:key-fn (comp str :id)
:on-scroll (fn [^js e]
(when fade-end?
(let [offset-x (oget e "nativeEvent.contentOffset.x")
content-width (oget e "nativeEvent.contentSize.width")
layout-width (oget e "nativeEvent.layoutMeasurement.width")
new-percentage (calculate-fade-end-percentage
{:offset-x offset-x
:content-width content-width
:layout-width layout-width
:max-fade-percentage fade-end-percentage})]
;; Avoid unnecessary re-rendering.
(when (not= new-percentage (get @fading :fade-end-percentage))
(swap! fading assoc :fade-end-percentage new-percentage))))
(when on-scroll
(on-scroll e)))
:render-fn (fn [{:keys [id label resource]} index]
[rn/view
{:style {:margin-right (if (= size default-tab-size) 12 8)
:padding-right (when (= index (dec (count data)))
(get-in props [:style :padding-left]))}}
[tag/tag
{:id id
:size size
:active (= id @active-tab-id)
:resource resource
:blurred? blurred?
:icon-color icon-color
:disabled? disabled?
:label label
:type type
:labelled? labelled?
:on-press (fn [id]
(reset! active-tab-id id)
(when scroll-on-press?
(.scrollToIndex ^js @flat-list-ref
#js
{:animated true
:index index
:viewPosition 0.5}))
(when on-change
(on-change id)))}
label]])})])
(merge (dissoc props
:default-active
:fade-end-percentage
:fade-end?
:on-change
:scroll-on-press?
:size)
(when scroll-on-press?
{:initial-scroll-index (utils/first-index #(= @active-tab-id (:id %)) data)})
{:ref #(reset! flat-list-ref %)
:extra-data (str @active-tab-id)
:horizontal true
:scroll-event-throttle scroll-event-throttle
:shows-horizontal-scroll-indicator false
:data data
:key-fn (comp str :id)
:on-scroll (fn [^js e]
(when fade-end?
(let [offset-x (oget e "nativeEvent.contentOffset.x")
content-width (oget e "nativeEvent.contentSize.width")
layout-width (oget e "nativeEvent.layoutMeasurement.width")
new-percentage (calculate-fade-end-percentage
{:offset-x offset-x
:content-width content-width
:layout-width layout-width
:max-fade-percentage fade-end-percentage})]
;; Avoid unnecessary re-rendering.
(when (not= new-percentage (get @fading :fade-end-percentage))
(swap! fading assoc :fade-end-percentage new-percentage))))
(when on-scroll
(on-scroll e)))
:render-fn (fn [{:keys [id label resource]} index]
[rn/view
{:style {:margin-right (if (= size default-tab-size) 12 8)
:padding-right (when (= index (dec (count data)))
(get-in props [:style :padding-left]))}}
[tag/tag
{:id id
:size size
:active (= id @active-tab-id)
:resource resource
:blurred? blurred?
:icon-color icon-color
:disabled? disabled?
:label label
:type type
:labelled? labelled?
:on-press (fn [id]
(reset! active-tab-id id)
(when scroll-on-press?
(.scrollToIndex ^js @flat-list-ref
#js
{:animated true
:index index
:viewPosition 0.5}))
(when on-change
(on-change id)))}
label]])})])
[rn/view {:style {:flex-direction :row}}
(for [{:keys [label id resource]} data]
^{:key id}
+6 -18
View File
@@ -20,8 +20,7 @@
quo2.components.dividers.date
quo2.components.dividers.divider-label
quo2.components.dividers.new-messages
quo2.components.drawers.action-drawers.view
quo2.components.drawers.permission-context.view
quo2.components.drawers.action-drawers
quo2.components.dropdowns.dropdown
quo2.components.header
quo2.components.icon
@@ -42,7 +41,6 @@
quo2.components.notifications.info-count
quo2.components.notifications.notification-dot
quo2.components.notifications.toast
quo2.components.profile.profile-card.view
quo2.components.reactions.reaction
quo2.components.selectors.disclaimer.view
quo2.components.selectors.filter.view
@@ -54,10 +52,8 @@
quo2.components.tabs.tabs
quo2.components.tags.context-tags
quo2.components.tags.status-tags
quo2.components.tags.permission-tag
quo2.components.tags.tag
quo2.components.tags.tags
quo2.components.tags.token-tag))
quo2.components.profile.profile-card.view
quo2.components.tags.tags))
(def toast quo2.components.notifications.toast/toast)
(def button quo2.components.buttons.button/button)
@@ -67,6 +63,7 @@
(def separator quo2.components.separator/separator)
(def counter quo2.components.counter.counter/counter)
(def header quo2.components.header/header)
(def action-drawer quo2.components.drawers.action-drawers/action-drawer)
(def dropdown quo2.components.dropdowns.dropdown/dropdown)
(def info-message quo2.components.info.info-message/info-message)
(def information-box quo2.components.info.information-box/information-box)
@@ -74,6 +71,7 @@
(def system-message quo2.components.messages.system-message/system-message)
(def reaction quo2.components.reactions.reaction/reaction)
(def add-reaction quo2.components.reactions.reaction/add-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 group-avatar-tag quo2.components.tags.context-tags/group-avatar-tag)
@@ -82,6 +80,7 @@
(def tabs quo2.components.tabs.tabs/tabs)
(def account-selector quo2.components.tabs.account-selector/account-selector)
(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.view/view)
(def checkbox quo2.components.selectors.selectors/checkbox)
@@ -119,10 +118,6 @@
(def new-messages quo2.components.dividers.new-messages/new-messages)
(def divider-date quo2.components.dividers.date/date)
;;;; DRAWERS
(def action-drawer quo2.components.drawers.action-drawers.view/action-drawer)
(def permission-context quo2.components.drawers.permission-context.view/view)
;;;; LIST ITEMS
(def channel-list-item quo2.components.list-items.channel/list-item)
(def menu-item quo2.components.list-items.menu-item/menu-item)
@@ -140,10 +135,3 @@
;;;; SETTINGS
(def privacy-option quo2.components.settings.privacy-option/card)
(def account quo2.components.settings.accounts.view/account)
;;;; TAGS
(def tag quo2.components.tags.tag/tag)
(def tags quo2.components.tags.tags/tags)
(def permission-tag quo2.components.tags.permission-tag/tag)
(def status-tag quo2.components.tags.status-tags/status-tag)
(def token-tag quo2.components.tags.token-tag/tag)
+3 -5
View File
@@ -3,10 +3,8 @@
[quo2.components.buttons.--tests--.buttons-component-spec]
[quo2.components.counter.--tests--.counter-component-spec]
[quo2.components.dividers.--tests--.divider-label-component-spec]
[quo2.components.drawers.action-drawers.component-spec]
[quo2.components.drawers.permission-context.component-spec]
[quo2.components.drawers.--tests--.action-drawers-component-spec]
[quo2.components.drawers.permission-context.--tests--.permission-context-component-spec]
[quo2.components.markdown.--tests--.text-component-spec]
[quo2.components.selectors.--tests--.selectors-component-spec]
[quo2.components.selectors.filter.component-spec]
[quo2.components.record-audio.record-audio.--tests--.record-audio-component-spec]
[quo2.components.record-audio.soundtrack.--tests--.soundtrack-component-spec]))
[quo2.components.selectors.filter.component-spec]))
-71
View File
@@ -1,71 +0,0 @@
(ns quo2.foundations.shadows
(:require [quo2.foundations.colors :as colors]
[quo2.theme :as theme]))
(defn- get-inverted
[inverted? number]
(if inverted? (* -1 number) number))
(defn- get-scales
[inverted?]
(if (theme/dark?)
{:shadow-1 {:shadow-color (colors/alpha colors/neutral-100 0.5)
:shadow-offset {:width 0
:height (get-inverted inverted? 4)}
:elevation 3
:shadow-opacity 1
:shadow-radius 20}
:shadow-2 {:shadow-color (colors/alpha colors/neutral-100 0.64)
:shadow-offset {:width 0
:height (get-inverted inverted? 4)}
:elevation 4
:shadow-opacity 1
:shadow-radius 20}
:shadow-3 {:shadow-color (colors/alpha colors/neutral-100 0.64)
:shadow-offset {:width 0
:height (get-inverted inverted? 12)}
:elevation 8
:shadow-opacity 1
:shadow-radius 20}
:shadow-4 {:shadow-color (colors/alpha colors/neutral-100 0.72)
:shadow-offset {:width 0
:height (get-inverted inverted? 16)}
:shadow-opacity 1
:shadow-radius 20
:elevation 15}}
{:shadow-1 {:shadow-color (colors/alpha colors/neutral-100 0.04)
:shadow-offset {:width 0
:height (get-inverted inverted? 4)}
:elevation 1
:shadow-opacity 1
:shadow-radius 16}
:shadow-2 {:shadow-color (colors/alpha colors/neutral-100 0.08)
:shadow-offset {:width 0
:height (get-inverted inverted? 4)}
:elevation 2
:shadow-opacity 1
:shadow-radius 16}
:shadow-3 {:shadow-color (colors/alpha colors/neutral-100 0.12)
:shadow-offset {:width 0
:height (get-inverted inverted? 12)}
:elevation 5
:shadow-opacity 1
:shadow-radius 16}
:shadow-4 {:shadow-color (colors/alpha colors/neutral-100 0.16)
:shadow-offset {:width 0
:height (get-inverted inverted? 16)}
:shadow-opacity 1
:shadow-radius 16
:elevation 13}}))
(def normal-scale (get-scales false))
(def inverted-scale (get-scales true))
(def inner-shadow
{:shadow-color (colors/alpha colors/neutral-100 0.08)
:shadow-offset {:width 0
:height 0}
:shadow-opacity 1
:shadow-radius 16
:elevation 13})
-10
View File
@@ -1,10 +0,0 @@
(ns react-native.clipboard
(:require ["@react-native-community/clipboard" :default Clipboard]))
(defn set-string
[text]
(.setString ^js Clipboard text))
(defn get-string
[callback]
(.then (.getString ^js Clipboard) #(callback %)))
+3 -15
View File
@@ -94,12 +94,11 @@
(defn use-effect
([effect-fn]
(use-effect effect-fn []))
([effect-fn deps]
(react/useEffect
#(let [ret (effect-fn)]
(if (fn? ret) ret js/undefined))
(bean/->js deps))))
(if (fn? ret) ret js/undefined))))
([effect-fn deps]
(react/useEffect effect-fn (bean/->js deps))))
(defn use-effect-once
[effect-fn]
@@ -118,14 +117,3 @@
{:ease-in-ease-out (-> ^js layout-animation .-Presets .-easeInEaseOut)
:linear (-> ^js layout-animation .-Presets .-linear)
:spring (-> ^js layout-animation .-Presets .-spring)})
(def find-node-handle (.-findNodeHandle ^js react-native))
(defn selectable-text-input-manager
[]
(when (exists? (.-NativeModules ^js react-native))
(.-RNSelectableTextInputManager ^js (.-NativeModules ^js react-native))))
(defonce selectable-text-input
(reagent/adapt-react-class
(.requireNativeComponent ^js react-native "RNSelectableTextInput")))
-40
View File
@@ -1,40 +0,0 @@
(ns react-native.permissions
(:require ["react-native-permissions" :refer (check requestMultiple PERMISSIONS RESULTS)]
[react-native.platform :as platform]))
(def permissions-map
{:read-external-storage (cond
platform/android? (.-READ_EXTERNAL_STORAGE (.-ANDROID PERMISSIONS)))
:write-external-storage (cond
platform/low-device? (.-WRITE_EXTERNAL_STORAGE (.-ANDROID PERMISSIONS)))
:camera (cond
platform/android? (.-CAMERA (.-ANDROID PERMISSIONS))
platform/ios? (.-CAMERA (.-IOS PERMISSIONS)))
:record-audio (cond
platform/android? (.-RECORD_AUDIO (.-ANDROID PERMISSIONS))
platform/ios? (.-MICROPHONE (.-IOS PERMISSIONS)))})
(defn all-granted?
[permissions]
(let [permission-vals (distinct (vals permissions))]
(and (= (count permission-vals) 1)
(not (#{(.-BLOCKED RESULTS) (.-DENIED RESULTS)} (first permission-vals))))))
(defn request-permissions
[{:keys [permissions on-allowed on-denied]
:or {on-allowed #()
on-denied #()}}]
(let [permissions (remove nil? (mapv #(get permissions-map %) permissions))]
(if (empty? permissions)
(on-allowed)
(-> (requestMultiple (clj->js permissions))
(.then #(if (all-granted? (js->clj %))
(on-allowed)
(on-denied)))
(.catch on-denied)))))
(defn permission-granted?
[permission on-result on-error]
(-> (check (get permissions-map permission))
(.then #(on-result (not (#{(.-BLOCKED RESULTS) (.-DENIED RESULTS)} %))))
(.catch #(on-error %))))
+16 -13
View File
@@ -1,7 +1,6 @@
(ns react-native.reanimated
(:require ["react-native" :as rn]
["react-native-linear-gradient" :default LinearGradient]
["@react-native-community/blur" :as blur]
["react-native-reanimated" :default reanimated :refer
(useSharedValue useAnimatedStyle
withTiming
@@ -14,6 +13,7 @@
SlideInUp
SlideOutUp
LinearTransition)]
[camel-snake-kebab.core :as csk]
[reagent.core :as reagent]
[utils.collection]))
@@ -30,7 +30,6 @@
(def touchable-opacity (create-animated-component (.-TouchableOpacity ^js rn)))
(def linear-gradient (create-animated-component LinearGradient))
(def blur-view (create-animated-component (.-BlurView blur)))
;; Hooks
(def use-shared-value useSharedValue)
@@ -61,27 +60,31 @@
(defn set-shared-value
[anim val]
(when (and anim (some? val))
(when anim
(set! (.-value anim) val)))
;; Worklets
(def worklet-factory (js/require "../src/js/worklet_factory.js"))
(defn interpolate
([shared-value input-range output-range]
(interpolate shared-value input-range output-range nil))
([shared-value input-range output-range extrapolation]
(.interpolateValue ^js worklet-factory
shared-value
(clj->js input-range)
(clj->js output-range)
(clj->js extrapolation))))
[shared-value input-range output-range]
(.interpolateValue ^js worklet-factory
shared-value
(clj->js input-range)
(clj->js output-range)))
;;;; Component Animations
;; kebab-case styles are not working for worklets
;; so first convert kebab case styles into camel case styles
(defn apply-animations-to-style
[animations style]
(use-animated-style
(.applyAnimationsToStyle ^js worklet-factory (clj->js animations) (clj->js style))))
(let [animations (utils.collection/map-keys csk/->camelCaseString animations)
style (apply dissoc
(utils.collection/map-keys csk/->camelCaseString style)
(keys animations))]
(use-animated-style
(.applyAnimationsToStyle ^js worklet-factory (clj->js animations) (clj->js style)))))
;; Animators
(defn animate-shared-value-with-timing
-5
View File
@@ -1,5 +0,0 @@
(ns react-native.slider
(:require ["@react-native-community/slider" :default Slider]
[reagent.core :as reagent]))
(def slider (reagent/adapt-react-class Slider))
View File
+2 -2
View File
@@ -2,7 +2,7 @@
(:require [clojure.string :as string]
[re-frame.core :as re-frame]
[status-im.add-new.db :as db]
[status-im2.contexts.chat.events :as chat]
[status-im.chat.models :as chat]
[status-im.contact.core :as contact]
[status-im.ethereum.core :as ethereum]
[status-im.ethereum.ens :as ens]
@@ -91,7 +91,7 @@
(i18n/label :t/use-valid-contact-code)
:yourself
(i18n/label :t/can-not-add-yourself))
:on-dismiss #(re-frame/dispatch [:pop-to-root-tab :shell-stack])}})))
:on-dismiss #(re-frame/dispatch [:pop-to-root-tab :chat-stack])}})))
(rf/defn qr-code-scanned
{:events [:contact/qr-code-scanned]}
+3 -3
View File
@@ -1,7 +1,7 @@
(ns status-im.add-new.db
(:require [cljs.spec.alpha :as spec]
[status-im.ethereum.ens :as ens]
[status-im2.utils.validators :as validators]))
[status-im.utils.db :as utils.db]))
(defn own-public-key?
[{:keys [multiaccount]} public-key]
@@ -10,7 +10,7 @@
(defn validate-pub-key
[db public-key]
(cond
(or (not (validators/valid-public-key? public-key))
(or (not (utils.db/valid-public-key? public-key))
(= public-key ens/default-key))
:invalid
(own-public-key? db public-key)
@@ -27,4 +27,4 @@
[topic]
(and topic
(spec/valid? ::topic topic)
(not (validators/valid-public-key? topic))))
(not (utils.db/valid-public-key? topic))))
+95
View File
@@ -0,0 +1,95 @@
(ns status-im.chat.db
(:require [status-im2.constants :as constants]))
(defn group-chat-name
[{:keys [public? name]}]
(str (when public? "#") name))
(defn intersperse-datemark
"Reduce step which expects the input list of messages to be sorted by clock value.
It makes best effort to group them by day.
We cannot sort them by :timestamp, as that represents the clock of the sender
and we have no guarantees on the order.
We naively and arbitrarly group them assuming that out-of-order timestamps
fall in the previous bucket.
A sends M1 to B with timestamp 2000-01-01T00:00:00
B replies M2 with timestamp 1999-12-31-23:59:59
M1 needs to be displayed before M2
so we bucket both in 1999-12-31"
[{:keys [acc last-timestamp last-datemark]} {:keys [whisper-timestamp datemark] :as msg}]
(cond
(empty? acc) ; initial element
{:last-timestamp whisper-timestamp
:last-datemark datemark
:acc (conj acc msg)}
(and (not= last-datemark datemark) ; not the same day
(< whisper-timestamp last-timestamp)) ; not out-of-order
{:last-timestamp whisper-timestamp
:last-datemark datemark
:acc (conj acc
{:value last-datemark ; intersperse datemark message
:type :datemark}
msg)}
:else
{:last-timestamp (min whisper-timestamp last-timestamp) ; use last datemark
:last-datemark last-datemark
:acc (conj acc (assoc msg :datemark last-datemark))}))
(defn add-datemarks
"Add a datemark in between an ordered seq of messages when two datemarks are not
the same. Ignore messages with out-of-order timestamps"
[messages]
(when (seq messages)
(let [messages-with-datemarks (:acc (reduce intersperse-datemark {:acc []} messages))]
; Append last datemark
(conj messages-with-datemarks
{:value (:datemark (peek messages-with-datemarks))
:type :datemark}))))
(defn last-gap
"last-gap is a special gap that is put last in the message stream"
[chat-id synced-from]
{:message-id "0x123"
:message-type constants/message-type-gap
:chat-id chat-id
:content-type constants/content-type-gap
:gap-ids #{:first-gap}
:gap-parameters {:from synced-from}})
(defn collapse-gaps
"collapse-gaps will take an array of messages and collapse any gap next to
each other in a single gap.
It will also append one last gap if the last message is a non-gap"
[messages chat-id synced-from now chat-type joined loading-messages?]
(let [messages-with-gaps (reduce
(fn [acc {:keys [gap-parameters message-id] :as message}]
(let [last-element (peek acc)]
(cond
;; If it's a message, just add
(empty? gap-parameters)
(conj acc message)
;; Both are gaps, merge them
(and
(seq (:gap-parameters last-element))
(seq gap-parameters))
(conj (pop acc) (update last-element :gap-ids conj message-id))
;; it's a gap
:else
(conj acc (assoc message :gap-ids #{message-id})))))
[]
messages)]
(if (or loading-messages? ; it's loading messages from the database
(nil? synced-from) ; it's still syncing
(= constants/timeline-chat-type chat-type) ; it's a timeline chat
(= constants/profile-chat-type chat-type) ; it's a profile chat
(and (not (nil? synced-from)) ; it's not more than a month
(<= synced-from (- (quot now 1000) constants/one-month)))
(and (= constants/private-group-chat-type chat-type) ; it's a private group chat
(or (not (pos? joined)) ; we haven't joined
(>= (quot joined 1000) synced-from))) ; the history goes before we joined
(:gap-ids (peek messages-with-gaps))) ; there's already a gap on top of the chat history
messages-with-gaps ; don't add an extra gap
(conj messages-with-gaps (last-gap chat-id synced-from)))))
+44
View File
@@ -0,0 +1,44 @@
(ns status-im.chat.db-test
(:require [cljs.test :refer-macros [deftest is testing]]
[status-im.chat.db :as db]))
(deftest group-chat-name
(testing "it prepends # if it's a public chat"
(is (= "#withhash"
(db/group-chat-name {:group-chat true
:chat-id "1"
:public? true
:name "withhash"}))))
(testing "it leaves the name unchanged if it's a group chat"
(is (= "unchanged"
(db/group-chat-name {:group-chat true
:chat-id "1"
:name "unchanged"})))))
(deftest intersperse-datemarks
(testing "it mantains the order even when timestamps are across days"
(let [message-1 {:datemark "Dec 31, 1999"
:whisper-timestamp 946641600000} ; 1999}
message-2 {:datemark "Jan 1, 2000"
:whisper-timestamp 946728000000} ; 2000 this will displayed in 1999
message-3 {:datemark "Dec 31, 1999"
:whisper-timestamp 946641600000} ; 1999
message-4 {:datemark "Jan 1, 2000"
:whisper-timestamp 946728000000} ; 2000
ordered-messages [message-4
message-3
message-2
message-1]
[m1 d1 m2 m3 m4 d2] (db/add-datemarks ordered-messages)]
(is (= "Jan 1, 2000"
(:datemark m1)))
(is (= {:type :datemark
:value "Jan 1, 2000"}
d1))
(is (= "Dec 31, 1999"
(:datemark m2)
(:datemark m3)
(:datemark m4)))
(is (= {:type :datemark
:value "Dec 31, 1999"}
d2)))))
+507 -9
View File
@@ -1,12 +1,344 @@
(ns status-im.chat.models
(:require [utils.i18n :as i18n]
(:require [clojure.set :as set]
[quo.design-system.colors :as colors]
[re-frame.core :as re-frame]
[utils.re-frame :as rf]
[taoensso.timbre :as log]
[status-im.add-new.db :as new-public-chat.db]
[status-im.data-store.chats :as chats-store]))
[status-im.chat.models.loading :as loading]
[status-im2.contexts.chat.messages.list.events :as message-list]
[status-im2.constants :as constants]
[status-im.data-store.chats :as chats-store]
[status-im.data-store.contacts :as contacts-store]
[utils.i18n :as i18n]
[status-im.mailserver.core :as mailserver]
[status-im.multiaccounts.model :as multiaccounts.model]
[status-im2.contexts.chat.messages.list.state :as chat.state]
[status-im.utils.clocks :as utils.clocks]
[utils.re-frame :as rf]
[status-im.utils.utils :as utils]
[status-im2.contexts.chat.messages.delete-message-for-me.events :as delete-for-me]
[status-im2.contexts.chat.messages.delete-message.events :as delete-message]
[status-im2.navigation.events :as navigation]
[taoensso.timbre :as log]))
;; OLD
(defn- get-chat
[cofx chat-id]
(get-in cofx [:db :chats chat-id]))
(defn multi-user-chat?
([chat]
(:group-chat chat))
([cofx chat-id]
(multi-user-chat? (get-chat cofx chat-id))))
(def one-to-one-chat?
(complement multi-user-chat?))
(defn public-chat?
([chat]
(:public? chat))
([cofx chat-id]
(public-chat? (get-chat cofx chat-id))))
(defn community-chat?
([{:keys [chat-type]}]
(= chat-type constants/community-chat-type))
([cofx chat-id]
(community-chat? (get-chat cofx chat-id))))
(defn active-chat?
[cofx chat-id]
(let [chat (get-chat cofx chat-id)]
(:active chat)))
(defn foreground-chat?
[{{:keys [current-chat-id view-id]} :db} chat-id]
(and (= current-chat-id chat-id)
(= view-id :chat)))
(defn group-chat?
([chat]
(and (multi-user-chat? chat)
(not (public-chat? chat))))
([cofx chat-id]
(group-chat? (get-chat cofx chat-id))))
(defn timeline-chat?
([chat]
(:timeline? chat))
([cofx chat-id]
(timeline-chat? (get-chat cofx chat-id))))
(defn profile-chat?
([chat]
(:profile-public-key chat))
([cofx chat-id]
(profile-chat? (get-chat cofx chat-id))))
(defn set-chat-ui-props
"Updates ui-props in active chat by merging provided kvs into them"
[{:keys [current-chat-id] :as db} kvs]
(update-in db [:chat-ui-props current-chat-id] merge kvs))
(defn- create-new-chat
[chat-id {:keys [db now]}]
(let [name (get-in db [:contacts/contacts chat-id :name])]
{:chat-id chat-id
:name (or name "")
:color (rand-nth colors/chat-colors)
:chat-type constants/one-to-one-chat-type
:group-chat false
:timestamp now
:contacts #{chat-id}
:last-clock-value 0}))
(defn map-chats
[{:keys [db] :as cofx}]
(fn [val]
(assoc
(merge
(or (get (:chats db) (:chat-id val))
(create-new-chat (:chat-id val) cofx))
val)
:invitation-admin
(:invitation-admin val))))
(defn filter-chats
[db]
(fn [val]
(and (not (get-in db [:chats (:chat-id val)])) (:public? val))))
(rf/defn leave-removed-chat
[{{:keys [view-id current-chat-id chats]} :db
:as cofx}]
(when (and (= view-id :chat)
(not (contains? chats current-chat-id)))
(navigation/navigate-back cofx)))
(rf/defn ensure-chats
"Add chats to db and update"
[{:keys [db] :as cofx} chats]
(let [{:keys [all-chats chats-home-list removed-chats]}
(reduce
(fn [acc {:keys [chat-id profile-public-key timeline? community-id active muted] :as chat}]
(if (not (or active muted))
(update acc :removed-chats conj chat-id)
(cond-> acc
(and (not profile-public-key) (not timeline?) (not community-id) active)
(update :chats-home-list conj chat-id)
:always
(assoc-in [:all-chats chat-id] chat))))
{:all-chats {}
:chats-home-list #{}
:removed-chats #{}}
(map (map-chats cofx) chats))]
(rf/merge
cofx
(merge {:db (-> db
(update :chats merge all-chats)
(update :chats-home-list set/union chats-home-list)
(update :chats #(apply dissoc % removed-chats))
(update :chats-home-list set/difference removed-chats))}
(when (not-empty removed-chats)
{:clear-message-notifications
[removed-chats
(get-in db [:multiaccount :remote-push-notifications-enabled?])]}))
leave-removed-chat)))
(rf/defn clear-history
"Clears history of the particular chat"
[{:keys [db] :as cofx} chat-id remove-chat?]
(let [{:keys [last-message public?
deleted-at-clock-value]}
(get-in db [:chats chat-id])
last-message-clock-value (if (and public? remove-chat?)
0
(or (:clock-value last-message)
deleted-at-clock-value
(utils.clocks/send 0)))]
{:db (-> db
(assoc-in [:messages chat-id] {})
(update-in [:message-lists] dissoc chat-id)
(update :chats
(fn [chats]
(if (contains? chats chat-id)
(update chats
chat-id
merge
{:last-message nil
:unviewed-messages-count 0
:unviewed-mentions-count 0
:deleted-at-clock-value last-message-clock-value})
chats))))}))
(rf/defn clear-history-handler
"Clears history of the particular chat"
{:events [:chat.ui/clear-history]}
[{:keys [db] :as cofx} chat-id remove-chat?]
(rf/merge cofx
{:db db
:json-rpc/call [{:method "wakuext_clearHistory"
:params [{:id chat-id}]
:on-success #(re-frame/dispatch [::history-cleared chat-id %])
:on-error #(log/error "failed to clear history " chat-id %)}]}
(clear-history chat-id remove-chat?)))
(rf/defn chat-deactivated
{:events [::chat-deactivated]}
[_ chat-id]
(log/debug "chat deactivated" chat-id))
(rf/defn deactivate-chat
"Deactivate chat in db, no side effects"
[{:keys [db now] :as cofx} chat-id]
(rf/merge
cofx
{:db (-> (if (get-in db [:chats chat-id :muted])
(assoc-in db [:chats chat-id :active] false)
(update db :chats dissoc chat-id))
(update :chats-home-list disj chat-id)
(assoc :current-chat-id nil))
:json-rpc/call [{:method "wakuext_deactivateChat"
:params [{:id chat-id}]
:on-success #(re-frame/dispatch [::chat-deactivated chat-id])
:on-error #(log/error "failed to create public chat" chat-id %)}]}
(clear-history chat-id true)))
(rf/defn offload-messages
{:events [:offload-messages]}
[{:keys [db]} chat-id]
(merge {:db (-> db
(update :messages dissoc chat-id)
(update :message-lists dissoc chat-id)
(update :pagination-info dissoc chat-id))}
(when (and (= chat-id constants/timeline-chat-id) (= (:view-id db) :status))
{:dispatch [:init-timeline-chat]})))
(rf/defn close-chat
{:events [:close-chat]}
[{:keys [db] :as cofx} navigate-to-shell?]
(when-let [chat-id (:current-chat-id db)]
(chat.state/reset-visible-item)
(rf/merge cofx
(merge
{:db (dissoc db :current-chat-id)}
(let [community-id (get-in db [:chats chat-id :community-id])]
;; When navigating back from community chat to community, update switcher card
(when (and community-id (not navigate-to-shell?))
{:dispatch [:shell/add-switcher-card
:community {:community-id community-id}]})))
(delete-for-me/sync-all)
(delete-message/send-all)
(offload-messages chat-id))))
(rf/defn force-close-chat
[{:keys [db] :as cofx} chat-id]
(do
(chat.state/reset-visible-item)
(rf/merge cofx
{:db (dissoc db :current-chat-id)}
(offload-messages chat-id))))
(rf/defn remove-chat
"Removes chat completely from app, producing all necessary effects for that"
{:events [:chat.ui/remove-chat]}
[{:keys [db now] :as cofx} chat-id]
(rf/merge cofx
{:clear-message-notifications
[[chat-id] (get-in db [:multiaccount :remote-push-notifications-enabled?])]
:dispatch [:shell/close-switcher-card chat-id]}
(deactivate-chat chat-id)
(offload-messages chat-id)))
(rf/defn show-more-chats
{:events [:chat.ui/show-more-chats]}
[{:keys [db]}]
(when (< (:home-items-show-number db) (count (:chats db)))
{:db (update db :home-items-show-number + 40)}))
(rf/defn preload-chat-data
"Takes chat-id and coeffects map, returns effects necessary when navigating to chat"
{:events [:chat.ui/preload-chat-data]}
[cofx chat-id]
(loading/load-messages cofx chat-id))
(rf/defn navigate-to-chat
"Takes coeffects map and chat-id, returns effects necessary for navigation and preloading data"
{:events [:chat.ui/navigate-to-chat]}
[{db :db :as cofx} chat-id]
(rf/merge cofx
{:dispatch [:navigate-to :chat]}
(navigation/change-tab :chat)
(when-not (= (:view-id db) :community)
(navigation/pop-to-root-tab :chat-stack))
(close-chat false)
(force-close-chat chat-id)
(fn [{:keys [db]}]
{:db (assoc db :current-chat-id chat-id)})
(preload-chat-data chat-id)
#(when (group-chat? cofx chat-id)
(loading/load-chat % chat-id))))
(rf/defn navigate-to-chat-nav2
"Takes coeffects map and chat-id, returns effects necessary for navigation and preloading data"
{:events [:chat.ui/navigate-to-chat-nav2]}
[{db :db :as cofx} chat-id from-shell?]
(rf/merge cofx
{:dispatch [:navigate-to-nav2 :chat chat-id from-shell?]}
(when-not (= (:view-id db) :community)
(navigation/pop-to-root-tab :shell-stack))
(close-chat false)
(force-close-chat chat-id)
(fn [{:keys [db]}]
{:db (assoc db :current-chat-id chat-id)})
(preload-chat-data chat-id)
#(when (group-chat? cofx chat-id)
(loading/load-chat % chat-id))))
(rf/defn handle-clear-history-response
{:events [::history-cleared]}
[{:keys [db]} chat-id response]
(let [chat (chats-store/<-rpc (first (:chats response)))]
{:db (assoc-in db [:chats chat-id] chat)}))
(rf/defn handle-one-to-one-chat-created
{:events [::one-to-one-chat-created]}
[{:keys [db]} chat-id response]
(let [chat (chats-store/<-rpc (first (:chats response)))
contact-rpc (first (:contacts response))
contact (when contact-rpc (contacts-store/<-rpc contact-rpc))]
{:db (cond-> db
contact
(assoc-in [:contacts/contacts chat-id] contact)
:always
(assoc-in [:chats chat-id] chat)
:always
(update :chats-home-list conj chat-id))
:dispatch [:chat.ui/navigate-to-chat-nav2 chat-id]}))
(rf/defn navigate-to-user-pinned-messages
"Takes coeffects map and chat-id, returns effects necessary for navigation and preloading data"
{:events [:chat.ui/navigate-to-pinned-messages]}
[cofx chat-id]
(navigation/navigate-to cofx :chat-pinned-messages {:chat-id chat-id}))
(rf/defn start-chat
"Start a chat, making sure it exists"
{:events [:chat.ui/start-chat]}
[{:keys [db] :as cofx} chat-id ens-name]
;; don't allow to open chat with yourself
(when (not= (multiaccounts.model/current-public-key cofx) chat-id)
{:json-rpc/call [{:method "wakuext_createOneToOneChat"
:params [{:id chat-id :ensName ens-name}]
:on-success #(re-frame/dispatch [::one-to-one-chat-created chat-id %])
:on-error #(log/error "failed to create one-to-on chat" chat-id %)}]}))
(defn profile-chat-topic
[public-key]
(str "@" public-key))
(defn my-profile-chat-topic
[db]
(profile-chat-topic (get-in db [:multiaccount :public-key])))
(rf/defn handle-public-chat-created
{:events [::public-chat-created]}
@@ -14,7 +346,7 @@
{:db (-> db
(assoc-in [:chats chat-id] (chats-store/<-rpc (first (:chats response))))
(update :chats-home-list conj chat-id))
:dispatch [:chat/navigate-to-chat chat-id]})
:dispatch [:chat.ui/navigate-to-chat chat-id]})
(rf/defn create-public-chat-go
[_ chat-id]
@@ -28,8 +360,174 @@
{:events [:chat.ui/start-public-chat]}
[cofx topic]
(if (new-public-chat.db/valid-topic? topic)
(create-public-chat-go
cofx
topic)
(if (active-chat? cofx topic)
(navigate-to-chat cofx topic)
(create-public-chat-go
cofx
topic))
{:utils/show-popup {:title (i18n/label :t/cant-open-public-chat)
:content (i18n/label :t/invalid-public-chat-topic)}}))
(rf/defn profile-chat-created
{:events [::profile-chat-created]}
[{:keys [db] :as cofx} chat-id response navigate-to?]
(rf/merge
cofx
{:db db}
#(when response
(let [chat (chats-store/<-rpc (first (:chats response)))]
{:db (assoc-in db [:chats chat-id] chat)}))
#(when navigate-to?
{:dispatch-n [[:chat.ui/preload-chat-data chat-id]
[:open-modal :profile]]})))
(rf/defn start-profile-chat
"Starts a new profile chat"
{:events [:start-profile-chat]}
[cofx profile-public-key navigate-to?]
(let [chat-id (profile-chat-topic profile-public-key)]
(if (active-chat? cofx chat-id)
{:dispatch [::profile-chat-created chat-id nil navigate-to?]}
{:json-rpc/call [{:method "wakuext_createProfileChat"
:params [{:id profile-public-key}]
:on-success #(re-frame/dispatch [::profile-chat-created chat-id % navigate-to?])
:on-error #(log/error "failed to create profile chat" chat-id %)}]})))
(rf/defn disable-chat-cooldown
"Turns off chat cooldown (protection against message spamming)"
{:events [:chat/disable-cooldown]}
[{:keys [db]}]
{:db (assoc db :chat/cooldown-enabled? false)})
;; effects
(re-frame/reg-fx
:show-cooldown-warning
(fn [_]
(utils/show-popup nil
(i18n/label :cooldown/warning-message)
#())))
(rf/defn mute-chat-failed
{:events [::mute-chat-failed]}
[{:keys [db] :as cofx} chat-id muted? error]
(log/error "mute chat failed" chat-id error)
{:db (assoc-in db [:chats chat-id :muted] (not muted?))})
(rf/defn mute-chat-toggled-successfully
{:events [::mute-chat-toggled-successfully]}
[_ chat-id]
(log/debug "muted chat successfully" chat-id))
(rf/defn mute-chat
{:events [::mute-chat-toggled]}
[{:keys [db] :as cofx} chat-id muted?]
(let [method (if muted? "wakuext_muteChat" "wakuext_unmuteChat")]
{:db (assoc-in db [:chats chat-id :muted] muted?)
:json-rpc/call [{:method method
:params [chat-id]
:on-error #(re-frame/dispatch [::mute-chat-failed chat-id muted? %])
:on-success #(re-frame/dispatch [::mute-chat-toggled-successfully chat-id])}]}))
(rf/defn show-profile
{:events [:chat.ui/show-profile]}
[{:keys [db] :as cofx} identity ens-name]
(let [my-public-key (get-in db [:multiaccount :public-key])]
(when (not= my-public-key identity)
(rf/merge
cofx
{:db (-> db
(assoc :contacts/identity identity)
(assoc :contacts/ens-name ens-name))}
(start-profile-chat identity true)))))
(rf/defn clear-history-pressed
{:events [:chat.ui/clear-history-pressed]}
[_ chat-id]
{:ui/show-confirmation
{:title (i18n/label :t/clear-history-title)
:content (i18n/label :t/clear-history-confirmation-content)
:confirm-button-text (i18n/label :t/clear-history-action)
:on-accept #(do
(re-frame/dispatch [:bottom-sheet/hide])
(re-frame/dispatch [:chat.ui/clear-history chat-id false]))}})
(rf/defn gaps-failed
{:events [::gaps-failed]}
[{:keys [db]} chat-id gap-ids error]
(log/error "failed to fetch gaps" chat-id gap-ids error)
{:db (dissoc db :mailserver/fetching-gaps-in-progress)})
(rf/defn sync-chat-from-sync-from-failed
{:events [::sync-chat-from-sync-from-failed]}
[{:keys [db]} chat-id error]
(log/error "failed to sync chat" chat-id error)
{:db (dissoc db :mailserver/fetching-gaps-in-progress)})
(rf/defn sync-chat-from-sync-from-success
{:events [::sync-chat-from-sync-from-success]}
[{:keys [db] :as cofx} chat-id synced-from]
(log/debug "synced success" chat-id synced-from)
{:db
(-> db
(assoc-in [:chats chat-id :synced-from] synced-from)
(dissoc :mailserver/fetching-gaps-in-progress))})
(rf/defn gaps-filled
{:events [::gaps-filled]}
[{:keys [db] :as cofx} chat-id message-ids]
(rf/merge
cofx
{:db (-> db
(update-in [:messages chat-id] (fn [messages] (apply dissoc messages message-ids)))
(dissoc :mailserver/fetching-gaps-in-progress))}
(message-list/rebuild-message-list chat-id)))
(rf/defn fill-gaps
[cofx chat-id gap-ids]
{:json-rpc/call [{:method "wakuext_fillGaps"
:params [chat-id gap-ids]
:on-success #(re-frame/dispatch [::gaps-filled chat-id gap-ids %])
:on-error #(re-frame/dispatch [::gaps-failed chat-id gap-ids %])}]})
(rf/defn sync-chat-from-sync-from
[cofx chat-id]
(log/debug "syncing chat from sync from")
{:json-rpc/call [{:method "wakuext_syncChatFromSyncedFrom"
:params [chat-id]
:on-success #(re-frame/dispatch [::sync-chat-from-sync-from-success chat-id %])
:on-error #(re-frame/dispatch [::sync-chat-from-sync-from-failed chat-id %])}]})
(rf/defn chat-ui-fill-gaps
{:events [:chat.ui/fill-gaps]}
[{:keys [db] :as cofx} chat-id gap-ids]
(let [use-status-nodes? (mailserver/fetch-use-mailservers? {:db db})]
(log/info "filling gaps if use-status-nodes = true" chat-id gap-ids)
(when use-status-nodes?
(rf/merge cofx
{:db (assoc db :mailserver/fetching-gaps-in-progress gap-ids)}
(if (= gap-ids #{:first-gap})
(sync-chat-from-sync-from chat-id)
(fill-gaps chat-id gap-ids))))))
(rf/defn chat-ui-remove-chat-pressed
{:events [:chat.ui/remove-chat-pressed]}
[_ chat-id]
{:ui/show-confirmation
{:title (i18n/label :t/delete-confirmation)
:content (i18n/label :t/delete-chat-confirmation)
:confirm-button-text (i18n/label :t/delete)
:on-accept #(do
(re-frame/dispatch [:bottom-sheet/hide])
(re-frame/dispatch [:chat.ui/remove-chat chat-id]))}})
(rf/defn decrease-unviewed-count
{:events [:chat/decrease-unviewed-count]}
[{:keys [db]} chat-id {:keys [count countWithMentions]}]
{:db (-> db
;; There might be some other requests being fired,
;; so we need to make sure the count has not been set to
;; 0 in the meantime
(update-in [:chats chat-id :unviewed-messages-count]
#(max (- % count) 0))
(update-in [:chats chat-id :unviewed-mentions-count]
#(max (- % countWithMentions) 0)))})
-63
View File
@@ -1,63 +0,0 @@
(ns status-im.chat.models.gaps
(:require [utils.re-frame :as rf]
[taoensso.timbre :as log]
[status-im2.contexts.chat.messages.list.events :as message-list]
[status-im.mailserver.core :as mailserver]))
(rf/defn gaps-filled
{:events [:gaps/filled]}
[{:keys [db] :as cofx} chat-id message-ids]
(rf/merge
cofx
{:db (-> db
(update-in [:messages chat-id] (fn [messages] (apply dissoc messages message-ids)))
(dissoc :mailserver/fetching-gaps-in-progress))}
(message-list/rebuild-message-list chat-id)))
(rf/defn gaps-failed
{:events [:gaps/failed]}
[{:keys [db]} chat-id gap-ids error]
(log/error "failed to fetch gaps" chat-id gap-ids error)
{:db (dissoc db :mailserver/fetching-gaps-in-progress)})
(rf/defn sync-chat-from-sync-from-failed
{:events [::sync-chat-from-sync-from-failed]}
[{:keys [db]} chat-id error]
(log/error "failed to sync chat" chat-id error)
{:db (dissoc db :mailserver/fetching-gaps-in-progress)})
(rf/defn sync-chat-from-sync-from-success
{:events [::sync-chat-from-sync-from-success]}
[{:keys [db] :as cofx} chat-id synced-from]
(log/debug "synced success" chat-id synced-from)
{:db
(-> db
(assoc-in [:chats chat-id :synced-from] synced-from)
(dissoc :mailserver/fetching-gaps-in-progress))})
(rf/defn fill-gaps
[_ chat-id gap-ids]
{:json-rpc/call [{:method "wakuext_fillGaps"
:params [chat-id gap-ids]
:on-success #(rf/dispatch [:gaps/filled chat-id gap-ids %])
:on-error #(rf/dispatch [:gaps/failed chat-id gap-ids %])}]})
(rf/defn sync-chat-from-sync-from
[_ chat-id]
(log/debug "syncing chat from sync from")
{:json-rpc/call [{:method "wakuext_syncChatFromSyncedFrom"
:params [chat-id]
:on-success #(rf/dispatch [::sync-chat-from-sync-from-success chat-id %])
:on-error #(rf/dispatch [::sync-chat-from-sync-from-failed chat-id %])}]})
(rf/defn chat-ui-fill-gaps
{:events [:chat.ui/fill-gaps]}
[{:keys [db] :as cofx} chat-id gap-ids]
(let [use-status-nodes? (mailserver/fetch-use-mailservers? {:db db})]
(log/info "filling gaps if use-status-nodes = true" chat-id gap-ids)
(when use-status-nodes?
(rf/merge cofx
{:db (assoc db :mailserver/fetching-gaps-in-progress gap-ids)}
(if (= gap-ids #{:first-gap})
(sync-chat-from-sync-from chat-id)
(fill-gaps chat-id gap-ids))))))
+33 -22
View File
@@ -3,6 +3,7 @@
["react-native-blob-util" :default ReactNativeBlobUtil]
[clojure.string :as string]
[re-frame.core :as re-frame]
[status-im.chat.models :as chat]
[utils.i18n :as i18n]
[status-im.ui.components.permissions :as permissions]
[status-im.ui.components.react :as react]
@@ -97,10 +98,10 @@
(re-frame/reg-fx
::image-selected
(fn [[image chat-id]]
(fn [[uri chat-id]]
(resize-and-call
(:uri image)
#(re-frame/dispatch [:chat.ui/image-selected chat-id image %]))))
uri
#(re-frame/dispatch [:chat.ui/image-selected chat-id uri %]))))
(re-frame/reg-fx
::camera-roll-get-photos
@@ -111,18 +112,9 @@
(-> (if end-cursor
(.getPhotos
CameraRoll
#js
{:first num
:after end-cursor
:assetType "Photos"
:groupTypes "All"
:include (clj->js ["imageSize"])})
#js {:first num :after end-cursor :assetType "Photos" :groupTypes "All"})
(.getPhotos CameraRoll
#js
{:first num
:assetType "Photos"
:groupTypes "All"
:include (clj->js ["imageSize"])}))
#js {:first num :assetType "Photos" :groupTypes "All"}))
(.then #(let [response (types/js->clj %)]
(re-frame/dispatch [:on-camera-roll-get-photos (:edges response)
(:page_info response) end-cursor])))
@@ -159,7 +151,7 @@
[{:keys [db] :as cofx} photos page-info end-cursor]
(let [photos_x (when end-cursor (:camera-roll/photos db))]
{:db (-> db
(assoc :camera-roll/photos (concat photos_x (map #(get-in % [:node :image]) photos)))
(assoc :camera-roll/photos (concat photos_x (map #(get-in % [:node :image :uri]) photos)))
(assoc :camera-roll/end-cursor (:end_cursor page-info))
(assoc :camera-roll/has-next-page (:has_next_page page-info))
(assoc :camera-roll/loading-more false))}))
@@ -175,17 +167,21 @@
(let [current-chat-id (or chat-id (:current-chat-id db))]
(clear-sending-images cofx current-chat-id)))
(rf/defn cancel-sending-image-timeline
{:events [:chat.ui/cancel-sending-image-timeline]}
[{:keys [db] :as cofx}]
(cancel-sending-image cofx (chat/my-profile-chat-topic db)))
(rf/defn image-selected
{:events [:chat.ui/image-selected]}
[{:keys [db]} current-chat-id original uri]
{:db
(update-in db [:chat/inputs current-chat-id :metadata :sending-image uri] merge original {:uri uri})})
{:db (update-in db [:chat/inputs current-chat-id :metadata :sending-image original] merge {:uri uri})})
(rf/defn image-unselected
{:events [:chat.ui/image-unselected]}
[{:keys [db]} original]
(let [current-chat-id (:current-chat-id db)]
{:db (update-in db [:chat/inputs current-chat-id :metadata :sending-image] dissoc (:uri original))}))
{:db (update-in db [:chat/inputs current-chat-id :metadata :sending-image] dissoc original)}))
(rf/defn chat-open-image-picker
{:events [:chat.ui/open-image-picker]}
@@ -195,6 +191,11 @@
(when (< (count images) config/max-images-batch)
{::chat-open-image-picker current-chat-id})))
(rf/defn chat-open-image-picker-timeline
{:events [:chat.ui/open-image-picker-timeline]}
[{:keys [db] :as cofx}]
(chat-open-image-picker cofx (chat/my-profile-chat-topic db)))
(rf/defn chat-show-image-picker-camera
{:events [:chat.ui/show-image-picker-camera]}
[{:keys [db]} chat-id]
@@ -203,17 +204,27 @@
(when (< (count images) config/max-images-batch)
{::chat-open-image-picker-camera current-chat-id})))
(rf/defn chat-show-image-picker-camera-timeline
{:events [:chat.ui/show-image-picker-camera-timeline]}
[{:keys [db] :as cofx}]
(chat-show-image-picker-camera cofx (chat/my-profile-chat-topic db)))
(rf/defn camera-roll-pick
{:events [:chat.ui/camera-roll-pick]}
[{:keys [db]} image chat-id]
[{:keys [db]} uri chat-id]
(let [current-chat-id (or chat-id (:current-chat-id db))
images (get-in db [:chat/inputs current-chat-id :metadata :sending-image])]
(if (get-in db [:chats current-chat-id :timeline?])
{:db (assoc-in db [:chat/inputs current-chat-id :metadata :sending-image] {})
::image-selected [image current-chat-id]}
::image-selected [uri current-chat-id]}
(when (and (< (count images) config/max-images-batch)
(not (some #(= (:uri image) (:uri %)) images)))
{::image-selected [image current-chat-id]}))))
(not (get images uri)))
{::image-selected [uri current-chat-id]}))))
(rf/defn camera-roll-pick-timeline
{:events [:chat.ui/camera-roll-pick-timeline]}
[{:keys [db] :as cofx} uri]
(camera-roll-pick cofx uri (chat/my-profile-chat-topic db)))
(rf/defn save-image-to-gallery
{:events [:chat.ui/save-image-to-gallery]}
+74 -27
View File
@@ -3,12 +3,14 @@
[clojure.string :as string]
[goog.object :as object]
[re-frame.core :as re-frame]
[status-im.chat.models :as chat]
[status-im.chat.models.mentions :as mentions]
[status-im.chat.models.message :as chat.message]
[status-im.chat.models.message-content :as message-content]
[status-im2.constants :as constants]
[utils.re-frame :as rf]
[utils.i18n :as i18n]
[utils.datetime :as datetime]
[status-im.utils.utils :as utils]
[taoensso.timbre :as log]
[status-im.ui.screens.chat.components.input :as input]))
@@ -23,14 +25,6 @@
(.-char ^js emoji-map)
original))))
;; effects
(re-frame/reg-fx
:show-cooldown-warning
(fn [_]
(utils/show-popup nil
(i18n/label :cooldown/warning-message)
#())))
(rf/defn set-chat-input-text
"Set input text for current-chat. Takes db and input text and cofx
as arguments and returns new fx. Always clear all validation messages."
@@ -39,6 +33,13 @@
(let [current-chat-id (or chat-id (:current-chat-id db))]
{:db (assoc-in db [:chat/inputs current-chat-id :input-text] (text->emoji new-input))}))
(rf/defn set-timeline-input-text
{:events [:chat.ui/set-timeline-input-text]}
[{db :db} new-input]
{:db (assoc-in db
[:chat/inputs (chat/my-profile-chat-topic db) :input-text]
(text->emoji new-input))})
(rf/defn select-mention
{:events [:chat.ui/select-mention]}
[{:keys [db] :as cofx} text-input-ref {:keys [alias name searched-text match] :as user}]
@@ -73,11 +74,45 @@
:end end}))
(mentions/recheck-at-idxs {alias user}))))
(rf/defn disable-chat-cooldown
"Turns off chat cooldown (protection against message spamming)"
{:events [:chat/disable-cooldown]}
[{:keys [db]}]
{:db (assoc db :chat/cooldown-enabled? false)})
(defn- start-cooldown
[{:keys [db]} cooldowns]
{:dispatch-later [{:dispatch [:chat/disable-cooldown]
:ms (constants/cooldown-periods-ms
cooldowns
constants/default-cooldown-period-ms)}]
:show-cooldown-warning nil
:db (assoc db
:chat/cooldowns (if
(=
constants/cooldown-reset-threshold
cooldowns)
0
cooldowns)
:chat/spam-messages-frequency 0
:chat/cooldown-enabled? true)})
(rf/defn process-cooldown
"Process cooldown to protect against message spammers"
[{{:keys [chat/last-outgoing-message-sent-at
chat/cooldowns
chat/spam-messages-frequency
current-chat-id]
:as db}
:db
:as cofx}]
(when (chat/public-chat? cofx current-chat-id)
(let [spamming-fast? (< (- (datetime/timestamp) last-outgoing-message-sent-at)
(+ constants/spam-interval-ms (* 1000 cooldowns)))
spamming-frequently? (= constants/spam-message-frequency-threshold
spam-messages-frequency)]
(cond-> {:db (assoc db
:chat/last-outgoing-message-sent-at (datetime/timestamp)
:chat/spam-messages-frequency (if spamming-fast?
(inc spam-messages-frequency)
0))}
(and spamming-fast? spamming-frequently?)
(start-cooldown (inc cooldowns))))))
(rf/defn reply-to-message
"Sets reference to previous chat message and focuses on input"
@@ -147,20 +182,15 @@
:ens-name preferred-name})))
(defn build-image-messages
[{db :db} chat-id input-text]
[{db :db} chat-id]
(let [images (get-in db [:chat/inputs chat-id :metadata :sending-image])
album-id (str (random-uuid))]
(mapv (fn [[_ {:keys [uri width height]}]]
(mapv (fn [[_ {:keys [uri]}]]
{:chat-id chat-id
:album-id album-id
:content-type constants/content-type-image
:image-path (utils/safe-replace uri #"file://" "")
:image-width width
:image-height height
;; TODO: message not received if text field is
;; nil or empty, issue:
;; https://github.com/status-im/status-mobile/issues/14754
:text (or input-text "placeholder")})
:text (i18n/label :t/update-to-see-image {"locale" "en"})})
images)))
(rf/defn clean-input
@@ -186,13 +216,27 @@
(rf/defn send-messages
[{:keys [db] :as cofx} input-text current-chat-id]
(let [image-messages (build-image-messages cofx current-chat-id input-text)
text-message (when-not (seq image-messages)
(build-text-message cofx input-text current-chat-id))
(let [image-messages (build-image-messages cofx current-chat-id)
text-message (build-text-message cofx input-text current-chat-id)
messages (keep identity (conj image-messages text-message))]
(when (seq messages)
(rf/merge cofx
(clean-input (:current-chat-id db))
(process-cooldown)
(chat.message/send-messages messages)))))
(rf/defn send-my-status-message
"when not empty, proceed by sending text message with public key topic"
{:events [:profile.ui/send-my-status-message]}
[{db :db :as cofx}]
(let [current-chat-id (chat/my-profile-chat-topic db)
{:keys [input-text]} (get-in db [:chat/inputs current-chat-id])
image-messages (build-image-messages cofx current-chat-id)
text-message (build-text-message cofx input-text current-chat-id)
messages (keep identity (conj image-messages text-message))]
(when (seq messages)
(rf/merge cofx
(clean-input current-chat-id)
(chat.message/send-messages messages)))))
(rf/defn send-audio-message
@@ -230,7 +274,8 @@
:js-response true
:on-error #(log/error "failed to edit message " %)
:on-success #(re-frame/dispatch [:sanitize-messages-and-process-response %])}]}
(cancel-message-edit)))
(cancel-message-edit)
(process-cooldown)))
(rf/defn send-current-message
"Sends message from current chat input"
@@ -259,7 +304,8 @@
:on-success #(re-frame/dispatch [:transport/message-sent %])}]}
(mentions/clear-mentions)
(mentions/clear-cursor)
(clean-input (:current-chat-id db))))
(clean-input (:current-chat-id db))
(process-cooldown)))
(rf/defn cancel-contact-request
"Cancels contact request"
@@ -270,7 +316,8 @@
{:db (assoc-in db [:chat/inputs current-chat-id :metadata :sending-contact-request] nil)}
(mentions/clear-mentions)
(mentions/clear-cursor)
(clean-input (:current-chat-id db)))))
(clean-input (:current-chat-id db))
(process-cooldown))))
(rf/defn chat-send-sticker
{:events [:chat/send-sticker]}
+72 -2
View File
@@ -1,9 +1,79 @@
(ns status-im.chat.models.input-test
(:require [cljs.test :refer-macros [deftest is]]
[status-im.chat.models.input :as input]))
(:require [cljs.test :refer-macros [deftest is testing]]
[status-im2.constants :as constants]
[status-im.chat.models.input :as input]
[utils.datetime :as datetime]))
(deftest text->emoji
(is (nil? (input/text->emoji nil)))
(is (= "" (input/text->emoji "")))
(is (= "test" (input/text->emoji "test")))
(is (= "word1 \uD83D\uDC4D word2" (input/text->emoji "word1 :+1: word2"))))
(deftest process-cooldown-fx
(let [db {:current-chat-id "chat"
:chats {"chat" {:public? true}}
:chat/cooldowns 0
:chat/spam-messages-frequency 0
:chat/cooldown-enabled? false}]
(with-redefs [datetime/timestamp (constantly 1527675198542)]
(testing "no spamming detected"
(let [expected {:db (assoc db :chat/last-outgoing-message-sent-at 1527675198542)}
actual (input/process-cooldown {:db db})]
(is (= expected actual))))
(testing "spamming detected in 1-1"
(let [db (assoc db
:chats {"chat" {:public? false}}
:chat/spam-messages-frequency constants/spam-message-frequency-threshold
:chat/last-outgoing-message-sent-at (- 1527675198542 900))
expected nil
actual (input/process-cooldown {:db db})]
(is (= expected actual))))
(testing "spamming detected"
(let [db (assoc db
:chat/last-outgoing-message-sent-at (- 1527675198542 900)
:chat/spam-messages-frequency constants/spam-message-frequency-threshold)
expected {:db (assoc db
:chat/last-outgoing-message-sent-at 1527675198542
:chat/cooldowns 1
:chat/spam-messages-frequency 0
:chat/cooldown-enabled? true)
:show-cooldown-warning nil
:dispatch-later [{:dispatch [:chat/disable-cooldown]
:ms (constants/cooldown-periods-ms 1)}]}
actual (input/process-cooldown {:db db})]
(is (= expected actual))))
(testing "spamming detected twice"
(let [db (assoc db
:chat/cooldowns 1
:chat/last-outgoing-message-sent-at (- 1527675198542 900)
:chat/spam-messages-frequency constants/spam-message-frequency-threshold)
expected {:db (assoc db
:chat/last-outgoing-message-sent-at 1527675198542
:chat/cooldowns 2
:chat/spam-messages-frequency 0
:chat/cooldown-enabled? true)
:show-cooldown-warning nil
:dispatch-later [{:dispatch [:chat/disable-cooldown]
:ms (constants/cooldown-periods-ms 2)}]}
actual (input/process-cooldown {:db db})]
(is (= expected actual))))
(testing "spamming reaching cooldown threshold"
(let [db (assoc db
:chat/cooldowns (dec constants/cooldown-reset-threshold)
:chat/last-outgoing-message-sent-at (- 1527675198542 900)
:chat/spam-messages-frequency constants/spam-message-frequency-threshold)
expected {:db (assoc db
:chat/last-outgoing-message-sent-at 1527675198542
:chat/cooldowns 0
:chat/spam-messages-frequency 0
:chat/cooldown-enabled? true)
:show-cooldown-warning nil
:dispatch-later [{:dispatch [:chat/disable-cooldown]
:ms (constants/cooldown-periods-ms 3)}]}
actual (input/process-cooldown {:db db})]
(is (= expected actual)))))))
+5 -4
View File
@@ -4,6 +4,7 @@
[status-im2.constants :as constants]
[status-im.data-store.chats :as data-store.chats]
[status-im.data-store.messages :as data-store.messages]
[status-im2.contexts.activity-center.events :as activity-center]
[taoensso.timbre :as log]
[utils.re-frame :as rf]))
@@ -68,15 +69,15 @@
(rf/defn handle-mark-all-read-successful
{:events [::mark-all-read-successful]}
[_]
{:dispatch [:activity-center.notifications/fetch-unread-count]})
[cofx]
(activity-center/notifications-fetch-unread-count cofx))
(rf/defn handle-mark-all-read-in-community-successful
{:events [::mark-all-read-in-community-successful]}
[{:keys [db] :as cofx} chat-ids]
(rf/merge cofx
{:db (reduce mark-chat-all-read db chat-ids)
:dispatch [:activity-center.notifications/fetch-unread-count]}))
{:db (reduce mark-chat-all-read db chat-ids)}
(activity-center/notifications-fetch-unread-count)))
(rf/defn handle-mark-all-read
{:events [:chat.ui/mark-all-read-pressed :chat/mark-all-as-read]}
+40
View File
@@ -1,9 +1,11 @@
(ns status-im.chat.models.message
(:require [clojure.string :as string]
[re-frame.core :as re-frame]
[status-im.chat.models :as chat-model]
[status-im.chat.models.loading :as chat.loading]
[status-im.chat.models.mentions :as mentions]
[status-im2.contexts.chat.messages.list.events :as message-list]
[status-im2.constants :as constants]
[status-im.data-store.messages :as data-store.messages]
[status-im.transport.message.protocol :as protocol]
[status-im2.contexts.chat.messages.list.state :as view.state]
@@ -54,6 +56,20 @@
{:db db}
messages))
(defn timeline-message?
[db chat-id]
(and
(get-in db [:pagination-info constants/timeline-chat-id :messages-initialized?])
(or
(= chat-id (chat-model/my-profile-chat-topic db))
(when-let [pub-key (get-in db [:chats chat-id :profile-public-key])]
(get-in db [:contacts/contacts pub-key :added])))))
(defn get-timeline-message
[db chat-id message-js]
(when (timeline-message? db chat-id)
(data-store.messages/<-rpc (types/js->clj message-js))))
(defn add-message
[{:keys [db] :as acc} message-js chat-id message-id cursor-clock-value]
(let [{:keys [replace from clock-value] :as message}
@@ -132,6 +148,30 @@
(when (seq senders)
[{:ms 100 :dispatch [:chat/add-senders-to-chat-users (vals senders)]}]))}))
(defn reduce-js-statuses
[db ^js message-js]
(let [chat-id (.-localChatId message-js)
profile-initialized (get-in db [:pagination-info chat-id :messages-initialized?])
timeline-message (timeline-message? db chat-id)
old-message (get-in db [:messages chat-id (.-id message-js)])]
(if (and (or profile-initialized timeline-message) (nil? old-message))
(let [{:keys [message-id] :as message} (data-store.messages/<-rpc (types/js->clj message-js))]
(cond-> db
profile-initialized
(update-in [:messages chat-id] assoc message-id message)
profile-initialized
(update-in [:message-lists chat-id] message-list/add message)
timeline-message
(update-in [:messages constants/timeline-chat-id] assoc message-id message)
timeline-message
(update-in [:message-lists constants/timeline-chat-id] message-list/add message)))
db)))
(rf/defn process-statuses
{:events [:process-statuses]}
[{:keys [db]} statuses]
{:db (reduce reduce-js-statuses db statuses)})
(rf/defn update-db-message-status
[{:keys [db] :as cofx} chat-id message-id status]
(when (get-in db [:messages chat-id message-id])
@@ -1,6 +1,6 @@
(ns status-im2.contexts.chat.events-test
(ns status-im.chat.models-test
(:require [cljs.test :refer-macros [deftest is testing]]
[status-im2.contexts.chat.events :as chat]
[status-im.chat.models :as chat]
[status-im.chat.models.images :as images]
[status-im.utils.clocks :as utils.clocks]))
@@ -49,7 +49,10 @@
:chats {chat-id {:last-message {:clock-value 10}}}}}]
(testing "it deletes all the messages"
(let [actual (chat/remove-chat cofx chat-id)]
(is (= nil (get-in actual [:db :messages chat-id])))))))
(is (= nil (get-in actual [:db :messages chat-id])))))
#_(testing "it sets a deleted-at-clock-value equal to the last message clock-value"
(let [actual (chat/remove-chat cofx chat-id)]
(is (= 10 (get-in actual [:db :chats chat-id :deleted-at-clock-value])))))))
(deftest multi-user-chat?
(let [chat-id "1"]
@@ -84,11 +87,11 @@
"opened" {}
"1-1" {}}})
(deftest navigate-to-chat
(deftest navigate-to-chat-nav2
(let [chat-id "test_chat"
db {:pagination-info {chat-id {:all-loaded? true}}}]
(testing "Pagination info should be reset on navigation"
(let [res (chat/navigate-to-chat {:db db} chat-id false)]
(let [res (chat/navigate-to-chat-nav2 {:db db} chat-id false)]
(is (nil? (get-in res [:db :pagination-info chat-id :all-loaded?])))))))
(deftest camera-roll-loading-more-test
+6 -10
View File
@@ -558,20 +558,16 @@
(rf/defn request-to-join-accepted
{:events [::request-to-join-accepted]}
[{:keys [db] :as cofx} community-id request-id response-js]
(rf/merge
cofx
{:db (update-in db [:communities/requests-to-join community-id] dissoc request-id)
:dispatch-n [[:sanitize-messages-and-process-response response-js]
[:activity-center.notifications/mark-as-read request-id]]}))
(rf/merge cofx
{:db (update-in db [:communities/requests-to-join community-id] dissoc request-id)}
(handle-response response-js)))
(rf/defn request-to-join-declined
{:events [::request-to-join-declined]}
[{:keys [db] :as cofx} community-id request-id response-js]
(rf/merge
cofx
{:db (update-in db [:communities/requests-to-join community-id] dissoc request-id)
:dispatch-n [[:sanitize-messages-and-process-response response-js]
[:activity-center.notifications/mark-as-read request-id]]}))
(rf/merge cofx
{:db (update-in db [:communities/requests-to-join community-id] dissoc request-id)}
(handle-response response-js)))
(rf/defn accept-request-to-join-pressed
{:events [:communities.ui/accept-request-to-join-pressed]}
+1 -1
View File
@@ -76,7 +76,7 @@
(re-frame/dispatch [:hide-popover])))
;; reset navigation to avoid going back to non existing one to one chat
(if from-one-to-one-chat?
(navigation/pop-to-root-tab :shell-stack)
(navigation/pop-to-root-tab :chat-stack)
(navigation/navigate-back)))))
(rf/defn contact-unblocked
+13 -1
View File
@@ -1,9 +1,16 @@
(ns status-im.contact.chat
(:require [re-frame.core :as re-frame]
[status-im.chat.models :as chat]
[status-im.contact.core :as contact]
[utils.re-frame :as rf]
[status-im2.navigation.events :as navigation]))
(rf/defn send-message-pressed
{:events [:contact.ui/send-message-pressed]
:interceptors [(re-frame/inject-cofx :random-id-generator)]}
[cofx {:keys [public-key ens-name]}]
(chat/start-chat cofx public-key ens-name))
(rf/defn contact-code-submitted
{:events [:contact.ui/contact-code-submitted]
:interceptors [(re-frame/inject-cofx :random-id-generator)]}
@@ -12,6 +19,11 @@
(rf/merge cofx
#(if new-contact?
(contact/add-contact % public-key nickname ens-name)
{:dispatch [:chat.ui/start-chat public-key ens-name]})
(chat/start-chat % public-key ens-name))
#(when new-contact?
(navigation/navigate-back %)))))
(rf/defn pinned-messages-pressed
{:events [:contact.ui/pinned-messages-pressed]}
[cofx public-key]
(chat/navigate-to-user-pinned-messages cofx public-key))
+5 -3
View File
@@ -39,6 +39,8 @@
blocked (:blocked contact)
was-blocked (contact.db/blocked? db public-key)]
(cond-> acc
(and added (not was-added))
(conj [:start-profile-chat public-key])
(and (not (:has-added-us contact))
(= constants/contact-request-state-none (:contact-request-state contact)))
@@ -49,7 +51,7 @@
(and blocked (not was-blocked))
(conj [::contact.block/contact-blocked contact chats]))))
[[:chat/offload-messages constants/timeline-chat-id]
[[:offload-messages constants/timeline-chat-id]
[:activity-center.notifications/fetch-unread-count]]
contacts)]
(merge
@@ -91,7 +93,7 @@
ens-name
#(do
(re-frame/dispatch [:sanitize-messages-and-process-response %])
(re-frame/dispatch [:chat/offload-messages constants/timeline-chat-id])))))
(re-frame/dispatch [:offload-messages constants/timeline-chat-id])))))
(rf/defn remove-contact
"Remove a contact from current account's contact list"
@@ -107,7 +109,7 @@
{:method "wakuext_retractContactRequest"
:params [{:contactId public-key}]
:on-success #(log/debug "contact removed successfully")}]
:dispatch [:chat/offload-messages constants/timeline-chat-id]})
:dispatch [:offload-messages constants/timeline-chat-id]})
(rf/defn accept-contact-request
{:events [:contact-requests.ui/accept-request]}
-2
View File
@@ -36,8 +36,6 @@
:deleted :deleted?
:deletedForMe :deleted-for-me?
:albumId :album-id
:imageWidth :image-width
:imageHeight :image-height
:new :new?})
(update :quoted-message
@@ -9,7 +9,6 @@
(set/rename-keys visibility-status-update
{:publicKey :public-key
:statusType :status-type}))
(defn <-rpc-settings
[settings]
(-> settings
+9 -10
View File
@@ -35,16 +35,15 @@
:on-error #(cb "0x")}))
(defn pubkey
([chain-id ens-name on-success on-error]
{:pre [(is-valid-eth-name? ens-name)]}
(json-rpc/call {:method "ens_publicKeyOf"
:params [chain-id ens-name]
:on-success on-success
:on-error on-error}))
;; At some point, infura started to return "execution reverted" error
;; instead of "0x" result. Our code expects "0x" result.
([chain-id ens-name cb]
(pubkey chain-id ens-name cb #(cb "0x"))))
[chain-id ens-name cb]
{:pre [(is-valid-eth-name? ens-name)]}
(json-rpc/call {:method "ens_publicKeyOf"
:params [chain-id ens-name]
:on-success cb
;;at some point infura started to return execution reverted error instead of "0x"
;;result
;;our code expects "0x" result
:on-error #(cb "0x")}))
(defn owner
[chain-id ens-name cb]
+12 -8
View File
@@ -2,12 +2,13 @@
(:require
clojure.set
[re-frame.core :as re-frame]
status-im.add-new.core
[status-im.async-storage.core :as async-storage]
status-im.backup.core
status-im.bootnodes.core
status-im.browser.core
status-im.browser.permissions
status-im.chat.models
[status-im.chat.models :as chat]
status-im.chat.models.images
status-im.chat.models.input
status-im.chat.models.loading
@@ -111,13 +112,10 @@
(rf/defn system-theme-mode-changed
{:events [:system-theme-mode-changed]}
[{:keys [db] :as cofx} _]
(let [current-theme-type (get-in cofx [:db :multiaccount :appearance])]
(when (and (multiaccounts.model/logged-in? cofx)
(= current-theme-type status-im2.constants/theme-type-system))
{:multiaccounts.ui/switch-theme
[(get-in db [:multiaccount :appearance])
(:view-id db) true]})))
[cofx _]
(when (multiaccounts.model/logged-in? cofx)
{:multiaccounts.ui/switch-theme (get-in cofx [:db :multiaccount :appearance])
:dispatch [:reload-new-ui]}))
(def authentication-options
{:reason (i18n/label :t/biometric-auth-reason-login)})
@@ -186,6 +184,12 @@
[{:keys [db]} dimensions]
{:db (assoc db :dimensions/window (dimensions/window dimensions))})
(rf/defn init-timeline-chat
{:events [:init-timeline-chat]}
[{:keys [db] :as cofx}]
(when-not (get-in db [:pagination-info constants/timeline-chat-id :messages-initialized?])
(chat/preload-chat-data cofx constants/timeline-chat-id)))
(rf/defn on-will-focus
{:events [:screens/on-will-focus]}
[{:keys [db] :as cofx} view-id]
+6 -6
View File
@@ -5,7 +5,7 @@
[utils.i18n :as i18n]
[oops.core :as oops]
[re-frame.core :as re-frame]
[status-im2.contexts.chat.events :as chat.events]
[status-im.chat.models :as models.chat]
[status-im2.constants :as constants]
[status-im2.contexts.activity-center.events :as activity-center]
[status-im2.navigation.events :as navigation]
@@ -15,7 +15,7 @@
{:events [:navigate-chat-updated]}
[cofx chat-id]
(when (get-in cofx [:db :chats chat-id])
(chat.events/navigate-to-chat cofx chat-id nil)))
(models.chat/navigate-to-chat-nav2 cofx chat-id nil)))
(rf/defn handle-chat-removed
{:events [:chat-removed]}
@@ -23,7 +23,7 @@
(rf/merge cofx
{:db (dissoc (:db cofx) :current-chat-id)
:dispatch-n [[:sanitize-messages-and-process-response response]
[:pop-to-root-tab :shell-stack]]}
[:pop-to-root-tab :chat-stack]]}
(activity-center/notifications-fetch-unread-count)))
(rf/defn handle-chat-update
@@ -76,7 +76,7 @@
(rf/defn create-from-link
[cofx {:keys [chat-id invitation-admin chat-name]}]
(if (get-in cofx [:db :chats chat-id])
{:dispatch [:chat/navigate-to-chat chat-id]}
{:dispatch [:chat.ui/navigate-to-chat-nav2 chat-id]}
{:json-rpc/call [{:method "wakuext_createGroupChatFromInvitation"
:params [chat-name chat-id invitation-admin]
:js-response true
@@ -123,8 +123,8 @@
{:events [:group-chats.ui/remove-chat-confirmed]}
[cofx chat-id]
(rf/merge cofx
(chat.events/deactivate-chat chat-id)
(navigation/pop-to-root-tab :shell-stack)))
(models.chat/deactivate-chat chat-id)
(navigation/pop-to-root-tab :chat-stack)))
(def not-blank?
(complement string/blank?))
+21 -17
View File
@@ -3,9 +3,9 @@
[clojure.string :as string]
[day8.re-frame.test :as rf-test]
[re-frame.core :as rf]
[status-im.chat.models :as chat.models]
[status-im.ethereum.core :as ethereum]
status-im.events
status-im2.events
[status-im.multiaccounts.logout.core :as logout]
[status-im.transport.core :as transport]
[status-im.utils.test :as utils.test]
@@ -250,8 +250,8 @@
(assert-messenger-started)
(rf/dispatch-sync [:chat.ui/start-chat chat-id]) ;; start a new chat
(rf-test/wait-for
[:chat/one-to-one-chat-created]
(rf/dispatch-sync [:chat/navigate-to-chat chat-id])
[:status-im.chat.models/one-to-one-chat-created]
(rf/dispatch-sync [:chat.ui/navigate-to-chat-nav2 chat-id])
(is (= chat-id @(rf/subscribe [:chats/current-chat-id])))
(logout!)
(rf-test/wait-for [::logout/logout-method] ; we need to logout to make sure the node is not in
@@ -275,17 +275,20 @@
(assert-messenger-started)
(rf/dispatch-sync [:chat.ui/start-chat chat-id]) ;; start a new chat
(rf-test/wait-for
[:chat/one-to-one-chat-created]
(rf/dispatch-sync [:chat/navigate-to-chat chat-id])
[:status-im.chat.models/one-to-one-chat-created]
(rf/dispatch-sync [:chat.ui/navigate-to-chat-nav2 chat-id])
(is (= chat-id @(rf/subscribe [:chats/current-chat-id])))
(is @(rf/subscribe [:chats/chat chat-id]))
(rf/dispatch-sync [:chat.ui/show-remove-confirmation chat-id])
(rf/dispatch-sync [:chat.ui/remove-chat-pressed chat-id])
(rf/dispatch-sync [:chat.ui/remove-chat chat-id])
(logout!)
(rf-test/wait-for [::logout/logout-method] ; we need to logout to make sure the node is not
; in an
; inconsistent state between tests
(assert-logout))))))))
(rf-test/wait-for
[::chat.models/chat-deactivated]
(is (not @(rf/subscribe [:chats/chat chat-id])))
(logout!)
(rf-test/wait-for [::logout/logout-method] ; we need to logout to make sure the node is not
; in an
; inconsistent state between tests
(assert-logout)))))))))
(deftest mute-chat-test
(log/info "========= mute-chat-test ==================")
@@ -303,17 +306,18 @@
(assert-messenger-started)
(rf/dispatch-sync [:chat.ui/start-chat chat-id]) ;; start a new chat
(rf-test/wait-for
[:chat/one-to-one-chat-created]
(rf/dispatch-sync [:chat/navigate-to-chat chat-id])
[:status-im.chat.models/one-to-one-chat-created]
(rf/dispatch-sync [:chat.ui/navigate-to-chat-nav2 chat-id])
(is (= chat-id @(rf/subscribe [:chats/current-chat-id])))
(is @(rf/subscribe [:chats/chat chat-id]))
(rf/dispatch-sync [:chat.ui/mute chat-id true])
(rf/dispatch-sync [::chat.models/mute-chat-toggled chat-id true])
(rf-test/wait-for
[:chat/mute-successfully]
[::chat.models/mute-chat-toggled-successfully]
(is @(rf/subscribe [:chats/muted chat-id]))
(rf/dispatch-sync [:chat.ui/mute chat-id false])
(rf/dispatch-sync [::chat.models/mute-chat-toggled chat-id false])
(rf-test/wait-for
[:chat/mute-successfully]
[::chat.models/mute-chat-toggled-successfully]
(is (not @(rf/subscribe [:chats/muted chat-id])))
(logout!)
(rf-test/wait-for [::logout/logout-method] ; we need to logout to make sure the node is
+7 -21
View File
@@ -8,15 +8,11 @@
[status-im.native-module.core :as native-module]
[status-im.theme.core :as theme]
[utils.re-frame :as rf]
[quo2.foundations.colors :as colors]
[status-im2.constants :as constants]
[status-im.utils.gfycat.core :as gfycat]
[status-im.utils.identicon :as identicon]
[status-im.utils.utils :as utils]
[status-im2.setup.hot-reload :as hot-reload]
[status-im2.common.theme.core :as utils.theme]
[taoensso.timbre :as log]
[status-im2.contexts.shell.animation :as shell.animation]))
[taoensso.timbre :as log]))
;; validate that the given mnemonic was generated from Status Dictionary
(re-frame/reg-fx
@@ -177,27 +173,17 @@
(re-frame/reg-fx
:multiaccounts.ui/switch-theme
(fn [[theme-type view-id reload-ui?]]
(let [[theme status-bar-theme nav-bar-color]
;; Status bar theme represents status bar icons colors, so opposite to app theme
(if (or (= theme-type constants/theme-type-dark)
(and (= theme-type constants/theme-type-system)
(utils.theme/dark-mode?)))
[:dark :light colors/neutral-100]
[:light :dark colors/white])]
(theme/change-theme theme)
(re-frame/dispatch [:change-root-status-bar-style
(if (shell.animation/home-stack-open?) status-bar-theme :light)])
(when reload-ui?
(hot-reload/reload)
(when-not (= view-id :shell-stack)
(re-frame/dispatch [:change-root-nav-bar-color nav-bar-color]))))))
(fn [theme-id]
(let [theme (if (or (= 2 theme-id) (and (= 0 theme-id) (utils.theme/dark-mode?)))
:dark
:light)]
(theme/change-theme theme))))
(rf/defn switch-appearance
{:events [:multiaccounts.ui/appearance-switched]}
[cofx theme]
(rf/merge cofx
{:multiaccounts.ui/switch-theme [theme :appearance true]}
{:multiaccounts.ui/switch-theme theme}
(multiaccounts.update/multiaccount-update :appearance theme {})))
(rf/defn switch-profile-picture-show-to
+14 -7
View File
@@ -42,7 +42,7 @@
[status-im2.contexts.activity-center.events :as activity-center]
[status-im2.contexts.chat.messages.link-preview.events :as link-preview]
[status-im2.navigation.events :as navigation]
[status-im2.common.log :as logging]
[status-im2.setup.log :as logging]
[taoensso.timbre :as log]
[utils.security.core :as security]))
@@ -351,7 +351,7 @@
(rf/defn initialize-appearance
[cofx]
{:multiaccounts.ui/switch-theme [(get-in cofx [:db :multiaccount :appearance]) nil false]})
{:multiaccounts.ui/switch-theme (get-in cofx [:db :multiaccount :appearance])})
(rf/defn get-group-chat-invitations
[_]
@@ -424,7 +424,7 @@
:key-uid
(fn [stored-key-uid]
(when (= stored-key-uid key-uid)
(re-frame/dispatch [:chat/navigate-to-chat chat-id])))))))))
(re-frame/dispatch [:chat.ui/navigate-to-chat-nav2 chat-id])))))))))
(rf/defn check-last-chat
{:events [::check-last-chat]}
@@ -490,7 +490,7 @@
"Decides which root should be initialised depending on user and app state"
[db]
(if (get db :tos/accepted?)
(re-frame/dispatch [:init-root :shell-stack])
(re-frame/dispatch [:init-root (if config/new-ui-enabled? :shell-stack :chat-stack)])
(re-frame/dispatch [:init-root :tos])))
(rf/defn login-only-events
@@ -539,7 +539,14 @@
(multiaccounts/switch-preview-privacy-mode-flag)
(link-preview/request-link-preview-whitelist)
(logging/set-log-level (:log-level multiaccount))
(navigation/init-root :shell-stack))))
(if config/new-ui-enabled?
(navigation/init-root :shell-stack)
;; if it's a first account, the ToS will be accepted at welcome carousel
;; if not a first account, the ToS might have been accepted by other account logins
(if (or first-account? tos-accepted?)
(navigation/init-root :onboarding-notification)
(navigation/init-root :tos))))))
(defn- keycard-setup?
[cofx]
@@ -557,7 +564,7 @@
:multiaccount)
(assoc :tos-accept-next-root
(if login-only?
:shell-stack
:chat-stack
:onboarding-notification))
(assoc :logged-in-since now)
(assoc :view-id :home)))
@@ -730,7 +737,7 @@
(rf/defn welcome-lets-go
{:events [:welcome-lets-go]}
[_]
{:init-root-fx :shell-stack})
{:init-root-fx :chat-stack})
(rf/defn multiaccount-selected
{:events [:multiaccounts.login.ui/multiaccount-selected]}
+1 -1
View File
@@ -7,7 +7,7 @@
[utils.re-frame :as rf]
[status-im.utils.keychain.core :as keychain]
[status-im.wallet.core :as wallet]
[status-im2.events :as init]))
[status-im2.setup.events :as init]))
(rf/defn logout-method
{:events [::logout-method]}
+3 -3
View File
@@ -1,7 +1,7 @@
(ns status-im.native-module.core
(:require ["react-native" :as react-native]
[re-frame.core :as re-frame]
[status-im2.utils.validators :as validators]
[status-im.utils.db :as utils.db]
[status-im.utils.platform :as platform]
[status-im.utils.react-native :as react-native-utils]
[status-im.utils.types :as types]
@@ -471,14 +471,14 @@
(defn generate-gfycat
"Generate a 3 words random name based on the user public-key, synchronously"
[public-key]
{:pre [(validators/valid-public-key? public-key)]}
{:pre [(utils.db/valid-public-key? public-key)]}
(log/debug "[native-module] generate-gfycat")
(.generateAlias ^js (status) public-key))
(defn generate-gfycat-async
"Generate a 3 words random name based on the user public-key, asynchronously"
[public-key callback]
{:pre [(validators/valid-public-key? public-key)]}
{:pre [(utils.db/valid-public-key? public-key)]}
(.generateAliasAsync ^js (status) public-key callback))
(defn identicon
+1 -1
View File
@@ -303,7 +303,7 @@
(re-frame/dispatch [:set :current-tab tab-key])
(when (= @state/root-comp-id comp)
(when (= :chat tab-key)
(re-frame/dispatch [:chat/close]))
(re-frame/dispatch [:close-chat]))
(when platform/android?
(.popToRoot Navigation (name comp))))
(reset! state/root-comp-id comp)))))
+2 -6
View File
@@ -5,6 +5,7 @@
[quo.platform :as platform]
[re-frame.core :as re-frame]
[status-im.async-storage.core :as async-storage]
[status-im.chat.models :as chat.models]
[status-im.ethereum.decode :as decode]
[status-im.ethereum.tokens :as tokens]
[utils.i18n :as i18n]
@@ -104,11 +105,6 @@
:user-info notification
:message description}))
(defn foreground-chat?
[{{:keys [current-chat-id view-id]} :db} chat-id]
(and (= current-chat-id chat-id)
(= view-id :chat)))
(defn show-message-pn?
[{{:keys [app-state multiaccount]} :db :as cofx}
notification]
@@ -117,7 +113,7 @@
(and
(not= notification-author (:public-key multiaccount))
(or (= app-state "background")
(not (foreground-chat? cofx chat-id))))))
(not (chat.models/foreground-chat? cofx chat-id))))))
(defn create-notification
([notification]
-9
View File
@@ -84,12 +84,3 @@
{:events [:profile/share-profile-link]}
[_ value]
{:profile/share-profile-link value})
(rf/defn show-profile
{:events [:chat.ui/show-profile]}
[{:keys [db]} identity ens-name]
(let [my-public-key (get-in db [:multiaccount :public-key])]
(when (not= my-public-key identity)
{:db (-> db
(assoc :contacts/identity identity)
(assoc :contacts/ens-name ens-name))})))
+3 -3
View File
@@ -47,7 +47,7 @@
(rf/defn handle-private-chat
[{:keys [db] :as cofx} {:keys [chat-id]}]
(if-not (new-chat.db/own-public-key? db chat-id)
{:dispatch [:chat.ui/start-chat chat-id]}
(chat/start-chat cofx chat-id nil)
{:utils/show-popup {:title (i18n/label :t/unable-to-read-this-code)
:content (i18n/label :t/can-not-add-yourself)}}))
@@ -76,7 +76,7 @@
:else
{:utils/show-popup {:title (i18n/label :t/unable-to-read-this-code)
:content (i18n/label :t/ens-name-not-found)
:on-dismiss #(re-frame/dispatch [:pop-to-root-tab :shell-stack])}})))
:on-dismiss #(re-frame/dispatch [:pop-to-root-tab :chat-stack])}})))
(rf/defn handle-eip681
[cofx data]
@@ -116,7 +116,7 @@
:event ::match-scanned-value})
{:dispatch [:navigate-back]
:utils/show-popup {:title (i18n/label :t/unable-to-read-this-code)
:on-dismiss #(re-frame/dispatch [:pop-to-root-tab :shell-stack])}})))
:on-dismiss #(re-frame/dispatch [:pop-to-root-tab :chat-stack])}})))
(rf/defn on-scan
{:events [::on-scan-success]}
@@ -5,7 +5,6 @@
{:empty-chats-header (js/require "../resources/images/ui/empty-chats-header.png")
:welcome (js/require "../resources/images/ui/welcome.jpg")
:welcome-dark (js/require "../resources/images/ui/welcome-dark.jpg")
:add-new-contact (js/require "../resources/images/ui/add-contact.png")
:chat (js/require "../resources/images/ui/chat.jpg")
:chat-dark (js/require "../resources/images/ui/chat-dark.jpg")
:wallet (js/require "../resources/images/ui/wallet.jpg")
+7 -7
View File
@@ -3,13 +3,13 @@
[clojure.string :as string]
[re-frame.core :as re-frame]
[status-im.add-new.db :as public-chat.db]
[status-im2.contexts.chat.events :as chat.events]
[status-im.chat.models :as chat.models]
[status-im2.constants :as constants]
[status-im.ethereum.core :as ethereum]
[status-im.ethereum.eip681 :as eip681]
[status-im.ethereum.ens :as ens]
[status-im.ethereum.stateofus :as stateofus]
[status-im2.utils.validators :as validators]
[status-im.utils.db :as utils.db]
[status-im.utils.http :as http]
[status-im.utils.wallet-connect :as wallet-connect]
[taoensso.timbre :as log]
@@ -71,7 +71,7 @@
(defn match-contact-async
[chain {:keys [user-id ens-name]} callback]
(let [valid-key (and (validators/valid-public-key? user-id)
(let [valid-key (and (utils.db/valid-public-key? user-id)
(not= user-id ens/default-key))]
(cond
valid-key
@@ -113,15 +113,15 @@
(not (string/blank? chat-name))
(> (count chat-id-parts) 1)
(not (string/blank? (first chat-id-parts)))
(validators/valid-public-key? admin-pk)
(validators/valid-public-key? (last chat-id-parts)))
(utils.db/valid-public-key? admin-pk)
(utils.db/valid-public-key? (last chat-id-parts)))
{:type :group-chat
:chat-id chat-id
:invitation-admin admin-pk
:chat-name chat-name}
(and (not (string/blank? chat-id))
(chat.events/group-chat? (get chats chat-id)))
(chat.models/group-chat? (get chats chat-id)))
(let [{:keys [chat-name invitation-admin]} (get chats chat-id)]
{:type :group-chat
:chat-id chat-id
@@ -228,7 +228,7 @@
(= handler :group-chat)
(cb (match-group-chat chats query-params))
(validators/valid-public-key? uri)
(utils.db/valid-public-key? uri)
(match-contact-async chain {:user-id uri} cb)
(= handler :community-requests)
+7 -2
View File
@@ -2,7 +2,7 @@
(:require
[clojure.string :as string]
[status-im.browser.core :as browser]
[status-im2.contexts.chat.events :as chat.events]
[status-im.chat.models :as models.chat]
[status-im.chat.models.message :as models.message]
[status-im.chat.models.reactions :as models.reactions]
[status-im.communities.core :as models.communities]
@@ -61,7 +61,7 @@
(js-delete response-js "chats")
(rf/merge cofx
(process-next response-js sync-handler)
(chat.events/ensure-chats (map data-store.chats/<-rpc (types/js->clj chats)))))
(models.chat/ensure-chats (map data-store.chats/<-rpc (types/js->clj chats)))))
(seq messages)
(models.message/receive-many cofx response-js)
@@ -200,10 +200,12 @@
message-type (.-messageType message-js)
from (.-from message-js)
mentioned (.-mentioned message-js)
profile (models.chat/profile-chat? {:db db} chat-id)
new (.-new message-js)
current (= current-chat-id chat-id)
should-update-unviewed? (and (not current)
new
(not profile)
(not (= message-type
constants/message-type-private-group-system-message))
(not (= from (multiaccounts.model/current-public-key {:db db}))))
@@ -213,6 +215,9 @@
current
(update :messages conj message-js)
profile
(update :statuses conj message-js)
;;update counter
should-update-unviewed?
(update-in [:db :chats chat-id :unviewed-messages-count] inc)
@@ -6,8 +6,6 @@
(defn build-message
[{:keys [chat-id
album-id
image-width
image-height
text
response-to
ens-name
@@ -19,8 +17,6 @@
content-type]}]
{:chatId chat-id
:albumId album-id
:imageWidth image-width
:imageHeight image-height
:text text
:responseTo response-to
:ensName ens-name
@@ -20,7 +20,7 @@
[status-im.ui.components.react :as react]
[status-im.ui.components.topbar :as topbar]
[status-im.ui.screens.chat.photos :as photos]
[status-im2.utils.validators :as validators]
[status-im.utils.db :as utils.db]
[status-im.utils.gfycat.core :as gfycat]
[status-im.utils.identicon :as identicon]
[status-im.utils.utils :as utils]
@@ -172,7 +172,7 @@
#(do
(reset! search-value %)
(re-frame/dispatch [:set-in [:contacts/new-identity :state] :empty])
(debounce/debounce-and-dispatch [:contacts/set-new-identity %] 600))
(debounce/debounce-and-dispatch [:new-chat/set-new-identity %] 600))
:on-submit-editing
#(when (= state :valid)
(debounce/dispatch-and-chill [:contact.ui/contact-code-submitted false nil] 3000))
@@ -301,7 +301,7 @@
(fn []
(let [{:keys [state ens-name public-key error]} @(re-frame/subscribe [:contacts/new-identity])
blocked? (and
(validators/valid-public-key? (or public-key ""))
(utils.db/valid-public-key? (or public-key ""))
@(re-frame/subscribe [:contacts/contact-blocked?
public-key]))]
[react/view {:style {:flex 1}}
@@ -326,7 +326,7 @@
{:on-change-text
#(do
(re-frame/dispatch [:set-in [:contacts/new-identity :state] :searching])
(debounce/debounce-and-dispatch [:contacts/set-new-identity %] 600))
(debounce/debounce-and-dispatch [:new-chat/set-new-identity %] 600))
:on-submit-editing
#(when (= state :valid)
(debounce/dispatch-and-chill [:contact.ui/contact-code-submitted true @entered-nickname]
@@ -5,12 +5,17 @@
[re-frame.core :as re-frame]
[utils.i18n :as i18n]
[status-im.react-native.resources :as resources]
[status-im.ui.components.react :as react]))
[status-im.ui.components.react :as react]
[status-im2.config :as config]))
(defn button
[label icon theme selected?]
[react/touchable-highlight
{:on-press #(re-frame/dispatch [:multiaccounts.ui/appearance-switched theme])}
{:on-press (fn []
(re-frame/dispatch [:multiaccounts.ui/appearance-switched theme])
(re-frame/dispatch (if config/new-ui-enabled? [:reload-new-ui] [:init-root :chat-stack]))
(re-frame/dispatch [:navigate-change-tab :profile])
(js/setTimeout #(re-frame/dispatch [:navigate-to :appearance]) 1000))}
[react/view
(merge {:align-items :center :padding 8 :border-radius 20}
(when selected?
@@ -7,6 +7,11 @@
[status-im.ui.screens.chat.styles.input.gap :as style]
[utils.datetime :as datetime]))
(defn on-press
[chat-id gap-ids]
(fn []
(re-frame/dispatch [:chat.ui/fill-gaps chat-id gap-ids])))
(views/defview gap
[{:keys [gap-ids chat-id gap-parameters public? community?]}]
(views/letsubs [in-progress? [:chats/fetching-gap-in-progress?
@@ -20,7 +25,7 @@
[react/view {:style (when-not in-progress? style/gap-container)}
[react/touchable-highlight
{:on-press (when (and (not in-progress?) use-status-nodes? connected?)
(re-frame/dispatch [:chat.ui/fill-gaps chat-id gap-ids]))
(on-press chat-id gap-ids))
:style {:height (if in-progress? window-height 48)}}
[react/view {:style style/label-container}
(if in-progress?
+3 -3
View File
@@ -40,7 +40,7 @@
:title (i18n/label :t/delete-chat)
:accessibility-label :delete-chat-button
:icon :main-icons/delete
:on-press #(re-frame/dispatch [:chat.ui/show-remove-confirmation chat-id])}]]))
:on-press #(re-frame/dispatch [:chat.ui/remove-chat-pressed chat-id])}]]))
(defn public-chat-accents
[chat-id]
@@ -69,13 +69,13 @@
:title (i18n/label :t/clear-history)
:accessibility-label :clear-history-button
:icon :main-icons/close
:on-press #(re-frame/dispatch [:chat.ui/show-clear-history-confirmation chat-id])}]
:on-press #(re-frame/dispatch [:chat.ui/clear-history-pressed chat-id])}]
[quo/list-item
{:theme :negative
:title (i18n/label :t/delete-chat)
:accessibility-label :delete-chat-button
:icon :main-icons/delete
:on-press #(re-frame/dispatch [:chat.ui/show-remove-confirmation chat-id])}]]))
:on-press #(re-frame/dispatch [:chat.ui/remove-chat-pressed chat-id])}]]))
(defn community-chat-accents
[]
@@ -64,5 +64,4 @@
:accessory :text
:accessory-text (count pinned-messages)
:chevron true
:on-press #(re-frame/dispatch [:chat.ui/navigate-to-pinned-messages
chat-id])}]])]))))
:on-press #(re-frame/dispatch [:contact.ui/pinned-messages-pressed chat-id])}]])]))))
@@ -171,7 +171,7 @@
(assoc home-item :public? true)
{:on-press (fn []
(rf/dispatch [:dismiss-keyboard])
(rf/dispatch [:chat/navigate-to-chat chat-id])
(rf/dispatch [:chat.ui/navigate-to-chat-nav2 chat-id])
(rf/dispatch [:search/home-filter-changed nil]))
:on-long-press #(rf/dispatch [:bottom-sheet/show-sheet
{:content (fn []
+10 -9
View File
@@ -7,6 +7,7 @@
[quo2.foundations.colors :as quo2.colors]
[re-frame.core :as re-frame]
[reagent.core :as reagent]
[status-im.add-new.core :as new-chat]
[status-im.add-new.db :as db]
[utils.i18n :as i18n]
[status-im.qr-scanner.core :as qr-scanner]
@@ -81,15 +82,15 @@
:on-blur (fn []
(when chats-empty
(re-frame/dispatch [:search/home-filter-changed nil]))
(re-frame/dispatch [:contacts/clear-new-identity]))
(re-frame/dispatch [::new-chat/clear-new-identity]))
:on-focus (fn [search-filter]
(when-not search-filter
(re-frame/dispatch [:search/home-filter-changed ""])
(re-frame/dispatch [:contacts/clear-new-identity])))
(re-frame/dispatch [::new-chat/clear-new-identity])))
:on-change (fn [text]
(re-frame/dispatch [:search/home-filter-changed text])
(re-frame/dispatch [:set-in [:contacts/new-identity :state] :searching])
(debounce/debounce-and-dispatch [:contacts/set-new-identity text] 300))}]])
(debounce/debounce-and-dispatch [:new-chat/set-new-identity text] 300))}]])
(defn search-input-wrapper-old
[search-filter chats-empty]
@@ -103,11 +104,11 @@
:on-blur (fn []
(when chats-empty
(re-frame/dispatch [:search/home-filter-changed nil]))
(re-frame/dispatch [:contacts/clear-new-identity]))
(re-frame/dispatch [::new-chat/clear-new-identity]))
:on-focus (fn [search-filter]
(when-not search-filter
(re-frame/dispatch [:search/home-filter-changed ""])
(re-frame/dispatch [:contacts/clear-new-identity])))
(re-frame/dispatch [::new-chat/clear-new-identity])))
:on-change (fn [text]
(re-frame/dispatch [:search/home-filter-changed text])
(re-frame/dispatch [:set-in [:contacts/new-identity :state] :searching])
@@ -150,7 +151,7 @@
home-item
{:on-press (fn []
(re-frame/dispatch [:dismiss-keyboard])
(re-frame/dispatch [:chat/navigate-to-chat chat-id])
(re-frame/dispatch [:chat.ui/navigate-to-chat-nav2 chat-id])
(re-frame/dispatch [:search/home-filter-changed nil]))
:on-long-press #(re-frame/dispatch [:bottom-sheet/show-sheet
{:content (fn []
@@ -165,7 +166,7 @@
home-item
{:on-press (fn []
(re-frame/dispatch [:dismiss-keyboard])
(re-frame/dispatch [:chat/navigate-to-chat chat-id])
(re-frame/dispatch [:chat.ui/navigate-to-chat-nav2 chat-id])
(re-frame/dispatch [:search/home-filter-changed nil]))
:on-long-press #(re-frame/dispatch [:bottom-sheet/show-sheet
{:content (fn []
@@ -192,7 +193,7 @@
[list/flat-list
{:key-fn chat-list-key-fn
:getItemLayout get-item-layout
:on-end-reached #(re-frame/dispatch [:chat/show-more-chats])
:on-end-reached #(re-frame/dispatch [:chat.ui/show-more-chats])
:keyboard-should-persist-taps :always
:data items
:render-fn render-fn
@@ -218,7 +219,7 @@
[list/flat-list
{:key-fn chat-list-key-fn
:getItemLayout get-item-layout
:on-end-reached #(re-frame/dispatch [:chat/show-more-chats])
:on-end-reached #(re-frame/dispatch [:chat.ui/show-more-chats])
:keyboard-should-persist-taps :always
:data items
:render-fn render-fn-old
@@ -5,12 +5,12 @@
[quo.design-system.colors :as colors]
[re-frame.core :as re-frame]
[reagent.core :as reagent]
[status-im.chat.models :as chat.models]
[utils.i18n :as i18n]
[status-im.multiaccounts.core :as multiaccounts]
[status-im.ui.components.chat-icon.screen :as chat-icon]
[status-im.ui.components.icons.icons :as icons]
[status-im.ui.components.keyboard-avoid-presentation :as kb-presentation]
[status-im.ui.components.profile-header.view :as profile-header]
[status-im.ui.components.react :as react]
[status-im.ui.components.toolbar :as toolbar]
[status-im.ui.components.topbar :as topbar]
@@ -24,7 +24,7 @@
(concat [{:label (i18n/label :t/chat)
:icon :main-icons/message
:disabled (not mutual?)
:action #(re-frame/dispatch [:chat.ui/start-chat
:action #(re-frame/dispatch [:contact.ui/send-message-pressed
{:public-key public-key
:ens-name ens-name}])
:accessibility-label :start-conversation-button}]
@@ -47,7 +47,8 @@
:selected muted?
:disabled blocked?
:action (when-not blocked?
#(re-frame/dispatch [:chat.ui/mute public-key (not muted?)]))}]
#(re-frame/dispatch [::chat.models/mute-chat-toggled public-key
(not muted?)]))}]
[{:label (i18n/label (if blocked? :t/unblock :t/block))
:negative true
:selected blocked?
@@ -100,7 +101,7 @@
:accessory :text
:accessory-text pin-count
:disabled (zero? pin-count)
:on-press #(re-frame/dispatch [:chat.ui/navigate-to-pinned-messages public-key])
:on-press #(re-frame/dispatch [:contact.ui/pinned-messages-pressed public-key])
:chevron true}])
(defn nickname-settings
@@ -206,11 +207,6 @@
[]
(let [{:keys [public-key name ens-verified] :as contact} @(re-frame/subscribe
[:contacts/current-contact])
muted? @(re-frame/subscribe [:chats/muted
public-key])
pinned-messages @(re-frame/subscribe [:chats/pinned
public-key])
[first-name second-name] (multiaccounts/contact-two-names contact true)
on-share #(re-frame/dispatch
[:show-popover
(merge
@@ -226,27 +222,4 @@
:on-press on-share}]
:left-accessories [{:icon :main-icons/close
:accessibility-label :back-button
:on-press #(re-frame/dispatch [:navigate-back])}]}]
[:<>
[(profile-header/extended-header
{:on-press on-share
:bottom-separator false
:title first-name
:photo (multiaccounts/displayed-photo contact)
:monospace (not ens-verified)
:subtitle second-name
:public-key public-key})]
[react/view
{:height 1 :background-color colors/gray-lighter :margin-top 8}]
[nickname-settings contact]
[pin-settings public-key (count pinned-messages)]
[react/view {:height 1 :background-color colors/gray-lighter}]
[react/view
{:padding-top 17
:flex-direction :row
:align-items :stretch
:flex 1}
(for [{:keys [label] :as action} (actions contact muted?)
:when label]
^{:key label}
[button-item action])]]])))
:on-press #(re-frame/dispatch [:navigate-back])}]}]])))
@@ -215,7 +215,7 @@
:accessory :text
:accessory-text (count pinned-messages)
:chevron true
:on-press #(re-frame/dispatch [:chat.ui/navigate-to-pinned-messages chat-id])}]
:on-press #(re-frame/dispatch [:contact.ui/pinned-messages-pressed chat-id])}]
(when member?
[quo/list-item
{:theme :negative
+10 -2
View File
@@ -1,6 +1,7 @@
(ns status-im.ui.screens.screens
(:require
[quo.design-system.colors :as colors]
[status-im.add-new.core :as new-chat.events]
[utils.i18n :as i18n]
[status-im.ui.components.icons.icons :as icons]
[status-im.ui.screens.about-app.views :as about-app]
@@ -544,7 +545,7 @@
;[Chat] New Chat
{:name :new-chat
:on-focus [:contacts/new-chat-focus]
:on-focus [::new-chat.events/new-chat-focus]
;;TODO accessories
:options {:topBar {:visible false}}
:component new-chat/new-chat}
@@ -568,7 +569,7 @@
:component contact/nickname}
{:name :new-chat-aio
:on-focus [:contacts/new-chat-focus]
:on-focus [::new-chat.events/new-chat-focus]
;;TODO accessories
:options {:topBar {:visible false}}
:component new-chat-aio/contact-selection-list}
@@ -594,6 +595,13 @@
:component communities.invite/invite
:insets {:bottom true}}
;New Contact
{:name :new-contact
:on-focus [::new-chat.events/new-chat-focus]
;;TODO accessories
:options {:topBar {:visible false}}
:component new-chat/new-contact}
;[Wallet] Recipient
{:name :recipient
:insets {:bottom true}
@@ -53,7 +53,7 @@
:on-change (fn [text]
(re-frame/dispatch [:search/recipient-filter-changed text])
(re-frame/dispatch [:set-in [:contacts/new-identity :state] :searching])
(debounce/debounce-and-dispatch [:contacts/set-new-identity text] 300))}]]))
(debounce/debounce-and-dispatch [:new-chat/set-new-identity text] 300))}]]))
(defn section
[_ _ _]
@@ -11,10 +11,10 @@
;; so we need some magic here with paddings so close button isn't cut
[rn/view {:padding-top 12 :padding-bottom 8 :padding-right 12}
[rn/image
{:source {:uri (:uri (val item))}
{:source {:uri (first item)}
:style style/small-image}]
[rn/touchable-opacity
{:on-press (fn [] (rf/dispatch [:chat.ui/image-unselected (val item)]))
{:on-press #(rf/dispatch [:chat.ui/image-unselected (first item)])
:style style/remove-photo-container
:hit-slop {:right 5
:left 5
@@ -1,20 +1,22 @@
(ns status-im.ui2.screens.chat.composer.input
(:require [clojure.string :as string]
(:require ["react-native" :as react-native]
[clojure.string :as string]
[oops.core :as oops]
[quo.design-system.colors :as quo.colors]
[quo.react]
[quo.react-native :as rn]
[quo2.foundations.colors :as colors]
[re-frame.core :as re-frame]
[reagent.core :as reagent]
[status-im2.constants :as chat.constants]
[status-im.chat.models.mentions :as mentions]
[utils.i18n :as i18n]
[status-im.ui.components.react :as react]
[utils.re-frame :as rf]
[status-im.utils.platform :as platform]
[status-im.utils.utils :as utils.utils]
[utils.transforms :as transforms]
[quo2.foundations.typography :as typography]
[react-native.background-timer :as background-timer]
[react-native.platform :as platform]
[react-native.core :as rn]
[react-native.clipboard :as clipboard]
[quo.react :as quo.react]))
[quo2.foundations.typography :as typography]))
(defonce input-texts (atom {}))
(defonce input-text-content-heights (atom {}))
@@ -92,7 +94,7 @@
(when platform/ios?
(reset!
timeout-id
(background-timer/set-timeout
(utils.utils/set-timeout
#(rf/dispatch [::mentions/on-selection-change
{:start start
:end end}
@@ -126,7 +128,7 @@
;; happens during typing because it is not needed for mention
;; suggestions calculation
(when (and platform/ios? @timeout-id)
(background-timer/clear-timeout @timeout-id))
(utils.utils/clear-timeout @timeout-id))
(when platform/android?
(reset! last-text-change (js/Date.now)))
@@ -167,7 +169,7 @@
:min-height 34
:margin 0
:flex-shrink 1
:color (colors/theme-colors colors/neutral-100 colors/white)
:color (:text-01 @quo.colors/theme)
:margin-horizontal 20}
(if platform/android?
{:padding-vertical 8
@@ -193,7 +195,7 @@
:blur-on-submit false
:auto-focus false
:max-length chat.constants/max-text-size
:placeholder-text-color (colors/theme-colors colors/neutral-40 colors/white-opa-30)
:placeholder-text-color (:text-02 @quo.colors/theme)
:placeholder (if cooldown-enabled?
(i18n/label :cooldown/text-input-disabled)
(i18n/label :t/type-a-message))
@@ -228,6 +230,14 @@
[rn/text-input props
children])))
(defn selectable-text-input-manager
[]
(when (exists? (.-NativeModules react-native))
(.-RNSelectableTextInputManager ^js (.-NativeModules react-native))))
(defonce rn-selectable-text-input
(reagent/adapt-react-class (.requireNativeComponent react-native "RNSelectableTextInput")))
(declare first-level-menu-items second-level-menu-items)
(defn update-input-text
@@ -259,24 +269,24 @@
;https://lightrun.com/answers/facebook-react-native-textinput-controlled-selection-broken-on-both-ios-and-android
;use native invoke instead! do not use setNativeProps! e.g. (.setNativeProps ^js text-input (clj->js
;{:selection {:start selection-start :end selection-end}}))
(let [manager (rn/selectable-text-input-manager)]
(let [manager (selectable-text-input-manager)]
(oops/ocall manager :setSelection text-input-handle selection-start selection-end)))
(def first-level-menus
{:cut (fn [{:keys [content] :as params}]
(let [new-text (calculate-input-text params "")]
(clipboard/set-string content)
(react/copy-to-clipboard content)
(update-input-text params new-text)))
:copy-to-clipboard (fn [{:keys [content]}]
(clipboard/set-string content))
(react/copy-to-clipboard content))
:paste (fn [params]
(let [callback (fn [paste-content]
(let [content (string/trim paste-content)
new-text (calculate-input-text params content)]
(update-input-text params new-text)))]
(clipboard/get-string callback)))
(react/get-from-clipboard callback)))
:biu (fn [{:keys [first-level text-input-handle menu-items selection-start
selection-end]}]
@@ -330,7 +340,7 @@
menu-items (reagent/atom first-level-menu-items)
first-level (reagent/atom true)
selection-event (atom nil)
manager (rn/selectable-text-input-manager)]
manager (selectable-text-input-manager)]
(reagent/create-class
{:component-did-mount
(fn [this]
@@ -385,6 +395,6 @@
:style (dissoc style :margin-horizontal)
:on-selection-change on-selection-change
:on-selection on-selection})]
[rn/selectable-text-input {:menuItems @menu-items :style style}
[rn-selectable-text-input {:menuItems @menu-items :style style}
[rn/text-input props
children]]))})))
@@ -0,0 +1,39 @@
(ns status-im.ui2.screens.chat.pin-limit-popover.style
(:require [quo2.foundations.colors :as colors]))
(defn pin-popover
[width]
{:width (- width 16)
:margin-left 8
:background-color (colors/theme-colors colors/neutral-80-opa-90 colors/white-opa-90)
:flex-direction :row
:border-radius 16
:padding 12})
(defn pin-alert-container
[]
{:background-color (colors/theme-colors colors/neutral-80-opa-20 colors/white-opa-40)
:width 36
:height 36
:border-radius 18
:justify-content :center
:align-items :center})
(def pin-alert-circle
{:width 18
:height 18
:border-radius 9
:border-color colors/danger-50-opa-40
:border-width 1
:justify-content :center
:align-items :center})
(def view-pinned-messages
{:background-color colors/primary-60
:border-radius 8
:justify-content :center
:align-items :center
:padding-horizontal 8
:padding-vertical 4
:align-self :flex-start
:margin-top 10})
@@ -0,0 +1,63 @@
(ns status-im.ui2.screens.chat.pin-limit-popover.view
(:require [utils.i18n :as i18n]
[quo.react :as react]
[quo2.core :as quo]
[quo2.foundations.colors :as colors]
[react-native.core :as rn]
[react-native.reanimated :as reanimated]
[status-im.ui2.screens.chat.pin-limit-popover.style :as style]
[utils.re-frame :as rf]))
;; TODO (flexsurfer) this should be an in-app notification component in quo2
;; https://github.com/status-im/status-mobile/issues/14527
(defn pin-limit-popover
[chat-id]
[:f>
(fn []
(let [width (rf/sub [:dimensions/window-width])
show-pin-limit-modal? (rf/sub [:chats/pin-modal chat-id])
opacity-animation (reanimated/use-shared-value 0)
z-index-animation (reanimated/use-shared-value -1)]
(react/effect!
#(do
(reanimated/set-shared-value opacity-animation
(reanimated/with-timing (if show-pin-limit-modal? 1 0)))
(reanimated/set-shared-value z-index-animation
(reanimated/with-timing (if show-pin-limit-modal? 10 -1)))))
(when show-pin-limit-modal?
[reanimated/view
{:style (reanimated/apply-animations-to-style
{:opacity opacity-animation
:z-index z-index-animation}
(style/pin-popover width))
:accessibility-label :pin-limit-popover}
[rn/view {:style (style/pin-alert-container)}
[rn/view {:style style/pin-alert-circle}
[rn/text {:style {:color colors/danger-50}} "!"]]]
[rn/view {:style {:margin-left 8}}
[quo/text {:weight :semi-bold :color (colors/theme-colors colors/white colors/neutral-100)}
(i18n/label :t/cannot-pin-title)]
[quo/text {:size :paragraph-2 :color (colors/theme-colors colors/white colors/neutral-100)}
(i18n/label :t/cannot-pin-desc)]
[rn/touchable-opacity
{:accessibility-label :view-pinned-messages
:active-opacity 1
:on-press (fn []
(rf/dispatch [:pin-message/hide-pin-limit-modal chat-id])
(rf/dispatch [:bottom-sheet/show-sheet :pinned-messages-list
chat-id]))
:style style/view-pinned-messages}
[quo/text {:size :paragraph-2 :weight :medium :color colors/white}
(i18n/label :t/view-pinned-messages)]]]
[rn/touchable-opacity
{:accessibility-label :close-pin-limit-popover
:active-opacity 1
:on-press #(rf/dispatch [:pin-message/hide-pin-limit-modal chat-id])
:style {:position :absolute
:top 16
:right 16}}
[quo/icon :i/close
{:color (colors/theme-colors colors/white colors/neutral-100)
:size 12}]]])))])
+5
View File
@@ -0,0 +1,5 @@
(ns status-im.utils.db)
(defn valid-public-key?
[s]
(and (string? s) (not-empty s) (boolean (re-matches #"0x04[0-9a-f]{128}" s))))
+1 -1
View File
@@ -13,7 +13,7 @@
[utils.datetime :as datetime]
[status-im.utils.platform :as platform]
[status-im.utils.types :as types]
[status-im2.common.log :as log]))
[status-im2.setup.log :as log]))
(def report-email "error-reports@status.im")
@@ -65,7 +65,7 @@
(log/info "universal-links: handling private chat" chat-id)
(when chat-id
(if-not (new-chat.db/own-public-key? db chat-id)
{:dispatch [:chat.ui/start-chat chat-id]}
(chat/start-chat cofx chat-id nil)
{:utils/show-popup {:title (i18n/label :t/unable-to-read-this-code)
:content (i18n/label :t/can-not-add-yourself)}})))
@@ -96,7 +96,7 @@
(rf/defn handle-community-chat
[cofx {:keys [chat-id]}]
(log/info "universal-links: handling community chat" chat-id)
{:dispatch [:chat/navigate-to-chat chat-id]})
{:dispatch [:chat.ui/navigate-to-chat-nav2 chat-id]})
(rf/defn handle-public-chat
[cofx {:keys [topic]}]
+26 -37
View File
@@ -83,16 +83,6 @@
(and @expanded? (< end-pan-y collapse-threshold))
(reset! expanded? false))))))))
(defn handle-comp
[window-width]
[rn/view
{:style {:width window-width
:position :absolute
:background-color :transparent
:top 0
:height 20}}
[rn/view {:style (styles/handle)}]])
(defn bottom-sheet
[props children]
(let [{on-cancel :on-cancel
@@ -150,9 +140,7 @@
expandable?
show-bottom-sheet?
expanded?
close-bottom-sheet)
handle-comp [gesture/gesture-detector {:gesture bottom-sheet-gesture}
[handle-comp window-width]]]
close-bottom-sheet)]
(react/effect! #(do
(cond
@@ -212,27 +200,28 @@
{:style (reanimated/apply-animations-to-style
{:opacity bg-opacity}
styles/backdrop)}]]
(cond->> [reanimated/view
{:style (reanimated/apply-animations-to-style
{:transform [{:translateY translate-y}]}
{:width window-width
:height window-height})}
[rn/view {:style styles/container}
(when selected-item
[rn/view {:style (styles/selected-background)}
[selected-item]])
[rn/view {:style (styles/background)}
[rn/keyboard-avoiding-view
{:behaviour (if platform/ios? :padding :height)
:style {:flex 1}}
[rn/view
{:style (styles/content-style insets)
:on-layout (when-not (and
(some? @content-height)
(> @content-height 0))
on-content-layout)}
children]]
(when show-handle?
handle-comp)]]]
(not show-handle?)
(conj [gesture/gesture-detector {:gesture bottom-sheet-gesture}]))]))])]))
[gesture/gesture-detector {:gesture bottom-sheet-gesture}
[reanimated/view
{:style (reanimated/apply-animations-to-style
{:transform [{:translateY translate-y}]}
{:width window-width
:height window-height})}
[rn/view {:style styles/container}
(when selected-item
[rn/view {:style (styles/selected-background)}
[selected-item]])
[rn/view {:style (styles/background)}
[rn/keyboard-avoiding-view
{:behaviour (if platform/ios? :padding :height)
:style {:flex 1}}
[rn/view
{:style (styles/content-style insets)
:on-layout (when-not (and
(some? @content-height)
(> @content-height 0))
on-content-layout)}
children]]
(when show-handle?
[rn/view {:style (styles/handle)}])]]]]]))])]))

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