Compare commits

..
Author SHA1 Message Date
Icaro Motta 9326fc5121 Demonstrate latency even with fake rectangles instead of notifications 2022-10-12 13:21:42 -03:00
Icaro Motta 2eddeb0753 Controllable performance degradation 2022-10-12 13:15:21 -03:00
Icaro Motta 2e0a594d63 Add timestamps 2022-10-12 11:48:44 -03:00
Ibrahem Khalil 6991f213af [#13569] Page Nav (#13773) 2022-10-11 15:01:28 +02:00
flexsurfer 35eae4587b clean deps and fix warnings (#14141) 2022-10-11 12:20:37 +02:00
Parvesh Monu 46fd30597a Implementation of Dynamic Button Component (#14127) 2022-10-11 14:10:54 +05:30
Icaro Motta af5f979443 [#13968] Fetch and reconcile notifications by type and read status (#14125)
- Extract tabs component from the main screen component to reduce rendering cost when switching tabs.
- Display empty state component when there are no notifications.
- Fix screen flickering due to quickly flushing and filling the db state.
- Display top bar as a sticky header.
- Namespace icon keywords.
- Correctly sorts notifications during reconciliation.
- Remove warning about children without unique key.
2022-10-10 16:33:42 -03:00
Omar Basem adfb0ddb5d feat: reply to message new ui (#14098)
* feat: reply to messsage new ui
2022-10-10 19:55:02 +04:00
Mohamed Javid 0a282b3a47 [Feature] Added floating button to scroll down in chat (#14129)
* [Feature][#14063] Added floating button to scroll down in chat

* [Fix][#14063] Crash on pressing floating button to scroll down in chat
2022-10-10 22:33:53 +08:00
Roman Volosovskyi 5f98e96d23 Use a single geth.log file for all networks on iOS 2022-10-09 16:09:18 +02:00
Churikova Tetiana 823fce2457 e2e: exception and new custom network 2022-10-07 00:31:34 +02:00
Jamie Caprani 1d5e1479f7 chore: add components to communities overview (#14108) 2022-10-06 09:32:45 -07:00
erikseppanen c2c281d9c6 [13939] Implement system message into quo2 components (#14061) 2022-10-06 10:59:39 -04:00
Parvesh Monu 1f6ebbb5e8 remove ens banner component (#14112) 2022-10-06 12:31:07 +05:30
Icaro Motta 73983b2abd [#13967] Displays filtering tabs and read/unread filter (#14105) 2022-10-05 14:44:37 -03:00
Ibrahem Khalilandjakub 390ac858dc Update starting guide doc (#14107)
* Update starting guide doc

* Fix bad placement of tut

* formatting and style fixes

Signed-off-by: Jakub Sokołowski <jakub@status.im>

* add adb link

Signed-off-by: Jakub Sokołowski <jakub@status.im>
Co-authored-by: Jakub Sokołowski <jakub@status.im>
2022-10-05 15:39:28 +02:00
John Ngei 69c49a5557 Renamed filter-tag/filter-tags to tag/tags and refactored token-tag tag to match figma naming (#14028) 2022-10-05 15:22:36 +03:00
Churikova Tetiana 2ed3b50663 e2e: dbs, improvements 2022-10-04 17:46:18 +02:00
Roman Volosovskyi 8dd75205f3 [#14021] Add goerli geth.log to logs archive on iOS 2022-10-04 13:33:33 +02:00
jakub 081f1a2115 ci: bump jenkins lib to use generalized nix library
Necessary so other repos can use the Nix functions.

Realted to:
https://github.com/status-im/nwaku/pull/1216

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

Signed-off-by: Jakub Sokołowski <jakub@status.im>
2022-10-03 15:43:11 +02:00
114 changed files with 2598 additions and 941 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.5.7'
library 'status-jenkins-lib@v1.6.0'
/* Options section can't access functions in objects. */
def isPRBuild = utils.isPRBuild()
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.5.7'
library 'status-jenkins-lib@v1.6.0'
pipeline {
agent { label 'linux' }
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.5.7'
library 'status-jenkins-lib@v1.6.0'
/* Options section can't access functions in objects. */
def isPRBuild = utils.isPRBuild()
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.5.7'
library 'status-jenkins-lib@v1.6.0'
pipeline {
agent { label params.AGENT_LABEL }
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.5.7'
library 'status-jenkins-lib@v1.6.0'
/* Options section can't access functions in objects. */
def isPRBuild = utils.isPRBuild()
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.5.7'
library 'status-jenkins-lib@v1.6.0'
pipeline {
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.5.7'
library 'status-jenkins-lib@v1.6.0'
pipeline {
agent { label 'macos' }
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.5.7'
library 'status-jenkins-lib@v1.6.0'
pipeline {
agent { label 'linux' }
+29 -4
View File
@@ -14,14 +14,25 @@ This step will take a while the first time as it will download all dependencies.
There are three steps necessary to start development, in this case for Android:
1. `make run-clojure` - Compiles Clojure into JavaScript, watches for changes on cljs files, and hot-reloads code in the app
2. `make run-metro` - Starts metro bundler and watches JavaScript code
3. `make run-android` - Builds the Android app and starts it on the device
1. `make run-clojure` - Compiles Clojure into JavaScript, watches for changes on cljs files, and hot-reloads code in the app.
2. `make run-metro` - Starts metro bundler and watches JavaScript code.
3. `make run-android` or `make run-ios` - Builds the Android/iOS app and starts it on the device.
The first two will continue watching for changes and keep re-building the app. They need to be ready first.
The last one will exit once the app is up and ready.
You need to have your emulator or real devices running and visible to adb, before you run `make run-android`.
## Simulators and Devices
### Android
You need to have an emulator like [AVD](https://developer.android.com/studio/run/emulator), or [Genymotion](#genymotion-virtualization), or a real device running and visible to [adb](https://developer.android.com/studio/command-line/adb), before you run `make run-android`.
### iOS
You can specify the simulator type by adding the `SIMULATOR` flag:
```sh
make run-ios SIMULATOR="iPhone 11 Pro"
```
Some manual steps are necesary for [developing on a physical iOS Device](#physical-ios-device).
# Build release
@@ -71,3 +82,17 @@ Steps:
2. [Setup Git to use your GPG key](https://help.github.com/en/github/authenticating-to-github/telling-git-about-your-signing-key)
3. [Setup Git to sign commits](https://help.github.com/en/github/authenticating-to-github/signing-commits)
4. [Setup GitHub to validate commits](https://help.github.com/en/github/authenticating-to-github/adding-a-new-gpg-key-to-your-github-account)
## Physical iOS Device
To use a physical iPhone your device UDID must be added to provisioning profiles and your Apple account invited as Developer to Status team.
1. [Get your UDID of your iPhone.](https://www.extentia.com/post/finding-the-udid-of-an-ios-device)
2. Request from someone with access like @cammellos or @jakubgs to
- Add the UDID to development devices on Apple Developer Portal.
- Invite your Apple account to be Developer in Status team.
3. Run a build in XCode using the project from `status-mobile/ios` directory.
- You might see error: `Select a development team in the Signing & Capabilities editor`
- Select `STATUS HOLDINGS PTE. LTD.` as the development team and rebuild again.
Once build finishes Status should start on your iPhone with its logs in terminal running `make run-metro`.
+3 -3
View File
@@ -238,7 +238,7 @@ PODS:
- React
- react-native-status (1.0.0):
- React
- react-native-status-keycard (2.5.37):
- react-native-status-keycard (2.5.38):
- Keycard
- React
- react-native-webview (11.16.0):
@@ -640,7 +640,7 @@ SPEC CHECKSUMS:
FBLazyVector: 3bb422f41b18121b71783a905c10e58606f7dc3e
FBReactNativeSpec: f2c97f2529dd79c083355182cc158c9f98f4bd6e
Folly: b73c3869541e86821df3c387eb0af5f65addfab4
glog: 64a900d72fb14cd1b2cdf3b717a5555ceb889d49
glog: 36ce0530c6d2c3a5a4326885ef4069564887a1db
HMSegmentedControl: 34c1f54d822d8308e7b24f5d901ec674dfa31352
Keycard: ac6df4d91525c3c82635ac24d4ddd9a80aca5fc8
libwebp: 60305b2e989864154bd9be3d772730f08fc6a59c
@@ -671,7 +671,7 @@ SPEC CHECKSUMS:
react-native-shake: de052eaa3eadc4a326b8ddd7ac80c06e8d84528c
react-native-slider: 12bd76d3d568c9c5500825db54123d44b48e4ad4
react-native-status: 21f75d492fd311dc111303da38a7a2b23a8a8466
react-native-status-keycard: 961d01ca190889ddf220206822fd752f8f4f3f7a
react-native-status-keycard: f60ca57d789aad6875c64ece81ab06ef0609e0d3
react-native-webview: 28a8636d97ee641f2ee8f20492d7a6c269c1d703
React-RCTActionSheet: 89a0ca9f4a06c1f93c26067af074ccdce0f40336
React-RCTAnimation: 1bde3ecc0c104c55df246eda516e0deb03c4e49b
@@ -164,9 +164,22 @@ RCT_EXPORT_METHOD(sendLogs:(NSString *)dbJson
NSString *networkDirPath = @"ethereum/mainnet_rpc";
#endif
#if DEBUG
NSString *goerliNetworkDirPath = @"ethereum/goerli_rpc_dev";
#else
NSString *goerliNetworkDirPath = @"ethereum/goerli_rpc";
#endif
NSURL *networkDir = [rootUrl URLByAppendingPathComponent:networkDirPath];
NSURL *originalGethLogsFile = [networkDir URLByAppendingPathComponent:@"geth.log"];
NSURL *gethLogsFile = [logsFolderName URLByAppendingPathComponent:@"geth.log"];
NSURL *gethLogsFile = [logsFolderName URLByAppendingPathComponent:@"mainnet_geth.log"];
NSURL *goerliNetworkDir = [rootUrl URLByAppendingPathComponent:goerliNetworkDirPath];
NSURL *goerliGethLogsFile = [goerliNetworkDir URLByAppendingPathComponent:@"geth.log"];
NSURL *goerliLogsFile = [logsFolderName URLByAppendingPathComponent:@"goerli_geth.log"];
NSURL *mainGethLogsFile = [rootUrl URLByAppendingPathComponent:@"geth.log"];
NSURL *mainLogsFile = [logsFolderName URLByAppendingPathComponent:@"geth.log"];
[dbJson writeToFile:dbFile.path atomically:YES encoding:NSUTF8StringEncoding error:nil];
[jsLogs writeToFile:jsLogsFile.path atomically:YES encoding:NSUTF8StringEncoding error:nil];
@@ -174,6 +187,8 @@ RCT_EXPORT_METHOD(sendLogs:(NSString *)dbJson
//NSString* gethLogs = StatusgoExportNodeLogs();
//[gethLogs writeToFile:gethLogsFile.path atomically:YES encoding:NSUTF8StringEncoding error:nil];
[fileManager copyItemAtPath:originalGethLogsFile.path toPath:gethLogsFile.path error:nil];
[fileManager copyItemAtPath:goerliGethLogsFile.path toPath:goerliLogsFile.path error:nil];
[fileManager copyItemAtPath:mainGethLogsFile.path toPath:mainLogsFile.path error:nil];
[SSZipArchive createZipFileAtPath:zipFile.path withContentsOfDirectory:logsFolderName.path];
[fileManager removeItemAtPath:logsFolderName.path error:nil];
@@ -411,12 +426,10 @@ RCT_EXPORT_METHOD(multiAccountDeriveAddresses:(NSString *)json
NSString *relativeDataDir = [configJSON objectForKey:@"DataDir"];
NSString *absDataDir = [rootUrl.path stringByAppendingString:relativeDataDir];
NSURL *absDataDirUrl = [NSURL fileURLWithPath:absDataDir];
NSURL *dataDirUrl = [NSURL fileURLWithPath:relativeDataDir];
NSURL *logUrl = [dataDirUrl URLByAppendingPathComponent:@"geth.log"];
NSString *keystoreDir = [@"/keystore/" stringByAppendingString:keyUID];
[configJSON setValue:keystoreDir forKey:@"KeyStoreDir"];
[configJSON setValue:@"" forKey:@"LogDir"];
[configJSON setValue:logUrl.path forKey:@"LogFile"];
[configJSON setValue:@"geth.log" forKey:@"LogFile"];
NSString *resultingConfig = [configJSON bv_jsonStringWithPrettyPrint:NO];
NSLog(@"node config %@", resultingConfig);
@@ -426,7 +439,7 @@ RCT_EXPORT_METHOD(multiAccountDeriveAddresses:(NSString *)json
withIntermediateDirectories:YES attributes:nil error:nil];
}
NSLog(@"logUrlPath %@ rootDir %@", logUrl.path, rootUrl.path);
NSLog(@"logUrlPath %@ rootDir %@", @"geth.log", rootUrl.path);
NSURL *absLogUrl = [absDataDirUrl URLByAppendingPathComponent:@"geth.log"];
if(![fileManager fileExistsAtPath:absLogUrl.path]) {
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
+1 -4
View File
@@ -27,13 +27,11 @@
"@walletconnect/client": "^2.0.0-beta.23",
"@walletconnect/client-legacy": "npm:@walletconnect/client@^1.7.1",
"bignumber.js": "git+https://github.com/status-im/bignumber.js.git#refs/tags/v4.0.2-status",
"buffer": "^5.4.2",
"chance": "^1.1.0",
"create-react-class": "^15.6.2",
"emojilib": "^2.4.0",
"eth-phishing-detect": "^1.1.13",
"eth-phishing-detect": "^1.2.0",
"functional-red-black-tree": "^1.0.1",
"hi-base32": "^0.5.0",
"i18n-js": "^3.3.0",
"node-libs-react-native": "^1.2.1",
"qrcode": "^1.4.1",
@@ -73,7 +71,6 @@
"react-native-touch-id": "^4.4.1",
"react-native-webview": "git+https://github.com/status-im/react-native-webview.git#refs/tags/v11.16.0-status",
"rn-emoji-keyboard": "git+https://github.com/status-im/rn-emoji-keyboard.git#refs/tags/v0.4.3",
"tdigest": "^0.1.1",
"web3-utils": "^1.2.1"
},
"devDependencies": {
Binary file not shown.

After

Width:  |  Height:  |  Size: 321 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 373 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 387 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 454 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 673 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 681 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 667 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 940 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 371 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 509 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 814 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 441 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 585 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 580 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 892 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 327 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 440 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

+3
View File
@@ -248,6 +248,8 @@
(def react-native-gradien #js {:default #js {}})
(def masked-view #js {:default #js {}})
(def react-native-permissions #js {:default #js {}})
(def push-notification-ios #js {:default #js {:abandonPermissions identity}})
@@ -308,6 +310,7 @@
"react-native-device-info" react-native-device-info
"react-native-push-notification" react-native-push-notification
"react-native-linear-gradient" react-native-gradien
"@react-native-community/masked-view" masked-view
"react-native-blob-util" react-native-blob-util
"react-native-navigation" react-native-navigation
"@react-native-community/push-notification-ios" push-notification-ios
+4 -3
View File
@@ -9,9 +9,10 @@
:small 20})
(defn icon-avatar
[{:keys [size icon color]}]
[{:keys [size icon color opacity]
:or {opacity 20}}]
(let [component-size (size sizes)
circle-color (colors/custom-color color 50 20)
circle-color (colors/custom-color color 50 opacity)
icon-color (colors/custom-color-by-theme color 50 60)
icon-size (case size
:big 20
@@ -25,4 +26,4 @@
:align-items :center}}
[icons/icon icon {:container-style {:width icon-size
:height icon-size}
:color icon-color}]]))
:color icon-color}]]))
+34 -36
View File
@@ -4,8 +4,7 @@
[quo2.foundations.colors :as colors]
[quo2.components.icon :as icons]
[clojure.string :refer [upper-case split blank?]]
[quo.theme :refer [dark?]]
[status-im.ui.components.react :as react]))
[quo.theme :refer [dark?]]))
(def sizes {:big {:outer 80
:inner 72
@@ -98,47 +97,46 @@
status-indicator?
profile-picture
full-name]
:or {full-name "empty name"
status-indicator? true
online? true
size :big
ring? true}}]
(let [initials (if full-name
(reduce str (map first (split full-name " ")))
"")
:or {full-name "empty name"
status-indicator? true
online? true
size :big
ring? true}}]
(let [initials (if full-name
(reduce str (map first (split full-name " ")))
"")
first-initial-letter (if full-name
(or (first full-name) "")
"")
identicon? (contains? identicon-sizes size)
small? (contains? small-sizes size)
using-profile-picture? (-> profile-picture
blank?
false?)
outer-dimensions (get-in sizes [size :outer])
inner-dimensions (get-in sizes [size (if ring?
:inner
:outer)])
font-size (get-in sizes [size :font-size])
icon-text (if-not (or (blank? first-initial-letter)
(blank? initials))
(if small?
first-initial-letter
initials)
"")]
[rn/view {:style {:width outer-dimensions
:height outer-dimensions
identicon? (contains? identicon-sizes size)
small? (contains? small-sizes size)
outer-dimensions (get-in sizes [size :outer])
inner-dimensions (get-in sizes [size (if ring?
:inner
:outer)])
font-size (get-in sizes [size :font-size])
icon-text (if-not (or (blank? first-initial-letter)
(blank? initials))
(if small?
first-initial-letter
initials)
"")]
[rn/view {:style {:width outer-dimensions
:height outer-dimensions
:border-radius outer-dimensions}}
(when (and ring? identicon?)
[icons/icon :main-icons/identicon-ring {:width outer-dimensions
:height outer-dimensions
:size outer-dimensions
[icons/icon :main-icons/identicon-ring {:width outer-dimensions
:height outer-dimensions
:size outer-dimensions
:no-color true}])
(if using-profile-picture?
[react/image {:style (container-styling inner-dimensions outer-dimensions)
:source {:uri profile-picture}}]
(if profile-picture
;; display image
[rn/image {:style (container-styling inner-dimensions outer-dimensions)
:source profile-picture}]
;; else display initials
[container inner-dimensions outer-dimensions
[text/text {:weight :semi-bold
:size font-size
:style {:color colors/white-opa-70}}
:size font-size
:style {:color colors/white-opa-70}}
(upper-case icon-text)]])
[dot-indicator size status-indicator? online? ring? (dark?)]]))
@@ -0,0 +1,103 @@
(ns quo2.components.buttons.dynamic-button
(:require [quo.react-native :as rn]
[reagent.core :as reagent]
[status-im.i18n.i18n :as i18n]
[quo2.components.icon :as icon]
[quo2.foundations.colors :as colors]
[quo2.components.markdown.text :as text]))
(defn- in?
"true if collection contains element"
[collection element]
(some #(= element %) collection))
(defn- get-button-color [type pressed? customization-color]
(if (in? '(:jump-to :mention) type)
(if pressed?
(colors/custom-color-by-theme customization-color 60 50)
(colors/custom-color-by-theme customization-color 50 60))
(if pressed?
(colors/theme-colors colors/neutral-80-opa-80 colors/white-opa-80)
(colors/theme-colors colors/neutral-80-opa-70 colors/white-opa-70))))
(defn- get-icon-and-text-color [type]
(if (in? '(:jump-to :mention) type)
colors/white
(colors/theme-colors colors/white colors/neutral-100)))
(defn- icon-view [type]
[icon/icon
(case type
:jump-to :main-icons2/jump-to
:mention :main-icons2/mention
:notification-down :main-icons2/arrow-down
:notification-up :main-icons2/arrow-up
:search-with-label :main-icons2/search
:search :main-icons2/search
:bottom :main-icons2/arrow-down)
{:size 12
:color (get-icon-and-text-color type)
:container-style {:margin-top 6
:margin-bottom 6
:margin-left (case type
:jump-to 0
:mention 8
:notification-down 2
:notification-up 2
:search-with-label 8
:search 6
:bottom 6)
:margin-right (case type
:jump-to 8
:mention 2
:notification-down 8
:notification-up 8
:search-with-label 4
:search 6
:bottom 6)}}])
(defn dynamic-button
"[:dynamic-button opts]
opts
{:type :jump-to/:mention/:notification-down/:notification-up/:search/:search-with-label/:bottom
:on-press fn
:count mentions or notifications count
:customization-color customize jump-to and mention button color}"
[_]
(let [pressed? (reagent/atom false)]
(fn [{:keys [type on-press count customization-color]
:or {customization-color :primary}}]
[rn/touchable-without-feedback
{:on-press-in #(reset! pressed? true)
:on-press-out #(reset! pressed? false)
:on-press on-press}
[rn/view {:style {:flex-direction :row
:height 24
:border-radius 12
:background-color (get-button-color type @pressed? customization-color)}}
(when (in? '(:mention :search :search-with-label :bottom) type)
[icon-view type])
(when (in? '(:jump-to :mention :notification-down :notification-up :search-with-label) type)
[text/text {:weight :medium
:size :paragraph-2
:style {:color (get-icon-and-text-color type)
:margin-top 2.5
:margin-bottom 3.5
:margin-left (case type
:jump-to 8
:mention 0
:notification-down 8
:notification-up 8
:search-with-label 0)
:margin-right (case type
:jump-to 0
:mention 8
:notification-down 0
:notification-up 0
:search-with-label 8)}}
(case type
:jump-to (i18n/label :t/jump-to)
:search-with-label (i18n/label :t/back)
(:mention :notification-down :notification-up) (str count))])
(when (in? '(:jump-to :notification-down :notification-up) type)
[icon-view type])]])))
@@ -4,7 +4,7 @@
[quo2.components.icon :as icons]
[quo2.foundations.colors :as colors]
[quo2.components.tags.permission-tag :as permission]
[quo2.components.tags.filter-tag :as filter-tag]
[quo2.components.tags.tag :as tag]
[status-im.ui.components.react :as react]
[status-im.utils.money :as money]
[status-im.i18n.i18n :as i18n]
@@ -49,7 +49,7 @@
(for [{:keys [id tag-label resource]} tags]
^{:key id}
[react/view {:margin-right 8}
[filter-tag/filter-tag
[tag/tag
{:id id
:size 24
:label tag-label
-75
View File
@@ -1,75 +0,0 @@
(ns quo2.components.info.nfc-prompt
(:require [quo.react-native :as rn]
[quo.theme :as theme]
[quo2.foundations.colors :as colors]
[status-im.ui.components.icons.icons :as icons]))
(defn nfc-prompt
[{:keys [prompt-status on-press]}]
(let [dark? (theme/dark?)
ready? (= :ready prompt-status)
connected? (= :connected prompt-status)
success? (= :success prompt-status)
ready-or-connected? (or ready? connected?)]
[rn/view {:style {:height 400
:flex-direction "column-reverse"}}
[rn/view {:style {:background-color (if dark?
colors/dark-prompt-bg
colors/white)
:width "100%"
:justify-content :center
:align-items :center
:height "100%"}}
[rn/view {:style (cond-> {:flex-direction :row
:justify-content :center
:align-items :center}
success? (assoc :display :none))}
[rn/text {:style {:font-size 26
:font-weight "400"
:color (if dark?
colors/dark-prompt-title
colors/light-prompt-title)
:line-height 31
:text-align :center}}
"Ready to Scan"]]
[rn/view {:style {:justify-content :center
:align-items :center
:margin-top 29}}
[icons/icon (if success?
:main-icons/nfc-prompt-success
(when ready-or-connected?
:main-icons/nfc-prompt))
{:width 114
:color "nil"
:height 114}]
[rn/view {:style {:width 291
:height 95
:justify-content :center
:align-items :center}}
[rn/text {:style {:font-size 16
:font-weight "400"
:color (if dark?
colors/white
colors/neutral-95)
:line-height 20}}
(if success?
"Success"
(if (= :ready prompt-status)
"Hold your iPhone near a Status Keycard"
"Connected. Dont move your card."))]]
[rn/touchable-opacity {:style (cond-> {:padding-vertical 18
:padding-horizontal 10
:align-items :center
:justify-content :center
:width 291
:border-radius 10
:background-color (if dark?
colors/dark-prompt-button
colors/light-prompt-button)
:height 52}
success? (assoc :display :none))
:on-press (fn [] on-press)}
[rn/text {:style {:color (if dark?
colors/white
colors/neutral-95)}}
"Cancel"]]]]]))
@@ -0,0 +1,162 @@
(ns quo2.components.messages.system-message
(:require [status-im.i18n.i18n :as i18n]
[quo.react-native :as rn]
[status-im.utils.core :as utils]
[quo.theme :as theme]
[quo2.components.buttons.button :as button]
[quo2.components.markdown.text :as text]
[quo2.reanimated :as ra]
[quo2.foundations.colors :as colors]
[quo2.components.avatars.user-avatar :as user-avatar]
[quo2.components.avatars.icon-avatar :as icon-avatar]))
(def themes-landed {:pinned colors/primary-50-opa-5
:added colors/primary-50-opa-5
:deleted colors/danger-50-opa-5})
(def themes
{:light {:text colors/neutral-100
:time colors/neutral-50
:bg {:default colors/white
:pressed colors/neutral-5
:landed themes-landed}}
:dark {:text colors/white
:time colors/neutral-40
:bg {:default colors/neutral-90
:pressed colors/neutral-80
:landed themes-landed}}})
(defn get-color [& keys]
(reduce (fn [acc k] (get acc k (reduced acc)))
((theme/get-theme) themes) (vec keys)))
(defn sm-timestamp [timestamp-str]
[rn/view {:margin-left 6}
[text/text {:size :label
:style {:color (get-color :time)
:text-transform :none}}
timestamp-str]])
(defn sm-icon [{:keys [icon color opacity]}]
[rn/view {:align-items :center
:margin-right 8}
[icon-avatar/icon-avatar {:size :medium
:icon icon
:color color
:opacity opacity}]])
(defn sm-user-avatar [image]
[rn/view {:margin-right 4}
[user-avatar/user-avatar {:status-indicator? false
:online? false
:size :xxxs
:profile-picture image
:ring? false}]])
(defmulti sm-render :type)
(defmethod sm-render :deleted [{:keys [state action timestamp-str]}]
[rn/view {:align-items :center
:justify-content :space-between
:flex 1
:flex-direction :row}
[rn/view {:align-items :center
:flex-direction :row}
[sm-icon {:icon :main-icons/delete16
:color :danger
:opacity (if (= state :landed) 0 5)}]
[text/text {:size :paragraph-2
:style {:color (get-color :text)
:margin-right 5}}
(i18n/label (if action :message-deleted-for-you :message-deleted))]
(when (nil? action) [sm-timestamp timestamp-str])]
(when action [button/button {:size 24
:before :main-icons/timeout
:type :grey} (i18n/label :undo)])])
(defmethod sm-render :added [{:keys [state mentions timestamp-str]}]
[rn/view {:align-items :center
:flex-direction :row}
[sm-icon {:icon :main-icons/add-user16
:color :primary
:opacity (if (= state :landed) 0 5)}]
[sm-user-avatar (:image (first mentions))]
[text/text {:weight :semi-bold
:size :paragraph-2}
(:name (first mentions))]
[text/text {:size :paragraph-2
:style {:color (get-color :text)
:margin-left 3
:margin-right 3}}
(i18n/label :added)]
[sm-user-avatar (:image (second mentions))]
[text/text {:weight :semi-bold
:size :paragraph-2}
(:name (second mentions))]
[sm-timestamp timestamp-str]])
(defmethod sm-render :pinned [{:keys [state pinned-by content timestamp-str]}]
[rn/view {:flex-direction :row
:flex 1
:align-items :center}
[sm-icon {:icon :main-icons/pin16
:color :primary
:opacity (if (= state :landed) 0 5)}]
[rn/view {:flex-direction :column
:flex 1}
[rn/view {:align-items :baseline
:flex-direction :row}
[text/text {:size :paragraph-2
:weight :semi-bold
:style {:color (get-color :text)}}
(utils/truncate-str pinned-by 18)]
[rn/view {:margin-left 4
:margin-right 2}
[text/text {:size :paragraph-2
:style {:color (get-color :text)}}
(i18n/label :pinned-a-message)]]
[sm-timestamp timestamp-str]]
[rn/view {:flex-direction :row}
[rn/view {:flex-direction :row
:margin-right 4}
[sm-user-avatar (:image (:mentions content))]
[text/text {:weight :semi-bold
:size :label}
(:name (:mentions content))]]
(when (seq (:text content))
[rn/view {:margin-right 20
:flex-direction :row
:flex 1}
[text/text {:size :label
:style {:color (get-color :text)}
:number-of-lines 1
:ellipsize-mode :tail}
(:text content)]])
[rn/view {:justify-content :flex-end
:flex-direction :row
:min-width 10}
(when (seq (:info content))
[text/text {:size :label
:style {:color (get-color :time)}}
(utils/truncate-str (:info content) 24)])]]]])
(defn system-message [{:keys [type] :as message}]
[:f>
(fn []
(let [sv-color (ra/use-shared-value (get-color :bg :landed type))]
(ra/animate-shared-value-with-delay
sv-color (get-color :bg :default type) 0 :linear 1000)
[ra/touchable-opacity
{:on-press #(ra/set-shared-value
sv-color (get-color :bg :pressed type))
:style (ra/apply-animations-to-style
{:background-color sv-color}
{:flex-direction :row
:flex 1
:border-radius 16
:padding-vertical 9
:padding-horizontal 11
:width 359
:height 52
:background-color sv-color})}
[sm-render message]]))])
@@ -0,0 +1,175 @@
(ns quo2.components.navigation.page-nav
(:require [quo.react-native :as rn]
[quo2.foundations.colors :as colors]
[quo2.components.icon :as icons]
[status-im.utils.dimensions :as dimensions]
[clojure.string :as string]
[quo2.components.markdown.text :as text]))
(def ^:private centrify-style
{:display :flex
:justify-content :center
:align-items :center})
(def ^:private align-left (assoc centrify-style :align-items :flex-start))
(def ^:private icon-styles (assoc centrify-style :width 32 :height 32 :border-radius 10))
(defn- big? [size] (= size :big))
(defn- icon-props [color size]
(merge {:size 20
:container-style {:width (if (big? size)
20
16)
:height (if (big? size)
20
16)}}
(if-not (string/blank? color)
{:color color}
{:no-color true})))
(defn- mid-section-comp
[{:keys [mid-section-description-user-icon horizontal-description? text-secondary-color align-mid? text-color mid-section-icon mid-section-main-text mid-section-type mid-section-description]}]
[rn/view {:style (assoc
centrify-style
:flex-direction :row
:margin-horizontal 2)}
(when (or (and (not horizontal-description?)
align-mid?
(not= :text-with-description mid-section-type))
(and mid-section-description-user-icon
(not mid-section-icon)))
[rn/image {:source {:uri mid-section-description-user-icon}
:style {:width 32
:height 32
:border-radius 32
:margin-right 8}}])
[rn/view {:style {:flex-direction (if horizontal-description?
:row
:column)}}
[text/text {:size :paragraph-1
:weight :semi-bold
:style {:color text-color
:line-height 21}}
mid-section-main-text]
(when mid-section-description
[text/text {:size :paragraph-2
:weight :medium
:style (cond-> {:padding-right 4
:color text-secondary-color
:line-height 18}
horizontal-description? (assoc :margin-left 4 :margin-top 2))}
mid-section-description])]])
(defn- mid-section
[{:keys [horizontal-description? one-icon-align-left? mid-section-type left-align? mid-section-main-text mid-section-right-icon mid-section-main-text-icon-color mid-section-left-icon] :as mid-section-props}]
(let [text-color (if (colors/dark?) colors/neutral-5 colors/neutral-95)
text-secondary-color (if (colors/dark?) colors/neutral-40 colors/neutral-50)
mid-section-comp-instance [mid-section-comp (assoc mid-section-props :text-secondary-color text-secondary-color)]]
[rn/view {:style (merge
(if left-align?
align-left
centrify-style)
{:flex 1
:margin-left 4
:text-align-vertical :center})}
(case mid-section-type
:text-only [text/text {:size :paragraph-1
:weight :semi-bold
:style {:color text-color}}
mid-section-main-text]
:text-with-two-icons [rn/view {:style (assoc centrify-style :flex-direction :row)}
[icons/icon mid-section-left-icon
(icon-props mid-section-main-text-icon-color :big)]
[text/text {:size :paragraph-1
:weight :semi-bold
:style {:padding-horizontal 4
:color text-color}}
mid-section-main-text]
[icons/icon mid-section-right-icon
(icon-props mid-section-main-text-icon-color :big)]]
:text-with-one-icon [rn/view {:style {:flex-direction :row}}
(if one-icon-align-left?
[rn/view {:style {:flex-direction :row
:align-items :center}}
(when horizontal-description?
[icons/icon mid-section-left-icon
(icon-props mid-section-main-text-icon-color :big)])
mid-section-comp-instance]
[rn/view {:style {:flex-direction :row
:align-items :center}}
mid-section-comp-instance
(when horizontal-description?
[icons/icon mid-section-left-icon
(icon-props mid-section-main-text-icon-color :big)])])]
:text-with-description mid-section-comp-instance)]))
(defn- right-section-icon
[{:keys [background-color icon icon-color push-to-the-left?] :or {push-to-the-left? false}}]
[rn/view {:style (assoc
icon-styles
:background-color background-color
:width 32
:height 32
:margin-right (if push-to-the-left? 8 0))}
[icons/icon icon (icon-props icon-color :big)]])
(defn page-nav
[{:keys [one-icon-align-left? horizontal-description? align-mid? page-nav-color page-nav-background-uri mid-section-type mid-section-icon mid-section-main-text mid-section-left-icon mid-section-right-icon mid-section-description mid-section-description-color mid-section-description-icon mid-section-description-user-icon mid-section-main-text-icon-color left-section-icon left-section-icon-color left-section-icon-background-color right-section-icons]}]
(let [{:keys [height width]} (dimensions/window)
put-middle-section-on-left? (or align-mid?
(> (count right-section-icons) 1))
mid-section-props {:mid-section-type mid-section-type
:horizontal-description? horizontal-description?
:mid-section-main-text mid-section-main-text
:one-icon-align-left? one-icon-align-left?
:mid-section-right-icon mid-section-right-icon
:mid-section-icon mid-section-icon
:mid-section-main-text-icon-color mid-section-main-text-icon-color
:mid-section-left-icon mid-section-left-icon}]
[rn/view {:style (cond->
{:display :flex
:flex-direction :row
:width width
:height (* 0.0497 height)
;; iPhone 11 Pro's height in Figma divided by Component height 56/1125
:align-items :center
:padding-horizontal 20
:justify-content :space-between}
page-nav-background-uri (assoc :background-color page-nav-color)
page-nav-color (assoc :background page-nav-background-uri))}
[rn/view {:style {:flex 1
:flex-direction :row
:align-items :center}}
[rn/view {:style (merge
icon-styles
{:background-color left-section-icon-background-color
:width 32
:height 32}
(when put-middle-section-on-left? {:margin-right 5}))}
[icons/icon left-section-icon (icon-props left-section-icon-color :big)]]
(when put-middle-section-on-left?
[mid-section (assoc mid-section-props
:left-align? true
:mid-section-description mid-section-description
:mid-section-description-color mid-section-description-color
:mid-section-description-icon mid-section-description-icon
:align-mid? align-mid?
:mid-section-description-user-icon mid-section-description-user-icon)])]
(when-not put-middle-section-on-left?
[mid-section mid-section-props])
[rn/view {:style (assoc
centrify-style
:flex-direction :row
:flex 1
:justify-content :flex-end)}
(let [last-icon-index (- (count right-section-icons) 1)]
(map-indexed (fn [index {:keys [icon background-color icon-color]}]
^{:key index}
[right-section-icon {:icon icon
:background-color background-color
:icon-color icon-color
:push-to-the-left? (if (= index last-icon-index) false true)}])
right-section-icons))]]))
+132 -11
View File
@@ -1,21 +1,142 @@
(ns quo2.components.tabs.tabs
(:require [reagent.core :as reagent]
(:require [oops.core :refer [oget]]
[quo.react-native :as rn]
[quo2.components.tabs.tab :as tab]))
[quo2.components.tabs.tab :as tab]
[reagent.core :as reagent]
[status-im.ui.components.react :as react]
[status-im.utils.core :as utils]
[status-im.utils.number :as number-utils]))
(def default-tab-size 32)
(defn tabs [{:keys [default-active on-change]}]
(let [active-tab-id (reagent/atom default-active)]
(fn [{:keys [data size] :or {size 32}}]
(fn [{:keys [data size] :or {size default-tab-size}}]
(let [active-id @active-tab-id]
[rn/view {:flex-direction :row}
(for [{:keys [label id]} data]
^{:key id}
[rn/view {:margin-right (if (= size 32) 12 8)}
[rn/view {:style {:margin-right (if (= size default-tab-size) 12 8)}}
[tab/tab
{:id id
:size size
:active (= id active-id)
:on-press #(do (reset! active-tab-id %)
(when on-change
(on-change %)))}
label]])]))))
{:id id
:size size
:active (= id active-id)
:on-press (fn [^js press-event id]
(reset! active-tab-id id)
(when on-change
(on-change press-event id)))}
label]])]))))
(defn- calculate-fade-end-percentage
[{:keys [offset-x content-width layout-width max-fade-percentage]}]
(let [fade-percentage (max max-fade-percentage
(/ (+ layout-width offset-x)
content-width))]
;; Truncate to avoid unnecessary rendering.
(if (> fade-percentage 0.99)
0.99
(number-utils/naive-round fade-percentage 2))))
(defn scrollable-tabs
"Just like the component `tabs`, displays horizontally scrollable tabs with
extra options to control if/how the end of the scroll view fades.
Tabs are rendered using ReactNative's FlatList, which offers the convenient
`scrollToIndex` method. FlatList accepts VirtualizedList and ScrollView props,
and so does this component.
Usage:
[tabs/scrollable-tabs
{:scroll-on-press? true
:fade-end? true
:on-change #(...)
:default-active :tab-a
:data [{:id :tab-a :label \"Tab A\"}
{:id :tab-b :label \"Tab B\"}]}]]
Opts:
- `size` number
- `scroll-on-press?` When non-nil, clicking on a tab centers it the middle
(with animation enabled).
- `fade-end?` When non-nil, causes the end of the scrollable view to fade out.
- `fade-end-percentage` Percentage where fading starts relative to the total
layout width of the `flat-list` data."
[{:keys [default-active fade-end-percentage]
:or {fade-end-percentage 0.8}}]
(let [active-tab-id (reagent/atom default-active)
fading (reagent/atom {:fade-end-percentage fade-end-percentage})
flat-list-ref (atom nil)]
(fn [{:keys [data
fade-end-percentage
fade-end?
on-change
on-scroll
scroll-event-throttle
scroll-on-press?
size]
:or {fade-end-percentage fade-end-percentage
fade-end? false
scroll-event-throttle 64
scroll-on-press? false
size default-tab-size}
:as props}]
(let [maybe-mask-wrapper (if fade-end?
[react/masked-view
{:mask-element (reagent/as-element
[react/linear-gradient {:colors [:black :transparent]
:locations [(get @fading :fade-end-percentage) 1]
:start {:x 0 :y 0}
:end {:x 1 :y 0}
:pointer-events :none
:style {:width "100%"
:height "100%"}}])}]
[:<>])]
(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/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]} 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]))}}
[tab/tab {:id id
:size size
:active (= id @active-tab-id)
:on-press (fn [id]
(reset! active-tab-id id)
(when scroll-on-press?
(.scrollToIndex @flat-list-ref
#js {:animated true
:index index
:viewPosition 0.5}))
(when on-change
(on-change id)))}
label]])})])))))
-80
View File
@@ -1,80 +0,0 @@
(ns quo2.components.tags.filter-tag
(:require [quo2.foundations.colors :as colors]
[quo.theme :as theme]
[quo.react-native :as rn]
[quo2.components.icon :as icons]
[quo2.components.markdown.text :as text]
[quo2.components.tags.base-tag :as base-tag]))
(def themes {:light {:default {:border-color colors/neutral-20
:blurred-border-color colors/neutral-80-opa-5
:text-color {:style {:color colors/neutral-100}}}
:active {:border-color colors/neutral-30
:blurred-border-color colors/neutral-80-opa-10
:text-color {:style {:color colors/neutral-100}}}
:disabled {:border-color colors/neutral-20
:blurred-border-color colors/neutral-80-opa-5
:text-color {:style {:color colors/neutral-100}}}}
:dark {:default {:border-color colors/neutral-70
:blurred-border-color colors/white-opa-10
:text-color {:style {:color colors/white}}}
:active {:border-color colors/neutral-60
:blurred-border-color colors/white-opa-20
:text-color {:style {:color colors/white}}}
:disabled {:border-color colors/neutral-70
:blurred-border-color colors/white-opa-10
:text-color {:style {:color colors/white}}}}})
(defn tag-resources [size type resource icon-color label text-color labelled]
[rn/view {:style (merge {:flex-direction :row
:align-items :center
:justify-content :center}
(when label
{:padding-horizontal (case size 32 12 24 8)}))}
(when (= type :icon)
[icons/icon resource {:container-style (when label
{:margin-right 4})
:resize-mode :center
:size (case size
32 20
24 12)
:color icon-color}])
(when (= type :emoji)
[rn/image {:source resource
:style (merge (case size
32 {:height 20
:width 20}
24 {:height 12
:width 12})
(when label
{:margin-right 4}))}])
(when labelled
[text/text (merge {:size (case size
32 :paragraph-1
24 :paragraph-2
20 :label nil)
:weight :medium
:number-of-lines 1}
text-color)
label])])
(defn filter-tag
[_ _]
(fn [{:keys [id on-press disabled size resource active accessibility-label
label type labelled blurred icon-color] :or {size 32}}]
(let [state (cond disabled :disabled active :active :else :default)
{:keys [border-color blurred-border-color text-color]}
(get-in themes [(theme/get-theme) state])]
[base-tag/base-tag {:id id
:size size
:border-width 1
:border-color (if blurred
blurred-border-color
border-color)
:on-press on-press
:accessibility-label accessibility-label
:disabled disabled
:type type
:label label}
[tag-resources size type resource icon-color label text-color labelled]])))
+74 -45
View File
@@ -1,51 +1,80 @@
(ns quo2.components.tags.tag
(:require [quo.react-native :as rn]
[quo2.foundations.colors :as colors]
(:require [quo2.foundations.colors :as colors]
[quo.theme :as theme]
[quo2.components.markdown.text :as text]))
[quo.react-native :as rn]
[quo2.components.icon :as icons]
[quo2.components.markdown.text :as text]
[quo2.components.tags.base-tag :as base-tag]))
(def themes {:light {:background-color colors/neutral-20}
:dark {:background-color colors/neutral-80}})
(def themes {:light {:default {:border-color colors/neutral-20
:blurred-border-color colors/neutral-80-opa-5
:text-color {:style {:color colors/neutral-100}}}
:active {:border-color colors/neutral-30
:blurred-border-color colors/neutral-80-opa-10
:text-color {:style {:color colors/neutral-100}}}
:disabled {:border-color colors/neutral-20
:blurred-border-color colors/neutral-80-opa-5
:text-color {:style {:color colors/neutral-100}}}}
:dark {:default {:border-color colors/neutral-70
:blurred-border-color colors/white-opa-10
:text-color {:style {:color colors/white}}}
:active {:border-color colors/neutral-60
:blurred-border-color colors/white-opa-20
:text-color {:style {:color colors/white}}}
:disabled {:border-color colors/neutral-70
:blurred-border-color colors/white-opa-10
:text-color {:style {:color colors/white}}}}})
(defn get-value-from-size [size big-option small-option]
(if (= size :big) big-option small-option))
(defn tag-resources [size type resource icon-color label text-color labelled]
[rn/view {:style (merge {:flex-direction :row
:align-items :center
:justify-content :center}
(when label
{:padding-horizontal (case size 32 12 24 8)}))}
(when (= type :icon)
[icons/icon resource {:container-style (when label
{:margin-right 4})
:resize-mode :center
:size (case size
32 20
24 12)
:color icon-color}])
(when (= type :emoji)
[rn/image {:source resource
:style (merge (case size
32 {:height 20
:width 20}
24 {:height 12
:width 12})
(when label
{:margin-right 4}))}])
(when labelled
[text/text (merge {:size (case size
32 :paragraph-1
24 :paragraph-2
20 :label nil)
:weight :medium
:number-of-lines 1}
text-color)
label])])
(defn tag-container [size]
{:height (get-value-from-size size 32 24)
:align-items :center
:flex-direction :row
:border-radius 20})
(defn tag "[tag opts \"label\"]]
opts
{
:size :small/:big
:token-img-src :token-img-src
:token-img-style {}
:border-color :color
:overlay child-elements
}"
(defn tag
[_ _]
(fn [{:keys [size token-img-src token-img-style border-color overlay]
:or {size :small}} label]
[rn/view
{:style (when border-color
{:border-color border-color
:border-radius 20
:border-width 1})}
[rn/view
{:style (merge (tag-container size) (get themes (theme/get-theme)))}
[rn/image
{:source token-img-src
:style (merge
{:height (get-value-from-size size 28 20)
:width (get-value-from-size size 28 20)
:margin-left 2
:margin-right (get-value-from-size size 8 6)} token-img-style)}]
[text/text
{:weight :medium
:number-of-lines 1
:style
{:margin-right (get-value-from-size size 12 11)}
:size (get-value-from-size size :paragraph-2 :label)} label]
overlay]]))
(fn [{:keys [id on-press disabled size resource active accessibility-label
label type labelled blurred icon-color] :or {size 32}}]
(let [state (cond disabled :disabled active :active :else :default)
{:keys [border-color blurred-border-color text-color]}
(get-in themes [(theme/get-theme) state])]
[base-tag/base-tag {:id id
:size size
:border-width 1
:border-color (if blurred
blurred-border-color
border-color)
:on-press on-press
:accessibility-label accessibility-label
:disabled disabled
:type type
:label label}
[tag-resources size type resource icon-color label text-color labelled]])))
@@ -1,7 +1,7 @@
(ns quo2.components.tags.filter-tags
(ns quo2.components.tags.tags
(:require [reagent.core :as reagent]
[quo.react-native :as rn]
[quo2.components.tags.filter-tag :as tag]))
[quo2.components.tags.tag :as tag]))
(defn tags [{:keys [default-active on-change]}]
(let [active-tab-id (reagent/atom default-active)]
@@ -11,7 +11,7 @@
(for [{:keys [tag-label id resource]} data]
^{:key id}
[rn/view {:margin-right 8}
[tag/filter-tag
[tag/tag
(merge {:id id
:size size
:type type
+44
View File
@@ -2,9 +2,13 @@
(:require [quo2.foundations.colors :as colors]
[quo.react-native :as rn]
[quo.theme :as theme]
[quo2.components.markdown.text :as text]
[status-im.ui.components.icons.icons :as icons]
[quo2.components.tags.tag :as tag]))
(def themes {:light {:background-color colors/neutral-20}
:dark {:background-color colors/neutral-80}})
(defn get-value-from-size [size big-option small-option]
(if (= size :big) big-option small-option))
@@ -16,6 +20,46 @@
:border-radius 20
:margin-left 2})
(defn tag-container [size]
{:height (get-value-from-size size 32 24)
:align-items :center
:flex-direction :row
:border-radius 20})
(defn tag "[tag opts \"label\"]]
opts
{
:size :small/:big
:token-img-src :token-img-src
:token-img-style {}
:border-color :color
:overlay child-elements
}"
[_ _]
(fn [{:keys [size token-img-src token-img-style border-color overlay]
:or {size :small}} label]
[rn/view
{:style (when border-color
{:border-color border-color
:border-radius 20
:border-width 1})}
[rn/view
{:style (merge (tag-container size) (get themes (theme/get-theme)))}
[rn/image
{:source token-img-src
:style (merge
{:height (get-value-from-size size 28 20)
:width (get-value-from-size size 28 20)
:margin-left 2
:margin-right (get-value-from-size size 8 6)} token-img-style)}]
[text/text
{:weight :medium
:number-of-lines 1
:style
{:margin-right (get-value-from-size size 12 11)}
:size (get-value-from-size size :paragraph-2 :label)} label]
overlay]]))
(defn token-tag
"[token-tag opts]
opts
+7 -7
View File
@@ -119,6 +119,7 @@
;;;; Customization
;; Colors for customizing profiles and communities themes
(def customization
{:primary {50 primary-50 ;; User can also use primary color as customisation color
60 primary-60}
@@ -145,6 +146,11 @@
:beige {50 "#CAAE93"
60 "#AA927C"}})
(def colors-map (merge {:danger {50 danger-50
60 danger-60}
:success {50 success-50
60 success-60}} customization))
(def custom-color
"(custom-color color suffix opacity)
color :primary/:purple/...
@@ -155,7 +161,7 @@
([color suffix]
(custom-color color suffix nil))
([color suffix opacity]
(let [base-color (get-in customization [(keyword color) suffix])]
(let [base-color (get-in colors-map [(keyword color) suffix])]
(if opacity (alpha base-color (/ opacity 100)) base-color))))))
(defn custom-color-by-theme
@@ -188,12 +194,6 @@
(let [theme (or override-theme (theme/get-theme))]
(if (= theme :light) light dark))))
(def dark-prompt-bg "#1C1C1E")
(def dark-prompt-title "#9F9FA5")
(def dark-prompt-button "#8E8E93")
(def light-prompt-title "#8F8E94")
(def light-prompt-button "#D5D4DB")
(defn dark?
[]
(theme/dark?))
+1 -1
View File
@@ -19,7 +19,7 @@
:type :select
:options [{:key :main-icons/placeholder20
:value "Placeholder"}
{:key :main-icons/walelt
{:key :main-icons/wallet
:value "Wallet"}
{:key :main-icons/play
:value "Play"}]}
+11 -4
View File
@@ -3,7 +3,8 @@
[quo2.foundations.colors :as colors]
[quo.previews.preview :as preview]
[quo2.components.avatars.user-avatar :as quo2]
[reagent.core :as reagent]))
[reagent.core :as reagent]
[status-im.react-native.resources :as resources]))
(def descriptor [{:label "Size:"
:key :size
@@ -32,9 +33,15 @@
{:label "Full name separated by space"
:key :full-name
:type :text}
{:label "Profile Picture URL"
:key :profile-picture
:type :text}])
{:label "Profile Picture"
:key :profile-picture
:type :select
:options [{:value "None"
:key nil}
{:value "Alicia Keys"
:key (resources/get-mock-image :user-picture-female2)}
{:value "pedro.eth"
:key (resources/get-mock-image :user-picture-male4)}]}])
(defn cool-preview []
(let [state (reagent/atom {:full-name "A Y"
@@ -0,0 +1,45 @@
(ns quo2.screens.buttons.dynamic-button
(:require [quo.react-native :as rn]
[reagent.core :as reagent]
[quo.previews.preview :as preview]
[quo2.foundations.colors :as colors]
[quo2.components.buttons.dynamic-button :as quo2]))
(def descriptor [{:label "Type:"
:key :type
:type :select
:options [{:key :jump-to
:value "Jump To"}
{:key :mention
:value "Mention"}
{:key :notification-down
:value "Notification Down"}
{:key :notification-up
:value "Notification Up"}
{:key :search
:value "Search"}
{:key :search-with-label
:value "Search With Label"}
{:key :bottom
:value "Bottom"}]}
{:label "Count"
:key :count
:type :text}])
(defn cool-preview []
(let [state (reagent/atom {:count 5 :type :jump-to})]
(fn []
[rn/touchable-without-feedback {:on-press rn/dismiss-keyboard!}
[rn/view {:padding-bottom 150}
[preview/customizer state descriptor]
[rn/view {:padding-vertical 60
:align-items :center}
[quo2/dynamic-button @state]]]])))
(defn preview-dynamic-button []
[rn/view {:background-color (colors/theme-colors colors/white colors/neutral-90)
:flex 1}
[rn/flat-list {:flex 1
:keyboardShouldPersistTaps :always
:header [cool-preview]
:key-fn str}]])
-33
View File
@@ -1,33 +0,0 @@
(ns quo2.screens.info.nfc-prompt
(:require [quo.react-native :as rn]
[reagent.core :as reagent]
[quo.previews.preview :as preview]
[quo2.foundations.colors :as colors]
[quo2.components.info.nfc-prompt :as quo2]))
(def descriptor [{:label "Prompt status"
:key :prompt-status
:type :select
:options [{:key :ready
:value "Ready"}
{:key :connected
:value "Connected"}
{:key :success
:value "Success"}]}])
(defn cool-preview []
(let [state (reagent/atom {:on-press identity
:prompt-status :ready})]
(fn []
[rn/view {:margin-bottom 50
:padding-vertical 16}
[preview/customizer state descriptor]
[quo2/nfc-prompt @state]])))
(defn preview-nfc-prompt []
[rn/view {:background-color (colors/theme-colors colors/white colors/neutral-90)
:flex 1}
[rn/flat-list {:flex 1
:keyboardShouldPersistTaps :always
:header [cool-preview]
:key-fn str}]])
+19 -11
View File
@@ -13,6 +13,7 @@
[quo2.screens.avatars.user-avatar :as user-avatar]
[quo2.screens.avatars.wallet-user-avatar :as wallet-user-avatar]
[quo2.screens.buttons.button :as button]
[quo2.screens.buttons.dynamic-button :as dynamic-button]
[quo2.screens.counter.counter :as counter]
[quo2.screens.community.community-card-view :as community-card]
[quo2.screens.dividers.divider-label :as divider-label]
@@ -25,6 +26,7 @@
[quo2.screens.list-items.channel :as channel]
[quo2.screens.markdown.text :as text]
[quo2.screens.messages.gap :as messages-gap]
[quo2.screens.messages.system-message :as system-message]
[quo2.screens.notifications.activity-logs :as activity-logs]
[quo2.screens.reactions.react :as react]
[quo2.screens.selectors.disclaimer :as disclaimer]
@@ -36,14 +38,14 @@
[quo2.screens.tabs.segmented-tab :as segmented]
[quo2.screens.tabs.tabs :as tabs]
[quo2.screens.tags.context-tags :as context-tags]
[quo2.screens.tags.filter-tags :as filter-tags]
[quo2.screens.tags.tags :as tags]
[quo2.screens.tags.permission-tag :as permission-tag]
[quo2.screens.tags.status-tags :as status-tags]
[quo2.screens.tags.token-tag :as token-tag]
[quo2.screens.wallet.token-overview :as token-overview]
[quo2.screens.wallet.network-breakdown :as network-breakdown]
[quo2.screens.wallet.network-amount :as network-amount]
[quo2.screens.info.nfc-prompt :as nfc-prompt]
[quo2.screens.navigation.page-nav :as page-nav]
[re-frame.core :as re-frame]))
(def screens-categories
@@ -64,7 +66,10 @@
:component channel-avatar/preview-channel-avatar}]
:buttons [{:name :button
:insets {:top false}
:component button/preview-button}]
:component button/preview-button}
{:name :dynamic-button
:insets {:top false}
:component dynamic-button/preview-dynamic-button}]
:community [{:name :community-cards
:insets {:top false}
:component community-card/preview-community-card}]
@@ -85,10 +90,7 @@
:component info-message/preview-info-message}
{:name :information-box
:insets {:top false}
:component information-box/preview-information-box}
{:name :nfc-prompt
:insets {:top false}
:component nfc-prompt/preview-nfc-prompt}]
:component information-box/preview-information-box}]
:list-items [{:name :channel
:insets {:top false}
:component channel/preview-channel}
@@ -100,13 +102,19 @@
:component text/preview-text}]
:messages [{:name :gap
:insets {:top false}
:component messages-gap/preview-messages-gap}]
:component messages-gap/preview-messages-gap}
{:name :system-messages
:insets {:top false}
:component system-message/preview-system-message}]
:navigation [{:name :bottom-nav-tab
:insets {:top false}
:component bottom-nav-tab/preview-bottom-nav-tab}
{:name :top-nav
:insets {:top false}
:component top-nav/preview-top-nav}]
:component top-nav/preview-top-nav}
{:name :page-nav
:insets {:top false}
:component page-nav/preview-page-nav}]
:notifications [{:name :activity-logs
:insets {:top false}
:component activity-logs/preview-activity-logs}]
@@ -134,9 +142,9 @@
:tags [{:name :context-tags
:insets {:top false}
:component context-tags/preview-context-tags}
{:name :filter-tags
{:name :tags
:insets {:top false}
:component filter-tags/preview-filter-tags}
:component tags/preview-tags}
{:name :permission-tag
:insets {:top false}
:component permission-tag/preview-permission-tag}
@@ -0,0 +1,68 @@
(ns quo2.screens.messages.system-message
(:require [reagent.core :as reagent]
[status-im.react-native.resources :as resources]
[quo.react-native :as rn]
[quo.previews.preview :as preview]
[quo2.components.messages.system-message :as system-message]
[quo2.foundations.colors :as colors]))
(def descriptor [{:label "Message Type"
:key :type
:type :select
:options [{:value "Message pinned"
:key :pinned}
{:value "User added"
:key :added}
{:value "Message deleted"
:key :deleted}]}
{:label "Action"
:key :action
:type :select
:options [{:value "none"
:key nil}
{:value "Undo"
:key :undo}]}
{:label "Pinned By"
:key :pinned-by
:type :text}
{:label "Content Text"
:key :content-text
:type :text}
{:label "Content Info"
:key :content-info
:type :text}
{:label "Timestamp"
:key :timestamp-str
:type :text}])
(defn finalize-state [state]
(merge @state
{:mentions [{:name "Alicia Keys"
:image (resources/get-mock-image :user-picture-female2)}
{:name "pedro.eth"
:image (resources/get-mock-image :user-picture-male4)}]
:content {:text (:content-text @state)
:info (:content-info @state)
:mentions {:name "Alisher"
:image (resources/get-mock-image :user-picture-male5)}}}))
(defn preview []
(let [state (reagent/atom {:type :deleted
:pinned-by "Steve"
:content-text "Hello! This is an example of a pinned message!"
:content-info "3 photos"
:timestamp-str "09:41"})]
(fn []
[rn/touchable-without-feedback {:on-press rn/dismiss-keyboard!}
[rn/view {:padding-bottom 150}
[preview/customizer state descriptor]
[rn/view {:padding-vertical 60
:align-items :center}
[system-message/system-message (finalize-state state)]]]])))
(defn preview-system-message []
[rn/view {:background-color (colors/theme-colors colors/white colors/neutral-90)
:flex 1}
[rn/flat-list {:flex 1
:header [preview]
:key-fn str
:keyboardShouldPersistTaps :always}]])
+106
View File
@@ -0,0 +1,106 @@
(ns quo2.screens.navigation.page-nav
(:require [quo.previews.preview :as preview]
[quo.react-native :as rn]
[quo2.components.navigation.page-nav :as quo2]
[quo2.foundations.colors :as colors]
[reagent.core :as reagent]))
(def ^:private descriptor
[{:label "Page nav variation"
:key :selected-variation
:type :select
:options [{:key :text-only?
:value "Text only"}
{:key :align-left?
:value "Align Left"}
{:key :align-left-top-down-text?
:value "Align left top down text?"}
{:key :align-left-with-icon?
:value "Align Left with icon ?"}
{:key :one-icon-align-left?
:value "One icon on the left ?"}
{:key :one-icon-align-right?
:value "One icon on the right ?"}
{:key :two-icons?
:value "Two icons ?"}
{:key :user-icon?
:value "User icon ?"}
{:key :empty?
:value "Empty ?"}]}
{:label "Number of right icons"
:key :number-of-right-icons
:type :select
:options [{:key 1
:value 1}
{:key 2
:value 2}
{:key 3
:value 3}]}])
(def ^:private selected-variation (reagent/atom {:selected-variation :text-only?
:number-of-right-icons 1}))
(defn- cool-preview []
(let [right-icon {:background-color (if (colors/dark?)
colors/neutral-80
colors/neutral-20)
:icon :main-icons/placeholder
:icon-color nil}
base-props {:horizontal-description? true
:one-icon-align-left? true
:align-mid? false
:page-nav-color :transparent
:page-nav-background-uri ""
:mid-section-type :text-with-description
:mid-section-icon :main-icons/placeholder
:mid-section-main-text "Status"
:mid-section-left-icon :main-icons/placeholder
:mid-section-right-icon :main-icons/placeholder
:mid-section-description "SNT"
:mid-section-description-color "black"
:mid-section-description-icon :main-icons/placeholder
:mid-section-description-user-icon "https://i.picsum.photos/id/810/200/300.jpg?hmac=HgwlXd-OaLOAqhGyCiZDUb_75EgUI4u0GtS7nfgxd8s"
:left-section-icon :main-icons/unlocked
:left-section-icon-background-color (if (colors/dark?)
colors/neutral-80
colors/neutral-20)}
create-variation #(merge %1 %2)
variations {:text-only? base-props
:align-left? (create-variation base-props {:align-mid? true})
:one-icon-align-left? (create-variation base-props {:one-icon-align-left? true
:mid-section-type :text-with-one-icon})
:one-icon-align-right? (create-variation base-props {:one-icon-align-left? false
:mid-section-type :text-with-one-icon})
:two-icons? (create-variation base-props {:mid-section-type :text-with-two-icons})
:user-icon? (create-variation base-props {:align-mid? true
:horizontal-description? false
:mid-section-type :text-with-one-icon})
:empty? (create-variation base-props {:mid-section-main-text ""
:mid-section-description ""})
:align-left-with-icon? (create-variation base-props {:align-mid? true
:mid-section-type :text-with-one-icon})
:align-left-top-down-text? (create-variation base-props {:align-mid? true
:horizontal-description? false
:mid-section-type :text-with-description})}
state (reagent/atom (-> (get variations (:selected-variation @selected-variation))
(assoc :right-section-icons (repeat (:number-of-right-icons @selected-variation) right-icon))))]
(fn []
[rn/view {:margin-bottom 50
:padding 16}
[rn/view {:flex 1}
[preview/customizer selected-variation descriptor]]
[rn/view {:padding-vertical 30
:flex-direction :row
:justify-content :center}
[quo2/page-nav @state]]])))
(def ^:private trackable-cool-preview (reagent/track cool-preview selected-variation))
(defn preview-page-nav []
[rn/view {:background-color (colors/theme-colors colors/white
colors/neutral-90)
:flex 1}
[rn/flat-list {:flex 1
:keyboardShouldPersistTaps :always
:header [@trackable-cool-preview]
:key-fn str}]])
+1 -2
View File
@@ -14,8 +14,7 @@
[rn/view {:padding-vertical 60
:align-items :center}
[quo2/disclaimer
{:checked? @checked?
:container-style {:margin-bottom 40}
{:container-style {:margin-bottom 40}
:on-change #(swap! checked? not)}
"I agree with the community rules"]
[button/button
+1 -1
View File
@@ -160,7 +160,7 @@
(defn preview-permission-tag []
[rn/view {:background-color (colors/theme-colors
colors/white
colors/neutral-80)
colors/neutral-90)
:flex 1}
[rn/flat-list {:flex 1
:keyboardShouldPersistTaps :always
@@ -1,9 +1,9 @@
(ns quo2.screens.tags.filter-tags
(ns quo2.screens.tags.tags
(:require [quo.react-native :as rn]
[quo.previews.preview :as preview]
[status-im.ui.components.react :as react]
[quo2.foundations.colors :as colors]
[quo2.components.tags.filter-tags :as quo2.tags]
[quo2.components.tags.tags :as tags]
[status-im.react-native.resources :as resources]
[reagent.core :as reagent]))
@@ -70,15 +70,15 @@
:align-items :center
:position :absolute
:padding-horizontal 10}
[quo2.tags/tags (merge @state
{:default-active 1
:data [{:id 1 :tag-label "Music" :resource (resources/get-image :music)}
{:id 2 :tag-label "Lifestyle" :resource (resources/get-image :lifestyle)}
{:id 3 :tag-label "Podcasts" :resource (resources/get-image :podcasts)}]})]]]]])))
(defn preview-filter-tags []
[tags/tags (merge @state
{:default-active 1
:data [{:id 1 :tag-label "Music" :resource (resources/get-image :music)}
{:id 2 :tag-label "Lifestyle" :resource (resources/get-image :lifestyle)}
{:id 3 :tag-label "Podcasts" :resource (resources/get-image :podcasts)}]})]]]]])))
(defn preview-tags []
[rn/view {:flex 1
:background-color (colors/theme-colors
colors/neutral-5
colors/white
colors/neutral-90)}
[rn/flat-list {:flex 1
:keyboardShouldPersistTaps :always
+619 -58
View File
@@ -1,97 +1,658 @@
(ns status-im.activity-center.core
(:require [re-frame.core :as re-frame]
[status-im.constants :as constants]
[status-im.data-store.activities :as data-store.activities]
[status-im.ethereum.json-rpc :as json-rpc]
[status-im.utils.fx :as fx]
[status-im.utils.datetime :as utils.datetime]
[taoensso.timbre :as log]))
;;;; Notification reconciliation
(def response-notifications
[{:replyMessage nil,
:read true,
:dismissed false,
:chatId
"0x047c654a4b35e4c3605c0eacf68364dd43140616e4d96ae362c4bf7c5ae3f1a3c03ad5935ae1face369bf96edda4ec02459b33964f1a7781036824f1e528c21b2f",
:name "0x047c65",
:accepted false,
:type 5,
:author
"0x047c654a4b35e4c3605c0eacf68364dd43140616e4d96ae362c4bf7c5ae3f1a3c03ad5935ae1face369bf96edda4ec02459b33964f1a7781036824f1e528c21b2f",
:lastMessage nil,
:id
"0x047c654a4b35e4c3605c0eacf68364dd43140616e4d96ae362c4bf7c5ae3f1a3c03ad5935ae1face369bf96edda4ec02459b33964f1a7781036824f1e528c21b2f20",
:timestamp 1665167287000,
:message
{:messageType 0,
:identicon
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAeElEQVR4nOzR0QnCMBhGURV3cR53cQx3cR6naRfoQ6EtXH7OeQwh5PI9bkMIqRFSI6RGSI2QGiE1QmqE1AipEVIzJuS59+L3/1u2zj+v9/2Mjxx9f8wiQmqE1AipEVIjpEZIjZAaIVxkzCJCaoTUCKkRUrMGAAD////ICGUlBQ5nAAAAAElFTkSuQmCC",
:replace "",
:chatId "",
:displayName "",
:lineCount 0,
:contactRequestState 2,
:whisperTimestamp 1665167287000,
:alias "Darkseagreen Colorless Honeybee",
:seen true,
:gapParameters {},
:from
"0x047c654a4b35e4c3605c0eacf68364dd43140616e4d96ae362c4bf7c5ae3f1a3c03ad5935ae1face369bf96edda4ec02459b33964f1a7781036824f1e528c21b2f",
:id
"0x047c654a4b35e4c3605c0eacf68364dd43140616e4d96ae362c4bf7c5ae3f1a3c03ad5935ae1face369bf96edda4ec02459b33964f1a7781036824f1e528c21b2f20",
:parsedText
[{:type "paragraph",
:children [{:literal "Please add me to your contacts"}]}],
:contentType 11,
:clock 0,
:localChatId "",
:timestamp 0,
:ensName "",
:quotedMessage nil,
:rtl false,
:responseTo "",
:text "Please add me to your contacts"}}
{:replyMessage nil,
:read true,
:dismissed false,
:chatId
"0x04892d0884fb44a940853ba988ce14b4c7c18351ea617bf29a5ceaa9eca03a8e1770f66282d9af9f5644578f8fcb8f4ff8fd926d8b389db4384ec91492aee8406d",
:name "0x04892d",
:accepted false,
:type 5,
:author
"0x04892d0884fb44a940853ba988ce14b4c7c18351ea617bf29a5ceaa9eca03a8e1770f66282d9af9f5644578f8fcb8f4ff8fd926d8b389db4384ec91492aee8406d",
:lastMessage nil,
:id
"0x04892d0884fb44a940853ba988ce14b4c7c18351ea617bf29a5ceaa9eca03a8e1770f66282d9af9f5644578f8fcb8f4ff8fd926d8b389db4384ec91492aee8406d20",
:timestamp 1665100538000,
:message
{:messageType 0,
:identicon
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAiElEQVR4nOzWUQmAYAwHcRW7GMwKxrCCwUyjBRSUMTj+3O/Rh0+Owdg0hDCExhAaQ2gMoYkJmbse3o/zevq+rcvY8b+YiRhCYwjN5w3SvYWq78dMxBAaQ2i8tWgMoTGExhAaQ2gMoSnfPW831V/VGyxmIobQGKImMRMxhMYQGkNoDKG5AwAA//9P5BRl2i6T5QAAAABJRU5ErkJggg==",
:replace "",
:chatId "",
:displayName "",
:lineCount 0,
:contactRequestState 2,
:whisperTimestamp 1665100538000,
:alias "Amused Neighboring Senegalpython",
:seen true,
:gapParameters {},
:from
"0x04892d0884fb44a940853ba988ce14b4c7c18351ea617bf29a5ceaa9eca03a8e1770f66282d9af9f5644578f8fcb8f4ff8fd926d8b389db4384ec91492aee8406d",
:id
"0x04892d0884fb44a940853ba988ce14b4c7c18351ea617bf29a5ceaa9eca03a8e1770f66282d9af9f5644578f8fcb8f4ff8fd926d8b389db4384ec91492aee8406d20",
:parsedText
[{:type "paragraph",
:children [{:literal "Please add me to your contacts"}]}],
:contentType 11,
:clock 0,
:localChatId "",
:timestamp 0,
:ensName "",
:quotedMessage nil,
:rtl false,
:responseTo "",
:text "Please add me to your contacts"}}
{:replyMessage nil,
:read true,
:dismissed false,
:chatId
"0x04d7b7648d9ef3cacdb64755d2fed9a70e4eb1e287e5f59c58cb3eda2485bdc0d0fb0f88f87dcd568637f2213205defee3fbd864bfe279bf87e43301a32647b88f",
:name "0x04d7b7",
:accepted false,
:type 5,
:author
"0x04d7b7648d9ef3cacdb64755d2fed9a70e4eb1e287e5f59c58cb3eda2485bdc0d0fb0f88f87dcd568637f2213205defee3fbd864bfe279bf87e43301a32647b88f",
:lastMessage nil,
:id
"0x04d7b7648d9ef3cacdb64755d2fed9a70e4eb1e287e5f59c58cb3eda2485bdc0d0fb0f88f87dcd568637f2213205defee3fbd864bfe279bf87e43301a32647b88f20",
:timestamp 1665097520000,
:message
{:messageType 0,
:identicon
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAgUlEQVR4nOzWwQmAMBAFURV7sRI7sgw7shKr0XtASMCF4TPvHALDXv4yhTCExhAaQ2gMoYkJWXsfnvf1jHx8bPtc+U8r5iKG0BhCYwiNITSG0HRvrS+jm+qvDdaKuYghNIaoSMxFDKExhMYQGkNoDKExhMYQGkNoDKExhOYNAAD//0iXEGcISwhOAAAAAElFTkSuQmCC",
:replace "",
:chatId "",
:displayName "",
:lineCount 0,
:contactRequestState 3,
:whisperTimestamp 1665097520000,
:alias "Honeydew Golden Amazondolphin",
:seen true,
:gapParameters {},
:from
"0x04d7b7648d9ef3cacdb64755d2fed9a70e4eb1e287e5f59c58cb3eda2485bdc0d0fb0f88f87dcd568637f2213205defee3fbd864bfe279bf87e43301a32647b88f",
:id
"0x04d7b7648d9ef3cacdb64755d2fed9a70e4eb1e287e5f59c58cb3eda2485bdc0d0fb0f88f87dcd568637f2213205defee3fbd864bfe279bf87e43301a32647b88f20",
:parsedText
[{:type "paragraph",
:children [{:literal "Please add me to your contacts"}]}],
:contentType 11,
:clock 0,
:localChatId "",
:timestamp 0,
:ensName "",
:quotedMessage nil,
:rtl false,
:responseTo "",
:text "Please add me to your contacts"}}
{:replyMessage nil,
:read true,
:dismissed false,
:chatId
"0x040185b964f42f2f11a07bb3275478605cf9f5a2f79af8a15d30baeaa9fe47e19d636d6461b797cfd2edf57a51e7be912c81e60698758bf145eaa611ae30f921df",
:name "0x040185",
:accepted false,
:type 5,
:author
"0x040185b964f42f2f11a07bb3275478605cf9f5a2f79af8a15d30baeaa9fe47e19d636d6461b797cfd2edf57a51e7be912c81e60698758bf145eaa611ae30f921df",
:lastMessage nil,
:id
"0x040185b964f42f2f11a07bb3275478605cf9f5a2f79af8a15d30baeaa9fe47e19d636d6461b797cfd2edf57a51e7be912c81e60698758bf145eaa611ae30f921df20",
:timestamp 1665097287000,
:message
{:messageType 0,
:identicon
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAfklEQVR4nOzYsQmAMABEURV3sXI7x3A7K6fRBSyEcPA5/istIp+DFFmmEobQGEJjCI0hNDUha+rg876er+/Hts+J/9UsYgiNITS/b5D0LTR6fs0ihtAYQmMIjSE0hiikZhFDaAyh8V2LxhAaQxRSs4ghNIbQGEJjCM0bAAD//5fvEGQwrcNWAAAAAElFTkSuQmCC",
:replace "",
:chatId "",
:displayName "",
:lineCount 0,
:contactRequestState 2,
:whisperTimestamp 1665097287000,
:alias "Quizzical Private Anteater",
:seen true,
:gapParameters {},
:from
"0x040185b964f42f2f11a07bb3275478605cf9f5a2f79af8a15d30baeaa9fe47e19d636d6461b797cfd2edf57a51e7be912c81e60698758bf145eaa611ae30f921df",
:id
"0x040185b964f42f2f11a07bb3275478605cf9f5a2f79af8a15d30baeaa9fe47e19d636d6461b797cfd2edf57a51e7be912c81e60698758bf145eaa611ae30f921df20",
:parsedText
[{:type "paragraph",
:children [{:literal "Please add me to your contacts"}]}],
:contentType 11,
:clock 0,
:localChatId "",
:timestamp 0,
:ensName "",
:quotedMessage nil,
:rtl false,
:responseTo "",
:text "Please add me to your contacts"}}
{:replyMessage nil,
:read true,
:dismissed false,
:chatId
"0x046bf5a02fd3a28f82f656bbfbfcaf9bec021b3617c3dc043dc8353f4f6f009ac53df5eba2a2ae63b6f77413a1ad9a054f5c4509ce68b4bf0e688186ab95d2fca7",
:name "0x046bf5",
:accepted false,
:type 5,
:author
"0x046bf5a02fd3a28f82f656bbfbfcaf9bec021b3617c3dc043dc8353f4f6f009ac53df5eba2a2ae63b6f77413a1ad9a054f5c4509ce68b4bf0e688186ab95d2fca7",
:lastMessage nil,
:id
"0x046bf5a02fd3a28f82f656bbfbfcaf9bec021b3617c3dc043dc8353f4f6f009ac53df5eba2a2ae63b6f77413a1ad9a054f5c4509ce68b4bf0e688186ab95d2fca720",
:timestamp 1665089696000,
:message
{:messageType 0,
:identicon
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAgklEQVR4nOzWQQpAYABEYeQu9s7mGM5m7zTsZaE09Zret5R+vab+TEMJQ2gMoTGExhCampA5dfB+Htfb821Zx8T3ahYxhMYQms+3VvoW+nt+zSKG0BiikJpFDKExhCb2r5V+/6lmEUNoDFFIzSKG0BhCYwiNITSG0BhCYwjNHQAA///zHxhnUGfT1gAAAABJRU5ErkJggg==",
:replace "",
:chatId "",
:displayName "",
:lineCount 0,
:contactRequestState 2,
:whisperTimestamp 1665089696000,
:alias "Growling Marvelous Parrot",
:seen true,
:gapParameters {},
:from
"0x046bf5a02fd3a28f82f656bbfbfcaf9bec021b3617c3dc043dc8353f4f6f009ac53df5eba2a2ae63b6f77413a1ad9a054f5c4509ce68b4bf0e688186ab95d2fca7",
:id
"0x046bf5a02fd3a28f82f656bbfbfcaf9bec021b3617c3dc043dc8353f4f6f009ac53df5eba2a2ae63b6f77413a1ad9a054f5c4509ce68b4bf0e688186ab95d2fca720",
:parsedText
[{:type "paragraph",
:children [{:literal "Please add me to your contacts"}]}],
:contentType 11,
:clock 0,
:localChatId "",
:timestamp 0,
:ensName "",
:quotedMessage nil,
:rtl false,
:responseTo "",
:text "Please add me to your contacts"}}
{:replyMessage nil,
:read true,
:dismissed false,
:chatId
"0x044a2e346d30266b1b0a05def37b91a1578ab5e420c6971da1bde04d125c40b40a3ea1fe9519398b3fb354986096700f5887b67bb82a93f6e3dd151edc633b8f67",
:name "0x044a2e",
:accepted false,
:type 5,
:author
"0x044a2e346d30266b1b0a05def37b91a1578ab5e420c6971da1bde04d125c40b40a3ea1fe9519398b3fb354986096700f5887b67bb82a93f6e3dd151edc633b8f67",
:lastMessage nil,
:id
"0x044a2e346d30266b1b0a05def37b91a1578ab5e420c6971da1bde04d125c40b40a3ea1fe9519398b3fb354986096700f5887b67bb82a93f6e3dd151edc633b8f6720",
:timestamp 1665075842000,
:message
{:messageType 0,
:identicon
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAiUlEQVR4nOzWwQmEQBAF0V0xF2Pzbhjejc1oNIKRkR6h+NS7KxYNH6dfCENoDKExhMYQGkNoDKExhCYmZK6+4Dj3a8SHrMv2rzwfcxFDaAyhKS3Fk9aaVdepJeYihtAYQtO9IKP+qd7qXbmYixhCY4g+EnMRQ2gMoTGExhAaQ2gMoTGE5g4AAP//09cMY3Qm6ZsAAAAASUVORK5CYII=",
:replace "",
:chatId "",
:displayName "",
:lineCount 0,
:contactRequestState 2,
:whisperTimestamp 1665075842000,
:alias "Stable Sniveling Baldeagle",
:seen true,
:gapParameters {},
:from
"0x044a2e346d30266b1b0a05def37b91a1578ab5e420c6971da1bde04d125c40b40a3ea1fe9519398b3fb354986096700f5887b67bb82a93f6e3dd151edc633b8f67",
:id
"0x044a2e346d30266b1b0a05def37b91a1578ab5e420c6971da1bde04d125c40b40a3ea1fe9519398b3fb354986096700f5887b67bb82a93f6e3dd151edc633b8f6720",
:parsedText
[{:type "paragraph",
:children [{:literal "Please add me to your contacts"}]}],
:contentType 11,
:clock 0,
:localChatId "",
:timestamp 0,
:ensName "",
:quotedMessage nil,
:rtl false,
:responseTo "",
:text "Please add me to your contacts"}}
{:replyMessage nil,
:read true,
:dismissed false,
:chatId
"0x043e742bfa6dcd0c93689727ad02ac9e9d418f2a03a783b40d72bc8f0b3144707022ef84ae59a787977275c27ac9421ab094b8fb3908bb80f729f483eff38d84fc",
:name "0x043e74",
:accepted false,
:type 5,
:author
"0x043e742bfa6dcd0c93689727ad02ac9e9d418f2a03a783b40d72bc8f0b3144707022ef84ae59a787977275c27ac9421ab094b8fb3908bb80f729f483eff38d84fc",
:lastMessage nil,
:id
"0x043e742bfa6dcd0c93689727ad02ac9e9d418f2a03a783b40d72bc8f0b3144707022ef84ae59a787977275c27ac9421ab094b8fb3908bb80f729f483eff38d84fc20",
:timestamp 1665075615000,
:message
{:messageType 0,
:identicon
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAkklEQVR4nOzWwQnDMBAF0SSkl3SQylJGKksHqca+GwvWyGuGz7yzDho+CD1uIQyhMYTGEBpDaGJCntWD399/6b3Kvs/7da+ci1nEEBpD1CRmEUNoDKGZ/muN/kLd57diFjGExhCa8qt1VPW1OUvMIobQGEIz/bJc/acaiVnEEBpD1CRmEUNoDKExhMYQmjUAAP//o5AZOL4SLM8AAAAASUVORK5CYII=",
:replace "",
:chatId "",
:displayName "",
:lineCount 0,
:contactRequestState 2,
:whisperTimestamp 1665075615000,
:alias "Trusty Nautical Zethusspinipes",
:seen true,
:gapParameters {},
:from
"0x043e742bfa6dcd0c93689727ad02ac9e9d418f2a03a783b40d72bc8f0b3144707022ef84ae59a787977275c27ac9421ab094b8fb3908bb80f729f483eff38d84fc",
:id
"0x043e742bfa6dcd0c93689727ad02ac9e9d418f2a03a783b40d72bc8f0b3144707022ef84ae59a787977275c27ac9421ab094b8fb3908bb80f729f483eff38d84fc20",
:parsedText
[{:type "paragraph",
:children [{:literal "Please add me to your contacts"}]}],
:contentType 11,
:clock 0,
:localChatId "",
:timestamp 0,
:ensName "",
:quotedMessage nil,
:rtl false,
:responseTo "",
:text "Please add me to your contacts"}}
{:replyMessage nil,
:read true,
:dismissed false,
:chatId
"0x0435b461849c0af696b2f71ad95e9c57053722a2390c833b304b4284b00466f52f940004d45f0fee149cf6ed65b8ff856921d40059344a01e68b2fa9dae82e4900",
:name "0x0435b4",
:accepted false,
:type 5,
:author
"0x0435b461849c0af696b2f71ad95e9c57053722a2390c833b304b4284b00466f52f940004d45f0fee149cf6ed65b8ff856921d40059344a01e68b2fa9dae82e4900",
:lastMessage nil,
:id
"0x0435b461849c0af696b2f71ad95e9c57053722a2390c833b304b4284b00466f52f940004d45f0fee149cf6ed65b8ff856921d40059344a01e68b2fa9dae82e490020",
:timestamp 1665074686000,
:message
{:messageType 0,
:identicon
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAgUlEQVR4nOzWsQmAMBgFYRV3cTwbx7BxPKfRPigYJOR43FdKEI7X/NMQwhAaQ2gMoTGEJiZk/vrw2M/r6fu6LWOP96WYRQyhMUSNxCxiCI0hNL9vrTe1N1Xtf0oxixhCYwiNITSG0BiiRmIWMYTGEBpDaAyhMYTGEBpDaO4AAAD//5TbFGUr+nc/AAAAAElFTkSuQmCC",
:replace "",
:chatId "",
:displayName "",
:lineCount 0,
:contactRequestState 3,
:whisperTimestamp 1665074686000,
:alias "Remote Vapid Honeyeater",
:seen true,
:gapParameters {},
:from
"0x0435b461849c0af696b2f71ad95e9c57053722a2390c833b304b4284b00466f52f940004d45f0fee149cf6ed65b8ff856921d40059344a01e68b2fa9dae82e4900",
:id
"0x0435b461849c0af696b2f71ad95e9c57053722a2390c833b304b4284b00466f52f940004d45f0fee149cf6ed65b8ff856921d40059344a01e68b2fa9dae82e490020",
:parsedText
[{:type "paragraph",
:children [{:literal "Please add me to your contacts"}]}],
:contentType 11,
:clock 0,
:localChatId "",
:timestamp 0,
:ensName "",
:quotedMessage nil,
:rtl false,
:responseTo "",
:text "Please add me to your contacts"}}
{:replyMessage nil,
:read true,
:dismissed false,
:chatId
"0x04f18135a0d5e904d5a877c24ad286d227b1a91ee583d61f16ba71de801d9d92df330696c5788b0810999c4710271584ec84eac223bc5f7f9eaf483b0ed2bd2093",
:name "0x04f181",
:accepted false,
:type 5,
:author
"0x04f18135a0d5e904d5a877c24ad286d227b1a91ee583d61f16ba71de801d9d92df330696c5788b0810999c4710271584ec84eac223bc5f7f9eaf483b0ed2bd2093",
:lastMessage nil,
:id
"0x04f18135a0d5e904d5a877c24ad286d227b1a91ee583d61f16ba71de801d9d92df330696c5788b0810999c4710271584ec84eac223bc5f7f9eaf483b0ed2bd209320",
:timestamp 1665073831000,
:message
{:messageType 0,
:identicon
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAkElEQVR4nOzY0QmAMAwGYRV3cQI3cww3cwKn0QUUAm3h+LnvUYpwBELpMoUwhMYQGkNoDKGJCVlbf3Cd9/P1fT+2ucf5qpiJGEJjCE15U/xtm9Gq2yxmIobQGELTfNfqdadq3YoxEzGExhAaQ2gMoTGEZti7Vq/zVTETMYTGEA0SMxFDaAyhMYTGEJo3AAD//6aKGsYhz2MYAAAAAElFTkSuQmCC",
:replace "",
:chatId "",
:displayName "",
:lineCount 0,
:contactRequestState 2,
:whisperTimestamp 1665073831000,
:alias "Nippy Idolized Armyworm",
:seen true,
:gapParameters {},
:from
"0x04f18135a0d5e904d5a877c24ad286d227b1a91ee583d61f16ba71de801d9d92df330696c5788b0810999c4710271584ec84eac223bc5f7f9eaf483b0ed2bd2093",
:id
"0x04f18135a0d5e904d5a877c24ad286d227b1a91ee583d61f16ba71de801d9d92df330696c5788b0810999c4710271584ec84eac223bc5f7f9eaf483b0ed2bd209320",
:parsedText
[{:type "paragraph",
:children [{:literal "Please add me to your contacts"}]}],
:contentType 11,
:clock 0,
:localChatId "",
:timestamp 0,
:ensName "",
:quotedMessage nil,
:rtl false,
:responseTo "",
:text "Please add me to your contacts"}}
{:replyMessage nil,
:read true,
:dismissed false,
:chatId
"0x04b5aed12d7de0f72a7e3494b7d3ab20b316a9fde5732a3eb1d0f41a957900f764d725a3e725152c37bb615ab2251349ef84e92a145333d8ece9a598cae75681e7",
:name "0x04b5ae",
:accepted false,
:type 5,
:author
"0x04b5aed12d7de0f72a7e3494b7d3ab20b316a9fde5732a3eb1d0f41a957900f764d725a3e725152c37bb615ab2251349ef84e92a145333d8ece9a598cae75681e7",
:lastMessage nil,
:id
"0x04b5aed12d7de0f72a7e3494b7d3ab20b316a9fde5732a3eb1d0f41a957900f764d725a3e725152c37bb615ab2251349ef84e92a145333d8ece9a598cae75681e720",
:timestamp 1665064413000,
:message
{:messageType 0,
:identicon
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAm0lEQVR4nOzXsQnDMBBA0SRkl7QZLmNkOLeexm5cCcmcsIQ/x3+lUfM5OM6vRxKG0BhCYwiNITRpQt7Rh/912Wrff5/v8473pTQTMYTGEJrQRjjT2ja9otupJc1EDKExhGbardXLW+tgCI0hNOGt1TLqT/CqNBMxhMYQmiF3Us3s26yUZiKG0BiiSdJMxBAaQ2gMoTGEZg8AAP//9ecjm0YKWeEAAAAASUVORK5CYII=",
:replace "",
:chatId "",
:displayName "",
:lineCount 0,
:contactRequestState 2,
:whisperTimestamp 1665064413000,
:alias "Scary Quickwitted Wrasse",
:seen true,
:gapParameters {},
:from
"0x04b5aed12d7de0f72a7e3494b7d3ab20b316a9fde5732a3eb1d0f41a957900f764d725a3e725152c37bb615ab2251349ef84e92a145333d8ece9a598cae75681e7",
:id
"0x04b5aed12d7de0f72a7e3494b7d3ab20b316a9fde5732a3eb1d0f41a957900f764d725a3e725152c37bb615ab2251349ef84e92a145333d8ece9a598cae75681e720",
:parsedText
[{:type "paragraph",
:children [{:literal "Please add me to your contacts"}]}],
:contentType 11,
:clock 0,
:localChatId "",
:timestamp 0,
:ensName "",
:quotedMessage nil,
:rtl false,
:responseTo "",
:text "Please add me to your contacts"}}])
(def response-unread-notifications
[{:replyMessage nil,
:read false,
:dismissed false,
:chatId
"0x047c654a4b35e4c3605c0eacf68364dd43140616e4d96ae362c4bf7c5ae3f1a3c03ad5935ae1face369bf96edda4ec02459b33964f1a7781036824f1e528c21b2f",
:name "0x047c65",
:accepted false,
:type 5,
:author
"0x047c654a4b35e4c3605c0eacf68364dd43140616e4d96ae362c4bf7c5ae3f1a3c03ad5935ae1face369bf96edda4ec02459b33964f1a7781036824f1e528c21b2f",
:lastMessage nil,
:id
"0x047c654a4b35e4c3605c0eacf68364dd43140616e4d96ae362c4bf7c5ae3f1a3c03ad5935ae1face369bf96edda4ec02459b33964f1a7781036824f1e528c21b2f20",
:timestamp 1665167287000,
:message
{:messageType 0,
:identicon
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAeElEQVR4nOzR0QnCMBhGURV3cR53cQx3cR6naRfoQ6EtXH7OeQwh5PI9bkMIqRFSI6RGSI2QGiE1QmqE1AipEVIzJuS59+L3/1u2zj+v9/2Mjxx9f8wiQmqE1AipEVIjpEZIjZAaIVxkzCJCaoTUCKkRUrMGAAD////ICGUlBQ5nAAAAAElFTkSuQmCC",
:replace "",
:chatId "",
:displayName "",
:lineCount 0,
:contactRequestState 2,
:whisperTimestamp 1665167287000,
:alias "Darkseagreen Colorless Honeybee",
:seen true,
:gapParameters {},
:from
"0x047c654a4b35e4c3605c0eacf68364dd43140616e4d96ae362c4bf7c5ae3f1a3c03ad5935ae1face369bf96edda4ec02459b33964f1a7781036824f1e528c21b2f",
:id
"0x047c654a4b35e4c3605c0eacf68364dd43140616e4d96ae362c4bf7c5ae3f1a3c03ad5935ae1face369bf96edda4ec02459b33964f1a7781036824f1e528c21b2f20",
:parsedText
[{:type "paragraph",
:children [{:literal "Please add me to your contacts"}]}],
:contentType 11,
:clock 0,
:localChatId "",
:timestamp 0,
:ensName "",
:quotedMessage nil,
:rtl false,
:responseTo "",
:text "Please add me to your contacts"}}])
(defn- update-notifications
[old new]
(let [ids-to-be-removed (->> new
(filter #(or (:dismissed %) (:accepted %)))
(map :id))
grouped-new (apply dissoc (group-by :id new) ids-to-be-removed)
grouped-old (apply dissoc (group-by :id old) ids-to-be-removed)]
(->> (merge grouped-old grouped-new)
vals
(map first))))
"Insert `new-notifications` in `db-notifications`.
Although correct, this is a naive implementation for reconciling notifications
because for every notification in `new-notifications`, linear scans will be
performed to remove it and sorting will be performed for every new insertion.
If the number of existing notifications cached in the app db becomes
~excessively~ big, this implementation will probably need to be revisited."
[db-notifications new-notifications]
(reduce (fn [acc {:keys [id type read] :as notification}]
(let [filter-status (if read :read :unread)]
(cond-> (-> acc
(update-in [type :read :data]
(fn [data]
(remove #(= id (:id %)) data)))
(update-in [type :unread :data]
(fn [data]
(remove #(= id (:id %)) data))))
(not (or (:dismissed notification) (:accepted notification)))
(update-in [type filter-status :data]
(fn [data]
(->> notification
(conj data)
(sort-by (juxt :timestamp :id))
reverse))))))
db-notifications
new-notifications))
(fx/defn notifications-reconcile
{:events [:activity-center.notifications/reconcile]}
[{:keys [db]} new-notifications]
(let [{read-new true
unread-new false} (group-by :read new-notifications)
read-old (get-in db [:activity-center :notifications-read :data])
unread-old (get-in db [:activity-center :notifications-unread :data])]
{:db (-> db
(assoc-in [:activity-center :notifications-read :data]
(update-notifications read-old read-new))
(assoc-in [:activity-center :notifications-unread :data]
(update-notifications unread-old unread-new)))}))
(when (seq new-notifications)
{:db (update-in db [:activity-center :notifications]
update-notifications new-notifications)}))
;;;; Notifications fetching and pagination
(def notifications-per-page
20)
(def defaults
{:filter-status :unread
:filter-type constants/activity-center-notification-type-no-type
:notifications-per-page 10})
(def start-or-end-cursor
"")
(defn notifications-group->rpc-method
[notifications-group]
(if (= notifications-group :notifications-read)
(defn- valid-cursor?
[cursor]
(and (some? cursor)
(not= cursor start-or-end-cursor)))
(defn- filter-status->rpc-method
[filter-status]
(if (= filter-status :read)
"wakuext_readActivityCenterNotifications"
"wakuext_unreadActivityCenterNotifications"))
(defn notifications-read-status->group
[status-filter]
(if (= status-filter :read)
:notifications-read
:notifications-unread))
(fx/defn notifications-fetch
[{:keys [db]} cursor notifications-group]
(when-not (get-in db [:activity-center notifications-group :loading?])
{:db (assoc-in db [:activity-center notifications-group :loading?] true)
::json-rpc/call [{:method (notifications-group->rpc-method notifications-group)
:params [cursor notifications-per-page]
:on-success #(re-frame/dispatch [:activity-center.notifications/fetch-success notifications-group %])
:on-error #(re-frame/dispatch [:activity-center.notifications/fetch-error notifications-group %])}]}))
[{:keys [db]} {:keys [cursor filter-type filter-status reset-data?]}]
(when-not (get-in db [:activity-center :notifications filter-type filter-status :loading?])
{:db (assoc-in db [:activity-center :notifications filter-type filter-status :loading?] true)
::json-rpc/call [{:method (filter-status->rpc-method filter-status)
:params [cursor (defaults :notifications-per-page) filter-type]
:on-success #(re-frame/dispatch [:activity-center.notifications/fetch-success filter-type filter-status reset-data? %])
:on-error #(re-frame/dispatch [:activity-center.notifications/fetch-error filter-type filter-status %])}]}))
(fx/defn notifications-fetch-first-page
{:events [:activity-center.notifications/fetch-first-page]}
[{:keys [db] :as cofx} {:keys [status-filter] :or {status-filter :unread}}]
(let [notifications-group (notifications-read-status->group status-filter)]
(fx/merge cofx
{:db (-> db
(assoc-in [:activity-center :current-status-filter] status-filter)
(update-in [:activity-center notifications-group] dissoc :loading?)
(update-in [:activity-center notifications-group] dissoc :data))}
(notifications-fetch start-or-end-cursor notifications-group))))
[{:keys [db] :as cofx} {:keys [filter-type filter-status]}]
(tap> {:EVT-fetch-first-page (utils.datetime/now->iso8601)})
(let [filter-type (or filter-type
(get-in db [:activity-center :filter :type]
(defaults :filter-type)))
filter-status (or filter-status
(get-in db [:activity-center :filter :status]
(defaults :filter-status)))]
(as-> {:db (-> db
(assoc-in [:activity-center :filter :type] filter-type)
(assoc-in [:activity-center :filter :status] filter-status)
(assoc-in [:activity-center :notifications filter-type filter-status :loading?] true)
;; Comment this assoc call and the UI becomes less
;; responsive. If you use the fake orange rectangles in the
;; UI you still notice the latency when this line is
;; commented. This is worrisome news.
(assoc-in [:activity-center :notifications filter-type filter-status :data] [])
)}
cofx'
(if (and (= 0 filter-type) (= :read filter-status))
(assoc cofx' :dispatch-later [;; Lower the delay and eventually the UI becomes less responsive.
{:ms 50
:dispatch [:activity-center.notifications/fetch-success
filter-type
filter-status
true
{:cursor "" :notifications response-notifications}]}])
(if (and (= 0 filter-type) (= :unread filter-status))
(assoc cofx' :dispatch-later [;; Lower the delay and eventually the UI becomes less responsive.
{:ms 50
:dispatch [:activity-center.notifications/fetch-success
filter-type
filter-status
true
{:cursor "" :notifications response-unread-notifications}]}])
(assoc cofx' :dispatch [:activity-center.notifications/fetch-success
filter-type
filter-status
true
{:cursor "" :notifications []}]))))))
(fx/defn notifications-fetch-next-page
{:events [:activity-center.notifications/fetch-next-page]}
[{:keys [db] :as cofx}]
(let [status-filter (get-in db [:activity-center :current-status-filter])
notifications-group (notifications-read-status->group status-filter)
{:keys [cursor]} (get-in db [:activity-center notifications-group])]
(when-not (= cursor start-or-end-cursor)
(notifications-fetch cofx cursor notifications-group))))
(let [{:keys [type status]} (get-in db [:activity-center :filter])
{:keys [cursor]} (get-in db [:activity-center :notifications type status])]
(when (valid-cursor? cursor)
(tap> {:EVT-fetch-next-page (utils.datetime/now->iso8601)})
(notifications-fetch cofx {:cursor cursor
:filter-type type
:filter-status status
:reset-data? false}))))
(fx/defn notifications-fetch-success
{:events [:activity-center.notifications/fetch-success]}
[{:keys [db]} notifications-group {:keys [cursor notifications]}]
{:db (-> db
(update-in [:activity-center notifications-group] dissoc :loading?)
(assoc-in [:activity-center notifications-group :cursor] cursor)
(update-in [:activity-center notifications-group :data]
concat
(map data-store.activities/<-rpc notifications)))})
[{:keys [db]}
filter-type
filter-status
reset-data?
{:keys [cursor notifications]}]
(tap> {:EVT-fetch-success (utils.datetime/now->iso8601)})
(let [processed (map data-store.activities/<-rpc notifications)]
{:db (-> db
(assoc-in [:activity-center :notifications filter-type filter-status :cursor] cursor)
(update-in [:activity-center :notifications filter-type filter-status] dissoc :loading?)
(update-in [:activity-center :notifications filter-type filter-status :data]
(if reset-data?
(constantly processed)
#(concat %1 processed))))}))
(fx/defn notifications-fetch-error
{:events [:activity-center.notifications/fetch-error]}
[{:keys [db]} notifications-group error]
[{:keys [db]} filter-type filter-status error]
(log/warn "Failed to load Activity Center notifications" error)
{:db (update-in db [:activity-center notifications-group] dissoc :loading?)})
{:db (update-in db [:activity-center :notifications filter-type filter-status] dissoc :loading?)})
(fx/defn notifications-quick-toggle
{:events [:activity-center.notifications/quick-toggle]}
[{:keys [db]}]
{:db (assoc db :activity-center-notifications-filter-status
(if (= :read (:activity-center-notifications-filter-status db))
:unread
:read))})
(comment
(fx/defn notifications-quick-toggle
{:events [:activity-center.notifications/quick-toggle]}
[{:keys [db]}]
{:db (assoc-in db [:activity-center :filter :status]
(if (= :read (get-in db [:activity-center :filter :status]))
:unread
:read))})
(re-frame/reg-event-db
:dev/reset-activity-center
(fn [db]
(-> db
(assoc-in [:activity-center]
{:filter {:status :read
:type 0}
:notifications {}}))))
(re-frame/dispatch [:dev/reset-activity-center]))
+283 -68
View File
@@ -2,99 +2,285 @@
(:require [cljs.test :refer [deftest is testing]]
[day8.re-frame.test :as rf-test]
[re-frame.core :as rf]
[status-im.constants :as c]
[status-im.ethereum.json-rpc :as json-rpc]
[status-im.test-helpers :as h]
status-im.events))
status-im.events
[status-im.test-helpers :as h]))
(defn setup []
(h/register-helper-events)
(rf/dispatch [:init/app-started]))
(defn remove-color-key
"Remove `:color` key from notifications because they have random values that we
can't assert against."
[grouped-notifications {:keys [type status]}]
(update-in grouped-notifications
[type status :data]
(fn [old _]
(map #(dissoc % :color) old))
nil))
(deftest notifications-reconcile-test
(testing "does nothing when there are no new notifications"
(rf-test/run-test-sync
(setup)
(let [read [{:id "0x1" :read true}]
unread [{:id "0x4" :read false}]]
(rf/dispatch [:test/assoc-in [:activity-center :notifications-read :data] read])
(rf/dispatch [:test/assoc-in [:activity-center :notifications-unread :data] unread])
(let [notifications {c/activity-center-notification-type-one-to-one-chat
{:read {:cursor ""
:data [{:id "0x1"
:read true
:type c/activity-center-notification-type-one-to-one-chat}
{:id "0x2"
:read true
:type c/activity-center-notification-type-one-to-one-chat}]}
:unread {:cursor ""
:data [{:id "0x3"
:read false
:type c/activity-center-notification-type-one-to-one-chat}]}}
c/activity-center-notification-type-private-group-chat
{:unread {:cursor ""
:data [{:id "0x4"
:read false
:type c/activity-center-notification-type-private-group-chat}]}}}]
(rf/dispatch [:test/assoc-in [:activity-center :notifications] notifications])
(rf/dispatch [:activity-center.notifications/reconcile nil])
(is (= read (get-in (h/db) [:activity-center :notifications-read :data])))
(is (= unread (get-in (h/db) [:activity-center :notifications-unread :data]))))))
(is (= notifications (get-in (h/db) [:activity-center :notifications]))))))
(testing "removes dismissed or accepted notifications"
(rf-test/run-test-sync
(setup)
(rf/dispatch [:test/assoc-in [:activity-center :notifications-read :data]
[{:id "0x1" :read true}
{:id "0x2" :read true}
{:id "0x3" :read true}]])
(rf/dispatch [:test/assoc-in [:activity-center :notifications-unread :data]
[{:id "0x4" :read false}
{:id "0x5" :read false}
{:id "0x6" :read false}]])
(rf/dispatch [:test/assoc-in [:activity-center :notifications]
{c/activity-center-notification-type-one-to-one-chat
{:read {:cursor ""
:data [{:id "0x1" :read true :type c/activity-center-notification-type-one-to-one-chat}
{:id "0x2" :read true :type c/activity-center-notification-type-one-to-one-chat}]}
:unread {:cursor ""
:data [{:id "0x3" :read false :type c/activity-center-notification-type-one-to-one-chat}]}}
2 {:unread {:cursor ""
:data [{:id "0x4" :read false :type 2}
{:id "0x6" :read false :type 2}]}}}])
(rf/dispatch [:activity-center.notifications/reconcile
[{:id "0x1" :read true :dismissed true}
{:id "0x3" :read true :accepted true}
{:id "0x4" :read false :dismissed true}
{:id "0x5" :read false :accepted true}]])
[{:id "0x1"
:read true
:type c/activity-center-notification-type-one-to-one-chat
:dismissed true}
{:id "0x3"
:read false
:type c/activity-center-notification-type-one-to-one-chat
:accepted true}
{:id "0x4"
:read false
:type c/activity-center-notification-type-private-group-chat
:dismissed true}
{:id "0x5"
:read false
:type c/activity-center-notification-type-private-group-chat
:accepted true}]])
(is (= [{:id "0x2" :read true}]
(get-in (h/db) [:activity-center :notifications-read :data])))
(is (= [{:id "0x6" :read false}]
(get-in (h/db) [:activity-center :notifications-unread :data])))))
(is (= {c/activity-center-notification-type-one-to-one-chat
{:read {:cursor ""
:data [{:id "0x2"
:read true
:type c/activity-center-notification-type-one-to-one-chat}]}
:unread {:cursor ""
:data []}}
c/activity-center-notification-type-private-group-chat
{:read {:data []}
:unread {:cursor ""
:data [{:id "0x6"
:read false
:type c/activity-center-notification-type-private-group-chat}]}}}
(get-in (h/db) [:activity-center :notifications])))))
(testing "replaces old notifications with newly arrived ones"
(rf-test/run-test-sync
(setup)
(rf/dispatch [:test/assoc-in [:activity-center :notifications-read :data]
[{:id "0x1" :read true}
{:id "0x2" :read true}]])
(rf/dispatch [:test/assoc-in [:activity-center :notifications-unread :data]
[{:id "0x3" :read false}
{:id "0x4" :read false}]])
(rf/dispatch [:test/assoc-in [:activity-center :notifications]
{c/activity-center-notification-type-one-to-one-chat
{:read {:cursor ""
:data [{:id "0x1"
:read true
:type c/activity-center-notification-type-one-to-one-chat}]}}
c/activity-center-notification-type-private-group-chat
{:unread {:cursor ""
:data [{:id "0x4"
:read false
:type c/activity-center-notification-type-private-group-chat}
{:id "0x6"
:read false
:type c/activity-center-notification-type-private-group-chat}]}}}])
(rf/dispatch [:activity-center.notifications/reconcile
[{:id "0x1" :read true :name "ABC"}
{:id "0x3" :read false :name "XYZ"}]])
[{:id "0x1"
:read true
:type c/activity-center-notification-type-one-to-one-chat
:last-message {}}
{:id "0x4"
:read false
:type c/activity-center-notification-type-private-group-chat
:author "0xabc"}
{:id "0x6"
:read false
:type c/activity-center-notification-type-private-group-chat}]])
(is (= [{:id "0x1" :read true :name "ABC"}
{:id "0x2" :read true}]
(get-in (h/db) [:activity-center :notifications-read :data])))
(is (= [{:id "0x3" :read false :name "XYZ"}
{:id "0x4" :read false}]
(get-in (h/db) [:activity-center :notifications-unread :data]))))))
(is (= {c/activity-center-notification-type-one-to-one-chat
{:read {:cursor ""
:data [{:id "0x1"
:read true
:type c/activity-center-notification-type-one-to-one-chat
:last-message {}}]}
:unread {:data []}}
c/activity-center-notification-type-private-group-chat
{:read {:data []}
:unread {:cursor ""
:data [{:id "0x6"
:read false
:type c/activity-center-notification-type-private-group-chat}
{:id "0x4"
:read false
:type c/activity-center-notification-type-private-group-chat
:author "0xabc"}]}}}
(get-in (h/db) [:activity-center :notifications])))))
(testing "reconciles notifications that switched their read/unread status"
(rf-test/run-test-sync
(setup)
(rf/dispatch [:test/assoc-in [:activity-center :notifications]
{c/activity-center-notification-type-one-to-one-chat
{:read {:cursor ""
:data [{:id "0x1"
:read true
:type c/activity-center-notification-type-one-to-one-chat}]}}}])
(rf/dispatch [:activity-center.notifications/reconcile
[{:id "0x1"
:read false
:type c/activity-center-notification-type-one-to-one-chat}]])
(is (= {c/activity-center-notification-type-one-to-one-chat
{:read {:cursor ""
:data []}
:unread {:data [{:id "0x1"
:read false
:type c/activity-center-notification-type-one-to-one-chat}]}}}
(get-in (h/db) [:activity-center :notifications])))))
;; Sorting by timestamp and ID is compatible with what the backend does when
;; returning paginated results.
(testing "sorts notifications by timestamp and id in descending order"
(rf-test/run-test-sync
(setup)
(rf/dispatch [:test/assoc-in [:activity-center :notifications]
{c/activity-center-notification-type-one-to-one-chat
{:read {:cursor ""
:data [{:id "0x1" :read true :type c/activity-center-notification-type-one-to-one-chat :timestamp 1}
{:id "0x2" :read true :type c/activity-center-notification-type-one-to-one-chat :timestamp 1}]}
:unread {:cursor ""
:data [{:id "0x3" :read false :type c/activity-center-notification-type-one-to-one-chat :timestamp 50}
{:id "0x4" :read false :type c/activity-center-notification-type-one-to-one-chat :timestamp 100}
{:id "0x5" :read false :type c/activity-center-notification-type-one-to-one-chat :timestamp 100}]}}}])
(rf/dispatch [:activity-center.notifications/reconcile
[{:id "0x1" :read true :type c/activity-center-notification-type-one-to-one-chat :timestamp 1 :last-message {}}
{:id "0x4" :read false :type c/activity-center-notification-type-one-to-one-chat :timestamp 100 :last-message {}}]])
(is (= {c/activity-center-notification-type-one-to-one-chat
{:read {:cursor ""
:data [{:id "0x2"
:read true
:type c/activity-center-notification-type-one-to-one-chat
:timestamp 1}
{:id "0x1"
:read true
:type c/activity-center-notification-type-one-to-one-chat
:timestamp 1
:last-message {}}]}
:unread {:cursor ""
:data [{:id "0x5"
:read false
:type c/activity-center-notification-type-one-to-one-chat
:timestamp 100}
{:id "0x4"
:read false
:type c/activity-center-notification-type-one-to-one-chat
:timestamp 100
:last-message {}}
{:id "0x3"
:read false
:type c/activity-center-notification-type-one-to-one-chat
:timestamp 50}]}}}
(get-in (h/db) [:activity-center :notifications]))))))
(deftest notifications-fetch-test
(testing "fetches first page"
(rf-test/run-test-sync
(setup)
(let [spy-queue (atom [])]
(h/stub-fx-with-callbacks ::json-rpc/call
:on-success (constantly {:cursor "10"
:notifications [{:chatId "0x1"}]}))
(h/stub-fx-with-callbacks
::json-rpc/call
:on-success (constantly {:cursor "10"
:notifications [{:id "0x1"
:type c/activity-center-notification-type-one-to-one-chat
:read false
:chatId "0x9"}]}))
(h/spy-fx spy-queue ::json-rpc/call)
(rf/dispatch [:activity-center.notifications/fetch-first-page])
(rf/dispatch [:activity-center.notifications/fetch-first-page
{:filter-type c/activity-center-notification-type-one-to-one-chat}])
(is (= :unread (get-in (h/db) [:activity-center :current-status-filter])))
(is (nil? (get-in (h/db) [:activity-center :notifications-unread :loading?])))
(is (= "10" (get-in (h/db) [:activity-center :notifications-unread :cursor])))
(is (= "" (get-in @spy-queue [0 :args 0 :params 0])))
(is (= [{:chat-id "0x1"}]
(->> (get-in (h/db) [:activity-center :notifications-unread :data])
(map #(select-keys % [:chat-id]))))))))
(is (= :unread (get-in (h/db) [:activity-center :filter :status])))
(is (= "" (get-in @spy-queue [0 :args 0 :params 0]))
"Should be called with empty cursor when fetching first page")
(is (= {c/activity-center-notification-type-one-to-one-chat
{:unread {:cursor "10"
:data [{:chat-id "0x9"
:chat-name nil
:chat-type c/activity-center-notification-type-one-to-one-chat
:group-chat false
:id "0x1"
:public? false
:last-message nil
:message nil
:read false
:reply-message nil
:type c/activity-center-notification-type-one-to-one-chat}]}}}
(remove-color-key (get-in (h/db) [:activity-center :notifications])
{:status :unread
:type c/activity-center-notification-type-one-to-one-chat}))))))
(testing "does not fetch next page when pagination cursor reached the end"
(rf-test/run-test-sync
(setup)
(let [spy-queue (atom [])]
(h/spy-fx spy-queue ::json-rpc/call)
(rf/dispatch [:test/assoc-in [:activity-center :current-status-filter] :unread])
(rf/dispatch [:test/assoc-in [:activity-center :notifications-unread :cursor] ""])
(rf/dispatch [:test/assoc-in [:activity-center :filter :status]
:unread])
(rf/dispatch [:test/assoc-in [:activity-center :filter :type]
c/activity-center-notification-type-one-to-one-chat])
(rf/dispatch [:test/assoc-in [:activity-center :notifications c/activity-center-notification-type-one-to-one-chat :unread :cursor]
""])
(rf/dispatch [:activity-center.notifications/fetch-next-page])
(is (= [] @spy-queue)))))
;; The cursor can be nil sometimes because the reconciliation doesn't care
;; about updating the cursor value, but we have to make sure the next page is
;; only fetched if the current cursor is valid.
(testing "does not fetch next page when cursor is nil"
(rf-test/run-test-sync
(setup)
(let [spy-queue (atom [])]
(h/spy-fx spy-queue ::json-rpc/call)
(rf/dispatch [:test/assoc-in [:activity-center :filter :status]
:unread])
(rf/dispatch [:test/assoc-in [:activity-center :filter :type]
c/activity-center-notification-type-one-to-one-chat])
(rf/dispatch [:test/assoc-in [:activity-center :notifications c/activity-center-notification-type-one-to-one-chat :unread :cursor]
nil])
(rf/dispatch [:activity-center.notifications/fetch-next-page])
@@ -104,30 +290,54 @@
(rf-test/run-test-sync
(setup)
(let [spy-queue (atom [])]
(h/stub-fx-with-callbacks ::json-rpc/call
:on-success (constantly {:cursor ""
:notifications [{:chatId "0x1"}]}))
(h/stub-fx-with-callbacks
::json-rpc/call
:on-success (constantly {:cursor ""
:notifications [{:id "0x1"
:type c/activity-center-notification-type-mention
:read false
:chatId "0x9"}]}))
(h/spy-fx spy-queue ::json-rpc/call)
(rf/dispatch [:test/assoc-in [:activity-center :current-status-filter] :unread])
(rf/dispatch [:test/assoc-in [:activity-center :notifications-unread :cursor] "10"])
(rf/dispatch [:test/assoc-in [:activity-center :filter :status]
:unread])
(rf/dispatch [:test/assoc-in [:activity-center :filter :type]
c/activity-center-notification-type-mention])
(rf/dispatch [:test/assoc-in [:activity-center :notifications c/activity-center-notification-type-mention :unread :cursor]
"10"])
(rf/dispatch [:activity-center.notifications/fetch-next-page])
(is (= "wakuext_unreadActivityCenterNotifications" (get-in @spy-queue [0 :args 0 :method])))
(is (= "10" (get-in @spy-queue [0 :args 0 :params 0])))
(is (= "" (get-in (h/db) [:activity-center :notifications-unread :cursor])))
(is (= [{:chat-id "0x1"}]
(->> (get-in (h/db) [:activity-center :notifications-unread :data])
(map #(select-keys % [:chat-id]))))))))
(is (= "10" (get-in @spy-queue [0 :args 0 :params 0]))
"Should be called with current cursor")
(is (= {c/activity-center-notification-type-mention
{:unread {:cursor ""
:data [{:chat-id "0x9"
:chat-name nil
:chat-type 3
:id "0x1"
:last-message nil
:message nil
:read false
:reply-message nil
:type c/activity-center-notification-type-mention}]}}}
(remove-color-key (get-in (h/db) [:activity-center :notifications])
{:status :unread
:type c/activity-center-notification-type-mention}))))))
(testing "does not fetch next page while it is still loading"
(rf-test/run-test-sync
(setup)
(let [spy-queue (atom [])]
(h/spy-fx spy-queue ::json-rpc/call)
(rf/dispatch [:test/assoc-in [:activity-center :current-status-filter] :read])
(rf/dispatch [:test/assoc-in [:activity-center :notifications-read :cursor] "10"])
(rf/dispatch [:test/assoc-in [:activity-center :notifications-read :loading?] true])
(rf/dispatch [:test/assoc-in [:activity-center :filter :status]
:read])
(rf/dispatch [:test/assoc-in [:activity-center :filter :type]
c/activity-center-notification-type-one-to-one-chat])
(rf/dispatch [:test/assoc-in [:activity-center :notifications c/activity-center-notification-type-one-to-one-chat :read :cursor]
"10"])
(rf/dispatch [:test/assoc-in [:activity-center :notifications c/activity-center-notification-type-one-to-one-chat :read :loading?]
true])
(rf/dispatch [:activity-center.notifications/fetch-next-page])
@@ -140,13 +350,18 @@
(h/stub-fx-with-callbacks ::json-rpc/call :on-error (constantly :fake-error))
(h/spy-event-fx spy-queue :activity-center.notifications/fetch-error)
(h/spy-fx spy-queue ::json-rpc/call)
(rf/dispatch [:test/assoc-in [:activity-center :current-status-filter] :unread])
(rf/dispatch [:test/assoc-in [:activity-center :notifications-unread :cursor] ""])
(rf/dispatch [:test/assoc-in [:activity-center :filter :status]
:unread])
(rf/dispatch [:test/assoc-in [:activity-center :filter :type]
c/activity-center-notification-type-one-to-one-chat])
(rf/dispatch [:test/assoc-in [:activity-center :notifications c/activity-center-notification-type-one-to-one-chat :unread :cursor]
""])
(rf/dispatch [:activity-center.notifications/fetch-first-page])
(is (nil? (get-in (h/db) [:activity-center :notifications-unread :loading?])))
(is (nil? (get-in (h/db) [:activity-center :notifications c/activity-center-notification-type-one-to-one-chat :unread :loading?])))
(is (= [:activity-center.notifications/fetch-error
:notifications-unread
c/activity-center-notification-type-one-to-one-chat
:unread
:fake-error]
(:args (last @spy-queue))))))))
+8
View File
@@ -176,6 +176,7 @@
(def ^:const docs-link "https://status.im/docs/")
(def ^:const principles-link "https://our.status.im/our-principles/")
(def ^:const activity-center-notification-type-no-type 0)
(def ^:const activity-center-notification-type-one-to-one-chat 1)
(def ^:const activity-center-notification-type-private-group-chat 2)
(def ^:const activity-center-notification-type-mention 3)
@@ -183,6 +184,13 @@
(def ^:const activity-center-notification-type-contact-request 5)
(def ^:const activity-center-notification-type-contact-request-retracted 6)
;; TODO: Replace with correct enum values once status-go implements them.
(def ^:const activity-center-notification-type-admin 66610)
(def ^:const activity-center-notification-type-identity-verification 66611)
(def ^:const activity-center-notification-type-tx 66612)
(def ^:const activity-center-notification-type-membership 66613)
(def ^:const activity-center-notification-type-system 66614)
(def ^:const visibility-status-unknown 0)
(def ^:const visibility-status-automatic 1)
(def ^:const visibility-status-dnd 2)
+3 -2
View File
@@ -328,8 +328,9 @@
;; Information Box
(def closable-information-boxes
[{:id :ens-banner
:global? true}]) ;; global? - close information box across all profiles
"[{:id information box id
:global? true/false (close information box across all profiles)}]"
[])
(defn information-box-id-hash [id public-key global?]
(if global?
+10 -7
View File
@@ -55,13 +55,16 @@
:podcasts (js/require "../resources/images/ui/podcasts.png")})
(def mock-images
{:photo1 (js/require "../resources/images/mock/photo1.png")
:photo2 (js/require "../resources/images/mock/photo2.png")
:photo3 (js/require "../resources/images/mock/photo3.png")
:community-banner (js/require "../resources/images/mock/community-banner.png")
:community-logo (js/require "../resources/images/mock/community-logo.png")
:gif (js/require "../resources/images/mock/gif.png")
:sticker (js/require "../resources/images/mock/sticker.png")})
{:photo1 (js/require "../resources/images/mock/photo1.png")
:photo2 (js/require "../resources/images/mock/photo2.png")
:photo3 (js/require "../resources/images/mock/photo3.png")
:community-banner (js/require "../resources/images/mock/community-banner.png")
:community-logo (js/require "../resources/images/mock/community-logo.png")
:gif (js/require "../resources/images/mock/gif.png")
:sticker (js/require "../resources/images/mock/sticker.png")
:user-picture-female2 (js/require "../resources/images/mock/user_picture_female2.png")
:user-picture-male4 (js/require "../resources/images/mock/user_picture_male4.png")
:user-picture-male5 (js/require "../resources/images/mock/user_picture_male5.png")})
(defn get-theme-image [k]
(get ui (when (colors/dark?) (keyword (str (name k) "-dark"))) (get ui k)))
+39 -18
View File
@@ -3,32 +3,53 @@
[status-im.utils.datetime :as datetime]
[status-im.multiaccounts.core :as multiaccounts]
[status-im.constants :as constants]
[status-im.utils.datetime :as utils.datetime]
[clojure.string :as string]))
(re-frame/reg-sub
:activity-center/notifications-read
(fn [db]
(get-in db [:activity-center :notifications-read :data])))
:activity-center/notifications
:<- [:activity-center]
(fn [activity-center]
(:notifications activity-center)))
(re-frame/reg-sub
:activity-center/notifications-unread
(fn [db]
(get-in db [:activity-center :notifications-unread :data])))
:activity-center/filter-status
:<- [:activity-center]
(fn [activity-center]
(get-in activity-center [:filter :status])))
(re-frame/reg-sub
:activity-center/current-status-filter
(fn [db]
(get-in db [:activity-center :current-status-filter])))
:activity-center/filter-type
:<- [:activity-center]
(fn [activity-center]
(get-in activity-center [:filter :type] constants/activity-center-notification-type-no-type)))
(re-frame/reg-sub
:activity-center/notifications-per-read-status
:<- [:activity-center/notifications-read]
:<- [:activity-center/notifications-unread]
:<- [:activity-center/current-status-filter]
(fn [[notifications-read notifications-unread status-filter]]
(if (= status-filter :unread)
notifications-unread
notifications-read)))
:activity-center/loading?
:<- [:activity-center/filter-type]
:<- [:activity-center/filter-status]
:<- [:activity-center/notifications]
(fn [[filter-type filter-status notifications]]
(get-in notifications [filter-type filter-status :loading?])))
(re-frame/reg-sub
:activity-center/filtered-notifications
:<- [:activity-center/filter-type]
:<- [:activity-center/filter-status]
:<- [:activity-center/notifications]
(fn [[filter-type filter-status notifications]]
(tap> {:SUB-filtered-notifications (utils.datetime/now->iso8601)})
(get-in notifications [filter-type filter-status :data])))
(re-frame/reg-sub
:activity-center/filter-status-unread-enabled?
:<- [:activity-center/filter-status]
(fn [filter-status]
(= :unread filter-status))
;; (fn [db]
;; (tap> {:SUB-filter-status-unread-enabled? (utils.datetime/now->iso8601)})
;; (= :read (:activity-center-notifications-filter-status db)))
)
(defn- group-notifications-by-date
[notifications]
@@ -64,4 +85,4 @@
(map #(assoc %
:timestamp (or (:timestamp %) (:timestamp (or (:message %) (:last-message %))))
:contact (multiaccounts/contact-by-identity contacts (get-in % [:message :from])))
supported-notifications)))))
supported-notifications)))))
+1
View File
@@ -228,6 +228,7 @@
(reg-root-key-sub :activity.center/notifications :activity.center/notifications)
(reg-root-key-sub :activity.center/notifications-count :activity.center/notifications-count)
(reg-root-key-sub :activity-center :activity-center)
(reg-root-key-sub :bug-report/description-error :bug-report/description-error)
(reg-root-key-sub :bug-report/details :bug-report/details)
+3
View File
@@ -12,6 +12,7 @@
:refer (SafeAreaProvider SafeAreaInsetsContext)]
["@react-native-community/clipboard" :default Clipboard]
["react-native-linear-gradient" :default LinearGradient]
["@react-native-community/masked-view" :default MaskedView]
["react-native-navigation" :refer (Navigation)]
["react-native-fast-image" :as FastImage]
["@react-native-community/blur" :as blur])
@@ -40,6 +41,8 @@
(def linear-gradient (reagent/adapt-react-class LinearGradient))
(def masked-view (reagent/adapt-react-class MaskedView))
(def blur-view (reagent/adapt-react-class (.-BlurView blur)))
(defn valid-source? [source]
@@ -1,15 +1,17 @@
(ns status-im.ui.screens.activity-center.views
(:require [quo.components.animated.pressable :as animation]
[quo.design-system.colors :as quo.colors]
[quo.react-native :as rn]
[quo2.components.buttons.button :as button]
[quo2.components.markdown.text :as text]
[quo2.components.notifications.activity-logs :as activity-logs]
[quo2.components.tabs.tabs :as tabs]
[quo2.components.tags.context-tags :as context-tags]
[quo2.foundations.colors :as colors]
[reagent.core :as reagent]
[status-im.constants :as constants]
[status-im.i18n.i18n :as i18n]
[status-im.multiaccounts.core :as multiaccounts]
[status-im.ui.components.topbar :as topbar]
[status-im.utils.datetime :as datetime]
[status-im.utils.handlers :refer [<sub >evt]]))
@@ -28,8 +30,8 @@
[{:keys [type]}]
(case type
constants/activity-center-notification-type-contact-request
:add-user
:placeholder))
:main-icons2/add-user
:main-icons2/placeholder))
(defn activity-context
[{:keys [message last-message type]}]
@@ -77,7 +79,7 @@
nil))
(defn activity-pressable
[notification & children]
[notification activity]
(case (get-in notification [:message :contact-request-state])
constants/contact-request-message-state-accepted
;; NOTE [2022-09-21]: We need to dispatch to
@@ -85,46 +87,154 @@
;; `:chat.ui/navigate-to-chat`, otherwise the chat screen looks completely
;; broken if it has never been opened before for the accepted contact.
[animation/pressable {:on-press #(>evt [:contact.ui/send-message-pressed {:public-key (:author notification)}])}
children]
[:<> children]))
activity]
activity))
(defn render-notification
[notification index]
[rn/view {:flex 1
:flex-direction :column
:margin-top (if (= 0 index) 0 4)}
[activity-pressable notification
[activity-logs/activity-log
(merge {:context (activity-context notification)
:icon (activity-icon notification)
:message (activity-message notification)
:status (activity-status notification)
:timestamp (datetime/timestamp->relative (:timestamp notification))
:title (activity-title notification)
:unread? (not (:read notification))}
(activity-buttons notification))]]])
(tap> {:render-notification (datetime/now->iso8601)})
[rn/view {:margin-top (if (= 0 index) 0 4)
:padding-horizontal 20}
;; Use this fake notification rectangle to see a massive rendering
;; performance difference.
(if true
[rn/view {:style {:width 100
:height 100
:background-color :orange
:padding-vertical 10}}]
[activity-pressable notification
[activity-logs/activity-log
(merge {:context (activity-context notification)
:icon (activity-icon notification)
:message (activity-message notification)
:status (activity-status notification)
:timestamp (datetime/timestamp->relative (:timestamp notification))
:title (activity-title notification)
:unread? (not (:read notification))}
(activity-buttons notification))]])])
(defn notifications-list
(defn filter-selector-read-toggle
[]
(let [notifications (<sub [:activity-center/notifications-per-read-status])]
[rn/flat-list {:style {:padding-horizontal 8}
:data notifications
:key-fn :id
:on-end-reached #(>evt [:activity-center.notifications/fetch-next-page])
:render-fn render-notification}]))
(tap> {:filter-selector-read-toggle (datetime/now->iso8601)})
(let [unread-filter-enabled? (<sub [:activity-center/filter-status-unread-enabled?])]
;; TODO: Replace the button by a Filter Selector component once available for use.
[button/button {:icon true
:type (if unread-filter-enabled? :primary :outline)
:size 32
:on-press #(do
(tap> {:filter-selector-read-toggle-ON-PRESS (datetime/now->iso8601)})
(>evt [:activity-center.notifications/fetch-first-page
{:filter-status (if unread-filter-enabled?
:read
:unread)}]))}
:main-icons2/unread]))
(defn activity-center []
;; TODO(2022-10-07): The empty state is still under design analysis, so we
;; shouldn't even care about translations at this point. A placeholder box is
;; used instead of an image.
(defn empty-tab
[]
[rn/view {:style {:align-items :center
:flex 1
:justify-content :center
:padding-vertical 12}}
[rn/view {:style {:background-color colors/neutral-80
:height 120
:margin-bottom 20
:width 120}}]
[text/text {:size :paragraph-1
:style {:padding-bottom 2}
:weight :semi-bold}
"No notifications"]
[text/text {:size :paragraph-2}
"Your notifications will be here"]])
(defn tabs
[]
(tap> {:tabs (datetime/now->iso8601)})
(let [filter-type (<sub [:activity-center/filter-type])]
[tabs/scrollable-tabs {:size 32
:style {:padding-left 20}
:fade-end-percentage 0.79
:scroll-on-press? true
:fade-end? true
:on-change #(>evt [:activity-center.notifications/fetch-first-page {:filter-type %}])
:default-active filter-type
:data [{:id constants/activity-center-notification-type-no-type
:label (i18n/label :t/all)}
{:id constants/activity-center-notification-type-admin
:label (i18n/label :t/admin)}
{:id constants/activity-center-notification-type-mention
:label (i18n/label :t/mentions)}
{:id constants/activity-center-notification-type-reply
:label (i18n/label :t/replies)}
{:id constants/activity-center-notification-type-contact-request
:label (i18n/label :t/contact-requests)}
{:id constants/activity-center-notification-type-identity-verification
:label (i18n/label :t/identity-verification)}
{:id constants/activity-center-notification-type-tx
:label (i18n/label :t/transactions)}
{:id constants/activity-center-notification-type-membership
:label (i18n/label :t/membership)}
{:id constants/activity-center-notification-type-system
:label (i18n/label :t/system)}]}]))
(defn header
[]
(tap> {:header (datetime/now->iso8601)})
(let [screen-padding 20]
;; TODO: Remove temporary (and old) background color when the screen and
;; header are properly blurred.
[rn/view {:background-color (:ui-background @quo.colors/theme)}
[button/button {:icon true
:type :grey
:size 32
:style {:margin-vertical 12
:margin-left screen-padding}
:on-press #(>evt [:navigate-back])}
:main-icons2/close]
[text/text {:size :heading-1
:weight :semi-bold
:style {:padding-horizontal screen-padding
:padding-vertical 12}}
(i18n/label :t/notifications)]
[rn/view {:flex-direction :row
:padding-vertical 12}
[rn/view {:flex 1
:align-self :stretch}
[tabs]]
[rn/view {:flex-grow 0
:margin-left 16
:padding-right screen-padding}
[filter-selector-read-toggle]]]]))
(defn loading-indicator
[]
(tap> {:loading-indicator (datetime/now->iso8601)})
(when (<sub [:activity-center/loading?])
[rn/view {:background-color :orange
:position :absolute
:top 0
:left 0
:width 100
:height 50}]))
(defn activity-center
[]
(reagent/create-class
{:component-did-mount #(>evt [:activity-center.notifications/fetch-first-page {:status-filter :unread}])
{:component-did-mount #(>evt [:activity-center.notifications/fetch-first-page])
:reagent-render
(fn []
[:<>
[topbar/topbar {:navigation {:on-press #(>evt [:navigate-back])}
:title (i18n/label :t/activity)}]
;; TODO(ilmotta): Temporary solution to switch between read/unread
;; notifications while the Design team works on the mockups.
[button/button {:on-press #(>evt [:activity-center.notifications/fetch-first-page {:status-filter :unread}])}
"Unread"]
[button/button {:on-press #(>evt [:activity-center.notifications/fetch-first-page {:status-filter :read}])}
"Read"]
[notifications-list]])}))
(tap> {:activity-center (datetime/now->iso8601)})
(let [notifications (<sub [:activity-center/filtered-notifications])]
[:<>
[loading-indicator]
[rn/flat-list {:content-container-style {:flex-grow 1}
:data notifications
:empty-component [empty-tab]
:header [header]
:key-fn :id
;; Commented to simplify analysis.
;; :on-end-reached #(>evt [:activity-center.notifications/fetch-next-page])
:render-fn render-notification
:sticky-header-indices [0]}]]))}))
@@ -2,12 +2,13 @@
(:require [status-im.ui.components.icons.icons :as icons]
[quo.react-native :as rn]
[oops.core :refer [oget]]
[quo.react :as react]
[quo.react :as quo.react]
[quo.platform :as platform]
[quo.components.text :as text]
[quo.design-system.colors :as colors]
[status-im.ui.screens.chat.components.style :as styles]
[status-im.utils.fx :as fx]
[status-im.utils.handlers :refer [<sub]]
[status-im.ui.screens.chat.components.reply :as reply]
[status-im.multiaccounts.core :as multiaccounts]
[status-im.chat.constants :as chat.constants]
@@ -27,7 +28,7 @@
[quo.components.safe-area :as safe-area]))
(defn input-focus [text-input-ref]
(some-> ^js (react/current-ref text-input-ref) .focus))
(some-> ^js (quo.react/current-ref text-input-ref) .focus))
(def panel->icons {:extensions :main-icons/commands
:images :main-icons/photo})
@@ -157,7 +158,7 @@
(quo.react/set-native-props sticker-ref #js {:width nil :right nil})))
(defn reset-input [refs chat-id]
(some-> ^js (react/current-ref (:text-input-ref refs)) .clear)
(some-> ^js (quo.react/current-ref (:text-input-ref refs)) .clear)
(swap! mentions-enabled update :render not)
(swap! input-texts dissoc chat-id))
@@ -204,11 +205,11 @@
(re-frame/reg-fx
:set-input-text
(fn [[chat-id text]]
;; We enable mentions
;; We enable mentions
(swap! mentions-enabled assoc chat-id true)
(on-text-change text chat-id)
;; We update the key so that we force a refresh of the text input, as those
;; are not ratoms
;; We update the key so that we force a refresh of the text input, as those
;; are not ratoms
(force-text-input-update!)))
(fx/defn set-input-text
@@ -461,12 +462,12 @@
:input-focus #(input-focus text-input-ref)
:set-active set-active-panel}])])]]]))))
(defn calculate-y [context keyboard-shown min-y max-y]
(defn calculate-y [context keyboard-shown min-y max-y added-value]
(if keyboard-shown
(if (= (:state @context) :max)
max-y
(if (< (:y @context) max-y)
(:y @context)
(+ (:y @context) added-value)
(do
(swap! context assoc :state :max)
max-y)))
@@ -554,30 +555,31 @@
:pdy 0 ;used for gesture
:state :min ;:min, :custom-chat-available, :custom-chat-unavailable, :max
:clear false})
keyboard-was-shown (atom false)]
keyboard-was-shown (atom false)
text-input-ref (quo.react/create-ref)
send-ref (quo.react/create-ref)
refs {:send-ref send-ref
:text-input-ref text-input-ref}]
(fn []
[:f>
(fn []
(let [text-input-ref (quo.react/create-ref)
send-ref (quo.react/create-ref)
refs {:send-ref send-ref
:text-input-ref text-input-ref}
(let [reply (<sub [:chats/reply-message])
{window-height :height} (rn/use-window-dimensions)
{:keys [keyboard-shown keyboard-height]} (rn/use-keyboard)
max-y (- window-height (if (> keyboard-height 0) keyboard-height 360) (:top insets)) ; 360 - defaul height
max-height (- max-y 56 (:bottom insets)) ; 56 - topbar height
y (calculate-y context keyboard-shown min-y max-y)
max-y (- window-height (if (> keyboard-height 0) keyboard-height 360) (:top insets)) ; 360 - default height
max-height (- max-y 56 (:bottom insets)) ; 56 - top-bar height
added-value (if reply 38 0) ; increased height of input box needed when reply
min-y (+ min-y added-value)
y (calculate-y context keyboard-shown min-y max-y added-value)
translate-y (reanimated/use-shared-value 0)
shared-height (reanimated/use-shared-value min-y)
bg-opacity (reanimated/use-shared-value 0)
bottom-sheet-gesture (get-bottom-sheet-gesture context translate-y text-input-ref keyboard-shown
min-y max-y shared-height max-height bg-opacity)
input-content-change (get-input-content-change context translate-y shared-height max-height
bg-opacity keyboard-shown min-y max-y)]
bg-opacity keyboard-shown min-y max-y)
bottom-sheet-gesture (get-bottom-sheet-gesture context translate-y (:text-input-ref refs) keyboard-shown
min-y max-y shared-height max-height bg-opacity)]
(quo.react/effect! #(do
(when (and @keyboard-was-shown (not keyboard-shown))
(swap! context assoc :state :min))
@@ -597,7 +599,8 @@
(styles/new-input-bottom-sheet window-height))}
;handle
[rn/view {:style (styles/new-bottom-sheet-handle)}]
[rn/view {:style {:height (- max-y 80)}}
[reply/reply-message-auto-focus-wrapper (:text-input-ref refs) reply]
[rn/view {:style {:height (- max-y 80 added-value)}}
[text-input {:chat-id chat-id
:on-content-size-change input-content-change
:sending-image false
@@ -619,4 +622,4 @@
;black background
[reanimated/view {:style (reanimated/apply-animations-to-style
{:opacity bg-opacity}
(styles/new-bottom-sheet-background window-height))}]]))])))])
(styles/new-bottom-sheet-background window-height))}]]))])))])
@@ -89,7 +89,7 @@
[rn/view {:style (styles/reply-content)}
[icons/icon :main-icons/connector {:color (theme-colors quo2.colors/neutral-40 quo2.colors/neutral-60)
:container-style {:position :absolute :left 10 :bottom -4 :width 16 :height 16}}]
[rn/view {:style {:position :absolute :left 34 :right 54 :top 3 :flex-direction :row :align-items :center}}
[rn/view {:style {:position :absolute :left 34 :top 3 :flex-direction :row :align-items :center :width "45%"}}
[photos/member-photo from identicon 16]
[quo2.text/text {:weight :semi-bold
:size :paragraph-2
@@ -144,7 +144,8 @@
(when-not (= reply @had-reply)
(reset! had-reply reply)
(when reply
(js/setTimeout #(input-focus text-input-ref) 250))))
;; A setTimeout of 0 is necessary to ensure the statement is enqueued and will get executed ASAP.
(js/setTimeout #(input-focus text-input-ref) 0))))
(defn reply-message-wrapper-old [reply]
[rn/view {:style {:padding-horizontal 15
@@ -155,8 +156,6 @@
(defn reply-message-wrapper [reply]
[rn/view {:style {:padding-horizontal 15
:border-top-width 1
:border-top-color (:ui-01 @quo.colors/theme)
:padding-vertical 8}}
[reply-message reply true]])
@@ -168,10 +167,9 @@
(when reply
[reply-message-wrapper-old reply])))))
(defn reply-message-auto-focus-wrapper [text-input-ref]
(defn reply-message-auto-focus-wrapper [text-input-ref _]
(let [had-reply (atom nil)]
(fn []
(let [reply @(re-frame/subscribe [:chats/reply-message])]
(focus-input-on-reply reply had-reply text-input-ref)
(when reply
[reply-message-wrapper reply])))))
(fn [_ reply]
(focus-input-on-reply reply had-reply text-input-ref)
(when reply
[reply-message-wrapper reply]))))
@@ -4,7 +4,6 @@
[reagent.core :as reagent]
[goog.string :as gstring]
[status-im.audio.core :as audio]
[status-im.utils.fx :as fx]
[status-im.ui.screens.chat.styles.message.audio :as style]
[status-im.ui.components.animation :as anim]
[quo.design-system.colors :as colors]
@@ -184,15 +183,6 @@
:accessibility-label :play-pause-audio-message-button
:color color}]])))
(fx/defn on-background
{:events [:audio-message/on-background]}
[_]
(when (and @current-active-state-ref-ref
@@current-active-state-ref-ref)
(update-state {:state-ref @current-active-state-ref-ref
:message-id @current-player-message-id}))
nil)
(defview message-content [{:keys [audio audio-duration-ms message-id]}]
(letsubs [state (reagent/atom nil)
progress (reagent/atom 0)
+68 -32
View File
@@ -423,6 +423,38 @@
:inverted (when platform/ios? true)
:style (when platform/android? {:scaleY -1})})]))
(def show-floating-scroll-down-button (reagent/atom false))
(def threshold-percentage-to-show-floating-scroll-down-button 75)
(defn scroll-to-bottom []
(some-> ^js @messages-list-ref (.scrollToOffset #js {:y 0 :animated true})))
(defn floating-scroll-down-button [show-input?]
[rn/touchable-without-feedback
{:on-press scroll-to-bottom}
[rn/view {:style {:position :absolute
:bottom (if show-input? 126 12)
:right 12
:height 24
:width 24
:align-items :center
:justify-content :center
:border-radius (/ 24 2)
:background-color (quo2.colors/theme-colors quo2.colors/neutral-80-opa-70 quo2.colors/white-opa-70)}}
[icons/icon
:main-icons/arrow-down {:color (quo2.colors/theme-colors quo2.colors/white quo2.colors/neutral-100)
:width 12
:height 12}]]])
(defn on-scroll [^js ev]
(let [y (-> ev .-nativeEvent .-contentOffset .-y)
layout-height (-> ev .-nativeEvent .-layoutMeasurement .-height)
threshold-height (* (/ layout-height 100) threshold-percentage-to-show-floating-scroll-down-button)
reached-threshold? (> y threshold-height)]
(when (not= reached-threshold? @show-floating-scroll-down-button)
(rn/configure-next (:ease-in-ease-out rn/layout-animation-presets))
(reset! show-floating-scroll-down-button reached-threshold?))))
(defn messages-view [{:keys [chat
bottom-space
pan-responder
@@ -438,39 +470,43 @@
mutual-contact-requests-enabled?
one-to-one?
(not contact-added?))]
;;do not use anonymous functions for handlers
[list/flat-list
(merge
pan-responder
{:key-fn list-key-fn
:ref list-ref
:header [list-header chat]
:footer [list-footer chat]
:data (when-not should-send-contact-request?
messages)
:render-data (get-render-data {:group-chat group-chat
:chat-id chat-id
:public? public?
:community-id community-id
:admins admins
:show-input? show-input?
:edit-enabled true
:in-pinned-view? false})
:render-fn render-fn
:on-viewable-items-changed on-viewable-items-changed
:on-end-reached list-on-end-reached
:on-scroll-to-index-failed identity ;;don't remove this
:content-container-style {:padding-top (+ bottom-space 16)
:padding-bottom 16}
:scroll-indicator-insets {:top bottom-space} ;;ios only
:keyboard-dismiss-mode :interactive
:keyboard-should-persist-taps :handled
:onMomentumScrollBegin state/start-scrolling
:onMomentumScrollEnd state/stop-scrolling
[:<>
;;do not use anonymous functions for handlers
[list/flat-list
(merge
pan-responder
{:key-fn list-key-fn
:ref list-ref
:header [list-header chat]
:footer [list-footer chat]
:data (when-not should-send-contact-request?
messages)
:render-data (get-render-data {:group-chat group-chat
:chat-id chat-id
:public? public?
:community-id community-id
:admins admins
:show-input? show-input?
:edit-enabled true
:in-pinned-view? false})
:render-fn render-fn
:on-viewable-items-changed on-viewable-items-changed
:on-end-reached list-on-end-reached
:on-scroll-to-index-failed identity ;;don't remove this
:content-container-style {:padding-top (+ bottom-space 16)
:padding-bottom 16}
:scroll-indicator-insets {:top bottom-space} ;;ios only
:keyboard-dismiss-mode :interactive
:keyboard-should-persist-taps :handled
:onMomentumScrollBegin state/start-scrolling
:onMomentumScrollEnd state/stop-scrolling
:scrollEventThrottle 16
:on-scroll on-scroll
;;TODO https://github.com/facebook/react-native/issues/30034
:inverted (when platform/ios? true)
:style (when platform/android? {:scaleY -1})})]))
:inverted (when platform/ios? true)
:style (when platform/android? {:scaleY -1})})]
(when @show-floating-scroll-down-button
[floating-scroll-down-button show-input?])]))
(defn back-button []
[quo2.button/button {:type :grey
@@ -8,8 +8,7 @@
[quo2.components.markdown.text :as quo2.text]
[quo2.components.buttons.button :as quo2.button]
[quo2.components.counter.counter :as quo2.counter]
[quo2.components.tags.filter-tags :as filter-tags]
[quo2.components.tags.filter-tag :as filter-tag]
[quo2.components.tags.tags :as tags]
[quo2.foundations.colors :as quo2.colors]
[quo.components.safe-area :as safe-area]
[quo2.components.tabs.tabs :as quo2.tabs]
@@ -29,14 +28,21 @@
(def sort-list-by (reagent/atom :name))
(def mock-community-item-data ;; TODO: remove once communities are loaded with this data.
{:data {:status :gated
:locked true
;; :images {:thumbnail {:uri []}} ;; TODO address issue with context tag image
:cover (resources/get-image :community-cover)
:tokens [{:id 1 :group [{:id 1 :token-icon (resources/get-image :status-logo)}]}]
:tags [{:id 1 :tag-label (i18n/label :t/music) :resource (resources/get-image :music)}
{:id 2 :tag-label (i18n/label :t/lifestyle) :resource (resources/get-image :lifestyle)}
{:id 3 :tag-label (i18n/label :t/podcasts) :resource (resources/get-image :podcasts)}]}})
{:data {:status :gated
:locked true
:cover (resources/get-image :community-cover)
:tokens [{:id 1
:group [{:id 1
:token-icon (resources/get-image :status-logo)}]}]
:tags [{:id 1
:tag-label (i18n/label :t/music)
:resource (resources/get-image :music)}
{:id 2
:tag-label (i18n/label :t/lifestyle)
:resource (resources/get-image :lifestyle)}
{:id 3
:tag-label (i18n/label :t/podcasts)
:resource (resources/get-image :podcasts)}]}})
(defn plus-button []
(let [logging-in? (<sub [:multiaccounts/login])]
@@ -180,7 +186,7 @@
(let [filters [{:id 1 :tag-label (i18n/label :t/music) :resource (resources/get-image :music)}
{:id 2 :tag-label (i18n/label :t/lifestyle) :resource (resources/get-image :lifestyle)}
{:id 3 :tag-label (i18n/label :t/podcasts) :resource (resources/get-image :podcasts)}
{:id 3 :tag-label (i18n/label :t/podcasts) :resource (resources/get-image :podcasts)}]]
{:id 4 :tag-label (i18n/label :t/podcasts) :resource (resources/get-image :podcasts)}]]
[react/scroll-view {:horizontal true
:height 48
:shows-horizontal-scroll-indicator false
@@ -188,20 +194,12 @@
:padding-top 4
:padding-bottom 12
:padding-horizontal 20}
[react/view {:flex-direction :row}
[react/view {:margin-right 8}
[filter-tag/filter-tag {:resource :main-icons2/search
:labelled false
:type :icon
:icon-color (quo2.colors/theme-colors
quo2.colors/neutral-100
quo2.colors/white)}]]
[filter-tags/tags {:data filters
:labelled true
:type :emoji
:icon-color (quo2.colors/theme-colors
quo2.colors/neutral-50
quo2.colors/neutral-40)}]]]))
[tags/tags {:data filters
:labelled true
:type :emoji
:icon-color (quo2.colors/theme-colors
quo2.colors/neutral-50
quo2.colors/neutral-40)}]]))
(defn communities-list []
(let [multiaccount (<sub [:multiaccount])
@@ -1,72 +1,141 @@
(ns status-im.ui.screens.communities.community-overview-redesign
(:require [status-im.i18n.i18n :as i18n]
[status-im.ui.components.react :as react]
[quo2.components.markdown.text :as quo2.text]
[quo2.components.buttons.button :as quo2.button]
[quo2.components.markdown.text :as text]
[quo2.components.buttons.button :as button]
[quo2.components.list-items.preview-list :as preview-list]
[quo2.components.list-items.channel :as channel]
[quo2.components.dividers.divider-label :as divider-label]
[quo2.components.community.community-view :as community-view]
[quo2.components.tags.status-tags :as status-tags]
[status-im.ui.screens.communities.request-to-join-bottom-sheet-redesign :as request-to-join]
[status-im.ui.components.list.views :as list]
[status-im.utils.handlers :refer [<sub >evt]]
[status-im.ui.screens.communities.styles :as styles]
[quo2.foundations.colors :as colors]
[status-im.ui.screens.communities.icon :as communities.icon]))
(defn preview-list-component []
[react/view {:style {:height 20 :margin-top 20}}
[quo2.text/text {:accessibility-label :communities-screen-title
:weight :semi-bold
:size :label}
"placeholder for preview list component"]])
;; Mocked list items
(def user-list
[{:full-name "Alicia K"}
{:full-name "Marcus C"}
{:full-name "MNO PQR"}
{:full-name "STU VWX"}])
(defn preview-user-list []
[react/view {:style {:flex-direction :row
:align-items :center
:margin-top 20}}
[preview-list/preview-list {:type :user
:user user-list :list-size 4 :size 24}]
[text/text {:accessibility-label :communities-screen-title
:style {:margin-left 8}
:size :label}
"Join Alicia, Marcus and 2 more"]]) ;; TODO remove mocked data and use from contacts list/communities members
(def list-of-channels {:Welcome [{:name "welcome"
:emoji "🤝"}
{:name "onboarding"
:emoji "🍑"}
{:name "intro"
:emoji "🦄"}]
:General [{:name "general"
:emoji "🐷"}
{:name "people-ops"
:emoji "🌏"}
{:name "announcements"
:emoji "🎺"}]
:Mobile [{:name "mobile"
:emoji "👽"}]})
(defn channel-list-component []
[react/view {:style {:height 200 :margin-top 20}}
[quo2.text/text {:accessibility-label :communities-screen-title
:weight :semi-bold
:size :label}
"placeholder for channel list component"]])
[react/scroll-view {:style {:margin-top 20}}
[:<> {:style {:flex 1}}
(map (fn [category]
^{:key (get category 0)}
[react/view {:flex 1}
[divider-label/divider-label
{:label (first category)
:chevron-position :left}]
[react/view
{:margin-left 8
:margin-top 10
:margin-bottom 8}
[list/flat-list
{:shows-horizontal-scroll-indicator false
:separator [react/view {:margin-top 4}]
:data ((first category) list-of-channels)
:render-fn channel/list-item}]]])
list-of-channels)]])
(defn community-card-page-view [{:keys [name description locked joined
status tokens cover tags] :as community}]
[react/view
{:style
{:height "100%"
{:flex 1
:border-radius 20}}
[react/view (styles/community-cover-container 148)
[react/image
{:source cover
:style {:height 180
:style {:position :relative
:flex 1}}]]
[react/view (styles/card-view-content-container 20)
[react/view (styles/card-view-chat-icon 80)
[communities.icon/community-icon-redesign community 80]]
(when (= status :gated)
[react/view (styles/permission-tag-styles)
[community-view/permission-tag-container
{:locked locked
:status status
:tokens tokens}]])
[community-view/community-title
{:title name
:size :large
:description description}]
[community-view/community-stats-column :card-view]
[community-view/community-tags tags]
[preview-list-component]
(when (not joined)
[quo2.button/button
{:on-press #(>evt [:bottom-sheet/show-sheet
{:content (fn [] [request-to-join/request-to-join community])
:content-height 300}])
:style
{:width "100%"
:margin-top 20
:margin-left :auto
:margin-right :auto}
:before :main-icons/community}
(i18n/label :join-open-community)])
[react/view {:flex 1
:height 20
:border-radius 16
:background-color (colors/theme-colors
colors/white
colors/neutral-90)}
[react/view {:padding-horizontal 20}
[react/view {:border-radius 40
:border-width 1
:border-color colors/white
:position :absolute
:top (- (/ 80 2))
:left (/ 70 4)
:padding 2
:background-color (colors/theme-colors
colors/white
colors/neutral-90)}
[communities.icon/community-icon-redesign community 80]]
(when (and (not joined)
(= status :gated))
[react/view (styles/permission-tag-styles)
[community-view/permission-tag-container
{:locked locked
:status status
:tokens tokens}]])
(when joined
[react/view {:position :absolute
:top 12
:right 12}
[status-tags/status-tag {:status {:type :positive} :label (i18n/label :joined)}]])
[community-view/community-title
{:title name
:size :large
:description description}]
[community-view/community-stats-column :card-view]
[community-view/community-tags tags]
[preview-user-list]
(when (not joined)
[button/button
{:on-press #(>evt [:bottom-sheet/show-sheet
{:content (constantly [request-to-join/request-to-join community])
:content-height 300}])
:style
{:width "100%"
:margin-top 20
:margin-left :auto
:margin-right :auto}
:before :main-icons2/communities}
(i18n/label :join-open-community)])]
[channel-list-component]]])
(defn overview []
(let [community (<sub [:get-screen-params :community-overview])]
[react/view {:style {}}
(let [community-mock (<sub [:get-screen-params :community-overview]) ;;TODO stop using mock data and only pass community id
community (<sub [:communities/community (:id community-mock)])]
[react/view {:style
{:height "100%"}}
[community-card-page-view
community]]))
(merge community-mock {:joined (:joined community)})]]))
@@ -1,16 +1,17 @@
(ns status-im.ui.screens.communities.request-to-join-bottom-sheet-redesign
(:require [status-im.i18n.i18n :as i18n]
[quo.core :as quo]
[reagent.core :as reagent]
[status-im.react-native.resources :as resources]
[status-im.ui.components.list.views :as list]
[status-im.ui.components.react :as react]
[quo2.components.markdown.text :as quo2.text]
[quo2.components.buttons.button :as quo2.button]
[quo2.components.icon :as quo2.icon]
[quo2.components.markdown.text :as text]
[quo2.components.buttons.button :as button]
[quo2.components.selectors.disclaimer :as disclaimer]
[quo2.components.icon :as icon]
[status-im.utils.handlers :refer [>evt]]
;; [quo2.components.tags.context-tags :as quo2.context-tags]
[quo2.components.tags.context-tags :as context-tags]
[status-im.communities.core :as communities]
[quo2.foundations.colors :as quo2.colors]))
[quo2.foundations.colors :as colors]))
;; TODO: update with real data
(def community-rules [{:index 1
@@ -46,84 +47,75 @@
:width 18
:margin-left 1
:margin-right 9
:background-color quo2.colors/white
:border-color quo2.colors/neutral-20
:background-color colors/white
:border-color colors/neutral-20
:border-width 1
:border-radius 6}}
[quo2.text/text {:style
{:margin-left :auto
:margin-right :auto
:margin-top :auto
:margin-bottom :auto}
:accessibility-label :communities-rule-index
:weight :medium
:size :label}
[text/text {:style
{:margin-left :auto
:margin-right :auto
:margin-top :auto
:margin-bottom :auto}
:accessibility-label :communities-rule-index
:weight :medium
:size :label}
(str index)]]
[quo2.text/text
[text/text
{:accessibility-label :communities-rule-title
:weight :semi-bold
:size :paragraph-2}
title]]
[quo2.text/text
{:style {:margin-left 28}
[text/text
{:style {:margin-left 28 :margin-top 1}
:accessibility-label :communities-rule-content
:size :paragraph-2}
content]])
(defn community-rules-list [rules]
[list/flat-list
{:shows-horizontal-scroll-indicator false
:data rules
:separator [react/view {:margin-top 1}]
:render-fn community-rule-item}])
(defn rules-checkbox [agreed-to-rules?]
[react/view {:style
{:flex 1
:flex-direction :row
:margin-top 20
:background-color quo2.colors/neutral-20
:padding 12
:border-radius 12
:border-width 1
:border-color quo2.colors/neutral-30}}
[quo/checkbox {:value @agreed-to-rules?
:on-change #(swap! agreed-to-rules? not)}]
[quo2.text/text {:accessibility-label :communities-accept-rules
:size :paragraph-2
:style {:margin-left 8}}
(i18n/label :t/accept-community-rules)]])
(defn request-to-join [community]
(let [agreed-to-rules? (reagent/atom false)]
(fn []
[react/view {:style {:flex 1 :margin-left 20 :margin-right 20 :margin-bottom 20}}
[react/view {:style {:flex 1 :margin-left 20 :margin-right 20 :margin-bottom 20}}
[react/view {:style {:flex 1 :flex-direction :row :align-items :center :justify-content :space-between}}
[quo2.text/text {:accessibility-label :communities-join-community
:weight :semi-bold
:size :heading-1}
[text/text {:accessibility-label :communities-join-community
:weight :semi-bold
:size :heading-1}
(i18n/label :t/join-open-community)]
[react/view {:style {:height 32
:width 32
:align-items :center
:background-color quo2.colors/white
:border-color quo2.colors/neutral-20
:background-color colors/white
:border-color colors/neutral-20
:border-width 1
:border-radius 6
:border-radius 8
:display :flex
:justify-content :center}}
[quo2.icon/icon :main-icons2/info]]]
;; TODO address issue with context tag image
;; [quo2.context-tags/context-tag {:style {:margin-right :auto :margin-top 8}} (get-in community [:images :thumbnail :uri]) (:name community)]
[quo2.text/text {:style {:margin-top 24}
:accessibility-label :communities-rules-title
:weight :semi-bold
:size :paragraph-1}
[icon/icon :main-icons2/info]]]
;; TODO get tag image from community data
[context-tags/context-tag
{:style
{:margin-right :auto
:margin-top 8}}
(resources/get-image :status-logo) (:name community)]
[text/text {:style {:margin-top 24}
:accessibility-label :communities-rules-title
:weight :semi-bold
:size :paragraph-1}
(i18n/label :t/community-rules)]
[community-rules-list community-rules]
[rules-checkbox agreed-to-rules?]
[disclaimer/disclaimer
{:container-style {:margin-top 20}
:on-change #(swap! agreed-to-rules? not)}
(i18n/label :t/accept-community-rules)]
[react/view {:style {:width "100%"
:margin-top 32 :margin-bottom 16
@@ -131,8 +123,10 @@
:flex-direction :row
:align-items :center
:justify-content :space-evenly}}
[quo2.button/button {:on-press #(>evt [:bottom-sheet/hide])
:type :grey :style {:flex 1 :margin-right 12}} (i18n/label :t/cancel)]
[quo2.button/button
{:on-press #(>evt [::communities/join (:id community)])
:disabled (not agreed-to-rules?) :style {:flex 1}} (i18n/label :t/join-open-community)]]])))
[button/button {:on-press #(>evt [:bottom-sheet/hide])
:type :grey :style {:flex 1 :margin-right 12}} (i18n/label :t/cancel)]
[button/button
{:on-press (fn []
(>evt [::communities/join (:id community)])
(>evt [:bottom-sheet/hide]))
:disabled (not @agreed-to-rules?) :style {:flex 1}} (i18n/label :t/join-open-community)]]])))
+2 -20
View File
@@ -26,12 +26,10 @@
[status-im.utils.config :as config]
[quo2.components.markdown.text :as quo2.text]
[status-im.qr-scanner.core :as qr-scanner]
[status-im.utils.datetime :as datetime]
[status-im.ui.components.chat-icon.screen :as chat-icon.screen]
[status-im.ui.components.chat-icon.styles :as chat-icon.styles]
[quo2.foundations.colors :as quo2.colors]
[quo2.components.buttons.button :as quo2.button]
[quo2.components.info.information-box :as information-box])
[quo2.components.buttons.button :as quo2.button])
(:require-macros [status-im.utils.views :as views]))
(defn home-tooltip-view []
@@ -189,12 +187,9 @@
[home-tooltip-view]
[react/view {:height 68}])}])))
(def ens-banner-expire-timestamp 1665010800)
(views/defview communities-and-chats-old []
(views/letsubs [{:keys [items search-filter]} [:home-items]
hide-home-tooltip? [:hide-home-tooltip?]
information-box-closed? [:information-box-closed? :ens-banner]]
hide-home-tooltip? [:hide-home-tooltip?]]
(if (and (empty? items)
(empty? search-filter)
hide-home-tooltip?
@@ -210,19 +205,6 @@
:header [:<>
(when (or (seq items) @search-active? (seq search-filter))
[search-input-wrapper-old search-filter (empty? items)])
(when (< (datetime/timestamp-sec) ens-banner-expire-timestamp)
[information-box/information-box
{:type :informative
:closable? true
:closed? information-box-closed?
:icon :main-icons/info
:style {:margin 20}
:button-label (i18n/label :t/open-dapp2)
:on-button-press #(re-frame/dispatch
[:browser.ui/open-url "https://ens-collect.status.im/"])
:id :ens-banner
:on-close #(re-frame/dispatch [:close-information-box :ens-banner true])}
(i18n/label :t/ens-banner-message)])
(when (and (empty? items)
(or @search-active? (seq search-filter)))
[start-suggestion search-filter])]
+1 -1
View File
@@ -181,4 +181,4 @@
;; TODO: Remove this (highly) temporary flag once the new Activity Center is
;; usable enough to replace the old one **in the new UI**.
(def new-activity-center-enabled?
(atom false))
(atom true))
+6
View File
@@ -240,3 +240,9 @@
(defn to-ms [sec]
(* 1000 sec))
(defn now->iso8601 []
(unparse {:format-str "HH:mm:ss.SSS"}
(-> (timestamp)
(from-long)
(plus time-zone-offset))))
+15
View File
@@ -0,0 +1,15 @@
(ns status-im.utils.number)
(defn naive-round
"Quickly and naively round number `n` up to `decimal-places`.
Example usage: use it to avoid re-renders caused by floating-point number
changes in Reagent atoms. Such numbers can be rounded up to a certain number
of `decimal-places` in order to avoid re-rendering due to tiny fractional
changes.
Don't use this function for arbitrary-precision arithmetic."
[n decimal-places]
(let [scale (Math/pow 10 decimal-places)]
(/ (Math/round (* n scale))
scale)))
-10
View File
@@ -1,10 +0,0 @@
(ns status-im.utils.profiler)
(defmacro with-measure
[name & body]
`(let [start# (js/performance.now)
res# (do ~@body)
end# (js/performance.now)
time# (.toFixed (- end# start#) 2)]
(taoensso.timbre/info "[perf|" ~name "] => " time#)
res#))
-39
View File
@@ -1,39 +0,0 @@
(ns status-im.utils.profiler
"Performance profiling for react components."
(:require-macros [status-im.utils.profiler])
(:require [reagent.core :as reagent]
[taoensso.timbre :as log]
[oops.core :refer [oget ocall]]
[goog.functions :as f]))
(defonce memo (atom {}))
(def td (js/require "tdigest"))
(def tdigest (oget td "TDigest"))
(def react (js/require "react"))
(def profiler (reagent/adapt-react-class (oget react "Profiler")))
(defn on-render-factory
[label]
(let [buf (new tdigest)
log (f/debounce
(fn [phase buf]
(log/info "[profile:" label "(" phase ")]: \n"
(ocall buf "summary")))
300)]
(fn [_ phase adur _ _ _ _]
(.push buf adur)
(log phase buf))))
(defn perf [{:keys [label]}]
(let [this (reagent/current-component)
children (reagent/children this)
on-render (if-let [render-fn (get @memo label)]
render-fn
(do
(swap! memo assoc label (on-render-factory label))
(get @memo label)))]
(into [profiler {:id label
:on-render on-render}]
children)))
-9
View File
@@ -105,15 +105,6 @@
(.then #(log/debug "[wallet-connect] session topic " topic " changed to account " (first accounts)))
(.catch #(log/error "[wallet-connect] " %)))))
(re-frame/reg-fx
:wc-2-change-session
(fn [[client topic accounts]]
(-> ^js client
(.update (clj->js {:topic topic
:state {:accounts accounts}}))
(.then #(log/debug "[wallet-connect] session topic " topic " changed to account " (first accounts)))
(.catch #(log/error "[wallet-connect] " %)))))
(re-frame/reg-fx
:wc-2-pair
(fn [[client uri]]
+3 -3
View File
@@ -89,6 +89,7 @@ class TestrailReport(BaseTestReport):
'include_all': False}
run = self.post('add_run/%s' % self.project_id, request_body)
self.run_id = run['id']
print("Testrun: %sruns/view/%s" % (self.url, self.run_id))
def get_cases(self, section_ids):
test_cases = list()
@@ -157,9 +158,7 @@ class TestrailReport(BaseTestReport):
else:
for phase in test_cases:
if phase != 'upgrade':
print("For %s phase" % phase)
for category in test_cases[phase]:
print("For %s category" % category)
for case in self.get_cases([test_cases[phase][category]]):
case_ids.append(case['id'])
return case_ids
@@ -299,7 +298,8 @@ class TestrailReport(BaseTestReport):
try:
test_id = self.get('get_results_for_case/%s/%s' % (test_run_id, test_case_id))['results'][0]['test_id']
return '%stests/view/%s' % (self.url, test_id)
except KeyError:
except (KeyError, JSONDecodeError):
print('Cannot extract result for %s e2e' % test_case_id)
return None
def get_not_executed_tests(self, test_run_id):
@@ -102,7 +102,7 @@ class TestCommandsMultipleDevicesMerged(MultipleSharedDeviceTestCase):
[message.transaction_status.wait_for_element_text(message.confirmed, 60) for message in
(sender_message, receiver_message)]
# TODO: should be added PNs for receiver after getting more stable feature (rechecked 23.11.21, valid)
# TODO: should be added PNs for receiver after getting more stable feature (rechecked 04.10.22, valid)
self.errors.verify_no_errors()
@marks.testrail_id(6265)
@@ -337,19 +337,19 @@ class TestOneToOneChatMultipleSharedDevices(MultipleSharedDeviceTestCase):
self.chat_2.send_message(message_before_edit_1_1)
self.chat_2.edit_message_in_chat(message_before_edit_1_1, message_after_edit_1_1)
if not self.home_1.element_by_text_part(message_after_edit_1_1).is_element_present():
if not self.home_1.element_by_text_part(message_after_edit_1_1).is_element_displayed():
self.errors.append('UNedited message version displayed on preview')
self.home_1.get_chat(self.default_username_2).click()
chat_element = self.chat_1.chat_element_by_text(message_after_edit_1_1)
if not chat_element.is_element_present(30):
if not chat_element.is_element_displayed(30):
self.errors.append('No edited message in 1-1 chat displayed')
if not self.chat_1.element_by_text_part("⌫ Edited").is_element_present(30):
if not self.chat_1.element_by_text_part("⌫ Edited").is_element_displayed(30):
self.errors.append('No mark in message bubble about this message was edited on receiver side')
self.device_2.just_fyi("Verify Device1 can not edit and delete received message from Device2")
chat_element.long_press_element()
for action in ("edit", "delete"):
if self.chat_1.element_by_translation_id(action).is_element_present():
if self.chat_1.element_by_translation_id(action).is_element_displayed():
self.errors.append('Option to %s someone else message available!' % action)
self.home_1.click_system_back_button()
@@ -532,9 +532,9 @@ class TestOneToOneChatMultipleSharedDevices(MultipleSharedDeviceTestCase):
self.chat_2.image_message_in_chat.click_until_presence_of_element(self.chat_2.share_image_icon_button)
self.chat_2.share_image_icon_button.click()
self.chat_2.share_via_messenger()
if not self.chat_2.image_in_android_messenger.is_element_present():
if not self.chat_2.image_in_android_messenger.is_element_displayed():
self.errors.append("Can't share image")
self.chat_2.click_system_back_button_until_element_is_shown()
self.chat_2.click_system_back_button_until_element_is_shown(element=self.chat_2.save_image_icon_button)
self.chat_2.save_image_icon_button.click()
self.chat_2.show_images_button.click()
self.chat_2.allow_button.wait_and_click()
@@ -1153,7 +1153,7 @@ class TestEnsStickersMultipleDevicesMerged(MultipleSharedDeviceTestCase):
self.errors.append('Sticker was not sent from Recent')
# self.home_2.just_fyi('Check that can install stickers by tapping on sticker message')
# TODO: disabled because of #13683 (rechecked 27.07.22, valid)
# TODO: disabled because of #13683 (rechecked 04.10.22, valid)
self.home_2.home_button.double_click()
self.home_2.get_chat(self.sender['username']).click()
# self.chat_2.chat_item.click()
@@ -50,7 +50,7 @@ class TestOnboardingOneDeviceMerged(MultipleSharedDeviceTestCase):
self.home.just_fyi("Sharing contact code via messenger")
self.profile.share_button.click()
self.profile.share_via_messenger()
if not self.profile.element_by_text_part(public_key).is_element_present():
if not self.profile.element_by_text_part(public_key).is_element_displayed():
self.errors.append("Can't share public key")
self.profile.click_system_back_button_until_element_is_shown()
@@ -78,7 +78,7 @@ class TestOnboardingOneDeviceMerged(MultipleSharedDeviceTestCase):
self.home.just_fyi("Sharing wallet address via messenger")
request.share_button.click()
wallet.share_via_messenger()
if not wallet.element_by_text_part(address).is_element_present():
if not wallet.element_by_text_part(address).is_element_displayed():
self.errors.append("Can't share address")
wallet.click_system_back_button_until_element_is_shown()
@@ -477,4 +477,4 @@ class TestRestoreOneDeviceMerged(MultipleSharedDeviceTestCase):
transaction_amount_added = wallet.get_unique_amount()
wallet.send_transaction(amount=transaction_amount_added, recipient=transaction_senders['ETH_8']['address'],
keycard=True, sign_transaction=True)
self.errors.verify_no_errors()
self.errors.verify_no_errors()
@@ -292,7 +292,7 @@ class TestPublicChatBrowserOneDeviceMerged(MultipleSharedDeviceTestCase):
def test_public_chat_copy_and_paste_message_in_chat_input(self):
message_text = {'text_message': 'mmmeowesage_text'}
formatted_message = {'message_with_link': 'https://status.im',
# TODO: blocked with 11161 (rechecked 23.11.21, valid)
# TODO: blocked with 11161 (rechecked 04.10.22, valid)
# 'message_with_tag': '#successishere'
}
message_input = self.chat.chat_message_input
@@ -155,7 +155,7 @@ class TestSendTxDeviceMerged(MultipleSharedDeviceTestCase):
self.errors.append("Tx is not sent!")
send_tx.ok_button.click()
# TODO: disabled due to 10838 (rechecked 27.07.22, valid)
# TODO: disabled due to 10838 (rechecked 04.10.22, valid)
# transactions_view = wallet.transaction_history_button.click()
# transactions_view.transactions_table.find_transaction(amount=amount, asset=symbol)
self.errors.verify_no_errors()
@@ -169,13 +169,13 @@ class TestActivityCenterMultipleDeviceMedium(MultipleSharedDeviceTestCase):
self.home_1.home_button.double_click()
self.home_1.just_fyi("Check there are no unread messages counter on chats after message is read")
if (self.home_1.notifications_unread_badge.is_element_present() or
if (self.home_1.notifications_unread_badge.is_element_displayed() or
self.home_1.get_chat_from_home_view(self.group_chat_name_1).new_messages_counter.text == "1"):
self.errors.append("Unread message indicator is kept after message is read in chat")
self.home_1.just_fyi("Check there is an empty view on Activity Center")
self.home_1.notifications_button.click()
if not self.home_1.element_by_translation_id('empty-activity-center').is_element_present():
if not self.home_1.element_by_translation_id('empty-activity-center').is_element_displayed():
self.errors.append("Activity Center still has some chats after user has opened all of them")
self.errors.verify_no_errors()
@@ -280,18 +280,21 @@ class TestBrowserProfileOneDevice(MultipleSharedDeviceTestCase):
@marks.testrail_id(702166)
def test_profile_add_custom_network(self):
self.home.get_back_to_home_view()
rpc, name, id, symbol = 'https://polygon-rpc.com/', 'Polygon', '137', 'MATIC'
profile = self.home.profile_button.click()
profile.add_custom_network()
profile.add_custom_network(rpc_url=rpc, netwrok_id=id, symbol=symbol, name=name)
self.sign_in.sign_in()
wallet = self.home.wallet_button.click()
if not wallet.element_by_text_part(symbol).is_element_displayed():
self.errors.append("No %s currency is shown when switching to custom network" % symbol)
self.home.profile_button.click()
profile.advanced_button.click()
profile.network_settings_button.scroll_to_element(10, 'up')
if not profile.element_by_text_part('custom_ropsten').is_element_displayed():
self.driver.fail("Network custom_ropsten was not added!")
if not profile.element_by_text_part(name).is_element_displayed():
self.driver.fail("Custom network %s was not added!" % name)
profile.get_back_to_home_view()
# Switching back to Goerli for the next cases
profile.switch_network('Goerli with upstream RPC')
self.errors.verify_no_errors()
@marks.testrail_id(702164)
+20 -20
View File
@@ -148,7 +148,7 @@ class TestTimelineHistoryNodesBootnodesMultipleDeviceMergedMedium(MultipleShared
self.profile_1.bootnodes_button.click()
self.profile_1.add_bootnode_button.click()
self.profile_1.specify_name_input.set_value('test')
# TODO: blocked as validation is missing for bootnodes (rechecked 27.07.22, valid)
# TODO: blocked as validation is missing for bootnodes (rechecked 04.10.22, valid)
# profile_1.bootnode_address_input.set_value('invalid_bootnode_address')
# if not profile_1.element_by_text_part('Invalid format').is_element_displayed():
# self.errors.append('Validation message about invalid format of bootnode is not shown')
@@ -420,7 +420,7 @@ class TestChatMediumMultipleDevice(MultipleSharedDeviceTestCase):
self.chat_1.send_message(self.message_1)
self.chat_1.send_message(self.message_2)
self.chat_1.pin_message(self.message_1)
if not self.chat_1.chat_element_by_text(self.message_1).pinned_by_label.is_element_present():
if not self.chat_1.chat_element_by_text(self.message_1).pinned_by_label.is_element_displayed():
self.drivers[0].fail("Message is not pinned!")
self.home_1.just_fyi("Check that Device2 can pin Device1 message in 1-1 chat and two pinned "
@@ -435,16 +435,16 @@ class TestChatMediumMultipleDevice(MultipleSharedDeviceTestCase):
self.chat_1.chat_options.click()
self.chat_1.view_profile_button.click()
self.chat_1.pinned_messages_button.click()
if not (self.chat_1.chat_element_by_text(self.message_1).pinned_by_label.is_element_present() and
self.chat_1.chat_element_by_text(self.message_2).pinned_by_label.is_element_present() and
self.chat_1.chat_element_by_text(self.message_1).is_element_present() and
self.chat_1.chat_element_by_text(self.message_2).is_element_present()):
if not (self.chat_1.chat_element_by_text(self.message_1).pinned_by_label.is_element_displayed() and
self.chat_1.chat_element_by_text(self.message_2).pinned_by_label.is_element_displayed() and
self.chat_1.chat_element_by_text(self.message_1).is_element_displayed() and
self.chat_1.chat_element_by_text(self.message_2).is_element_displayed()):
self.drivers[0].fail("Something missed on Pinned messaged on Device 1!")
self.chat_2.pinned_messages_button.click()
if not (self.chat_1.chat_element_by_text(self.message_1).pinned_by_label.is_element_present() and
self.chat_2.chat_element_by_text(self.message_2).pinned_by_label.is_element_present() and
self.chat_2.chat_element_by_text(self.message_1).is_element_present() and
self.chat_2.chat_element_by_text(self.message_2).is_element_present()):
if not (self.chat_1.chat_element_by_text(self.message_1).pinned_by_label.is_element_displayed() and
self.chat_2.chat_element_by_text(self.message_2).pinned_by_label.is_element_displayed() and
self.chat_2.chat_element_by_text(self.message_1).is_element_displayed() and
self.chat_2.chat_element_by_text(self.message_2).is_element_displayed()):
self.drivers[0].fail("Something missed on Pinned messaged on Device 2!")
self.chat_1.close_button.click()
@@ -454,25 +454,25 @@ class TestChatMediumMultipleDevice(MultipleSharedDeviceTestCase):
self.chat_1.send_message(self.message_4)
self.chat_1.pin_message(self.message_3)
self.chat_1.pin_message(self.message_4)
if not self.chat_1.unpin_message_popup.is_element_present():
if not self.chat_1.unpin_message_popup.is_element_displayed():
self.drivers[0].fail("No 'Unpin' dialog appears when pining 4th message")
self.home_1.just_fyi("Unpin one message so that another could be pinned")
self.chat_1.unpin_message_popup.message_text(self.message_1).click()
self.chat_1.unpin_message_popup.click_unpin_message_button()
if self.chat_1.unpin_message_popup.is_element_present():
if self.chat_1.unpin_message_popup.is_element_displayed():
self.drivers[0].fail("Unpin message pop up keep staying after Unpin button pressed")
if self.chat_1.chat_element_by_text(self.message_1).pinned_by_label.is_element_present():
if self.chat_1.chat_element_by_text(self.message_1).pinned_by_label.is_element_displayed():
self.drivers[0].fail("Message is not unpinned!")
if not self.chat_1.chat_element_by_text(self.message_4).pinned_by_label.is_element_present():
if not self.chat_1.chat_element_by_text(self.message_4).pinned_by_label.is_element_displayed():
self.drivers[0].fail("Message is not pinned!")
self.home_1.just_fyi("Unpin another message and check it's unpinned for another user")
self.chat_2.close_button.click()
self.chat_2.pin_message(self.message_4, action="unpin")
self.chat_1.chat_element_by_text(self.message_4).pinned_by_label.wait_for_invisibility_of_element()
if self.chat_1.chat_element_by_text(self.message_4).pinned_by_label.is_element_present():
if self.chat_1.chat_element_by_text(self.message_4).pinned_by_label.is_element_displayed():
self.drivers[0].fail("Message_4 is not unpinned!")
@marks.testrail_id(702065)
@@ -577,13 +577,13 @@ class TestChatMediumMultipleDevice(MultipleSharedDeviceTestCase):
[home.get_chat(self.new_group_chat_name).click() for home in (self.home_1, self.home_2)]
self.group_chat_1.send_message(self.message_1)
self.group_chat_1.pin_message(self.message_1)
if not (self.group_chat_1.chat_element_by_text(self.message_1).pinned_by_label.is_element_present(30) and
self.group_chat_2.chat_element_by_text(self.message_1).pinned_by_label.is_element_present(30)):
if not (self.group_chat_1.chat_element_by_text(self.message_1).pinned_by_label.is_element_displayed(30) and
self.group_chat_2.chat_element_by_text(self.message_1).pinned_by_label.is_element_displayed(30)):
self.errors.append("Message is not pinned in group chat!")
self.home_1.just_fyi("Check that non admin user can not unpin messages")
self.group_chat_2.chat_element_by_text(self.message_1).long_press_element()
if self.group_chat_2.element_by_translation_id("unpin").is_element_present():
if self.group_chat_2.element_by_translation_id("unpin").is_element_displayed():
self.errors.append("Unpin option is available for non-admin user")
self.home_1.just_fyi("Grant another user with admin rights and check he can unpin message now")
@@ -593,8 +593,8 @@ class TestChatMediumMultipleDevice(MultipleSharedDeviceTestCase):
options.make_admin_button.click()
self.group_chat_2.click_system_back_button()
self.group_chat_2.pin_message(self.message_1, action="unpin")
if (self.group_chat_1.chat_element_by_text(self.message_1).pinned_by_label.is_element_present() and
self.group_chat_2.chat_element_by_text(self.message_1).pinned_by_label.is_element_present()):
if (self.group_chat_1.chat_element_by_text(self.message_1).pinned_by_label.is_element_displayed() and
self.group_chat_2.chat_element_by_text(self.message_1).pinned_by_label.is_element_displayed()):
self.errors.append("Message failed be unpinned by user who granted admin permissions!")
self.errors.verify_no_errors()
@@ -43,7 +43,7 @@ class TestDeeplinkChatProfileOneDevice(MultipleSharedDeviceTestCase):
self.sign_in.open_weblink_and_login(deep_link)
self.home = self.sign_in.get_home_view()
self.home.plus_button.click_until_presence_of_element(self.home.start_new_chat_button)
if not self.home.start_new_chat_button.is_element_present():
if not self.home.start_new_chat_button.is_element_displayed():
self.errors.append(
"Can't navigate to start new chat after app opened from deep link with invalid public key")
self.drivers[0].close_app()
@@ -78,7 +78,7 @@ class TestDeeplinkChatProfileOneDevice(MultipleSharedDeviceTestCase):
web_view = self.sign_in.get_chat_view()
try:
test_dapp_view = web_view.open_in_status_button.click()
test_dapp_view.allow_button.is_element_present()
test_dapp_view.allow_button.is_element_displayed()
except NoSuchElementException:
self.drivers[0].fail("DApp '%s' is not opened!" % dapp_name)
self.home.reopen_app()
@@ -96,7 +96,7 @@ class TestDeeplinkChatProfileOneDevice(MultipleSharedDeviceTestCase):
chat.share_button.click()
chat.share_via_messenger()
if not chat.element_by_text_part(
'https://join.status.im/u/%s' % dummy_user["public_key"]).is_element_present():
'https://join.status.im/u/%s' % dummy_user["public_key"]).is_element_displayed():
self.errors.append("Can't share public key of contact")
for _ in range(2):
chat.click_system_back_button_until_element_is_shown()
@@ -112,7 +112,7 @@ class TestDeeplinkChatProfileOneDevice(MultipleSharedDeviceTestCase):
self.public_chat.share_chat_button.click()
self.public_chat.share_via_messenger()
if not self.public_chat.element_by_text_part(
'https://join.status.im/%s' % self.public_chat_name).is_element_present():
'https://join.status.im/%s' % self.public_chat_name).is_element_displayed():
self.errors.append("Can't share link to public chat")
for _ in range(2):
self.public_chat.click_system_back_button_until_element_is_shown()
@@ -130,8 +130,8 @@ class TestDeeplinkChatProfileOneDevice(MultipleSharedDeviceTestCase):
expeceted_text_1 = 'https://join.status.im/b/https://dap.ps'
expeceted_text_2 = 'https://join.status.im/b/http://dap.ps'
if not (browser.element_by_text_part(expeceted_text_1).is_element_present() or
browser.element_by_text_part(expeceted_text_2).is_element_present()):
if not (browser.element_by_text_part(expeceted_text_1).is_element_displayed() or
browser.element_by_text_part(expeceted_text_2).is_element_displayed()):
self.errors.append("Can't share link to URL")
browser.click_system_back_button_until_element_is_shown()
@@ -108,8 +108,6 @@ class TestPairingSyncMediumMultipleDevicesMerged(MultipleSharedDeviceTestCase):
self.profile_1.sync_all_button.wait_for_visibility_of_element(20)
[device.profile_button.double_click() for device in (self.profile_1, self.profile_2)]
[device.home_button.double_click() for device in (self.profile_1, self.device_3)]
# TODO Relogin to workaround #13647, should be removed after fix of the issue(rechecked 27.07.22, valid)
self.profile_2.relogin()
self.device_3.put_app_to_background_and_back()
@marks.testrail_id(702133)
@@ -470,13 +470,13 @@ class TestChatManagement(SingleDeviceTestCase):
sign_in = SignInView(self.driver)
sign_in.create_user()
wallet = sign_in.wallet_button.click()
if wallet.backup_recovery_phrase_warning_text.is_element_present():
if wallet.backup_recovery_phrase_warning_text.is_element_displayed():
self.driver.fail("'Back up your seed phrase' warning is shown on Wallet while no funds are present")
address = wallet.get_wallet_address()
self.click = wallet.close_button.click()
w3.donate_testnet_eth(address, 0.0001)
wallet.wait_balance_is_changed()
if not wallet.backup_recovery_phrase_warning_text.is_element_present(30):
if not wallet.backup_recovery_phrase_warning_text.is_element_displayed(30):
self.driver.fail("'Back up your seed phrase' warning is not shown on Wallet with funds")
profile = wallet.get_profile_view()
wallet.backup_recovery_phrase_warning_text.click()
@@ -813,7 +813,7 @@ class TestChatManagement(SingleDeviceTestCase):
no_link_tos_error_msg = 'Could not open Terms of Use from'
signin.just_fyi("Checking privacy policy and TOS links")
if not signin.privacy_policy_link.is_element_present():
if not signin.privacy_policy_link.is_element_displayed():
self.errors.append('%s Sign in view!' % no_link_found_error_msg)
if not signin.terms_of_use_link.is_element_displayed():
self.driver.fail("No Terms of Use link on Sign in view!")
@@ -990,6 +990,7 @@ class TestChatManagement(SingleDeviceTestCase):
self.driver.fail("Unblocked user not added previously in contact list added in contacts!")
@marks.testrail_id(5721)
@marks.xfail(reason="may be failed due to #14013")
def test_group_chat_cant_add_more_twenty_participants(self):
user_20_contacts = dict()
user_20_contacts[
@@ -1123,7 +1124,7 @@ class TestChatManagement(SingleDeviceTestCase):
@marks.testrail_id(6300)
@marks.skip
# TODO: waiting mode (rechecked 27.07.22, valid)
# TODO: waiting mode (rechecked 04.10.22, valid)
def test_webview_security(self):
home_view = SignInView(self.driver).create_user()
daap_view = home_view.dapp_tab_button.click()
+7 -8
View File
@@ -95,10 +95,9 @@ class BaseElement(object):
counter = 0
self.driver.info("Click until `%s` by `%s`: `%s` will be presented" % (
desired_element.name, desired_element.by, desired_element.locator))
while not desired_element.is_element_present(1) and counter <= attempts:
while not desired_element.is_element_displayed(1) and counter <= attempts:
try:
self.find_element().click()
desired_element.is_element_present(5)
return self.navigate()
except (NoSuchElementException, TimeoutException):
counter += 1
@@ -178,11 +177,11 @@ class BaseElement(object):
self.scroll_to_element(direction=direction)
self.click()
def is_element_present(self, sec=5):
try:
return self.wait_for_element(sec)
except TimeoutException:
return False
# def is_element_present(self, sec=5):
# try:
# return self.wait_for_element(sec)
# except TimeoutException:
# return False
def is_element_displayed(self, sec=5, ignored_exceptions=None):
try:
@@ -374,7 +373,7 @@ class Button(BaseElement):
counter = 0
self.driver.info("Click until `%s` by `%s`: `%s` is NOT presented" % (
desired_element.name, desired_element.by, desired_element.locator))
while desired_element.is_element_present(1) and counter <= attempts:
while desired_element.is_element_displayed(1) and counter <= attempts:
try:
self.find_element().click()
counter += 1

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