Compare commits

...
Author SHA1 Message Date
Prem Chaitanya Prathi 7a1a33f459 chore: take status-go fixes for push notifications that is already in 2.30 2024-09-20 13:34:29 +05:30
Volodymyr Kozieiev 1190fc8069 Fixed amounts for multiple collectibles (#21237) 2024-09-19 12:35:14 +01:00
Siddarth Kumar 92456612b1 chore: fix status-go versioning script (#21276) 2024-09-19 16:49:35 +05:30
Mohamed Javid 9912136ac9 fix(wallet)_: crash on viewing dynamic file type collectibles (#21245)
This commit removes the support for GIF file types for the collectibles.

Signed-off-by: Mohamed Javid <19339952+smohamedjavid@users.noreply.github.com>
2024-09-19 15:45:52 +05:30
Parvesh MonuandAndrea Maria Piana 0eb065bbb7 Implement seed phrase fallback flow (#21090)
Co-authored-by: Parvesh Monu <parvesh.dhullmonu@gmail.com>
Co-authored-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
2024-09-19 15:23:23 +05:30
frank 2e61f94d84 fix(mention)_: async call for mention suggestion (#21171)
* fix(mention)_: ensure async call work as expected

https://github.com/status-im/status-go/compare/81cfce70...b2c2e407

* chore_: remove call-time and rename

* chore_: update status go version
2024-09-19 13:24:48 +08:00
Ulises Manuel f606dc065b [#21212] - Fix not joined options for token gated communities (#21247)
* Fix not joined drawer options for token gated communities

* Remove `show-not-implemented-features?` flag for token gated options

* Fix component tests
2024-09-18 17:52:01 -06:00
Icaro Motta 6464ef96b6 chore(waku): Enable message missing verification flag (#21005)
As recommended in
https://github.com/status-im/status-go/pull/5570#discussion_r1692345230 [1] and
https://github.com/status-im/status-go/pull/5570#discussion_r1690794119 [2], we set
the Waku config flag WakuV2EnableMissingMessageVerification to true. The flag
can't be toggled in the UI because there's no supporting endpoint for that in
status-go at the moment.

[1] I doubt the hash based store-query going to be as expensive as legacy-store.
Because we only query hashes and if we miss any messages we fetch them which
would improve message reliability.
https://github.com/status-im/status-go/pull/5511 is expensive as it doesn't
query hashes rather full messages. I think we can keep this new flag enabled by
default and provide a switch in the app in case it becomes expensive to disable
it.

[2] we can keep this enabled for mobile by default. The reason
WakuV2EnableStoreConfirmationForMessagesSent was disabled is as it was giving
bad UX to users where messages were sent but still showing as not sent due to
storeNode performance issues. But this flag doesn't give any bad UX, so we can
keep it enabled by default.

Fixes https://github.com/status-im/status-mobile/issues/20879
2024-09-18 17:52:48 -03:00
Brian Sztamfater 9068801e4f feat(swap): switch pay/receive assets (#21179)
Signed-off-by: Brian Sztamfater <brian@status.im>
2024-09-18 13:07:13 -03:00
Parvesh Monu 2f3d3fc7f9 fix hidden community appears on "Opened" Tab (#21238) 2024-09-18 20:17:44 +05:30
Yevheniia Berdnyk 69664322b8 e2e: requests.log for failed tests 2024-09-18 12:07:50 +03:00
a1662752bc chore(onboarding): Remove welcome screen from onboarding (#20912)
* Fix customization color in disclaimer on onboarding

* Remove welcome page from onboarding

* Fix notification permission request on onboarding

* e2e: updated create user flow

---------

Co-authored-by: Ulises M <ulises.ssb@hotmail.com>
Co-authored-by: Yevheniia Berdnyk <ie.berdnyk@gmail.com>
2024-09-17 16:20:34 -06:00
Alexander 3363152160 WC: Last account that established connection is not selected when connecting through global scanner #20801 (#20913)
* WC: Last account that established connection is not selected when connecting through global scanner

* WC: Last account that established connection is not selected when connecting through global scanner

* Fixes
2024-09-17 15:29:05 +02:00
Volodymyr Kozieiev 7cad13c3c7 Strip out eip-3770 prefixes when scan qr (#21256) 2024-09-17 12:57:06 +01:00
frank 699986c0b2 log go request and response (#21218)
this commit implemented logging request/response between status-mobile and status-go. 

to access `requests.log`, shake your phone and share the logs. 

TBD: 
- adding `device id` into request to proxy server, we can implement it in a separate PR if needed.
- not sure if we need logging the request made in the backend that not asked by frontend directly
2024-09-17 08:58:38 +05:30
flexsurfer 85b5445296 [#21035] Allow users to independently enable Waku telemetry and in-ap… (#21168) 2024-09-16 16:22:18 +02:00
Parvesh Monu cdb2370cf3 fix contrast between Chats and Communities with new messages (#21242) 2024-09-16 19:13:29 +05:30
Mohamed Javid 5d6b46a914 fix(wallet)_: Error on sending ERC1155 collectible (#21209)
This commit:
- fixes crash while sending an ERC115 collectible
- fixes routes are fetched if we enter 0 in the input

Signed-off-by: Mohamed Javid <19339952+smohamedjavid@users.noreply.github.com>
2024-09-13 17:56:53 +05:30
flexsurfer 77ff5b473b Data randomly remains in composer after sending a message #18172 (#21262) 2024-09-13 14:14:16 +02:00
Siddarth Kumar ac8b06b6bf ci: use default IPFS Gateway URL (#21106) 2024-09-13 08:36:38 +05:30
Mohamed Javid dc12a0ff97 fix_: delay in fetching the currencies list (#21239)
This commit fixes the delay in showing the currency symbol in the wallet by moving the RPC call to login phase 1 along with wallet initialisation. This helps us to improve the wallet UX by skipping the waiting to start the messenger.

Signed-off-by: Mohamed Javid <19339952+smohamedjavid@users.noreply.github.com>
2024-09-12 21:45:11 +05:30
c329d5e69f chore: bump status-go to v0.186.0 (#21210)
Co-authored-by: Parvesh Monu <parvesh.dhullmonu@gmail.com>
Co-authored-by: Igor Sirotin <sirotin@status.im>
2024-09-12 20:34:59 +05:30
Siddarth Kumar 009ac7eec2 chore: bump version to 2.30 2024-09-12 16:15:47 +05:30
Alexander 2bf3e6ce13 Swaps: Get full list of supported tokens from backend (#21139)
* 20380 Fetch tokens

* Address updates

* Events update

* Events test
2024-09-12 08:38:03 +02:00
flexsurfer babcd96fb6 simplify composer (#20125) 2024-09-11 18:37:12 +02:00
Yevheniia Berdnyk bf6c89e263 e2e: fix for getting profile link 2024-09-11 17:07:04 +03:00
Alexander 72a02df646 Router clean up (#21235)
* Router changes

* Fix

* Updated status-go version
2024-09-11 14:20:00 +02:00
John Ngei 462101dbf5 chore: Compress syncing images (#20907) 2024-09-10 20:06:46 -03:00
Parvesh Monu 00f1463e94 Change order of bottom tabs and select wallet-stack as default tab (#21240)
* Changed order of bottom tabs
* Changed default bottom to wallet-stack
2024-09-10 19:41:34 +05:30
Lungu CristianandMohsen 9135e915ab Restructure wallet-connect namespaces (#21167)
Co-authored-by: Mohsen <maxghafori@gmail.com>
2024-09-10 15:59:18 +03:00
John NgeiandUlises M d7242f1c70 fix(chats): Last message overlapped by the composer when a message is pinned (#20606)
* fixed overlapping last message by the composer when there is a pinned message

* prevent a user who is not a member of a community from pinning or unpinning channel messages

---------

Co-authored-by: Ulises M <ulises.ssb@hotmail.com>
2024-09-09 17:19:10 -06:00
Ulises Manuel fa4ea0640d fix(onboarding): Can't create profile (#21170)
* Remove unnecessary React.Fragment and reagent functional components notation

* Fix continue button not responding in the Create Profile screen
2024-09-09 14:43:36 -06:00
Ulises Manuel 8f94332d59 feat(wallet): Hide account switcher in send flow (#20892)
* Fix input amount step skipped while sending a token

* Rename events to remove the `wallet` unnecessary suffix

* Code style fixes

* Fix React warnings:
    1. About unique key in `select-address.tabs.view` along with a refactor
    2. The deprecated `:keyboard-should-persist-taps` as `true` in `send.select-address.view`

* Hide account switcher while sending a collectible

* Fix tests
2024-09-09 14:24:00 -06:00
Brian Sztamfater 337ec963b7 feat: autorefresh swap proposal (#21143)
Signed-off-by: Brian Sztamfater <brian@status.im>
2024-09-09 01:27:31 -03:00
Ulises Manuel cf5a395b29 feat(wallet): Show collectibles in activity tab (#21109)
* Add UNKNOWN collectible variant for context tags

* Show fallbacks for collectibles in the activity tab
2024-09-06 15:44:12 -06:00
Brian Sztamfater e2d597401c feat(swap): trigger swap transactions (#21134)
Signed-off-by: Brian Sztamfater <brian@status.im>
2024-09-06 11:30:15 -03:00
Mohsen 3ae500ff68 [#20909] fix: dapp wrong network refusal (#21123) 2024-09-06 15:56:04 +03:00
Parvesh Monu 779eadd787 Update Sign in flow on Profiles list screen (#21160) 2024-09-06 15:46:15 +05:30
Siddarth Kumar 110475a973 fixes for building universal APK (#21175)
In my previous PR -> https://github.com/status-im/status-mobile/pull/21163 I had attempted to build universalAPK only for release builds but had missed out on these changes.

This commit ensures `universalApk` value of `gradle` config is properly set.
This commit also points to my branch in `status-jenkins-lib` which modifies the APK size restrictions.
Its not `110 MB` for Universal APKs and `70 MB` for Individual Architecture APKs
2024-09-05 14:31:03 +05:30
Ulises Manuel 74469c2b61 feat(Activity Center): Add support for preview images in chat mentions (#21150)
* fix `clojure.core/type` shadowed

* Remove comment about rn/delay-render
2024-09-04 12:25:14 -06:00
Brian Sztamfater d5238ac82f feat: approve token transactions on swap (#21076)
Signed-off-by: Brian Sztamfater <brian@status.im>
2024-09-04 13:53:45 -03:00
Brian Sztamfater c861d95d69 feat(swap): fetch swap proposals (#21040)
Signed-off-by: Brian Sztamfater <brian@status.im>
2024-09-04 13:40:06 -03:00
225 changed files with 5006 additions and 3835 deletions
+1
View File
@@ -36,3 +36,4 @@ TEST_NETWORKS_ENABLED=1
SHOW_NOT_IMPLEMENTED_FEATURES=0
ENABLE_ALERT_BANNER=0
FLAG_WALLET_CONNECT_ENABLED=1
LOG_REQUEST_GO=1
+1
View File
@@ -40,3 +40,4 @@ DELETE_MESSAGE_UNDO_TIME_LIMIT=10000
ENABLE_ALERT_BANNER=0
FLAG_WALLET_CONNECT_ENABLED=1
MOBILE_DATA_SYNCING_TOGGLE_ENABLE=0
LOG_REQUEST_GO=1
+1
View File
@@ -37,3 +37,4 @@ FAST_CREATE_COMMUNITY_ENABLED=1
TEST_NETWORKS_ENABLED=1
ENABLE_ALERT_BANNER=1
FLAG_WALLET_CONNECT_ENABLED=1
LOG_REQUEST_GO=1
+1
View File
@@ -24,3 +24,4 @@ FAST_CREATE_COMMUNITY_ENABLED=0
TEST_NETWORKS_ENABLED=0
ENABLE_ALERT_BANNER=1
FLAG_WALLET_CONNECT_ENABLED=1
LOG_REQUEST_GO=0
+1
View File
@@ -21,3 +21,4 @@ FAST_CREATE_COMMUNITY_ENABLED=0
TEST_NETWORKS_ENABLED=0
STATUS_PROXY_STAGE_NAME=prod
FLAG_WALLET_CONNECT_ENABLED=1
LOG_REQUEST_GO=0
+1 -1
View File
@@ -1 +1 @@
2.29.0
2.30.0
+1 -1
View File
@@ -211,7 +211,7 @@ android {
reset()
enable getEnvOrConfig('ANDROID_ABI_SPLIT').toBoolean()
include getEnvOrConfig('ANDROID_ABI_INCLUDE').split(";")
universalApk false
universalApk getEnvOrConfig('ORG_GRADLE_PROJECT_universalApk').toBoolean()
}
}
signingConfigs {
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env groovy
library 'status-jenkins-lib@v1.9.8'
library 'status-jenkins-lib@v1.9.10'
/* Options section can't access functions in objects. */
def isPRBuild = utils.isPRBuild()
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env groovy
library 'status-jenkins-lib@v1.9.8'
library 'status-jenkins-lib@v1.9.10'
import groovy.json.JsonBuilder
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env groovy
library 'status-jenkins-lib@v1.9.8'
library 'status-jenkins-lib@v1.9.10'
pipeline {
agent { label 'linux' }
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env groovy
library 'status-jenkins-lib@v1.9.8'
library 'status-jenkins-lib@v1.9.10'
/* Options section can't access functions in objects. */
def isPRBuild = utils.isPRBuild()
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env groovy
library 'status-jenkins-lib@v1.9.8'
library 'status-jenkins-lib@v1.9.10'
/* Options section can't access functions in objects. */
def isPRBuild = utils.isPRBuild()
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env groovy
library 'status-jenkins-lib@v1.9.8'
library 'status-jenkins-lib@v1.9.10'
pipeline {
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env groovy
library 'status-jenkins-lib@v1.9.8'
library 'status-jenkins-lib@v1.9.10'
pipeline {
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env groovy
library 'status-jenkins-lib@v1.9.8'
library 'status-jenkins-lib@v1.9.10'
pipeline {
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env groovy
library 'status-jenkins-lib@v1.9.8'
library 'status-jenkins-lib@v1.9.10'
pipeline {
agent { label 'macos' }
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env groovy
library 'status-jenkins-lib@v1.9.8'
library 'status-jenkins-lib@v1.9.10'
pipeline {
agent { label params.AGENT_LABEL }
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env groovy
library 'status-jenkins-lib@v1.9.8'
library 'status-jenkins-lib@v1.9.10'
pipeline {
agent { label 'linux' }
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env groovy
library 'status-jenkins-lib@v1.9.8'
library 'status-jenkins-lib@v1.9.10'
pipeline {
agent {
@@ -25,13 +25,18 @@ class LogManager(private val reactContext: ReactApplicationContext) : ReactConte
override fun getName() = "LogManager"
private fun getLogsFile(): File {
private fun getRequestLogFile(): File {
val pubDirectory = utils.getPublicStorageDirectory()
return File(pubDirectory, requestsLogFileName)
}
private fun getGethLogFile(): File {
val pubDirectory = utils.getPublicStorageDirectory()
return File(pubDirectory, gethLogFileName)
}
fun prepareLogsFile(context: Context): File? {
val logFile = utils.getLogsFile()
val logFile = getGethLogFile()
try {
logFile.setReadable(true)
@@ -149,7 +154,8 @@ class LogManager(private val reactContext: ReactApplicationContext) : ReactConte
val zipFile = File(logsTempDir, logsZipFileName)
val statusLogFile = File(logsTempDir, statusLogFileName)
val gethLogFile = getLogsFile()
val gethLogFile = getGethLogFile()
val requestLogFile = getRequestLogFile()
try {
if (zipFile.exists() || zipFile.createNewFile()) {
@@ -165,7 +171,11 @@ class LogManager(private val reactContext: ReactApplicationContext) : ReactConte
dumpAdbLogsTo(FileOutputStream(statusLogFile))
val errorList = Stack<String>()
val zipped = zip(arrayOf(dbFile, gethLogFile, statusLogFile), zipFile, errorList)
val filesToZip = mutableListOf(dbFile, gethLogFile, statusLogFile)
if (requestLogFile.exists()) {
filesToZip.add(requestLogFile)
}
val zipped = zip(filesToZip.toTypedArray(), zipFile, errorList)
if (zipped && zipFile.exists()) {
zipFile.setReadable(true, false)
val extUri = FileProvider.getUriForFile(context, "${context.packageName}.provider", zipFile)
@@ -186,12 +196,14 @@ class LogManager(private val reactContext: ReactApplicationContext) : ReactConte
}
@ReactMethod
fun initLogging(enabled: Boolean, mobileSystem: Boolean, logLevel: String, callback: Callback) {
fun initLogging(enabled: Boolean, mobileSystem: Boolean, logLevel: String, logRequestGo: Boolean, callback: Callback) {
val jsonConfig = JSONObject().apply {
put("Enabled", enabled)
put("MobileSystem", mobileSystem)
put("Level", logLevel)
put("File", getLogsFile().absolutePath)
put("File", getGethLogFile().absolutePath)
put("LogRequestGo", logRequestGo)
put("LogRequestFile", getRequestLogFile().absolutePath)
}
val config = jsonConfig.toString()
utils.executeRunnableStatusGoMethod({ Statusgo.initLogging(config) }, callback)
@@ -206,6 +218,7 @@ class LogManager(private val reactContext: ReactApplicationContext) : ReactConte
private const val TAG = "LogManager"
private const val gethLogFileName = "geth.log"
private const val statusLogFileName = "Status.log"
private const val requestsLogFileName = "requests.log"
private const val logsZipFileName = "Status-debug-logs.zip"
}
}
@@ -17,7 +17,6 @@ import statusgo.Statusgo
class Utils(private val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
companion object {
private const val gethLogFileName = "geth.log"
private const val TAG = "Utils"
}
@@ -40,11 +39,6 @@ class Utils(private val reactContext: ReactApplicationContext) : ReactContextBas
return reactContext.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)
}
fun getLogsFile(): File {
val pubDirectory = getPublicStorageDirectory()
return File(pubDirectory, gethLogFileName)
}
fun getKeyUID(json: String): String {
val jsonObj = JSONObject(json)
return jsonObj.getString("key-uid")
@@ -57,6 +57,8 @@ RCT_EXPORT_METHOD(sendLogs:(NSString *)dbJson
NSURL *mainGethLogsFile = [rootUrl URLByAppendingPathComponent:@"geth.log"];
NSURL *mainLogsFile = [logsFolderName URLByAppendingPathComponent:@"geth.log"];
NSURL *requestsLogFile = [rootUrl URLByAppendingPathComponent:@"requests.log"];
[dbJson writeToFile:dbFile.path atomically:YES encoding:NSUTF8StringEncoding error:nil];
[jsLogs writeToFile:jsLogsFile.path atomically:YES encoding:NSUTF8StringEncoding error:nil];
@@ -65,6 +67,10 @@ RCT_EXPORT_METHOD(sendLogs:(NSString *)dbJson
[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];
if ([fileManager fileExistsAtPath:requestsLogFile.path]) {
[fileManager copyItemAtPath:requestsLogFile.path toPath:[logsFolderName URLByAppendingPathComponent:@"requests.log"].path error:nil];
}
[SSZipArchive createZipFileAtPath:zipFile.path withContentsOfDirectory:logsFolderName.path];
[fileManager removeItemAtPath:logsFolderName.path error:nil];
@@ -75,17 +81,20 @@ RCT_EXPORT_METHOD(sendLogs:(NSString *)dbJson
RCT_EXPORT_METHOD(initLogging:(BOOL)enabled
mobileSystem:(BOOL)mobileSystem
logLevel:(NSString *)logLevel
logRequestGo:(BOOL)logRequestGo
callback:(RCTResponseSenderBlock)callback)
{
NSString *logDirectory = [self logFileDirectory];
NSString *logFilePath = [logDirectory stringByAppendingPathComponent:@"geth.log"];
NSString *logRequestFilePath = [logDirectory stringByAppendingPathComponent:@"requests.log"];
NSMutableDictionary *jsonConfig = [NSMutableDictionary dictionary];
jsonConfig[@"Enabled"] = @(enabled);
jsonConfig[@"MobileSystem"] = @(mobileSystem);
jsonConfig[@"Level"] = logLevel;
jsonConfig[@"File"] = logFilePath;
jsonConfig[@"LogRequestGo"] = @(logRequestGo);
jsonConfig[@"LogRequestFile"] = logRequestFilePath;
NSError *error = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:jsonConfig options:0 error:&error];
+2
View File
@@ -23,6 +23,7 @@
# Android architectures to build for
# Used to detect end-to-end builds
androidAbiInclude ? lib.getEnvWithDefault "ANDROID_ABI_INCLUDE" "arm64-v8a",
universalApk ? lib.getEnvWithDefault "ORG_GRADLE_PROJECT_universalApk" "false"
}:
let
@@ -89,6 +90,7 @@ in stdenv.mkDerivation rec {
ORG_GRADLE_PROJECT_commitHash = commitHash;
ORG_GRADLE_PROJECT_buildUrl = buildUrl;
ORG_GRADLE_PROJECT_hermesEnabled = hermesEnabled;
ORG_GRADLE_PROJECT_universalApk = universalApk;
# Fix for ERR_OSSL_EVP_UNSUPPORTED error.
NODE_OPTIONS = "--openssl-legacy-provider";
Binary file not shown.

After

Width:  |  Height:  |  Size: 722 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 424 KiB

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 752 KiB

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 547 KiB

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 306 KiB

+1 -1
View File
@@ -29,7 +29,7 @@ identify_git_sha() {
STATUS_GO_MATCHING_REFS=$(git ls-remote ${REPO_URL} ${1})
# It's possible that there's both a branch and a tag matching the given version
STATUS_GO_TAG_SHA1=$(echo "${STATUS_GO_MATCHING_REFS}" | grep 'refs/tags' | cut -f1)
STATUS_GO_TAG_SHA1=$(echo "${STATUS_GO_MATCHING_REFS}" | grep "refs/tags/${1}" | cut -f1)
STATUS_GO_BRANCH_SHA1=$(echo "${STATUS_GO_MATCHING_REFS}" | grep 'refs/heads' | cut -f1)
# Prefer tag over branch if both are found
+36 -44
View File
@@ -1,8 +1,7 @@
(ns legacy.status-im.chat.models.mentions
(:require
[clojure.set :as set]
[taoensso.timbre :as log]
[utils.re-frame :as rf]))
(:require [clojure.set :as set]
[taoensso.timbre :as log]
[utils.re-frame :as rf]))
(defn- transfer-input-segments
[segments]
@@ -59,18 +58,20 @@
(defn- transfer-mention-result
[result]
(let [{:keys [input-segments mentionable-users state chat-id new-text]}
(let [{:keys [input-segments mentionable-users state chat-id new-text call-id]}
(set/rename-keys result
{:InputSegments :input-segments
:MentionSuggestions :mentionable-users
:MentionState :state
:ChatID :chat-id
:NewText :new-text})]
:NewText :new-text
:CallID :call-id})]
{:chat-id chat-id
:input-segments (transfer-input-segments input-segments)
:mentionable-users (rename-mentionable-users mentionable-users)
:state (rename-state state)
:new-text new-text}))
:new-text new-text
:call-id call-id}))
(rf/defn on-error
{:events [:mention/on-error]}
@@ -91,23 +92,25 @@
:on-error #(rf/dispatch [:mention/on-error
{:method method
:params params} %])}]}))
(rf/defn on-to-input-field-success
{:events [:mention/on-to-input-field-success]}
[{:keys [db]} result]
(log/debug "[mentions] on-to-input-field-success" {:result result})
(let [{:keys [input-segments state chat-id new-text]} (transfer-mention-result result)]
{:db (-> db
(assoc-in [:chats/mentions chat-id :mentions] state)
(assoc-in [:chat/inputs-with-mentions chat-id] input-segments))}))
(let [{:keys [chat-id new-text]} (transfer-mention-result result)]
{:effects/set-input-text-value [(get-in db [:chat/inputs chat-id :input-ref]) new-text]
:dispatch [:chat.ui/set-chat-input-text new-text chat-id]}))
(rf/defn on-change-text
{:events [:mention/on-change-text]}
[{:keys [db]} text]
(let [chat-id (:current-chat-id db)
params [chat-id text]
call-id (inc (get-in db [:chat/inputs chat-id :current-mentions-call-id] 0))
params [chat-id text call-id]
method "wakuext_chatMentionOnChangeText"]
(log/debug "[mentions] on-change-text" {:params params})
{:json-rpc/call [{:method method
{:db (assoc-in db [:chat/inputs chat-id :current-mentions-call-id] call-id)
:json-rpc/call [{:method method
:params params
:on-success #(rf/dispatch [:mention/on-change-text-success %])
:on-error #(rf/dispatch [:mention/on-error
@@ -118,49 +121,38 @@
{:events [:mention/on-change-text-success]}
[{:keys [db]} result]
(log/debug "[mentions] on-change-text-success" {:result result})
(let [{:keys [state chat-id mentionable-users input-segments]} (transfer-mention-result result)]
{:db (-> db
(assoc-in [:chats/mention-suggestions chat-id] mentionable-users)
(assoc-in [:chats/mentions chat-id :mentions] state)
(assoc-in [:chat/inputs-with-mentions chat-id] input-segments))}))
(let [{:keys [state chat-id mentionable-users
input-segments call-id]} (transfer-mention-result result)
current-mentions-call-id (get-in db [:chat/inputs chat-id :current-mentions-call-id])]
(when (= call-id current-mentions-call-id)
{:db (-> db
(assoc-in [:chat/inputs-with-mentions chat-id] input-segments)
(assoc-in [:chats/mention-suggestions chat-id] mentionable-users)
(assoc-in [:chats/mentions chat-id :mentions] state))})))
(rf/defn on-select-mention-success
{:events [:mention/on-select-mention-success]}
[{:keys [db] :as cofx} result primary-name match searched-text public-key]
[{:keys [db]} result primary-name match searched-text public-key]
(log/debug "[mentions] on-select-mention-success"
{:result result
:primary-name primary-name
:match match
:searched-text searched-text
:public-key public-key})
(let [{:keys [new-text chat-id state input-segments]} (transfer-mention-result result)]
{:db (-> db
(assoc-in [:chats/mentions chat-id :mentions] state)
(assoc-in [:chat/inputs-with-mentions chat-id] input-segments)
(assoc-in [:chats/mention-suggestions chat-id] nil))
:dispatch [:chat.ui/set-chat-input-text new-text chat-id]}))
(rf/defn clear-suggestions
[{:keys [db]}]
(log/debug "[mentions] clear suggestions")
(let [chat-id (:current-chat-id db)]
{:db (update db :chats/mention-suggestions dissoc chat-id)}))
(let [{:keys [new-text chat-id]} (transfer-mention-result result)]
{:db (assoc-in db [:chats/mention-suggestions chat-id] nil)
:effects/set-input-text-value [(get-in db [:chat/inputs (:current-chat-id db) :input-ref]) new-text]
:dispatch [:chat.ui/set-chat-input-text new-text chat-id]}))
(rf/defn clear-mentions
[{:keys [db] :as cofx}]
(log/debug "[mentions] clear mentions")
[{:keys [db]}]
(let [chat-id (:current-chat-id db)]
(rf/merge
cofx
{:db (-> db
(update-in [:chats/mentions chat-id] dissoc :mentions)
(update :chat/inputs-with-mentions dissoc chat-id))
:json-rpc/call [{:method "wakuext_chatMentionClearMentions"
:params [chat-id]
:on-success #()
:on-error #(log/error "Error while calling wakuext_chatMentionClearMentions"
{:error %})}]}
(clear-suggestions))))
{:db (update db :chats/mention-suggestions dissoc chat-id)
:json-rpc/call [{:method "wakuext_chatMentionClearMentions"
:params [chat-id]
:on-success #()
:on-error #(log/error "Error while calling wakuext_chatMentionClearMentions"
{:error %})}]}))
(rf/defn select-mention
{:events [:chat.ui/select-mention]}
@@ -62,6 +62,7 @@
:chatId :chat-id
:contactVerificationStatus :contact-verification-status
:communityId :community-id
:installationId :installation-id
:membershipStatus :membership-status
:albumMessages :album-messages})
(update :last-message #(when % (messages/<-rpc %)))
@@ -117,20 +117,24 @@
(deftest parse-notification-counts-response-test
(is
(= {notification-types/one-to-one-chat 15
notification-types/private-group-chat 16
notification-types/mention 17
notification-types/reply 18
notification-types/contact-request 19
notification-types/admin 20
notification-types/contact-verification 21}
(= {notification-types/one-to-one-chat 15
notification-types/private-group-chat 16
notification-types/mention 17
notification-types/reply 18
notification-types/contact-request 19
notification-types/admin 20
notification-types/contact-verification 21
notification-types/new-installation-received 22
notification-types/new-installation-created 23}
(store/parse-notification-counts-response
{(keyword (str notification-types/one-to-one-chat)) 15
(keyword (str notification-types/private-group-chat)) 16
(keyword (str notification-types/mention)) 17
(keyword (str notification-types/reply)) 18
(keyword (str notification-types/contact-request)) 19
(keyword (str notification-types/admin)) 20
(keyword (str notification-types/contact-verification)) 21
{(keyword (str notification-types/one-to-one-chat)) 15
(keyword (str notification-types/private-group-chat)) 16
(keyword (str notification-types/mention)) 17
(keyword (str notification-types/reply)) 18
(keyword (str notification-types/contact-request)) 19
(keyword (str notification-types/admin)) 20
(keyword (str notification-types/contact-verification)) 21
(keyword (str notification-types/new-installation-received)) 22
(keyword (str notification-types/new-installation-created)) 23
;; Unsupported type in the response is ignored
:999 100}))))
:999 100}))))
+11
View File
@@ -57,6 +57,11 @@
(fn [options]
(permissions/request-permissions options)))
(re-frame/reg-fx
:request-notifications-fx
(fn [options]
(permissions/request-notifications options)))
(re-frame/reg-fx
:ui/show-error
(fn [content]
@@ -164,6 +169,12 @@
[_ options]
{:request-permissions-fx options})
(rf/reg-event-fx
:request-notifications
(fn [_ [options]]
(prn options)
{:request-notifications-fx options}))
(rf/defn update-window-dimensions
{:events [:update-window-dimensions]}
[{:keys [db]} dimensions]
@@ -13,17 +13,19 @@
(native-module/logout)))
(rf/defn initialize-app-db
[{{:keys [keycard initials-avatar-font-file biometrics]
:network/keys [type status expensive?]}
[{{:keys [keycard initials-avatar-font-file biometrics]
:network/keys [type status expensive?]
:centralized-metrics/keys [user-confirmed?]}
:db}]
{:db (assoc db/app-db
:network/type type
:network/status status
:network/expensive? expensive?
:initials-avatar-font-file initials-avatar-font-file
:keycard (dissoc keycard :secrets :pin :application-info)
:biometrics biometrics
:syncing nil)})
:centralized-metrics/user-confirmed? user-confirmed?
:network/type type
:network/status status
:network/expensive? expensive?
:initials-avatar-font-file initials-avatar-font-file
:keycard (dissoc keycard :secrets :pin :application-info)
:biometrics biometrics
:syncing nil)})
(rf/defn logout-method
{:events [::logout-method]}
+17
View File
@@ -243,6 +243,23 @@
{}
installations))})
(rf/defn finish-seed-phrase-fallback-syncing
{:events [:pairing/finish-seed-phrase-fallback-syncing]}
[{:keys [db]}]
{:fx [[:json-rpc/call
[{:method "wakuext_enableInstallationAndPair"
:params [{:installationId (:syncing/installation-id db)}]
:js-response true
:on-success [:sanitize-messages-and-process-response]}]]]})
(rf/defn pair-and-sync
{:events [:pairing/pair-and-sync]}
[cofx installation-id]
{:fx [[:json-rpc/call
[{:method "wakuext_enableInstallationAndSync"
:params [{:installationId installation-id}]
:on-success #(log/debug "successfully synced devices")}]]]})
(rf/defn enable-installation-success
{:events [:pairing.callback/enable-installation-success]}
[cofx installation-id]
@@ -5,7 +5,6 @@
[legacy.status-im.ui.components.list.views :as list]
[quo.core :as quo]
[re-frame.core :as re-frame]
[status-im.feature-flags :as ff]
[utils.i18n :as i18n]
[utils.re-frame :as rf])
(:require-macros [legacy.status-im.utils.views :as views]))
@@ -17,7 +16,6 @@
(defn- normal-mode-settings-data
[{:keys [current-log-level
telemetry-enabled?
light-client-enabled?
store-confirmations-enabled?
current-fleet
@@ -60,14 +58,6 @@
:on-press
#(re-frame/dispatch [:open-modal :peers-stats])
:chevron true}
(when (ff/enabled? ::ff/settings.telemetry)
{:size :small
:title "Telemetry"
:accessibility-label :telemetry-enabled
:container-margin-bottom 8
:on-press #(re-frame/dispatch [:profile.settings/toggle-telemetry])
:accessory :switch
:active telemetry-enabled?})
{:size :small
:title (i18n/label :t/light-client-enabled)
:accessibility-label :light-client-enabled
@@ -109,7 +99,6 @@
[]
(views/letsubs [light-client-enabled? [:profile/light-client-enabled?]
store-confirmations-enabled? [:profile/store-confirmations-enabled?]
telemetry-enabled? [:profile/telemetry-enabled?]
current-log-level [:log-level/current-log-level]
current-fleet [:fleets/current-fleet]
peer-syncing-enabled? [:profile/peer-syncing-enabled?]]
@@ -123,7 +112,6 @@
[list/flat-list
{:data (flat-list-data
{:current-log-level current-log-level
:telemetry-enabled? telemetry-enabled?
:light-client-enabled? light-client-enabled?
:store-confirmations-enabled? store-confirmations-enabled?
:current-fleet current-fleet
+2 -2
View File
@@ -531,8 +531,8 @@
(.logFileDirectory ^js (log-manager)))
(defn init-status-go-logging
[{:keys [enable? mobile-system? log-level callback]}]
(.initLogging ^js (log-manager) enable? mobile-system? log-level callback))
[{:keys [enable? mobile-system? log-level log-request-go? callback]}]
(.initLogging ^js (log-manager) enable? mobile-system? log-level log-request-go? callback))
(defn get-random-mnemonic
[callback]
@@ -7,6 +7,7 @@
[:map {:closed true}
[:action? {:optional true} [:maybe boolean?]]
[:text {:optional true} [:maybe string?]]
[:container-style {:optional true} [:maybe :map]]
[:button-text {:optional true} [:maybe string?]]
[:on-button-press {:optional true} [:maybe fn?]]]]]
:any])
@@ -1,12 +1,15 @@
(ns quo.components.banners.alert-banner.style
(:require [quo.foundations.colors :as colors]))
(def container
{:flex-direction :row
:align-items :center
:height 50
:padding-horizontal 20
:padding-vertical 12})
(defn container
[container-style]
(merge
{:flex-direction :row
:align-items :center
:height 50
:padding-horizontal 20
:padding-vertical 12}
container-style))
(defn label
[theme]
@@ -11,12 +11,12 @@
[schema.core :as schema]))
(defn- view-internal
[{:keys [action? text button-text on-button-press]}]
[{:keys [action? text button-text container-style on-button-press]}]
(let [theme (quo.theme/use-theme)]
[rn/view
{:accessibility-label :alert-banner}
[linear-gradient/linear-gradient
{:style style/container
{:style (style/container container-style)
:start {:x 0 :y 0}
:end {:x 0 :y 1}
:colors [(colors/theme-colors
@@ -81,12 +81,20 @@
:unread-mentions-count unread-mentions-count
:unread-messages? unread-messages?}])]]]]))
(defn- title-color
[unread-messages? muted theme]
(cond
muted (colors/theme-colors colors/neutral-40 colors/neutral-60 theme)
unread-messages? (colors/theme-colors colors/neutral-100 colors/white theme)
:else (colors/theme-colors colors/neutral-50 colors/neutral-40 theme)))
(defn communities-membership-list-item
[{:keys [customization-color] :as props}
bottom-sheet?
{:keys [name muted unviewed-messages-count unviewed-mentions-count status
images tokens locked? style]}]
(let [theme (quo.theme/use-theme)]
(let [theme (quo.theme/use-theme)
unread-messages? (pos? unviewed-messages-count)]
[rn/touchable-highlight
(merge {:underlay-color (colors/theme-colors
colors/neutral-5
@@ -108,11 +116,7 @@
:ellipsize-mode :tail
:weight :semi-bold
:size :paragraph-1
:style (when muted
{:color (colors/theme-colors
colors/neutral-40
colors/neutral-60
theme)})}
:style {:color (title-color unread-messages? muted theme)}}
name]]
[rn/view
@@ -128,4 +132,4 @@
:customization-color customization-color
:muted? muted
:unread-mentions-count unviewed-mentions-count
:unread-messages? (pos? unviewed-messages-count)}])]]]))
:unread-messages? unread-messages?}])]]]))
+4 -1
View File
@@ -46,11 +46,14 @@
(let [theme (quo.theme/use-theme)]
[rn/view {:style (style/container container-style)}
[rn/view {:style style/index}
(if (= type :step)
(case type
:step
[step/view
{:in-blur-view? blur?
:customization-color customization-color
:type (if customization-color :complete :neutral)} step-number]
:lock
[icon/icon :i/locked {:color (get-colors theme blur?)}]
[icon/icon :i/bullet {:color (get-colors theme blur?)}])]
[rn/view {:style style/text-container}
(when title
@@ -126,7 +126,7 @@
[:subtitle-type {:optional true} [:maybe [:enum :default :icon :network :account :editable]]]
[:size {:optional true} [:maybe [:enum :default :small :large]]]
[:title :string]
[:subtitle {:optional true} [:maybe :string]]
[:subtitle {:optional true} [:maybe [:or :string :double]]]
[:custom-subtitle {:optional true} [:maybe fn?]]
[:icon {:optional true} [:maybe :keyword]]
[:emoji {:optional true} [:maybe :string]]
@@ -5,9 +5,7 @@
(def container
{:padding-horizontal 12
:padding-top 12
:padding-bottom 14
:flex-direction :row
:justify-content :space-between})
:padding-bottom 14})
(defn left-sub-container
[{:keys [tag description]}]
@@ -108,20 +108,25 @@
nil)])
(defn view
[{:keys [title on-press action-props accessibility-label blur? container-style] :as props}]
[{:keys [title on-press action-props accessibility-label blur? container-style content] :as props}]
[rn/pressable
{:style (merge style/container container-style)
:on-press (or on-press (:on-change action-props))
:accessibility-label accessibility-label}
[rn/view {:style (style/left-sub-container props)}
[image-component props]
[rn/view {:style (style/left-container (:image props))}
[text/text
{:weight :medium
:style {:color (when blur? colors/white)}}
title]
[description-component props]
[tag-component props]]]
[rn/view {:style (style/sub-container (:alignment action-props))}
[label-component props]
[action-component props]]])
[rn/view
{:style {:flex-direction :row
:justify-content :space-between}}
[rn/view {:style (style/left-sub-container props)}
[image-component props]
[rn/view {:style (style/left-container (:image props))}
[text/text
{:weight :medium
:style {:color (when blur? colors/white)}}
title]
[description-component props]
[tag-component props]]]
[rn/view {:style (style/sub-container (:alignment action-props))}
[label-component props]
[action-component props]]]
(when content
content)])
@@ -60,8 +60,12 @@
shrinkable? (assoc :flex-shrink 1)))
(defn text
[theme]
{:color (colors/theme-colors colors/neutral-100 colors/white theme)})
([theme]
(text theme false))
([theme gray-text?]
{:color (if gray-text?
colors/neutral-50
(colors/theme-colors colors/neutral-100 colors/white theme))}))
(defn token-logo
[size]
+94 -77
View File
@@ -1,5 +1,6 @@
(ns quo.components.tags.context-tag.view
(:require
[clojure.string :as string]
[quo.components.avatars.account-avatar.view :as account-avatar]
[quo.components.avatars.group-avatar.view :as group-avatar]
[quo.components.avatars.user-avatar.view :as user-avatar]
@@ -17,15 +18,16 @@
[schema.core :as schema]))
(defn- tag-skeleton
[{:keys [size text theme shrinkable?]
:or {size 24
theme (quo.theme/use-theme)}}
[{:keys [size text theme shrinkable? gray-text?]
:or {size 24
gray-text? false
theme (quo.theme/use-theme)}}
logo-component]
[rn/view {:style (style/tag-container size)}
logo-component
[rn/view {:style (style/tag-spacing size shrinkable?)}
[text/text
{:style (style/text theme)
{:style (style/text theme gray-text?)
:weight :medium
:size (if (= size 24) :paragraph-2 :paragraph-1)
:number-of-lines 1
@@ -94,92 +96,107 @@
state :default
theme (quo.theme/use-theme)}
:as props}]
[rn/view {:style (merge {:align-items :flex-start} container-style)}
[rn/view
{:style (style/container {:theme theme
:type type
:size size
:state state
:blur? blur?
:customization-color customization-color})
:accessibility-label :context-tag}
(case type
:default
[tag-skeleton {:theme theme :size size :text full-name}
[user-avatar/user-avatar
{:full-name full-name
:profile-picture profile-picture
:size (if (= size 24) :xxs 28)
:status-indicator? false
:ring? false
:customization-color customization-color}]]
(let [[image-error? set-image-error] (rn/use-state false)]
[rn/view {:style (merge {:align-items :flex-start} container-style)}
[rn/view
{:style (style/container {:theme theme
:type type
:size size
:state state
:blur? blur?
:customization-color customization-color})
:accessibility-label :context-tag}
(case type
:default
[tag-skeleton {:theme theme :size size :text full-name}
[user-avatar/user-avatar
{:full-name full-name
:profile-picture profile-picture
:size (if (= size 24) :xxs 28)
:status-indicator? false
:ring? false
:customization-color customization-color}]]
:multiuser
[preview-list/view {:type :user :size :size-20}
users]
:multiuser
[preview-list/view {:type :user :size :size-20}
users]
:multinetwork
[preview-list/view {:type :network :size :size-20}
networks]
:multinetwork
[preview-list/view {:type :network :size :size-20}
networks]
:audio
[tag-skeleton {:theme theme :text (str duration)}
[rn/view {:style (style/audio-tag-icon-container customization-color theme)}
[icons/icon :i/play {:color style/audio-tag-icon-color :size 12}]]]
:audio
[tag-skeleton {:theme theme :text (str duration)}
[rn/view {:style (style/audio-tag-icon-container customization-color theme)}
[icons/icon :i/play {:color style/audio-tag-icon-color :size 12}]]]
:group
[tag-skeleton {:theme theme :size size :text group-name}
[group-avatar/view
{:icon-name :i/members
:size (if (= size 24) :size-20 :size-28)
:customization-color (colors/custom-color customization-color 50)}]]
:group
[tag-skeleton {:theme theme :size size :text group-name}
[group-avatar/view
{:icon-name :i/members
:size (if (= size 24) :size-20 :size-28)
:customization-color (colors/custom-color customization-color 50)}]]
(:channel :community)
[communities-tag (assoc props :channel? (= type :channel))]
(:channel :community)
[communities-tag (assoc props :channel? (= type :channel))]
:token
[tag-skeleton {:theme theme :size size :text (str amount " " token)}
[token/view
{:style (style/token-logo size)
:token token
:size (if (= size 24) :size-20 :size-28)}]]
:token
[tag-skeleton {:theme theme :size size :text (str amount " " token)}
[token/view
{:style (style/token-logo size)
:token token
:size (if (= size 24) :size-20 :size-28)}]]
:network
[tag-skeleton {:theme theme :size size :text network-name}
[rn/image {:style (style/circle-logo size) :source network-logo}]]
:network
[tag-skeleton {:theme theme :size size :text network-name}
[rn/image {:style (style/circle-logo size) :source network-logo}]]
:collectible
[tag-skeleton
{:theme theme
:size size
:text (str collectible-name " #" collectible-number)
:shrinkable? true}
[rn/image {:style (style/rounded-logo size) :source collectible}]]
:collectible
(let [gray-text? (string/blank? collectible-name)
collectible-text (cond
gray-text? "UNKNOWN"
collectible-number (str collectible-name
" #"
collectible-number)
:else collectible-name)
nft-placeholder? (or image-error? (string/blank? collectible))]
[tag-skeleton
{:theme theme
:size size
:text collectible-text
:shrinkable? true
:gray-text? gray-text?}
(if nft-placeholder?
[icons/icon :i/nft {:size 20}]
[rn/image
{:style (style/rounded-logo size)
:source collectible
:on-error #(set-image-error true)}])])
:account
[tag-skeleton {:theme theme :size size :text account-name}
[account-avatar/view
{:customization-color customization-color
:emoji emoji
:size (if (= size 24) 20 28)}]]
:account
[tag-skeleton {:theme theme :size size :text account-name}
[account-avatar/view
{:customization-color customization-color
:emoji emoji
:size (if (= size 24) 20 28)}]]
:address
[address-tag props]
:address
[address-tag props]
:icon
[icon-tag props]
:icon
[icon-tag props]
:wallet-user
[tag-skeleton {:theme theme :size size :text full-name}
[wallet-user-avatar/wallet-user-avatar
{:full-name full-name
:size (if (= size 24) :size-20 :size-24)
:customization-color customization-color}]]
:wallet-user
[tag-skeleton {:theme theme :size size :text full-name}
[wallet-user-avatar/wallet-user-avatar
{:full-name full-name
:size (if (= size 24) :size-20 :size-24)
:customization-color customization-color}]]
:dapp
[tag-skeleton {:theme theme :size size :text dapp-name}
[rn/image {:style (style/circle-logo size) :source dapp-logo}]]
:dapp
[tag-skeleton {:theme theme :size size :text dapp-name}
[rn/image {:style (style/circle-logo size) :source dapp-logo}]]
nil)]])
nil)]]))
(def view (schema/instrument #'view-internal component-schema/?schema))
@@ -9,6 +9,7 @@
[:token-value :string]
[:token-symbol :string]
[:container-style {:optional true} [:maybe :map]]
[:show-view-button? {:optional true} [:maybe :boolean]]
[:button-props {:optional true} [:maybe :map]]
[:customization-color {:optional true} [:maybe :schema.common/customization-color]]]]]
:any])
@@ -24,7 +24,7 @@
(defn- view-internal
[{:keys [status token-value token-symbol
container-style button-props]
container-style button-props show-view-button?]
:as props}]
(let [theme (quo.theme/use-theme)
customization-color (or (:customization-color props) :blue)
@@ -56,7 +56,9 @@
(i18n/label (status-message status)
{:amount token-value
:symbol token-symbol})]]
(when button-props
(when (and button-props
(or (= status :approve)
(and (not= status :approve) show-view-button?)))
[button/button
(merge {:type (if (= status :approve) :primary :grey)
:background (when-not (= status :approve) :blur)
@@ -1,5 +1,6 @@
(ns quo.components.wallet.swap-input.style
(:require [quo.foundations.colors :as colors]
[quo.foundations.shadows :as shadows]
[quo.foundations.typography :as typography]))
(defn- border-color
@@ -11,11 +12,13 @@
(colors/theme-colors colors/neutral-5 colors/neutral-90 theme))
(defn content
[theme]
{:border-width 1
:border-radius 16
:border-color (border-color theme)
:background-color (colors/theme-colors colors/white colors/neutral-95 theme)})
[typing? theme]
(merge
{:border-width 1
:border-radius 16
:border-color (border-color theme)
:background-color (colors/theme-colors colors/white colors/neutral-95 theme)}
(when typing? (shadows/get 1 theme))))
(defn row-1
[loading?]
+11 -5
View File
@@ -19,13 +19,15 @@
[:props
[:map {:closed true}
[:type {:optional true} [:maybe [:enum :pay :receive]]]
[:status {:optional true} [:maybe [:enum :default :disabled :loading]]]
[:status {:optional true} [:maybe [:enum :default :typing :disabled :loading]]]
[:token {:optional true} [:maybe :string]]
[:value {:optional true} [:maybe :string]]
[:default-value {:optional true} [:maybe :string]]
[:currency-symbol {:optional true} [:maybe :string]]
[:fiat-value {:optional true} [:maybe :string]]
[:show-approval-label? {:optional true} [:maybe :boolean]]
[:auto-focus? {:optional true} [:maybe :boolean]]
[:input-disabled? {:optional true} [:maybe :boolean]]
[:error? {:optional true} [:maybe :boolean]]
[:show-keyboard? {:optional true} [:maybe :boolean]]
[:approval-label-props {:optional true} [:maybe approval-label.schema/?schema]]
@@ -33,6 +35,7 @@
[:on-change-text {:optional true} [:maybe fn?]]
[:enable-swap? {:optional true} [:maybe :boolean]]
[:on-swap-press {:optional true} [:maybe fn?]]
[:on-input-focus {:optional true} [:maybe fn?]]
[:on-token-press {:optional true} [:maybe fn?]]
[:on-max-press {:optional true} [:maybe fn?]]
[:customization-color {:optional true} [:maybe :schema.common/customization-color]]
@@ -41,13 +44,14 @@
(defn view-internal
[{:keys [type status token value fiat-value show-approval-label? error? network-tag-props
approval-label-props default-value enable-swap?
approval-label-props default-value auto-focus? input-disabled? enable-swap?
currency-symbol on-change-text show-keyboard?
container-style on-swap-press on-token-press on-max-press]}]
container-style on-swap-press on-token-press on-max-press on-input-focus]}]
(let [theme (quo.theme/use-theme)
pay? (= type :pay)
disabled? (= status :disabled)
loading? (= status :loading)
typing? (= status :typing)
controlled-input? (some? value)
input-ref (rn/use-ref-atom nil)
set-input-ref (rn/use-callback (fn [ref] (reset! input-ref ref)) [])
@@ -58,7 +62,7 @@
[rn/view
{:style container-style
:accessibility-label :swap-input}
[rn/view {:style (style/content theme)}
[rn/view {:style (style/content typing? theme)}
[rn/view
{:style (style/row-1 loading?)}
[rn/pressable {:on-press on-token-press}
@@ -78,7 +82,9 @@
colors/neutral-50
theme)
:keyboard-type :numeric
:auto-focus true
:editable (not input-disabled?)
:auto-focus auto-focus?
:on-focus on-input-focus
:on-change-text on-change-text
:show-soft-input-on-focus show-keyboard?
:default-value default-value
+22 -2
View File
@@ -1,7 +1,10 @@
(ns react-native.permissions
(:require
["react-native-permissions" :refer (check openSettings requestMultiple PERMISSIONS RESULTS)]
[react-native.platform :as platform]))
["react-native-permissions" :refer
[check openSettings PERMISSIONS requestMultiple requestNotifications
RESULTS]]
[react-native.platform :as platform]
[taoensso.timbre :as log]))
(def permissions-map
{:read-external-storage (cond
@@ -45,3 +48,20 @@
(.catch #(on-error %))))
(def open-settings openSettings)
(defn request-notifications
"`notification-options` is only used on iOS.
A map with `:status` and `settings` (only for iOS) is passed to the callbacks.
See https://github.com/zoontek/react-native-permissions?tab=readme-ov-file#requestnotifications."
[{:keys [notification-options on-allowed on-denied]
:or {notification-options #js ["alert"]}}]
(-> (requestNotifications notification-options)
(.then (fn [js-response]
(let [response (js->clj js-response :keywordize-keys true)]
(if (= (:status response) "granted")
(do
(on-allowed response)
(log/debug "Notification permission were allowed" response))
(do
(on-denied response)
(log/debug "Notification permission were denied" response))))))))
@@ -14,7 +14,11 @@
[state]
(:value state))
(defn numeric-value
(defn value-numeric
[state]
(or (parse-double (input-value state)) 0))
(defn value-bn
[state]
(money/bignumber (input-value state)))
@@ -27,28 +31,32 @@
(assoc state :error? error?))
(defn upper-limit
[state]
(:upper-limit state))
(defn upper-limit-bn
[state]
(money/bignumber (:upper-limit state)))
(defn lower-limit
[state]
(:lower-limit state))
(defn lower-limit-bn
[state]
(money/bignumber (:lower-limit state)))
(defn upper-limit-exceeded?
[state]
(let [num-value (numeric-value state)]
(and
(upper-limit state)
(when (money/bignumber? num-value)
(money/greater-than (numeric-value state) (upper-limit state))))))
(and (upper-limit state)
(when (money/bignumber? (value-bn state))
(money/greater-than (value-bn state) (upper-limit-bn state)))))
(defn- lower-limit-exceeded?
[state]
(let [num-value (numeric-value state)]
(and
(lower-limit state)
(when (money/bignumber? num-value)
(money/less-than (numeric-value state) (lower-limit state))))))
(and (lower-limit state)
(when (money/bignumber? (value-bn state))
(money/less-than (value-bn state) (lower-limit-bn state)))))
(defn- recheck-errorness
[state]
@@ -62,7 +70,7 @@
(assoc :value value)
recheck-errorness))
(defn set-numeric-value
(defn set-value-numeric
[state value]
(set-input-value state (str value)))
@@ -81,11 +89,11 @@
(defn increase
[state]
(set-input-value state (str (money/add (numeric-value state) 1))))
(set-input-value state (str (money/add (value-bn state) 1))))
(defn decrease
[state]
(set-input-value state (str (money/add (numeric-value state) -1))))
(set-input-value state (str (money/add (value-bn state) -1))))
(def ^:private not-digits-or-dot-pattern
#"[^0-9+\.]")
@@ -122,10 +130,12 @@
state))
(defn delete-last
[state]
(let [value (input-value state)
new-value (subs value 0 (dec (count value)))]
(set-input-value state new-value)))
([state]
(delete-last state ""))
([state default-value]
(let [value (input-value state)
new-value (subs value 0 (dec (count value)))]
(set-input-value state (if (string/blank? new-value) default-value new-value)))))
(defn delete-all
[state]
@@ -133,7 +143,7 @@
(defn empty-value?
[state]
(string/blank? (:value state)))
(or (string/blank? (:value state)) (<= (value-numeric state) 0)))
(defn- fiat->crypto
[value conversion-rate]
@@ -200,13 +200,13 @@
(.remove keyboard-hide-listener))))
(defn screen
[{:keys [initial-insets title keypair navigation-icon recovering-keypair? render-controls]}]
(let [{navigation-bar-top :top} initial-insets
banner-offset (rf/sub [:alert-banners/top-margin])]
[{:keys [title keypair navigation-icon recovering-keypair? render-controls]}]
(let [[insets _] (rn/use-state (safe-area/get-insets))
banner-offset (rf/sub [:alert-banners/top-margin])]
[rn/view {:style style/full-layout}
[rn/keyboard-avoiding-view {:style style/page-container}
[quo/page-nav
{:margin-top navigation-bar-top
{:margin-top (:top insets)
:background :blur
:icon-name (or navigation-icon
(if recovering-keypair? :i/close :i/arrow-left))
@@ -216,15 +216,14 @@
:keypair keypair
:render-controls render-controls
:banner-offset banner-offset
:initial-insets initial-insets
:initial-insets insets
:recovering-keypair? recovering-keypair?}]]]))
(defn view
[]
(let [insets (safe-area/get-insets)]
(fn []
(let [{:keys [recovering-keypair?]} (rf/sub [:get-screen-params])]
[screen
{:title (i18n/label :t/use-recovery-phrase)
:initial-insets insets
:recovering-keypair? recovering-keypair?}]))))
(let [{:keys [recovering-keypair?]} (rf/sub [:get-screen-params])]
(rn/use-unmount
#(rf/dispatch [:onboarding/clear-navigated-to-enter-seed-phrase-from-screen]))
[screen
{:title (i18n/label :t/use-recovery-phrase)
:recovering-keypair? recovering-keypair?}]))
+12
View File
@@ -2,6 +2,7 @@
(:require
[clojure.string :as string]
[native-module.core :as native-module]
[promesa.core :as promesa]
[re-frame.core :as re-frame]
[react-native.background-timer :as background-timer]
[taoensso.timbre :as log]
@@ -89,6 +90,17 @@
(rf/dispatch (conj on-success result request-id))
(on-success result request-id)))))))))))
(defn call-async
"Helper to handle RPC calls to status-go as promises"
[method js-response? & args]
(promesa/create
(fn [p-resolve p-reject]
(call {:method method
:params args
:on-success p-resolve
:on-error p-reject
:js-response js-response?}))))
(re-frame/reg-fx
:json-rpc/call
(fn [params]
+6 -4
View File
@@ -4,6 +4,7 @@
[clojure.string :as string]
[native-module.core :as native-module]
[re-frame.core :as re-frame]
[status-im.config :as config]
[taoensso.timbre :as log]
[utils.transforms :as transforms]))
@@ -24,10 +25,11 @@
(let [{:keys [error]} (transforms/json->clj res)]
(when-not (string/blank? error)
(log/error "init statusgo logging failed" error))))
logging-params {:enable? true
:mobile-system? false
:log-level level
:callback handle-error}]
logging-params {:enable? true
:mobile-system? false
:log-level level
:log-request-go? config/log-request-go
:callback handle-error}]
(log/merge-config! {:ns-filter {:allow #{"*"} :deny #{"taoensso.sente"}}})
(if (string/blank? level)
(native-module/init-status-go-logging (merge logging-params {:log-level "WARN"}))
@@ -17,14 +17,16 @@
[]
(rf/dispatch [:hide-bottom-sheet]))
(defn- toggle-metrics
[enabled?]
(rf/dispatch [:centralized-metrics/toggle-centralized-metrics enabled?]))
(def ^:private will-receive-for-current-points
[:t/number-of-messages-sent
:t/connected-peers
:t/successful-messages-rate
:t/connection-type
:t/os-app-version-bandwidth])
(def ^:private will-receive-points
[:t/ip-address
:t/universally-unique-identifiers-of-device
:t/logs-of-actions-withing-the-app])
(def ^:private will-receive-for-all-points
[:t/action-logs
:t/ip-addresses-uuid])
(def ^:private not-receive-points
[:t/your-profile-information
@@ -32,7 +34,7 @@
:t/information-you-input-and-send])
(defn- bullet-points
[{:keys [title points]}]
[{:keys [title points lock?]}]
[rn/view
[quo/text {:weight :semi-bold}
title]
@@ -41,37 +43,36 @@
[quo/markdown-list
{:description (i18n/label label)
:blur? true
:type (when lock? :lock)
:container-style style/item-text}])])
(defn- on-share-usage
[]
(toggle-metrics true)
(rf/dispatch [:centralized-metrics/toggle-centralized-metrics true true])
(hide-bottom-sheet))
(defn- on-do-not-share
[]
(toggle-metrics false)
(rf/dispatch [:centralized-metrics/toggle-centralized-metrics false true])
(hide-bottom-sheet))
(declare view)
(defn- on-privacy-policy-press
[settings?]
[]
(rf/dispatch
[:show-bottom-sheet
{:content (fn []
[quo.theme/provider :dark
[privacy/privacy-statement]])
{:content privacy/privacy-statement
:on-close (fn []
(rf/dispatch [:show-bottom-sheet
{:content (fn []
[quo.theme/provider :dark
[view {:settings? settings?}]])
{:content view
:theme :dark
:shell? true}]))
:theme :dark
:shell? true}]))
(defn- privacy-policy-text
[settings?]
[]
[rn/view {:style style/privacy-policy}
[quo/text
[quo/text
@@ -81,39 +82,38 @@
[quo/text
{:size :paragraph-2
:weight :bold
:on-press #(on-privacy-policy-press settings?)}
:on-press on-privacy-policy-press}
(i18n/label :t/more-details-in-privacy-policy-2)]]])
(defn view
[{:keys [settings?]}]
[]
(rn/use-mount #(dismiss-keyboard))
[:<>
[quo/drawer-top
{:title (i18n/label :t/help-us-improve-status)
:description (i18n/label :t/collecting-usage-data)}]
[rn/view {:style style/points-wrapper}
[bullet-points
{:title (i18n/label :t/what-we-will-receive)
:points will-receive-points}]
[bullet-points
{:title (i18n/label :t/what-we-wont-receive)
:points not-receive-points}]
(if settings?
[quo/text
{:size :paragraph-2
:style style/info-text}
(i18n/label :t/usage-data-shared-from-all-profiles)]
[quo/text
{:size :paragraph-2
:style style/info-text}
(i18n/label :t/usage-data-shared-from-all-profiles)
(i18n/label :t/sharing-usage-data-can-be-turned-off)])]
[rn/scroll-view
[rn/view {:style style/points-wrapper}
[bullet-points
{:title (i18n/label :t/we-will-receive-from-all-profiles)
:points will-receive-for-all-points}]
[bullet-points
{:title (i18n/label :t/we-will-receive-from-the-current-profile)
:points will-receive-for-current-points}]
[bullet-points
{:title (i18n/label :t/what-we-wont-receive)
:points not-receive-points
:lock? true}]
[quo/text
{:size :paragraph-2
:style style/info-text}
(i18n/label :t/sharing-usage-data-can-be-turned-off)]]]
[quo/bottom-actions
{:actions :two-actions
:blur? true
:button-one-label (i18n/label :t/share-usage-data)
:button-one-label (i18n/label :t/help-us-improve-status)
:button-one-props {:on-press on-share-usage}
:button-two-label (i18n/label :t/not-now)
:button-two-props {:type :grey
:on-press on-do-not-share}}]
[privacy-policy-text settings?]])
[privacy-policy-text]])
@@ -0,0 +1,23 @@
(ns status-im.common.new-device-sheet.style
(:require [quo.foundations.colors :as colors]))
(def heading
{:padding-left 20
:padding-bottom 8})
(def message
{:padding-horizontal 20
:padding-top 4})
(def warning
{:margin-horizontal 20
:margin-top 10})
(def drawer-container
{:padding-horizontal 13
:padding-top 16})
(def settings-subtext
{:color colors/white-opa-70
:align-self :center
:margin-bottom 12})
@@ -0,0 +1,75 @@
(ns status-im.common.new-device-sheet.view
(:require
[quo.core :as quo]
[react-native.core :as rn]
[status-im.common.events-helper :as events-helper]
[status-im.common.new-device-sheet.style :as style]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn- pair-and-sync
[installation-id]
(rf/dispatch [:pairing/pair-and-sync installation-id])
(events-helper/hide-bottom-sheet))
(defn installation-request-receiver-view
[installation-id]
(rn/use-mount events-helper/dismiss-keyboard)
[:<>
[quo/text
{:weight :semi-bold
:size :heading-2
:accessibility-label :new-device-sheet-heading
:style style/heading}
(i18n/label :t/pair-new-device-and-sync)]
[quo/text
{:weight :regular
:size :paragraph-1
:accessibility-label :new-device-sheet-message
:style style/message}
(i18n/label :t/new-device-detected-recovered-device-message)]
[quo/text
{:weight :semi-bold
:size :heading-2
:accessibility-label :new-device-installation-id
:style style/heading}
installation-id]
[quo/bottom-actions
{:actions :two-actions
:blur? true
:container-style {:margin-top 12}
:button-two-label (i18n/label :t/cancel)
:button-two-props {:type :grey
:on-press events-helper/hide-bottom-sheet}
:button-one-label (i18n/label :t/pair-and-sync)
:button-one-props {:on-press #(pair-and-sync installation-id)}}]])
(defn installation-request-creator-view
[installation-id]
(rn/use-mount events-helper/dismiss-keyboard)
[:<>
[quo/text
{:weight :semi-bold
:size :heading-2
:accessibility-label :new-device-sheet-heading
:style style/heading}
(i18n/label :t/pair-this-device-and-sync)]
[quo/text
{:weight :regular
:size :paragraph-1
:accessibility-label :new-device-sheet-message
:style style/message}
(i18n/label :t/new-device-detected-other-device-message)]
[quo/text
{:weight :semi-bold
:size :heading-2
:accessibility-label :new-device-installation-id
:style style/heading}
installation-id]
[quo/bottom-actions
{:actions :one-action
:blur? true
:container-style {:margin-top 12}
:button-one-label (i18n/label :t/close)
:button-one-props {:type :grey
:on-press events-helper/hide-bottom-sheet}}]])
+1
View File
@@ -28,6 +28,7 @@
:notifications (js/require "../resources/images/ui2/notifications.png")
:nfc-prompt (js/require "../resources/images/ui2/nfc-prompt.png")
:nfc-success (js/require "../resources/images/ui2/nfc-success.png")
:preparing-status (js/require "../resources/images/ui2/preparing-status.png")
:syncing-devices (js/require "../resources/images/ui2/syncing_devices.png")
:syncing-wrong (js/require "../resources/images/ui2/syncing_wrong.png")})
+1
View File
@@ -76,6 +76,7 @@
;; CONFIG VALUES
(def log-level (string/upper-case (get-config :LOG_LEVEL "")))
(def log-request-go (enabled? (get-config :LOG_REQUEST_GO "0")))
(def fleet (get-config :FLEET ""))
(def apn-topic (get-config :APN_TOPIC "im.status.ethereum"))
(def default-network (get-config :DEFAULT_NETWORK "goerli_rpc"))
+14 -1
View File
@@ -551,6 +551,7 @@
(def ^:const send-type-bridge 5)
(def ^:const send-type-erc-721-transfer 6)
(def ^:const send-type-erc-1155-transfer 7)
(def ^:const send-type-swap 8)
(def ^:const multi-transaction-type-send 0)
(def ^:const multi-transaction-type-approve 1)
@@ -564,6 +565,7 @@
(def ^:const bridge-name-erc-721-transfer "ERC721Transfer")
(def ^:const bridge-name-erc-1155-transfer "ERC1155Transfer")
(def ^:const bridge-name-hop "Hop")
(def ^:const bridge-name-paraswap "Paraswap")
(def ^:const bridge-assets #{"ETH" "USDT" "USDC" "DAI"})
@@ -590,9 +592,20 @@
(def ^:const default-slippage 0.5)
(def ^:const max-recommended-slippage 5)
(def ^:const max-slippage-decimal-places 2)
(def ^:const swap-default-provider
(def ^:const swap-provider-paraswap
{:name :paraswap
:full-name "Paraswap"
:color :blue
:contract-address "0xdef171fe48cf0115b1d80b88dc8eab59176fee57"
:terms-and-conditions-url "https://files.paraswap.io/tos_v4.pdf"})
(def ^:const swap-providers
{:paraswap swap-provider-paraswap})
(def ^:const token-for-fees-symbol "ETH")
(def ^:const transaction-status-success "Success")
(def ^:const transaction-status-pending "Pending")
(def ^:const transaction-status-failed "Failed")
(def ^:const min-token-decimals-to-display 6)
(def ^:const swap-proposal-refresh-interval-ms 15000)
@@ -7,17 +7,10 @@
[taoensso.timbre :as log]
[utils.re-frame :as rf]))
(def ^:const user-confirmed-key :centralized-metrics/user-confirmed?)
(def ^:const enabled-key :centralized-metrics/enabled?)
(defn show-confirmation-modal?
[db]
(not (user-confirmed-key db)))
(defn push-event?
[db]
(or (not (user-confirmed-key db))
(enabled-key db)))
(or (not (:centralized-metrics/user-confirmed? db))
(:centralized-metrics/enabled? db)))
(defn centralized-metrics-interceptor
[context]
@@ -33,17 +26,16 @@
:after centralized-metrics-interceptor))
(rf/reg-event-fx :centralized-metrics/toggle-centralized-metrics
(fn [{:keys [db]} [enabled?]]
(fn [{:keys [db]} [enabled? onboarding?]]
{:fx [[:effects.centralized-metrics/toggle-metrics enabled?]]
:db (assoc db
user-confirmed-key
true
enabled-key
enabled?)}))
:db (-> db
(assoc :centralized-metrics/user-confirmed? true)
(assoc :centralized-metrics/enabled? enabled?)
(assoc :centralized-metrics/onboarding-enabled? (and onboarding? enabled?)))}))
(rf/reg-event-fx :centralized-metrics/check-modal
(fn [{:keys [db]} [modal-view]]
(when (show-confirmation-modal? db)
(when-not (:centralized-metrics/user-confirmed? db)
{:fx [[:dispatch
[:show-bottom-sheet
{:content (fn [] [modal-view])
@@ -6,11 +6,6 @@
[status-im.contexts.centralized-metrics.tracking :as tracking]
[test-helpers.unit :as h]))
(deftest show-confirmation-modal-test
(testing "returns true if the user confirmed"
(is (false? (events/show-confirmation-modal? {events/user-confirmed-key true})))
(is (true? (events/show-confirmation-modal? {})))))
(deftest push-event-test
(testing "returns correct boolean value"
(is (true? (events/push-event? {:centralized-metrics/user-confirmed? false})))
@@ -37,6 +37,7 @@
:screen/onboarding.enable-biometrics
:screen/onboarding.generating-keys
:screen/onboarding.enable-notifications
:screen/onboarding.preparing-status
:screen/onboarding.sign-in-intro
:screen/onboarding.sign-in
:screen/onboarding.syncing-progress
@@ -50,7 +50,7 @@
item])))))
(defn add-manage-members
[{:keys [scroll-enabled? on-scroll]}]
[{:keys [on-scroll]}]
(let [theme (quo.theme/use-theme)
selected-participants (rf/sub [:group-chat/selected-participants])
deselected-members (rf/sub [:group-chat/deselected-members])
@@ -74,7 +74,6 @@
(i18n/label (if admin? :t/manage-members :t/add-members))]
[gesture/section-list
{:key-fn :title
:scroll-enabled @scroll-enabled?
:on-scroll on-scroll
:sticky-section-headers-enabled false
:sections (rf/sub [:contacts/grouped-by-first-letter])
@@ -146,9 +146,16 @@
"")]
(subs preview-text 0 (min (count preview-text) max-subheader-length))))
(defn- last-message-color
[unread-messages? muted theme]
(cond
muted (colors/theme-colors colors/neutral-50 colors/neutral-60 theme)
unread-messages? (colors/theme-colors colors/neutral-100 colors/white theme)
:else (colors/theme-colors colors/neutral-50 colors/neutral-40 theme)))
(defn last-message-preview
"Render the preview of a last message to a maximum of max-subheader-length characters"
[group-chat {:keys [deleted? outgoing from deleted-for-me?] :as message}]
[group-chat {:keys [deleted? outgoing from deleted-for-me?] :as message} muted unread-messages?]
(let [theme (quo.theme/use-theme)
[primary-name _] (rf/sub [:contacts/contact-two-names-by-identity from])
preview-text (if deleted-for-me?
@@ -162,9 +169,7 @@
(preview-text-from-content group-chat primary-name message)))]
[quo/text
{:size :paragraph-2
:style {:color (colors/theme-colors colors/neutral-50
colors/neutral-40
theme)
:style {:color (last-message-color unread-messages? muted theme)
:flex 1}
:number-of-lines 1
:ellipsize-mode :tail
@@ -231,14 +236,16 @@
unviewed-messages-count]])))
(defn chat-item
[{:keys [chat-id group-chat color name last-message timestamp muted image]
[{:keys [chat-id group-chat color name last-message timestamp muted image
unviewed-messages-count]
:as item}]
(let [[primary-name secondary-name]
(if group-chat
[name ""]
(rf/sub [:contacts/contact-two-names-by-identity chat-id]))
{:keys [ens-verified added?] :as contact} (when-not group-chat
(rf/sub [:contacts/contact-by-address chat-id]))]
(rf/sub [:contacts/contact-by-address chat-id]))
unread-messages? (pos? unviewed-messages-count)]
[rn/view {:style {:flex-direction :row}}
[avatar-view
{:contact contact
@@ -258,7 +265,7 @@
:muted? muted
:time-str (datetime/to-short-str timestamp)
:style {:flex-shrink 1}}]
[last-message-preview group-chat last-message muted]]]
[last-message-preview group-chat last-message muted unread-messages?]]]
[notification item]]))
(defn chat-user
@@ -0,0 +1,54 @@
(ns status-im.contexts.chat.messenger.composer.actions.image.view
(:require [quo.core :as quo]
[react-native.permissions :as permissions]
[react-native.platform :as platform]
[status-im.common.alert.effects :as alert.effects]
[status-im.common.device-permissions :as device-permissions]
[status-im.constants :as constants]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn photo-limit-toast
[]
(rf/dispatch [:toasts/upsert
{:id :random-id
:type :negative
:text (i18n/label :t/hit-photos-limit
{:max-photos constants/max-album-photos})}]))
(defn go-to-camera
[images-count]
(device-permissions/camera #(if (>= images-count constants/max-album-photos)
(photo-limit-toast)
(rf/dispatch [:navigate-to :camera-screen]))))
(defn camera-button
[]
(let [images-count (count (vals (rf/sub [:chats/sending-image])))]
[quo/composer-button
{:on-press #(go-to-camera images-count)
:accessibility-label :camera-button
:icon :i/camera
:container-style {:margin-right 12}}]))
(defn open-photo-selector
[input-ref]
(permissions/request-permissions
{:permissions [(if platform/is-below-android-13? :read-external-storage :read-media-images)
:write-external-storage]
:on-allowed (fn []
(when (and platform/android? @input-ref)
(.blur ^js @input-ref))
(when platform/ios?
(rf/dispatch [:alert-banners/hide]))
(rf/dispatch [:photo-selector/navigate-to-photo-selector]))
:on-denied #(alert.effects/show-popup (i18n/label :t/error)
(i18n/label :t/external-storage-denied))}))
(defn image-button
[input-ref]
[quo/composer-button
{:on-press #(open-photo-selector input-ref)
:accessibility-label :open-images-button
:container-style {:margin-right 12}
:icon :i/image}])
@@ -7,26 +7,14 @@
{:height constants/actions-container-height
:justify-content :space-between
:align-items :center
:z-index 2
:flex-direction :row})
(defn send-button
[opacity z-index]
[opacity]
(reanimated/apply-animations-to-style
{:opacity opacity}
{:position :absolute
:right 0
:z-index z-index
:padding-vertical 3
:padding-left 2}))
(defn record-audio-container
[]
{:align-items :center
:background-color :transparent
:flex-direction :row
:position :absolute
:left -20
:right -20
:bottom 0
:height constants/composer-default-height})
@@ -2,246 +2,60 @@
(:require
[quo.core :as quo]
[react-native.core :as rn]
[react-native.permissions :as permissions]
[react-native.platform :as platform]
[react-native.reanimated :as reanimated]
[reagent.core :as reagent]
[status-im.common.alert.effects :as alert.effects]
[status-im.common.device-permissions :as device-permissions]
[status-im.config :as config]
[status-im.constants :as constants]
[status-im.contexts.chat.messenger.composer.actions.image.view :as actions.image]
[status-im.contexts.chat.messenger.composer.actions.style :as style]
[status-im.contexts.chat.messenger.composer.constants :as comp-constants]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn send-message
"Minimize composer, animate-out background overlay, clear input and flush state"
[{:keys [sending-images? sending-links?]}
{:keys [text-value maximized?]}
{:keys [height saved-height last-height opacity background-y]}
window-height
edit]
(reanimated/animate height comp-constants/input-height)
(reanimated/set-shared-value saved-height comp-constants/input-height)
(reanimated/set-shared-value last-height comp-constants/input-height)
(reanimated/animate opacity 0)
(js/setTimeout #(reanimated/set-shared-value background-y
(- window-height))
300)
[input-ref edit btn-opacity]
(when @input-ref
(.clear ^js @input-ref))
(reanimated/animate btn-opacity 0)
(rf/dispatch [:chat.ui/send-current-message])
(rf/dispatch [:chat.ui/set-input-maximized false])
(rf/dispatch [:chat.ui/set-input-content-height comp-constants/input-height])
(rf/dispatch [:chat.ui/set-chat-input-text nil])
(reset! maximized? false)
(reset! text-value "")
(reset! sending-links? false)
(reset! sending-images? false)
(when-not (some? edit)
(rf/dispatch [:chat.ui/scroll-to-bottom])))
(defn f-send-button
[props state animations window-height images? btn-opacity z-index edit]
(let [{:keys [text-value]} state
(defn send-button
[input-ref edit]
(let [btn-opacity (reanimated/use-shared-value 0)
chat-input (rf/sub [:chats/current-chat-input])
input-text (:input-text chat-input)
images? (boolean (seq (rf/sub [:chats/sending-image])))
profile-customization-color (rf/sub [:profile/customization-color])
{:keys [chat-id chat-type]
chat-color :color} (rf/sub [:chats/current-chat-chat-view])
contact-customization-color (when (= chat-type constants/one-to-one-chat-type)
(rf/sub [:contacts/contact-customization-color-by-address
chat-id]))]
chat-id]))
on-press (rn/use-callback #(send-message input-ref edit btn-opacity) [edit])]
(rn/use-effect (fn []
;; Handle send button opacity animation and z-index when input content changes
(if (or (seq @text-value) images?)
(when (or (not= @z-index 1) (not= (reanimated/get-shared-value btn-opacity) 1))
(reset! z-index 1)
(if (or (seq input-text) images?)
(when (not= (reanimated/get-shared-value btn-opacity) 1)
(js/setTimeout #(reanimated/animate btn-opacity 1) 50))
(when (or (not= @z-index 0) (not= (reanimated/get-shared-value btn-opacity) 0))
(reanimated/animate btn-opacity 0)
(js/setTimeout #(when (and (empty? @text-value) (not images?))
(reset! z-index 0))
300))))
[(and (empty? @text-value) (not images?))])
(when (not= (reanimated/get-shared-value btn-opacity) 0)
(reanimated/animate btn-opacity 0))))
[(and (empty? input-text) (not images?))])
[reanimated/view
{:style (style/send-button btn-opacity @z-index)}
{:style (style/send-button btn-opacity)}
[quo/button
{:icon-only? true
:size 32
:customization-color (or contact-customization-color chat-color profile-customization-color)
:accessibility-label :send-message-button
:on-press #(send-message props state animations window-height edit)}
:on-press on-press}
:i/arrow-up]]))
(defn send-button
[props {:keys [text-value] :as state} animations window-height images? edit btn-opacity]
(let [z-index (reagent/atom (if (and (empty? @text-value) (not images?)) 0 1))]
[:f> f-send-button props state animations window-height images? btn-opacity z-index edit]))
(defn disabled-audio-button
[opacity]
[reanimated/view {:style (reanimated/apply-animations-to-style {:opacity opacity} {})}
[quo/composer-button
{:on-press (fn []
(rf/dispatch [:chat.ui/set-input-focused false])
(rn/dismiss-keyboard!)
(js/alert "to be implemented"))
:icon :i/audio}]])
(defn audio-button
[{:keys [record-reset-fn input-ref]}
{:keys [record-permission? recording? gesture-enabled? focused?]}]
(let [audio (rf/sub [:chats/sending-audio])]
[rn/view
{:style (style/record-audio-container)
:pointer-events :box-none}
[quo/record-audio
{:record-audio-permission-granted @record-permission?
:on-init (fn [reset-fn]
(reset! record-reset-fn reset-fn))
:on-start-recording (fn []
(rf/dispatch [:chat.ui/set-recording true])
(reset! recording? true)
(reset! gesture-enabled? false))
:audio-file audio
:on-lock (fn []
(rf/dispatch [:chat.ui/set-recording false]))
:on-reviewing-audio (fn [file]
(rf/dispatch [:chat.ui/set-recording false])
(rf/dispatch [:chat.ui/set-input-audio file]))
:on-send (fn [{:keys [file-path duration]}]
(rf/dispatch [:chat.ui/set-recording false])
(reset! recording? false)
(reset! gesture-enabled? true)
(rf/dispatch [:chat/send-audio file-path duration])
(when @focused?
(js/setTimeout #(when @input-ref (.focus ^js @input-ref))
300))
(rf/dispatch [:chat.ui/set-input-audio nil]))
:on-cancel (fn []
(when @recording?
(rf/dispatch [:chat.ui/set-recording false])
(reset! recording? false)
(reset! gesture-enabled? true)
(when @focused?
(js/setTimeout #(when @input-ref
(.focus ^js @input-ref))
300))
(rf/dispatch [:chat.ui/set-input-audio nil])))
:on-check-audio-permissions (fn []
(permissions/permission-granted?
:record-audio
#(reset! record-permission? %)
#(reset! record-permission? false)))
:on-request-record-audio-permission (fn []
(rf/dispatch
[:request-permissions
{:permissions [:record-audio]
:on-allowed
#(reset! record-permission? true)
:on-denied
#(js/setTimeout
(fn []
(alert.effects/show-popup
(i18n/label :t/audio-recorder-error)
(i18n/label
:t/audio-recorder-permissions-error)
nil
{:text (i18n/label :t/settings)
:accessibility-label :settings-button
:onPress (fn [] (permissions/open-settings))}))
50)}]))
:max-duration-ms constants/audio-max-duration-ms}]]))
(defn photo-limit-toast
[]
(rf/dispatch [:toasts/upsert
{:id :random-id
:type :negative
:text (i18n/label :t/hit-photos-limit
{:max-photos constants/max-album-photos})}]))
(defn go-to-camera
[images-count]
(device-permissions/camera #(if (>= images-count constants/max-album-photos)
(photo-limit-toast)
(rf/dispatch [:navigate-to :camera-screen]))))
(defn camera-button
[edit]
(let [images-count (count (vals (rf/sub [:chats/sending-image])))]
[quo/composer-button
{:on-press (if edit
#(js/alert "This feature is temporarily unavailable in edit mode.")
#(go-to-camera images-count))
:accessibility-label :camera-button
:icon :i/camera
:container-style {:margin-right 12}}]))
(defn open-photo-selector
[{:keys [input-ref]}
{:keys [height]}]
(permissions/request-permissions
{:permissions [(if platform/is-below-android-13? :read-external-storage :read-media-images)
:write-external-storage]
:on-allowed (fn []
(when (and platform/android? @input-ref)
(.blur ^js @input-ref))
(when platform/ios?
(rf/dispatch [:alert-banners/hide]))
(rf/dispatch [:chat.ui/set-input-content-height
(reanimated/get-shared-value height)])
(rf/dispatch [:photo-selector/navigate-to-photo-selector]))
:on-denied (fn []
(alert.effects/show-popup (i18n/label :t/error)
(i18n/label
:t/external-storage-denied)))}))
(defn image-button
[props animations edit]
[quo/composer-button
{:on-press (if edit
#(js/alert "This feature is temporarily unavailable in edit mode.")
#(open-photo-selector props animations))
:accessibility-label :open-images-button
:container-style {:margin-right 12}
:icon :i/image}])
(defn reaction-button
[]
[quo/composer-button
{:icon :i/reaction
:on-press (fn []
(rf/dispatch [:chat.ui/set-input-focused false])
(rn/dismiss-keyboard!)
(js/alert "to be implemented"))
:container-style {:margin-right 12}}])
(defn format-button
[]
[quo/composer-button
{:on-press (fn []
(rf/dispatch [:chat.ui/set-input-focused false])
(rn/dismiss-keyboard!)
(js/alert "to be implemented"))
:icon :i/format}])
(defn view
[props state animations window-height {:keys [edit images]}]
(let [send-btn-opacity (reanimated/use-shared-value 0)
audio-btn-opacity (reanimated/interpolate send-btn-opacity [0 1] [1 0])]
[input-ref]
(let [edit (rf/sub [:chats/edit-message])]
[rn/view {:style style/actions-container}
[rn/view
{:style {:flex-direction :row
:display (if @(:recording? state) :none :flex)}}
[camera-button edit]
[image-button props animations edit]
(when config/show-not-implemented-features?
[reaction-button])
(when config/show-not-implemented-features?
[format-button])]
[:f> send-button props state animations window-height images edit send-btn-opacity]
(when (and (not edit) (not images) config/show-not-implemented-features?)
;; TODO(alwx): needs to be replaced with an `audio-button` later. See
;; https://github.com/status-im/status-mobile/issues/16084 for more details.
[:f> disabled-audio-button audio-btn-opacity])]))
[rn/view {:style {:flex-direction :row}}
(when-not edit
[:<>
[actions.image/camera-button]
[actions.image/image-button input-ref edit]])]
[send-button input-ref edit]]))
@@ -13,10 +13,6 @@
(def ^:const line-height (if platform/ios? 18 (:line-height typography/paragraph-1)))
(def ^:const multiline-minimized-height (+ input-height line-height))
(def ^:const empty-opacity 0.7)
(def ^:const images-padding-top 12)
(def ^:const images-padding-bottom 8)
(def ^:const images-container-height
@@ -33,16 +29,6 @@
(def ^:const mentions-max-height 240)
(def ^:const extra-content-offset (if platform/ios? 6 -8))
(def ^:const content-change-threshold 10)
(def ^:const drag-threshold 30)
(def ^:const velocity-threshold (if platform/ios? -1000 -500))
(def ^:const background-threshold 0.75)
(def ^:const max-text-size 4096)
(def ^:const unfurl-debounce-ms
@@ -7,12 +7,13 @@
[react-native.reanimated :as reanimated]
[status-im.contexts.chat.messenger.composer.constants :as constants]
[status-im.contexts.chat.messenger.composer.edit.style :as style]
[status-im.contexts.chat.messenger.composer.effects :as effects]
[status-im.contexts.chat.messenger.composer.utils :as utils]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn edit-message
[{:keys [text-value input-ref input-height]}]
[input-ref]
(let [theme (quo.theme/use-theme)]
[rn/view
{:style style/container
@@ -32,18 +33,16 @@
{:size 24
:icon-only? true
:accessibility-label :edit-cancel-button
:on-press #(utils/cancel-edit-message text-value input-ref input-height)
:on-press #(utils/cancel-edit-message input-ref)
:type :outline}
:i/close]]))
(defn- f-view
[props]
(defn view
[input-ref]
(let [edit (rf/sub [:chats/edit-message])
height (reanimated/use-shared-value (if edit constants/edit-container-height 0))]
(effects/use-edit input-ref edit)
(rn/use-effect #(reanimated/animate height (if edit constants/edit-container-height 0)) [edit])
[reanimated/view {:style (reanimated/apply-animations-to-style {:height height} {})}
(when edit [edit-message props])]))
(defn view
[props]
[:f> f-view props])
(when edit
[edit-message input-ref])]))
@@ -1,225 +1,20 @@
(ns status-im.contexts.chat.messenger.composer.effects
(:require
[clojure.string :as string]
[oops.core :as oops]
[react-native.async-storage :as async-storage]
[react-native.core :as rn]
[react-native.platform :as platform]
[react-native.reanimated :as reanimated]
[reagent.core :as reagent]
[status-im.contexts.chat.messenger.composer.constants :as constants]
[status-im.contexts.chat.messenger.composer.keyboard :as kb]
[status-im.contexts.chat.messenger.composer.utils :as utils]
[utils.number]
[utils.re-frame :as rf]))
(defn reenter-screen-effect
[{:keys [text-value saved-cursor-position maximized?]}
{:keys [content-height]}
{:keys [input-content-height input-text input-maximized?]}
{:keys [height]}]
(let [lines (utils/calc-lines input-content-height)
minimized-height (if (or (= lines 1) (empty? input-text))
constants/input-height
constants/multiline-minimized-height)]
(when (and (empty? @text-value) (not= input-text nil))
(reset! text-value input-text)
(reset! content-height input-content-height)
(reset! saved-cursor-position (count input-text))
(reanimated/set-shared-value height minimized-height))
(when input-maximized?
(reset! maximized? true))))
(defn maximized-effect
[{:keys [maximized?]}
{:keys [height saved-height last-height]}
{:keys [max-height]}
{:keys [input-content-height]}]
(when (or @maximized? (>= input-content-height max-height))
(reanimated/animate height max-height)
(reanimated/set-shared-value saved-height max-height)
(reanimated/set-shared-value last-height max-height)))
(defn layout-effect
[{:keys [lock-layout?]}]
(when-not @lock-layout?
(js/setTimeout #(reset! lock-layout? true) 500)))
(defn kb-default-height-effect
[{:keys [kb-default-height kb-height]}]
(when (zero? @kb-default-height)
(async-storage/get-item :kb-default-height
(fn [height]
(reset! kb-default-height (utils.number/parse-int height 0))
(reset! kb-height (utils.number/parse-int height 0))))))
(defn background-effect
[{:keys [maximized?]}
{:keys [opacity background-y]}
{:keys [max-height]}
{:keys [input-content-height]}]
(when (or @maximized? (>= input-content-height (* max-height constants/background-threshold)))
(reanimated/set-shared-value background-y 0)
(reanimated/animate opacity 1)))
(defn link-preview-effect
[{:keys [text-value]}]
(let [text @text-value]
(when-not (string/blank? text)
(rf/dispatch [:link-preview/unfurl-urls text]))))
(defn audio-effect
[{:keys [recording? gesture-enabled?]}
audio]
(when (and audio (not @recording?))
(reset! recording? true)
(reset! gesture-enabled? false)))
(defn empty-effect
[{:keys [empty-input?]} subscriptions]
(reanimated/set-shared-value
empty-input?
(utils/empty-input? subscriptions)))
(defn component-will-unmount
[{:keys [keyboard-show-listener keyboard-hide-listener keyboard-frame-listener]}]
(.remove ^js @keyboard-show-listener)
(.remove ^js @keyboard-hide-listener)
(.remove ^js @keyboard-frame-listener))
(defn initialize
[props state animations {:keys [max-height] :as dimensions}
{:keys [chat-input audio input-text images link-previews? reply edit] :as subscriptions}]
(rn/use-effect
(fn []
(maximized-effect state animations dimensions chat-input)
(layout-effect state)
(kb-default-height-effect state)
(background-effect state animations dimensions chat-input)
(link-preview-effect state)
(audio-effect state audio)
(kb/add-kb-listeners props state animations dimensions)
#(component-will-unmount props))
[max-height])
(rn/use-effect
(fn []
(empty-effect animations subscriptions))
[input-text images link-previews? reply edit audio])
(rn/use-mount #(reenter-screen-effect state dimensions subscriptions animations)))
[utils.number]))
(defn use-edit
[{:keys [input-ref]}
{:keys [text-value saved-cursor-position cursor-position]}
{:keys [edit input-with-mentions]}
chat-screen-layout-calculations-complete?]
(let [mention? (some #(= :mention (first %)) (seq input-with-mentions))]
(rn/use-effect
(fn []
(let [mention-text (reduce (fn [acc item]
(str acc (second item)))
""
input-with-mentions)
edit-text (cond
mention? mention-text
;; NOTE: using text-value for cases when the user
;; leaves the app with an unfinished edit and re-opens
;; the chat.
(and (seq @text-value)
(not (reanimated/get-shared-value
chat-screen-layout-calculations-complete?)))
@text-value
:else (get-in edit [:content :text]))
selection-pos (count edit-text)
inject-edit-text (fn []
(reset! text-value edit-text)
(reset! cursor-position selection-pos)
(reset! saved-cursor-position selection-pos)
(when @input-ref
(.setNativeProps ^js @input-ref
(clj->js {:text edit-text}))))]
(when (and edit @input-ref)
;; NOTE: A small setTimeout is necessary to ensure the focus is enqueued and is executed
;; ASAP. Check https://github.com/software-mansion/react-native-screens/issues/472
;;
;; The nested setTimeout is necessary to avoid both `on-focus` and
;; `on-content-size-change` handlers triggering the height animation simultaneously, as
;; this causes a jump in the
;; UI. This way, `on-focus` will trigger first without changing the height, after which
;; `on-content-size-change` will animate the height of the input based on the injected
;; text.
(js/setTimeout #(do (when (reanimated/get-shared-value
chat-screen-layout-calculations-complete?)
(.focus ^js @input-ref))
(reagent/next-tick inject-edit-text))
600))))
[(:message-id edit)])))
[input-ref edit]
(rn/use-effect
(fn []
(when (and edit @input-ref)
(js/setTimeout #(.focus ^js @input-ref) 600)))
[(:message-id edit)]))
(defn use-reply
[{:keys [input-ref]}
{:keys [reply]}
chat-screen-layout-calculations-complete?]
[input-ref reply]
(rn/use-effect
(fn []
(when (and reply @input-ref (reanimated/get-shared-value chat-screen-layout-calculations-complete?))
(when (and reply @input-ref)
(js/setTimeout #(.focus ^js @input-ref) 600)))
[(:message-id reply)]))
(defn update-input-mention
[{:keys [input-ref]}
{:keys [text-value]}
{:keys [input-text]}]
(rn/use-effect
(fn []
(when (and input-text (not= @text-value input-text))
(when @input-ref
(.setNativeProps ^js @input-ref (clj->js {:text input-text})))
(reset! text-value input-text)
(rf/dispatch [:mention/on-change-text input-text])))
[input-text]))
(defn link-previews
[{:keys [sending-links?]}
{:keys [text-value maximized?]}
{:keys [height saved-height]}
{:keys [link-previews?]}]
(rn/use-effect
(fn []
(if-not @maximized?
(when (not= @sending-links? link-previews?)
(reanimated/animate height (reanimated/get-shared-value saved-height)))
(let [curr-text @text-value]
(reset! text-value (str @text-value " "))
(js/setTimeout #(reset! text-value curr-text) 100)))
(reset! sending-links? link-previews?))
[link-previews?]))
(defn use-images
[{:keys [sending-images? input-ref]}
{:keys [text-value maximized?]}
{:keys [height saved-height]}
{:keys [images]}]
(rn/use-effect
(fn []
(when (and (not @sending-images?) (seq images) @input-ref)
(.focus ^js @input-ref))
(if-not @maximized?
(when (not= @sending-images? (boolean (seq images)))
(reanimated/animate height (reanimated/get-shared-value saved-height)))
(let [curr-text @text-value]
(reset! text-value (str @text-value " "))
(js/setTimeout #(reset! text-value curr-text) 100)))
(reset! sending-images? (boolean (seq images))))
[(boolean (seq images))]))
(defn did-mount
[{:keys [selectable-input-ref input-ref selection-manager]}]
(rn/use-mount
(fn []
(when platform/android?
(let [selectable-text-input-handle (rn/find-node-handle @selectable-input-ref)
text-input-handle (rn/find-node-handle @input-ref)]
(oops/ocall selection-manager
:setupMenuItems
selectable-text-input-handle
text-input-handle))))))
@@ -6,6 +6,7 @@
[status-im.contexts.chat.messenger.composer.link-preview.events :as link-preview]
[status-im.contexts.chat.messenger.messages.transport.events :as messages.transport]
[taoensso.timbre :as log]
[utils.debounce :as debounce]
[utils.emojilib :as emoji]
[utils.i18n :as i18n]
[utils.re-frame :as rf]
@@ -21,6 +22,12 @@
(when (empty? new-input)
(mentions/clear-mentions)))))
(rf/defn set-chat-input-ref
{:events [:chat/set-input-ref]}
[{:keys [db]} input-ref]
(let [current-chat-id (:current-chat-id db)]
{:db (assoc-in db [:chat/inputs current-chat-id :input-ref] input-ref)}))
(rf/defn set-input-content-height
{:events [:chat.ui/set-input-content-height]}
[{db :db} content-height chat-id]
@@ -69,15 +76,14 @@
[{:keys [db]} message]
(let [current-chat-id (:current-chat-id db)
text (get-in message [:content :text])]
{:db (-> db
(assoc-in [:chat/inputs current-chat-id :metadata :editing-message]
message)
(assoc-in [:chat/inputs current-chat-id :metadata :responding-to-message] nil)
(update-in [:chat/inputs current-chat-id :metadata]
dissoc
:sending-image))
:dispatch-n [[:chat.ui/set-chat-input-text nil current-chat-id]
[:mention/to-input-field text current-chat-id]]}))
{:db (-> db
(assoc-in [:chat/inputs current-chat-id :metadata :editing-message]
message)
(assoc-in [:chat/inputs current-chat-id :metadata :responding-to-message] nil)
(update-in [:chat/inputs current-chat-id :metadata]
dissoc
:sending-image))
:dispatch [:mention/to-input-field text current-chat-id]}))
(rf/defn cancel-message-reply
"Cancels stage message reply"
@@ -257,6 +263,13 @@
[{{:keys [current-chat-id] :as db} :db :as cofx}]
(let [{:keys [input-text metadata]} (get-in db [:chat/inputs current-chat-id])
editing-message (:editing-message metadata)]
(debounce/clear :link-preview/unfurl-urls)
(debounce/clear :mention/on-change-text)
(if editing-message
(send-edited-message cofx input-text editing-message)
(send-messages cofx input-text current-chat-id))))
(rf/reg-fx :effects/set-input-text-value
(fn [[input-ref text-value]]
(when (and (not (string/blank? text-value)) input-ref)
(.setNativeProps ^js input-ref (clj->js {:text (emoji/text->emoji text-value)})))))
@@ -1,112 +0,0 @@
(ns status-im.contexts.chat.messenger.composer.gesture
(:require
[oops.core :as oops]
[react-native.gesture :as gesture]
[react-native.reanimated :as reanimated]
[status-im.contexts.chat.messenger.composer.constants :as constants]
[status-im.contexts.chat.messenger.composer.utils :as utils]
[utils.number]
[utils.re-frame :as rf]))
(defn set-opacity
[velocity opacity translation expanding? min-height max-height new-height saved-height]
(let [remaining-height (if expanding?
(- max-height (reanimated/get-shared-value saved-height))
(- (reanimated/get-shared-value saved-height) min-height))
progress (if (= new-height min-height) 1 (/ translation remaining-height))
currently-expanding? (neg? velocity)
max-opacity? (and currently-expanding? (= (reanimated/get-shared-value opacity) 1))
min-opacity? (and (not currently-expanding?)
(= (reanimated/get-shared-value opacity) 0))]
(if (>= translation 0)
(when (and (not expanding?) (not min-opacity?))
(reanimated/set-shared-value opacity (- 1 progress)))
(when (and expanding? (not max-opacity?))
(reanimated/set-shared-value opacity (Math/abs progress))))))
(defn maximize
[{:keys [maximized?]}
{:keys [height saved-height background-y opacity]}
{:keys [max-height]}]
(reanimated/animate height max-height)
(reanimated/set-shared-value saved-height max-height)
(reanimated/set-shared-value background-y 0)
(reanimated/animate opacity 1)
(js/setTimeout (fn []
(reset! maximized? true)
(rf/dispatch [:chat.ui/set-input-maximized true]))
300))
(defn minimize
[{:keys [input-ref emoji-kb-extra-height saved-emoji-kb-extra-height]}
{:keys [maximized?]}]
(when @emoji-kb-extra-height
(reset! saved-emoji-kb-extra-height @emoji-kb-extra-height)
(reset! emoji-kb-extra-height nil))
(reset! maximized? false)
(rf/dispatch [:chat.ui/set-input-maximized false])
(utils/blur-input input-ref))
(defn bounce-back
[{:keys [height saved-height opacity background-y]}
{:keys [window-height]}
starting-opacity]
(reanimated/animate height (reanimated/get-shared-value saved-height))
(when (zero? starting-opacity)
(reanimated/animate opacity 0)
(reanimated/animate-delay background-y (- window-height) 300)))
(defn drag-gesture
[{:keys [input-ref] :as props}
{:keys [gesture-enabled?] :as state}
{:keys [height saved-height last-height opacity background-y] :as animations}
{:keys [max-height lines] :as dimensions}
keyboard-shown]
(let [expanding? (atom true)
starting-opacity (reanimated/get-shared-value opacity)]
(-> (gesture/gesture-pan)
(gesture/enabled @gesture-enabled?)
(gesture/on-start (fn [event]
(if-not keyboard-shown
(do ; focus and end
(when (< (oops/oget event "velocityY") constants/velocity-threshold)
(reanimated/set-shared-value last-height max-height)
(maximize state animations dimensions))
(when @input-ref
(.focus ^js @input-ref))
(reset! gesture-enabled? false))
(do ; else, will start gesture
(reanimated/set-shared-value background-y 0)
(reset! expanding? (neg? (oops/oget event "velocityY")))))))
(gesture/on-update
(fn [event]
(let [translation (oops/oget event "translationY")
min-height (utils/get-min-height lines)
new-height (- (reanimated/get-shared-value saved-height) translation)
bounded-height (utils.number/value-in-range new-height min-height max-height)]
(when keyboard-shown
(if (>= new-height min-height)
(do ; expand sheet
(reanimated/set-shared-value height bounded-height)
(set-opacity (oops/oget event "velocityY")
opacity
translation
@expanding?
min-height
max-height
bounded-height
saved-height))
; sheet at min-height, collapse keyboard
(utils/blur-input input-ref))))))
(gesture/on-end (fn []
(let [diff (- (reanimated/get-shared-value height)
(reanimated/get-shared-value saved-height))]
(if @gesture-enabled?
(if (and @expanding? (>= diff 0))
(if (> diff constants/drag-threshold)
(maximize state animations dimensions)
(bounce-back animations dimensions starting-opacity))
(if (> (Math/abs diff) constants/drag-threshold)
(minimize props state)
(bounce-back animations dimensions starting-opacity)))
(reset! gesture-enabled? true))))))))
@@ -3,139 +3,17 @@
[clojure.string :as string]
[oops.core :as oops]
[react-native.core :as rn]
[react-native.reanimated :as reanimated]
[reagent.core :as reagent]
[status-im.contexts.chat.messenger.composer.constants :as constants]
[status-im.contexts.chat.messenger.composer.keyboard :as kb]
[status-im.contexts.chat.messenger.composer.selection :as selection]
[status-im.contexts.chat.messenger.composer.utils :as utils]
[utils.debounce :as debounce]
[utils.number]
[utils.re-frame :as rf]))
(defn focus
"Animate to the `saved-height`, display background-overlay if needed, and set cursor position"
[{:keys [input-ref] :as props}
{:keys [text-value focused? lock-selection? saved-cursor-position maximized?]}
{:keys [height saved-height last-height opacity background-y composer-focused?]
:as animations}
{:keys [max-height] :as dimensions}]
(reanimated/set-shared-value composer-focused? true)
(reset! focused? true)
(rf/dispatch [:chat.ui/set-input-focused true])
(let [last-height-value (reanimated/get-shared-value last-height)
new-height (min max-height last-height-value)]
(reanimated/animate height new-height)
(reanimated/set-shared-value saved-height new-height)
(when (> last-height-value (* constants/background-threshold max-height))
(reset! maximized? true)
(reanimated/animate opacity 1)
(reanimated/set-shared-value background-y 0)))
(js/setTimeout #(reset! lock-selection? false) 300)
(when (and (not-empty @text-value) @input-ref)
(.setNativeProps ^js @input-ref
(clj->js {:selection {:start @saved-cursor-position :end @saved-cursor-position}})))
(kb/handle-refocus-emoji-kb-ios props animations dimensions))
(defn blur
"Save the current height, minimize the composer, animate-out the background, and save cursor position"
[{:keys [text-value focused? lock-selection? cursor-position saved-cursor-position gradient-z-index
maximized? recording?]}
{:keys [height saved-height last-height gradient-opacity opacity background-y composer-focused?]}
{:keys [content-height max-height window-height]}]
(when-not @recording?
(let [lines (utils/calc-lines (- @content-height constants/extra-content-offset))
min-height (utils/get-min-height lines)
reopen-height (utils/calc-reopen-height text-value
min-height
max-height
content-height
saved-height)]
(reanimated/set-shared-value composer-focused? false)
(reset! focused? false)
(rf/dispatch [:chat.ui/set-input-focused false])
(reanimated/set-shared-value last-height reopen-height)
(reanimated/animate height min-height)
(reanimated/set-shared-value saved-height min-height)
(reanimated/animate opacity 0)
(js/setTimeout #(reanimated/set-shared-value background-y (- window-height)) 300)
(reanimated/animate gradient-opacity 0)
(reset! lock-selection? true)
(reset! saved-cursor-position @cursor-position)
(reset! gradient-z-index (if (= (reanimated/get-shared-value gradient-opacity) 1) -1 0))
(when (not= reopen-height max-height)
(reset! maximized? false)
(rf/dispatch [:chat.ui/set-input-maximized false])))))
(defn content-size-change
"Save new text height, expand composer if possible, show background overlay if needed"
[event
{:keys [maximized? lock-layout? text-value]}
{:keys [height saved-height last-height opacity background-y]}
{:keys [content-height window-height max-height]}
keyboard-shown]
(when keyboard-shown
(let [event-size (oops/oget event "nativeEvent.contentSize.height")
content-size (+ event-size constants/extra-content-offset)
lines (utils/calc-lines event-size)
content-size (if (or (= lines 1) (empty? @text-value))
constants/input-height
(if (= lines 2) constants/multiline-minimized-height content-size))
new-height (utils.number/value-in-range content-size
constants/input-height
max-height)
new-height (min new-height max-height)]
(reset! content-height content-size)
(when (utils/update-height? content-size height max-height)
(reanimated/animate height new-height)
(reanimated/set-shared-value last-height new-height)
(reanimated/set-shared-value saved-height new-height))
(when (= new-height max-height)
(reset! maximized? true)
(rf/dispatch [:chat.ui/set-input-maximized true]))
(if (utils/show-background? max-height new-height maximized?)
(do
(reanimated/set-shared-value background-y 0)
(reanimated/animate opacity 1))
(when (= (reanimated/get-shared-value opacity) 1)
(reanimated/animate opacity 0)
(js/setTimeout #(reanimated/set-shared-value background-y (- window-height)) 300)))
(rf/dispatch [:chat.ui/set-input-content-height content-size])
(reset! lock-layout? (> lines 2)))))
(defn scroll
"Hide or show top gradient while scroll"
[event
{:keys [scroll-y]}
{:keys [gradient-z-index focused?]}
{:keys [gradient-opacity]}
{:keys [lines max-lines]}]
(let [y (oops/oget event "nativeEvent.contentOffset.y")]
(reset! scroll-y y)
(when (utils/show-top-gradient? y lines max-lines gradient-opacity focused?)
(reset! gradient-z-index 1)
(js/setTimeout #(reanimated/animate gradient-opacity 1) 0))
(when (utils/hide-top-gradient? y gradient-opacity)
(reanimated/animate gradient-opacity 0)
(js/setTimeout #(reset! gradient-z-index 0) 300))))
(defn change-text
"Update `text-value`, update cursor selection, find links, find mentions"
[text
{:keys [input-ref record-reset-fn]}
{:keys [text-value cursor-position recording?]}]
(reset! text-value text)
(reagent/next-tick #(when @input-ref
(.setNativeProps ^js @input-ref
(clj->js {:selection {:start @cursor-position
:end @cursor-position}}))))
(when @recording?
(@record-reset-fn)
(reset! recording? false))
[text]
(rf/dispatch [:chat.ui/set-chat-input-text text])
(debounce/debounce-and-dispatch [:link-preview/unfurl-urls text]
constants/unfurl-debounce-ms)
(debounce/debounce-and-dispatch [:link-preview/unfurl-urls text] constants/unfurl-debounce-ms)
(if (string/ends-with? text "@")
(rf/dispatch [:mention/on-change-text text])
(debounce/debounce-and-dispatch [:mention/on-change-text text] 300)))
@@ -26,7 +26,7 @@
[rn/view {:style style/remove-photo-inner-container}
[quo/icon :i/clear {:size 20 :color colors/neutral-50 :color-2 colors/white}]]]])
(defn f-images-list
(defn images-list
[]
(let [theme (quo.theme/use-theme)
images (rf/sub [:chats/sending-image])
@@ -48,7 +48,3 @@
:horizontal true
:shows-horizontal-scroll-indicator false
:keyboard-should-persist-taps :handled}]]))
(defn images-list
[]
[:f> f-images-list])
@@ -1,87 +0,0 @@
(ns status-im.contexts.chat.messenger.composer.keyboard
(:require
[oops.core :as oops]
[react-native.async-storage :as async-storage]
[react-native.core :as rn]
[react-native.platform :as platform]
[react-native.reanimated :as reanimated]
[status-im.contexts.chat.messenger.composer.utils :as utils]))
(defn get-kb-height
[curr-height default-height]
(if (and default-height (< curr-height default-height))
default-height
curr-height))
(defn store-kb-height
[event {:keys [kb-default-height kb-height]}]
(let [height (- (:height (rn/get-window))
(oops/oget event "endCoordinates.screenY"))]
(reset! kb-height height)
(when (zero? @kb-default-height)
(async-storage/set-item! :kb-default-height (str height)))))
(defn handle-emoji-kb-ios
"Opening emoji KB on iOS will cause a flicker up and down due to height differences.
This method handles that by adding the extra difference between the keyboards. When the input is
expanded to a point where the added difference will make the composer go beyond the screen causing a flicker,
we're subtracting the difference so it only reaches the allowed max-height. We're not animating these
changes to make it appear seamless during transitions between keyboard types when maximized."
[event
{:keys [emoji-kb-extra-height]}
{:keys [text-value kb-height]}
{:keys [height saved-height]}
{:keys [max-height]}]
(let [start-h (oops/oget event "startCoordinates.height")
end-h (oops/oget event "endCoordinates.height")
diff (- end-h start-h)
max-height-diff (- max-height diff)
curr-text @text-value
bigger-than-default-kb? (> end-h @kb-height)
almost-expanded? (> (reanimated/get-shared-value height) max-height-diff)]
(if (and almost-expanded? bigger-than-default-kb? (pos? diff))
(do
(reanimated/set-shared-value height (- (reanimated/get-shared-value height) diff))
(reanimated/set-shared-value saved-height (- (reanimated/get-shared-value saved-height) diff))
(reset! text-value (str @text-value " "))
(js/setTimeout #(reset! text-value curr-text) 0)
(reset! emoji-kb-extra-height diff))
(when @emoji-kb-extra-height
(reanimated/set-shared-value height
(+ (reanimated/get-shared-value height) @emoji-kb-extra-height))
(reanimated/set-shared-value saved-height
(+ (reanimated/get-shared-value saved-height)
@emoji-kb-extra-height))
(reset! emoji-kb-extra-height nil)))))
(defn add-kb-listeners
[{:keys [keyboard-show-listener keyboard-frame-listener keyboard-hide-listener input-ref] :as props}
state animations dimensions]
(reset! keyboard-show-listener (.addListener
rn/keyboard
"keyboardDidShow"
#(store-kb-height % state)))
(reset! keyboard-frame-listener (.addListener
rn/keyboard
"keyboardWillChangeFrame"
#(handle-emoji-kb-ios % props state animations dimensions)))
(reset! keyboard-hide-listener (.addListener rn/keyboard
"keyboardDidHide"
#(when platform/android?
(utils/blur-input input-ref)))))
(defn handle-refocus-emoji-kb-ios
[{:keys [saved-emoji-kb-extra-height]}
{:keys [height saved-height last-height]}
{:keys [lines max-lines]}]
(when @saved-emoji-kb-extra-height
(js/setTimeout (fn []
(when (> lines max-lines)
(reanimated/animate height
(+ (reanimated/get-shared-value last-height)
@saved-emoji-kb-extra-height))
(reanimated/set-shared-value saved-height
(+ (reanimated/get-shared-value last-height)
@saved-emoji-kb-extra-height)))
(reset! saved-emoji-kb-extra-height nil))
600)))
@@ -21,7 +21,7 @@
[previews?])
height))
(defn f-view
(defn view
[]
(let [previews (rf/sub [:chats/link-previews-unfurled])
height (use-animated-height (boolean (seq previews)))]
@@ -48,7 +48,3 @@
:thumbnail (:data-uri thumbnail)
:url url})
previews)}]]))
(defn view
[]
[:f> f-view])
@@ -5,7 +5,6 @@
[react-native.reanimated :as reanimated]
[status-im.contexts.chat.messenger.composer.constants :as constants]))
(defn shadow
[theme]
(if platform/ios?
@@ -16,12 +15,12 @@
{:elevation 10}))
(defn container
[opacity bottom theme]
[opacity top theme]
(reanimated/apply-animations-to-style
{:opacity opacity}
(merge
{:position :absolute
:bottom bottom
:top (- (+ 8 top))
:left 8
:right 8
:border-radius 16
@@ -2,70 +2,45 @@
(:require
[quo.theme]
[react-native.core :as rn]
[react-native.platform :as platform]
[react-native.reanimated :as reanimated]
[react-native.safe-area :as safe-area]
[reagent.core :as reagent]
[status-im.common.contact-list-item.view :as contact-list-item]
[status-im.contexts.chat.messenger.composer.constants :as constants]
[status-im.contexts.chat.messenger.composer.mentions.style :as style]
[status-im.contexts.chat.messenger.composer.utils :as utils]
[status-im.contexts.chat.messenger.messages.constants :as messages.constants]
[utils.re-frame :as rf]))
(defn update-cursor
[user {:keys [cursor-position input-ref]}]
(when platform/android?
(let [new-cursor-pos (+ (count (:primary-name user)) @cursor-position 1)]
(reset! cursor-position new-cursor-pos)
(reagent/next-tick #(when @input-ref
(.setNativeProps ^js @input-ref
(clj->js {:selection {:start new-cursor-pos
:end
new-cursor-pos}})))))))
(defn mention-item
[user _ _ render-data]
[user]
[contact-list-item/contact-list-item
{:on-press (fn []
(rf/dispatch [:chat.ui/select-mention user])
(update-cursor user render-data))}
{:on-press #(rf/dispatch [:chat.ui/select-mention user])}
user])
(defn- f-view
[suggestions-atom props state animations max-height cursor-pos images link-previews? reply edit]
(let [suggestions (rf/sub [:chat/mention-suggestions])
theme (quo.theme/use-theme)
opacity (reanimated/use-shared-value (if (seq suggestions) 1 0))
size (count suggestions)
data {:keyboard-height @(:kb-height state)
:insets (safe-area/get-insets)
:curr-height (reanimated/get-shared-value (:height animations))
:window-height (:height (rn/get-window))
:images images
:link-previews? link-previews?
:reply reply
:edit edit}
mentions-pos
(utils/calc-suggestions-position cursor-pos max-height size state data images link-previews?)]
(defn view
[{:keys [layout-height]}]
(let [suggestions (rf/sub [:chat/mention-suggestions])
suggestions? (seq suggestions)
theme (quo.theme/use-theme)
opacity (reanimated/use-shared-value (if suggestions? 1 0))
[suggestions-state set-suggestions-state] (rn/use-state suggestions)
top (min constants/mentions-max-height
(* (count suggestions-state) 56)
(- @layout-height
(+ (safe-area/get-top)
messages.constants/top-bar-height
5)))]
(rn/use-effect
(fn []
(if (seq suggestions)
(reset! suggestions-atom suggestions)
(js/setTimeout #(reset! suggestions-atom suggestions) 300))
(reanimated/animate opacity (if (seq suggestions) 1 0)))
[(seq suggestions)])
(if suggestions?
(set-suggestions-state suggestions)
(js/setTimeout #(set-suggestions-state suggestions) 300))
(reanimated/animate opacity (if suggestions? 1 0)))
[suggestions?])
[reanimated/view
{:style (style/container opacity mentions-pos theme)}
{:style (style/container opacity top theme)}
[rn/flat-list
{:keyboard-should-persist-taps :always
:data (vals @suggestions-atom)
:data (vals suggestions-state)
:key-fn :key
:render-fn mention-item
:render-data {:cursor-position (:cursor-position state)
:input-ref (:input-ref props)}
:accessibility-label :mentions-list}]]))
(defn view
[props state animations max-height cursor-pos images link-previews? reply edit]
(let [suggestions-atom (reagent/atom {})]
[:f> f-view suggestions-atom props state animations max-height cursor-pos images link-previews? reply
edit]))
@@ -9,6 +9,7 @@
[react-native.reanimated :as reanimated]
[status-im.constants :as constant]
[status-im.contexts.chat.messenger.composer.constants :as constants]
[status-im.contexts.chat.messenger.composer.effects :as effects]
[status-im.contexts.chat.messenger.composer.reply.style :as style]
[status-im.contexts.chat.messenger.composer.utils :as utils]
[utils.ens.stateofus :as stateofus]
@@ -164,14 +165,12 @@
:end {:x 0.7 :y 0}
:style style/gradient}])]))
(defn- f-view
[recording? input-ref]
(defn view
[input-ref]
(let [reply (rf/sub [:chats/reply-message])
height (reanimated/use-shared-value (if reply constants/reply-container-height 0))]
(effects/use-reply input-ref reply)
(rn/use-effect #(reanimated/animate height (if reply constants/reply-container-height 0)) [reply])
[reanimated/view {:style (reanimated/apply-animations-to-style {:height height} {})}
(when reply [quoted-message reply true false recording? input-ref])]))
(defn view
[{:keys [recording?]} input-ref]
[:f> f-view @recording? input-ref])
(when reply
[quoted-message reply true false false input-ref])]))
@@ -2,7 +2,6 @@
(:require
[quo.foundations.colors :as colors]
[quo.foundations.typography :as typography]
[react-native.platform :as platform]
[react-native.reanimated :as reanimated]
[status-im.contexts.chat.messenger.composer.constants :as constants]
[status-im.contexts.shell.jump-to.constants :as shell.constants]
@@ -10,34 +9,6 @@
(def border-top-radius 20)
(defn shadow
[theme]
(when platform/ios?
{:shadow-radius 20
:shadow-opacity (colors/theme-colors 0.1 0.7 theme)
:shadow-color colors/neutral-100
:shadow-offset {:width 0 :height (colors/theme-colors -4 -8 theme)}}))
(def composer-sheet-and-jump-to-container
{:position :absolute
:bottom 0
:left 0
:right 0})
(defn sheet-container
[insets {:keys [container-opacity composer-elevation]} theme]
(reanimated/apply-animations-to-style
{:opacity container-opacity
:elevation composer-elevation}
(merge
{:border-top-left-radius border-top-radius
:border-top-right-radius border-top-radius
:padding-horizontal 20
:background-color (colors/theme-colors colors/white colors/neutral-95 theme)
:z-index 3
:padding-bottom (:bottom insets)}
(shadow theme))))
(def bar-container
{:height constants/bar-container-height
:left 0
@@ -54,65 +25,14 @@
:border-radius 100
:background-color (colors/theme-colors colors/neutral-100-opa-5 colors/white-opa-10 theme)})
(defn input-container
[height max-height]
(reanimated/apply-animations-to-style
{:height height}
{:max-height max-height
:z-index 1}))
(defn input-view
[{:keys [recording?]}]
{:overflow :hidden
:z-index 1
:flex 1
:display (if @recording? :none :flex)
:min-height constants/input-height})
(defn input-text
[{:keys [saved-emoji-kb-extra-height]}
{:keys [focused? maximized?]}
{:keys [max-height theme]}]
[theme]
(assoc typography/paragraph-1
:color (colors/theme-colors :black :white theme)
:text-align-vertical :top
:position (if @saved-emoji-kb-extra-height :relative :absolute)
:top 0
:left 0
:right (when (or focused? platform/ios?) 0)
:max-height max-height
:padding-bottom (when @maximized? 0)))
(defn background
[opacity background-y window-height]
(reanimated/apply-animations-to-style
{:opacity opacity
:transform [{:translate-y background-y}]}
{:position :absolute
:left 0
:right 0
:bottom 0
:height window-height
:background-color colors/neutral-95-opa-70}))
(defn blur-container
[composer-default-height {:keys [blur-container-elevation]}]
[{:elevation blur-container-elevation}
{:position :absolute
:left 0
:right 0
:bottom 0
:height composer-default-height
:border-top-right-radius border-top-radius
:border-top-left-radius border-top-radius
:overflow :hidden}])
(defn blur-view
[theme]
{:style {:flex 1}
:blur-radius (if platform/ios? 20 10)
:blur-type theme
:blur-amount 20})
:max-height 150))
(defn shell-button
[translate-y opacity]
@@ -1,48 +0,0 @@
(ns status-im.contexts.chat.messenger.composer.sub-view
(:require
[quo.core :as quo]
[react-native.core :as rn]
[react-native.reanimated :as reanimated]
[status-im.contexts.chat.messenger.composer.style :as style]
[status-im.feature-flags :as ff]
[utils.i18n :as i18n]
[utils.re-frame :as rf]
[utils.worklets.chat.messenger.composer :as worklets]))
(defn bar
[theme]
[rn/view {:style style/bar-container}
[rn/view {:style (style/bar theme)}]])
(defn- f-shell-button
[{:keys [composer-focused?]} chat-list-scroll-y window-height]
(let [customization-color (rf/sub [:profile/customization-color])
scroll-down-button-opacity (worklets/scroll-down-button-opacity
chat-list-scroll-y
composer-focused?
window-height)
jump-to-button-opacity (worklets/jump-to-button-opacity
scroll-down-button-opacity
composer-focused?)
jump-to-button-position (worklets/jump-to-button-position
scroll-down-button-opacity
composer-focused?)]
[rn/view {:style (style/shell-button-container)}
(when (ff/enabled? ::ff/shell.jump-to)
[reanimated/view
{:style (style/shell-button jump-to-button-position jump-to-button-opacity)}
[quo/floating-shell-button
{:jump-to
{:on-press #(rf/dispatch [:shell/navigate-to-jump-to])
:customization-color customization-color
:label (i18n/label :t/jump-to)
:style {:align-self :center}}}
{}]])
[quo/floating-shell-button
{:scroll-to-bottom {:on-press #(rf/dispatch [:chat.ui/scroll-to-bottom])}}
style/scroll-to-bottom-button
scroll-down-button-opacity]]))
(defn shell-button
[shared-values chat-list-scroll-y window-height]
[:f> f-shell-button shared-values chat-list-scroll-y window-height])
@@ -1,52 +1,8 @@
(ns status-im.contexts.chat.messenger.composer.utils
(:require
[clojure.string :as string]
[react-native.core :as rn]
[react-native.platform :as platform]
[react-native.reanimated :as reanimated]
[reagent.core :as reagent]
[status-im.contexts.chat.messenger.composer.constants :as constants]
[status-im.contexts.chat.messenger.composer.selection :as selection]
[utils.number]
[utils.re-frame :as rf]
[utils.worklets.chat.messenger.composer :as worklets]))
(defn bounded-val
[v min-v max-v]
(max min-v (min v max-v)))
(defn update-height?
[content-size height max-height]
(let [diff (Math/abs (- content-size (reanimated/get-shared-value height)))]
(and (not= (reanimated/get-shared-value height) max-height)
(> diff constants/content-change-threshold))))
(defn show-top-gradient?
[y lines max-lines gradient-opacity focused?]
(and
(> y constants/line-height)
(>= lines max-lines)
(= (reanimated/get-shared-value gradient-opacity) 0)
@focused?))
(defn hide-top-gradient?
[y gradient-opacity]
(and
(<= y constants/line-height)
(= (reanimated/get-shared-value gradient-opacity) 1)))
(defn show-bottom-gradient?
[{:keys [text-value focused?]} {:keys [lines]}]
(and (not-empty @text-value) (not @focused?) (> lines 2)))
(defn show-background?
[max-height new-height maximized?]
(or @maximized?
(> new-height (* constants/background-threshold max-height))))
(defn calc-lines
[height]
(Math/floor (/ height constants/line-height)))
[utils.re-frame :as rf]))
(defn calc-top-content-height
[reply? edit?]
@@ -60,35 +16,6 @@
(seq images) (+ constants/images-container-height)
link-previews? (+ constants/links-container-height)))
(defn calc-reopen-height
[text-value min-height max-height content-height saved-height]
(if (empty? @text-value)
min-height
(let [input-height (min @content-height
(reanimated/get-shared-value saved-height))]
(min max-height input-height))))
(defn get-min-height
[lines]
(if (> lines 1)
constants/multiline-minimized-height
constants/input-height))
(defn calc-max-height
[{:keys [reply edit images link-previews?]} window-height kb-height insets]
(let [margin-top (if platform/ios? (:top insets) (+ 10 (:top insets)))]
(- window-height
margin-top
kb-height
constants/bar-container-height
constants/actions-container-height
(calc-top-content-height reply edit)
(calc-bottom-content-height images link-previews?))))
(defn empty-input?
[{:keys [input-text images link-previews? reply audio edit]}]
(not (or (not-empty input-text) images link-previews? reply audio edit)))
(defn blur-input
[input-ref]
(when @input-ref
@@ -101,36 +28,17 @@
(rf/dispatch [:chat.ui/cancel-message-reply]))
(defn cancel-edit-message
[text-value input-ref input-height]
(reset! text-value "")
[input-ref]
;; NOTE: adding a timeout to assure the input is blurred on the next tick
;; after the `text-value` was cleared. Otherwise the height will be calculated
;; with the old `text-value`, leading to wrong composer height after blur.
(js/setTimeout
(fn []
(blur-input input-ref)
(reanimated/set-shared-value input-height constants/input-height))
(blur-input input-ref))
100)
(.setNativeProps ^js @input-ref (clj->js {:text ""}))
(rf/dispatch [:chat.ui/set-input-content-height constants/input-height])
(rf/dispatch [:chat.ui/cancel-message-edit]))
(defn count-lines
[s]
(-> s
(string/split #"\n" -1)
(butlast)
count))
(defn cursor-y-position-relative-to-container
[{:keys [scroll-y]}
{:keys [cursor-position text-value]}]
(let [sub-text (subs @text-value 0 @cursor-position)
sub-text-lines (count-lines sub-text)
scrolled-lines (Math/round (/ @scroll-y constants/line-height))
sub-text-lines-in-view (- sub-text-lines scrolled-lines)]
(* sub-text-lines-in-view constants/line-height)))
(defn calc-suggestions-position
[cursor-pos max-height size
{:keys [maximized?]}
@@ -153,85 +61,3 @@
(let [bottom-content-height (calc-bottom-content-height images link-previews?)]
(+ base bottom-content-height))
(+ constants/actions-container-height (:bottom insets) (- curr-height cursor-pos) 18)))))
(defn init-non-reactive-state
[]
{:input-ref (atom nil)
:selectable-input-ref (atom nil)
:keyboard-show-listener (atom nil)
:keyboard-frame-listener (atom nil)
:keyboard-hide-listener (atom nil)
:emoji-kb-extra-height (atom nil)
:saved-emoji-kb-extra-height (atom nil)
:sending-images? (atom false)
:sending-links? (atom false)
:record-reset-fn (atom nil)
:scroll-y (atom 0)
:selection-event (atom nil)
:selection-manager (rn/selectable-text-input-manager)})
(defn init-reactive-state
[]
{:text-value (reagent/atom "")
:cursor-position (reagent/atom 0)
:saved-cursor-position (reagent/atom 0)
:gradient-z-index (reagent/atom 0)
:kb-default-height (reagent/atom 0)
:kb-height (reagent/atom 0)
:gesture-enabled? (reagent/atom true)
:lock-selection? (reagent/atom true)
:focused? (reagent/atom false)
:lock-layout? (reagent/atom false)
:maximized? (reagent/atom false)
:record-permission? (reagent/atom true)
:recording? (reagent/atom false)
:first-level? (reagent/atom true)
:menu-items (reagent/atom selection/first-level-menu-items)})
(defn init-subs
[]
(let [chat-input (rf/sub [:chats/current-chat-input])]
{:images (seq (rf/sub [:chats/sending-image]))
:link-previews? (or (rf/sub [:chats/link-previews?])
(rf/sub [:chats/status-link-previews?]))
:audio (rf/sub [:chats/sending-audio])
:reply (rf/sub [:chats/reply-message])
:edit (rf/sub [:chats/edit-message])
:input-with-mentions (rf/sub [:chat/input-with-mentions])
:input-text (:input-text chat-input)
:alert-banners-top-margin (rf/sub [:alert-banners/top-margin])
:input-content-height (:input-content-height chat-input)}))
(defn init-shared-values
[]
(let [composer-focused? (reanimated/use-shared-value false)
empty-input-shared-value? (reanimated/use-shared-value true)]
{:composer-focused? composer-focused?
:empty-input? empty-input-shared-value?
:container-opacity (worklets/composer-container-opacity composer-focused?
empty-input-shared-value?
constants/empty-opacity)
:blur-container-elevation (worklets/blur-container-elevation composer-focused?
empty-input-shared-value?)
:composer-elevation (worklets/composer-elevation composer-focused?
empty-input-shared-value?)}))
(defn init-animations
[lines content-height max-height opacity background-y shared-values]
(let [initial-height (if (> lines 1)
constants/multiline-minimized-height
constants/input-height)
bottom-content-height 0]
(assoc shared-values
:gradient-opacity (reanimated/use-shared-value 0)
:height (reanimated/use-shared-value
initial-height)
:saved-height (reanimated/use-shared-value
initial-height)
:last-height (reanimated/use-shared-value
(utils.number/value-in-range
(+ @content-height bottom-content-height)
constants/input-height
max-height))
:opacity opacity
:background-y background-y)))
@@ -1,183 +1,56 @@
(ns status-im.contexts.chat.messenger.composer.view
(:require
[clojure.string :as string]
[quo.core :as quo]
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[react-native.gesture :as gesture]
[react-native.hooks :as hooks]
[react-native.platform :as platform]
[react-native.reanimated :as reanimated]
[reagent.core :as reagent]
[react-native.safe-area :as safe-area]
[status-im.contexts.chat.messenger.composer.actions.view :as actions]
[status-im.contexts.chat.messenger.composer.constants :as constants]
[status-im.contexts.chat.messenger.composer.edit.view :as edit]
[status-im.contexts.chat.messenger.composer.effects :as effects]
[status-im.contexts.chat.messenger.composer.gesture :as drag-gesture]
[status-im.contexts.chat.messenger.composer.gradients.view :as gradients]
[status-im.contexts.chat.messenger.composer.handlers :as handler]
[status-im.contexts.chat.messenger.composer.images.view :as images]
[status-im.contexts.chat.messenger.composer.link-preview.view :as link-preview]
[status-im.contexts.chat.messenger.composer.mentions.view :as mentions]
[status-im.contexts.chat.messenger.composer.reply.view :as reply]
[status-im.contexts.chat.messenger.composer.selection :as selection]
[status-im.contexts.chat.messenger.composer.style :as style]
[status-im.contexts.chat.messenger.composer.sub-view :as sub-view]
[status-im.contexts.chat.messenger.composer.utils :as utils]
[status-im.contexts.chat.messenger.messages.contact-requests.bottom-drawer.view :as
contact-requests.bottom-drawer]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn sheet-component
[{:keys [insets
chat-list-scroll-y
chat-screen-layout-calculations-complete?
opacity
background-y
theme
window-height]} props state shared-values]
(let [subscriptions (utils/init-subs)
top-margin (if (pos? (:alert-banners-top-margin subscriptions))
;; top margin increased to avoid composer overlapping with the
;; alert banner
(+ (:alert-banners-top-margin subscriptions) 12)
0)
window-height (- window-height top-margin)
content-height (reagent/atom (or (:input-content-height ; Actual text height
subscriptions)
constants/input-height))
{:keys [keyboard-shown]} (hooks/use-keyboard)
max-height (utils/calc-max-height subscriptions ; Max allowed height for the
; composer view
window-height
@(:kb-height state)
insets)
lines (utils/calc-lines (- @content-height constants/extra-content-offset)) ; Current
; lines
; count
;; Maximum number of lines that can be displayed when composer in maximized
max-lines (utils/calc-lines max-height)
animations (utils/init-animations
lines
content-height
max-height
opacity
background-y
shared-values)
dimensions {:content-height content-height
:max-height max-height
:window-height window-height
:lines lines
:max-lines max-lines}
show-bottom-gradient? (utils/show-bottom-gradient? state dimensions)
;; Cursor position, needed to determine where to display the mentions view
cursor-pos (utils/cursor-y-position-relative-to-container
props
state)]
(effects/did-mount props)
(effects/initialize props
state
animations
dimensions
subscriptions)
(effects/use-edit props state subscriptions chat-screen-layout-calculations-complete?)
(effects/use-reply props subscriptions chat-screen-layout-calculations-complete?)
(effects/update-input-mention props state subscriptions)
(effects/link-previews props state animations subscriptions)
(effects/use-images props state animations subscriptions)
[:<>
[mentions/view props state animations max-height cursor-pos
(:images subscriptions)
(:link-previews? subscriptions)
(:reply subscriptions)
(:edit subscriptions)]
[rn/view
{:style style/composer-sheet-and-jump-to-container}
[sub-view/shell-button shared-values chat-list-scroll-y window-height]
[gesture/gesture-detector
{:gesture
(drag-gesture/drag-gesture props state animations dimensions keyboard-shown)}
[reanimated/view
{:style (style/sheet-container insets animations theme)}
[sub-view/bar theme]
[:<>
[reply/view state (:input-ref props)]
[edit/view
{:text-value (:text-value state)
:input-height (:height animations)
:input-ref (:input-ref props)}]]
[reanimated/touchable-opacity
{:active-opacity 1
:on-press (fn []
(when-let [ref @(:input-ref props)]
(.focus ^js ref)))
:style (style/input-container (:height animations) max-height)
:accessibility-label :message-input-container}
[rn/selectable-text-input
{:ref #(reset! (:selectable-input-ref props) %)
:menu-items @(:menu-items state)
:style (style/input-view state)}
[rn/text-input
{:ref #(reset! (:input-ref props) %)
:default-value @(:text-value state)
:on-focus #(handler/focus props state animations dimensions)
:on-blur #(handler/blur state animations dimensions)
:on-content-size-change #(handler/content-size-change %
state
animations
dimensions
(or keyboard-shown
(:edit subscriptions)))
:on-scroll #(handler/scroll % props state animations dimensions)
:on-change-text #(handler/change-text % props state)
:on-selection-change #(handler/selection-change % props state)
:on-selection #(selection/on-selection % props state)
:keyboard-appearance theme
:max-font-size-multiplier 1
:multiline true
:placeholder (i18n/label :t/type-something)
:placeholder-text-color (colors/theme-colors colors/neutral-40 colors/neutral-50 theme)
:style (style/input-text props
state
{:max-height max-height
:theme theme})
:max-length constants/max-text-size
:accessibility-label :chat-message-input}]]]
[:<>
[gradients/view props state animations show-bottom-gradient?]
[link-preview/view]
[images/images-list]]
[:f> actions/view props state animations window-height subscriptions]]]]]))
(defn input
[_ _]
(let [default-value (:input-text (rf/sub [:chats/current-chat-input]))]
(fn [set-ref theme]
[rn/text-input
{:ref set-ref
:on-change-text handler/change-text
:keyboard-appearance theme
:max-font-size-multiplier 1
:multiline true
:placeholder (i18n/label :t/type-something)
:placeholder-text-color (colors/theme-colors colors/neutral-40 colors/neutral-50 theme)
:max-length constants/max-text-size
:accessibility-label :chat-message-input
:style (style/input-text theme)
:default-value default-value}])))
(defn f-composer
(defn view
[props]
(let [theme (quo.theme/use-theme)
opacity (reanimated/use-shared-value 0)
window-height (:height (rn/get-window))
background-y (reanimated/use-shared-value (- window-height))
composer-default-height (+ constants/composer-default-height (:bottom (:insets props)))
shared-values (utils/init-shared-values)
extra-params (assoc props
:window-height window-height
:opacity opacity
:background-y background-y
:theme theme)
props (utils/init-non-reactive-state)
state (utils/init-reactive-state)]
[rn/view (when platform/ios? {:style {:z-index 1}})
[reanimated/view {:style (style/background opacity background-y window-height)}]
[reanimated/view {:style (style/blur-container composer-default-height shared-values)}
[quo/blur (style/blur-view theme)]]
[:f> sheet-component extra-params props state shared-values]]))
(defn composer
[props]
(let [current-chat-id (rf/sub [:chats/current-chat-id])
able-to-send-message? (rf/sub [:chats/able-to-send-message?])]
(when-not (string/blank? current-chat-id)
(if able-to-send-message?
[:f> f-composer props]
[contact-requests.bottom-drawer/view
{:contact-id current-chat-id}]))))
(let [theme (quo.theme/use-theme)
bottom (safe-area/get-bottom)
input-ref (rn/use-ref-atom nil)
set-ref (rn/use-callback (fn [value]
(rf/dispatch [:chat/set-input-ref value])
(reset! input-ref value)))]
[rn/view {:style {:margin-bottom bottom}}
[mentions/view props]
[quo/separator]
[rn/view {:style {:padding-horizontal 20 :padding-top 20}}
[:<>
[reply/view input-ref]
[edit/view input-ref]]
[input set-ref theme]
[:<>
[link-preview/view]
[images/images-list]]
[actions/view input-ref]]]))
@@ -97,7 +97,7 @@
[{:keys [outgoing content pinned-by outgoing-status deleted? deleted-for-me? content-type
bridge-message]
:as message-data}
{:keys [able-to-send-message? community? can-delete-message-for-everyone?
{:keys [able-to-send-message? community? community-member? can-delete-message-for-everyone?
message-pin-enabled group-chat group-admin?]}]
(concat
(when (and outgoing
@@ -131,7 +131,8 @@
:id :copy}])
;; pinning images are temporarily disabled
(when (and message-pin-enabled
(not= content-type constants/content-type-image))
(not= content-type constants/content-type-image)
(or community-member? (not community?)))
[{:type :main
:on-press #(pin-message message-data)
:label (i18n/label (if pinned-by
@@ -5,6 +5,8 @@
[react-native.reanimated :as reanimated]
[status-im.contexts.chat.messenger.messages.constants :as messages.constants]))
(def permission-context-height 46)
(defn keyboard-avoiding-container
[theme]
{:flex 1
@@ -73,3 +75,8 @@
(reanimated/apply-animations-to-style
{:top top}
{:row-gap 16}))
(defn permission-context-sheet
[margin-bottom?]
{:flex 3 ;; Pushes composer to bottom
:margin-bottom (when margin-bottom? permission-context-height)})
@@ -26,9 +26,7 @@
[utils.worklets.chat.messenger.messages :as worklets]))
(defonce ^:const distance-from-last-message 4)
(defonce ^:const loading-indicator-extra-spacing 250)
(defonce ^:const loading-indicator-page-loading-height 100)
(defonce ^:const min-message-height 32)
(defn list-key-fn [{:keys [message-id value]}] (or message-id value))
(defn list-ref [ref] (reset! state/messages-list-ref ref))
@@ -336,8 +334,7 @@
(defn list-group-chat-header
[{:keys [chat-id invitation-admin]}]
[rn/view
[chat.group/group-chat-footer chat-id invitation-admin]])
[chat.group/group-chat-footer chat-id invitation-admin])
(defn render-fn
[{:keys [type value] :as message-data} _ _
@@ -400,18 +397,18 @@
{:keys [keyboard-shown]} (hooks/use-keyboard)
{window-height :height} (rn/get-window)
context (rf/sub [:chats/current-chat-message-list-view-context])
able-to-send-message? (:able-to-send-message? context)
messages (rf/sub [:chats/raw-chat-messages-stream chat-id])
margin-bottom? (and community-channel? (not able-to-send-message?))
recording? (rf/sub [:chats/recording?])]
[rn/view {:style {:flex 3}} ;; Pushes composer to bottom
[rn/view {:style (style/permission-context-sheet margin-bottom?)}
[rn/view {:style {:flex-shrink 1}} ;; Keeps flat list on top
[reanimated/flat-list
{:key-fn list-key-fn
:ref list-ref
:bounces false
:header [:<>
[list-header insets (:able-to-send-message? context)]
(when (= (:chat-type chat) constants/private-group-chat-type)
[list-group-chat-header chat])]
:header (when (= (:chat-type chat) constants/private-group-chat-type)
[list-group-chat-header chat])
:footer [list-footer
{:theme theme
:chat chat
@@ -431,15 +428,11 @@
:distance-from-list-top distance-from-list-top})
:on-end-reached #(list-on-end-reached distance-from-list-top)
:on-scroll-to-index-failed identity
:scroll-indicator-insets {:top (if (:able-to-send-message? context)
(- composer.constants/composer-default-height 16)
0)
:right 1}
:keyboard-dismiss-mode :interactive
:keyboard-should-persist-taps :always
:on-scroll-begin-drag #(do
(rf/dispatch [:chat.ui/set-input-focused false])
(rn/dismiss-keyboard!))
:on-scroll-begin-drag (fn []
(rf/dispatch [:chat.ui/set-input-focused false])
(rn/dismiss-keyboard!))
:on-momentum-scroll-begin state/start-scrolling
:on-momentum-scroll-end state/stop-scrolling
:scroll-event-throttle 16
@@ -461,4 +454,5 @@
:chat-screen-layout-calculations-complete?
chat-screen-layout-calculations-complete?})
:scroll-enabled (not recording?)
:content-inset-adjustment-behavior :never}]]]))
:content-inset-adjustment-behavior :never
:scroll-indicator-insets {:right 1}}]]]))
@@ -5,12 +5,13 @@
(defn navigation-view
[navigation-view-height pinned-banner-height]
{:top 0
:left 0
:right 0
:position :absolute
:height (+ navigation-view-height pinned-banner-height)
:z-index 1})
{:top 0
:left 0
:right 0
:position :absolute
:pointer-events :box-none
:height (+ navigation-view-height pinned-banner-height)
:z-index 1})
(defn animated-background-view
[background-opacity navigation-view-height]
@@ -0,0 +1,11 @@
(ns status-im.contexts.chat.messenger.messages.scroll-to-bottom.style
(:require [status-im.contexts.shell.jump-to.constants :as shell.constants]))
(def shell-button-container
{:z-index 1
:bottom shell.constants/floating-shell-button-height})
(def scroll-to-bottom-button
{:position :absolute
:right 0
:left 0})
@@ -0,0 +1,20 @@
(ns status-im.contexts.chat.messenger.messages.scroll-to-bottom.view
(:require
[quo.core :as quo]
[react-native.core :as rn]
[status-im.contexts.chat.messenger.messages.scroll-to-bottom.style :as style]
[utils.re-frame :as rf]
[utils.worklets.chat.messenger.composer :as worklets]))
(defn button
[{:keys [chat-list-scroll-y]}]
(let [{window-height :height} (rn/get-window)
scroll-down-button-opacity (worklets/scroll-down-button-opacity
chat-list-scroll-y
false
window-height)]
[rn/view {:style style/shell-button-container}
[quo/floating-shell-button
{:scroll-to-bottom {:on-press #(rf/dispatch [:chat.ui/scroll-to-bottom])}}
style/scroll-to-bottom-button
scroll-down-button-opacity]]))
@@ -1,19 +1,32 @@
(ns status-im.contexts.chat.messenger.messages.view
(:require
[clojure.string :as string]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[react-native.platform :as platform]
[react-native.reanimated :as reanimated]
[react-native.safe-area :as safe-area]
[reagent.core :as reagent]
[status-im.contexts.chat.messenger.composer.view :as composer.view]
[status-im.contexts.chat.messenger.composer.view :as composer]
[status-im.contexts.chat.messenger.messages.contact-requests.bottom-drawer.view :as
contact-requests.bottom-drawer]
[status-im.contexts.chat.messenger.messages.list.style :as style]
[status-im.contexts.chat.messenger.messages.list.view :as list.view]
[status-im.contexts.chat.messenger.messages.navigation.view :as messages.navigation]
[status-im.contexts.chat.messenger.messages.scroll-to-bottom.view :as scroll-to-bottom]
[status-im.contexts.chat.messenger.placeholder.view :as placeholder.view]
[status-im.feature-flags :as ff]
[utils.re-frame :as rf]))
(defn- footer
[props]
(let [current-chat-id (rf/sub [:chats/current-chat-id])
able-to-send-message? (rf/sub [:chats/able-to-send-message?])]
(when-not (string/blank? current-chat-id)
(if able-to-send-message?
[composer/view props]
[contact-requests.bottom-drawer/view {:contact-id current-chat-id}]))))
(defn- chat-screen
[{:keys [insets] :as props}]
(let [theme (quo.theme/use-theme)
@@ -23,9 +36,11 @@
[rn/keyboard-avoiding-view
{:style (style/keyboard-avoiding-container theme)
:keyboard-vertical-offset (- (if platform/ios? alert-banners-top-margin 0) (:bottom insets))}
[list.view/messages-list-content props]
[:<>
[list.view/messages-list-content props]
[scroll-to-bottom/button props]]
[messages.navigation/view props]
[composer.view/composer props]])))
[footer props]])))
(defn lazy-chat-screen
[chat-screen-layout-calculations-complete? *screen-loaded?*]
@@ -8,7 +8,8 @@
(h/test "joined options - Non token Gated"
(h/setup-subs {:communities/my-pending-request-to-join nil
:communities/community {:joined true}})
:communities/community {:joined true}
:profile/test-networks-enabled? false})
(h/render [options/community-options-bottom-sheet {:id "test"}])
(h/is-truthy (h/get-by-translation-text :t/view-members))
(h/is-truthy (h/get-by-translation-text :t/view-community-rules))
@@ -23,7 +24,8 @@
(h/test "joined options - Token Gated"
(h/setup-subs {:communities/my-pending-request-to-join nil
:communities/community {:joined true
:role-permissions? true}})
:role-permissions? true}
:profile/test-networks-enabled? false})
(h/render [options/community-options-bottom-sheet {:id "test"}])
(h/is-truthy (h/get-by-translation-text :t/view-members))
(h/is-truthy (h/get-by-translation-text :t/view-community-rules))
@@ -38,7 +40,8 @@
(h/test "admin options - Non token Gated"
(h/setup-subs {:communities/my-pending-request-to-join nil
:communities/community {:admin true}})
:communities/community {:admin true}
:profile/test-networks-enabled? false})
(h/render [options/community-options-bottom-sheet {:id "test"}])
(h/is-truthy (h/get-by-translation-text :t/view-members))
(h/is-truthy (h/get-by-translation-text :t/view-community-rules))
@@ -52,7 +55,8 @@
(h/test "admin options - Token Gated"
(h/setup-subs {:communities/my-pending-request-to-join nil
:communities/community {:admin true
:role-permissions? true}})
:role-permissions? true}
:profile/test-networks-enabled? false})
(h/render [options/community-options-bottom-sheet {:id "test"}])
(h/is-truthy (h/get-by-translation-text :t/view-members))
(h/is-truthy (h/get-by-translation-text :t/view-community-rules))
@@ -65,7 +69,8 @@
(h/test "request sent options - Non token Gated"
(h/setup-subs {:communities/my-pending-request-to-join "mock-id"
:communities/community {}})
:communities/community {}
:profile/test-networks-enabled? false})
(h/render [options/community-options-bottom-sheet {:id "test"}])
(h/is-truthy (h/get-by-translation-text :t/view-members))
(h/is-truthy (h/get-by-translation-text :t/view-community-rules))
@@ -76,7 +81,8 @@
(h/test "request sent options - Token Gated"
(h/setup-subs {:communities/my-pending-request-to-join "mock-id"
:communities/community {:role-permissions? true}})
:communities/community {:role-permissions? true}
:profile/test-networks-enabled? false})
(h/render [options/community-options-bottom-sheet {:id "test"}])
(h/is-truthy (h/get-by-translation-text :t/invite-people-from-contacts))
;(h/is-truthy (h/get-by-translation-text :t/view-token-gating))
@@ -86,7 +92,8 @@
(h/test "banned options - Non token Gated"
(h/setup-subs {:communities/my-pending-request-to-join nil
:communities/community {:banList true}})
:communities/community {:banList true}
:profile/test-networks-enabled? false})
(h/render [options/community-options-bottom-sheet {:id "test"}])
(h/is-truthy (h/get-by-translation-text :t/view-members))
(h/is-truthy (h/get-by-translation-text :t/view-community-rules))
@@ -97,7 +104,8 @@
(h/test "banned options - Token Gated"
(h/setup-subs {:communities/my-pending-request-to-join nil
:communities/community {:banList 100
:role-permissions? true}})
:role-permissions? true}
:profile/test-networks-enabled? false})
(h/render [options/community-options-bottom-sheet {:id "test"}])
(h/is-truthy (h/get-by-translation-text :t/invite-people-from-contacts))
;(h/is-truthy (h/get-by-translation-text :t/view-token-gating))
@@ -107,7 +115,8 @@
(h/test "banned options - Token Gated"
(h/setup-subs {:communities/my-pending-request-to-join nil
:communities/community {:banList 100
:role-permissions? true}})
:role-permissions? true}
:profile/test-networks-enabled? false})
(h/render [options/community-options-bottom-sheet {:id "test"}])
(h/is-truthy (h/get-by-translation-text :t/invite-people-from-contacts))
;(h/is-truthy (h/get-by-translation-text :t/view-token-gating))
@@ -118,6 +127,7 @@
(h/setup-subs {:communities/my-pending-request-to-join nil
:communities/community {:joined true
:muted true
:role-permissions? true}})
:role-permissions? true}
:profile/test-networks-enabled? false})
(h/render [options/community-options-bottom-sheet {:id "test"}])
(h/is-truthy (h/get-by-translation-text :t/unmute-community))))
@@ -36,14 +36,13 @@
(defn view-token-gating
[id]
(when config/show-not-implemented-features?
{:icon :i/token
:right-icon :i/chevron-right
:accessibility-label :view-token-gating
:on-press #(rf/dispatch [:show-bottom-sheet
{:content (fn [] [permissions-sheet/view id])
:padding-bottom-override 16}])
:label (i18n/label :t/view-token-gating)}))
{:icon :i/token
:right-icon :i/chevron-right
:accessibility-label :view-token-gating
:on-press #(rf/dispatch [:show-bottom-sheet
{:content (fn [] [permissions-sheet/view id])
:padding-bottom-override 16}])
:label (i18n/label :t/view-token-gating)})
(defn edit-shared-addresses
[id]
@@ -56,7 +55,7 @@
(defn mark-as-read
[id]
{:icon :i/up-to-date
{:icon :i/mark-as-read
:accessibility-label :mark-as-read
:label (i18n/label :t/mark-as-read)
:on-press #(hide-sheet-and-dispatch [:chat.ui/mark-all-read-in-community-pressed id])})
@@ -136,23 +135,27 @@
request-id])}])})
(defn not-joined-options
[id token-gated? intro-message]
[[(when-not token-gated? (view-members id))
(when-not token-gated? (view-rules id intro-message))
(mark-as-read id)
(invite-contacts id)
(when token-gated? (view-token-gating id))
(show-qr id)
(share-community id)]])
[id token-gated? intro-message test-networks-enabled?]
(let [common [(show-qr id) (share-community id)]
specific (cond
(and token-gated? (not test-networks-enabled?))
[(invite-contacts id) (view-token-gating id)]
token-gated?
[(invite-contacts id)]
(not token-gated?)
[(view-members id) (view-rules id intro-message) (invite-contacts id)])]
[(concat specific common)]))
(defn join-request-sent-options
[id token-gated? request-id intro-message]
[(conj (first (not-joined-options id token-gated? intro-message))
[id token-gated? request-id intro-message test-networks-enabled?]
[(conj (first (not-joined-options id token-gated? intro-message test-networks-enabled?))
(assoc (cancel-request-to-join id request-id) :add-divider? true))])
(defn banned-options
[id token-gated? intro-message]
(not-joined-options id token-gated? intro-message))
[id token-gated? intro-message test-networks-enabled?]
(not-joined-options id token-gated? intro-message test-networks-enabled?))
(defn joined-options
[id token-gated? muted? muted-till color intro-message]
@@ -186,13 +189,19 @@
(let [{:keys [role-permissions? admin joined
muted banList muted-till color
intro-message]} (rf/sub [:communities/community id])
request-id (rf/sub [:communities/my-pending-request-to-join id])]
request-id (rf/sub [:communities/my-pending-request-to-join id])
test-networks? (rf/sub [:profile/test-networks-enabled?])]
(cond
admin (owner-options id role-permissions? muted muted-till intro-message)
joined (joined-options id role-permissions? muted muted-till color intro-message)
request-id (join-request-sent-options id role-permissions? request-id intro-message)
banList (banned-options id role-permissions? intro-message)
:else (not-joined-options id role-permissions? intro-message))))
admin
(owner-options id role-permissions? muted muted-till intro-message)
joined
(joined-options id role-permissions? muted muted-till color intro-message)
request-id
(join-request-sent-options id role-permissions? request-id intro-message test-networks?)
banList
(banned-options id role-permissions? intro-message test-networks?)
:else
(not-joined-options id role-permissions? intro-message test-networks?))))
(defn community-options-bottom-sheet
[id]
+36 -29
View File
@@ -2,6 +2,7 @@
(:require
[clojure.string :as string]
[legacy.status-im.data-store.communities :as data-store.communities]
[oops.core :as oops]
[schema.core :as schema]
[status-im.constants :as constants]
[status-im.contexts.chat.messenger.messages.link-preview.events :as link-preview.events]
@@ -25,7 +26,7 @@
token-permissions-check id last-opened-at]
:as community} (data-store.communities/<-rpc community-js)
previous-last-opened-at (get-in db [:communities id :last-opened-at])]
(when (>= clock (get-in db [:communities id :clock]))
(when (and id (>= clock (get-in db [:communities id :clock])))
{:db (assoc-in db
[:communities id]
(assoc community :last-opened-at (max last-opened-at previous-last-opened-at)))
@@ -223,14 +224,14 @@
:fx [[:dispatch [:communities/check-permissions-to-join-community community-id]]]})))
(defn community-fetched
[{:keys [db]} [community-id community]]
(when community
[{:keys [db]} [community-id community-js]]
(when community-js
{:db (update db :communities/fetching-communities dissoc community-id)
:fx [[:dispatch [:communities/handle-community community]]
:fx [[:dispatch [:communities/handle-community community-js]]
[:dispatch [:chat.ui/spectate-community community-id]]
[:dispatch
[:chat.ui/cache-link-preview-data (link-preview.events/community-link community-id)
community]]]}))
(data-store.communities/<-rpc community-js)]]]}))
(rf/reg-event-fx :chat.ui/community-fetched community-fetched)
@@ -253,18 +254,19 @@
(not (get-in db [:communities community-id]))
(not (get-in db [:communities/fetching-communities community-id])))
{:db (assoc-in db [:communities/fetching-communities community-id] true)
:json-rpc/call [{:method "wakuext_fetchCommunity"
:params [{:CommunityKey community-id
:TryDatabase true
:WaitForResponse true}]
:on-success (fn [community]
(if community
(rf/dispatch [:chat.ui/community-fetched community-id
community])
(failed-to-fetch-community
community-id
"community wasn't found at the store node")))
:on-error (partial failed-to-fetch-community community-id)}]}))
:json-rpc/call [{:method "wakuext_fetchCommunity"
:params [{:CommunityKey community-id
:TryDatabase true
:WaitForResponse true}]
:js-response true
:on-success (fn [community]
(if community
(rf/dispatch [:chat.ui/community-fetched community-id
community])
(failed-to-fetch-community
community-id
"community wasn't found at the store node")))
:on-error (partial failed-to-fetch-community community-id)}]}))
(schema/=> fetch-community
[:=>
@@ -283,11 +285,15 @@
(rf/reg-event-fx :communities/fetch-community fetch-community)
(defn spectate-community-success
[{:keys [db]} [{:keys [communities]}]]
(when-let [community (first communities)]
{:db (-> db
(assoc-in [:communities (:id community) :spectated] true))
:fx [[:dispatch [:communities/handle-community community]]]}))
[{:keys [db]} [response-js]]
(when response-js
(let [communities (oops/oget response-js "communities")
community (first communities)
community-id (when community (oops/oget community "id"))]
(when community
{:db (-> db
(assoc-in [:communities community-id :spectated] true))
:fx [[:dispatch [:communities/handle-community community]]]}))))
(rf/reg-event-fx :chat.ui/spectate-community-success spectate-community-success)
@@ -295,13 +301,14 @@
[{:keys [db]} [community-id]]
(let [{:keys [spectated joined]} (get-in db [:communities community-id])]
(when (and (not joined) (not spectated))
{:json-rpc/call [{:method "wakuext_spectateCommunity"
:params [community-id]
:on-success [:chat.ui/spectate-community-success]
:on-error (fn [err]
(log/error {:message
"Failed to spectate community"
:error err}))}]})))
{:json-rpc/call [{:method "wakuext_spectateCommunity"
:params [community-id]
:js-response true
:on-success [:chat.ui/spectate-community-success]
:on-error (fn [err]
(log/error {:message
"Failed to spectate community"
:error err}))}]})))
(schema/=> spectate-community
[:=>
@@ -1,5 +1,6 @@
(ns status-im.contexts.communities.events-test
(:require [cljs.test :refer [deftest is testing]]
[legacy.status-im.data-store.communities :as data-store.communities]
matcher-combinators.test
[status-im.contexts.chat.messenger.messages.link-preview.events :as link-preview.events]
[status-im.contexts.communities.events :as events]))
@@ -51,7 +52,7 @@
[:dispatch [:chat.ui/spectate-community community-id]]
[:dispatch
[:chat.ui/cache-link-preview-data "community-link+community-id"
{:id community-id}]]]}
(data-store.communities/<-rpc #js {"id" community-id})]]]}
(events/community-fetched cofx arg))))))
(testing "given a joined community"
(let [cofx {:db {:communities/fetching-communities {community-id true}}}
@@ -62,7 +63,7 @@
[:dispatch [:chat.ui/spectate-community community-id]]
[:dispatch
[:chat.ui/cache-link-preview-data "community-link+community-id"
{:id community-id}]]]}
(data-store.communities/<-rpc #js {"id" community-id})]]]}
(events/community-fetched cofx arg))))))
(testing "given a token-gated community"
(let [cofx {:db {:communities/fetching-communities {community-id true}}}
@@ -73,7 +74,7 @@
[:dispatch [:chat.ui/spectate-community community-id]]
[:dispatch
[:chat.ui/cache-link-preview-data "community-link+community-id"
{:id community-id}]]]}
(data-store.communities/<-rpc #js {"id" community-id})]]]}
(events/community-fetched cofx arg))))))
(testing "given nil community"
(testing "do nothing"
@@ -103,26 +104,38 @@
(events/spectate-community {:db {:communities {community-id {}}}} [community-id]))))))
(deftest spectate-community-success-test
(testing "given communities"
(testing "mark first community spectated true"
(is (match?
{:db {:communities {community-id {:spectated true}}}}
(events/spectate-community-success {} [{:communities [{:id community-id}]}]))))
(testing "dispatch fxs for first community"
(is (match?
{:fx [[:dispatch [:communities/handle-community {:id community-id}]]
]}
(events/spectate-community-success {} [{:communities [{:id community-id}]}])))))
(testing "given empty community"
(testing "do nothing"
(is (match?
nil
(events/spectate-community-success {} [{:communities []}])))))
(testing "given nil community"
(testing "do nothing"
(is (match?
nil
(events/spectate-community-success {} []))))))
(let [community-id-1 1
community-id-2 2]
(testing "given communities"
(testing "mark first community spectated true"
(is (match?
{:db {:communities {community-id-1 {:spectated true}}}}
(events/spectate-community-success {}
(clj->js [{:communities [{:id community-id-1}
{:id community-id-2}]}])))))
(testing "dispatch fxs for first community"
;; Convert the JavaScript object back to a Clojure map because `match?` does not work well
;; with JavaScript objects.
(is (match?
{:fx [[:dispatch [:communities/handle-community {:id community-id-1}]]]}
(let [result (events/spectate-community-success {}
(clj->js [{:communities
[{:id community-id-1}
{:id community-id-2}]}]))]
(update-in result [:fx 0 1] #(js->clj % :keywordize-keys true)))))))
(testing "given empty community"
(testing "do nothing"
(is (match?
nil
(events/spectate-community-success {}
(clj->js [{:communities []}]))))))
(testing "given nil community"
(testing "do nothing"
(is (match?
nil
(events/spectate-community-success {}
(clj->js []))))))))
(deftest get-revealed-accounts-test
(let [community {:id community-id}]
@@ -47,8 +47,8 @@
(- window-height
(* 2 56) ;; two other list items
(* 2 16) ;; spacing between items
220) ;; extra spacing (top bar)
)
220)) ;; extra spacing (top bar)
(defn- create-profile-option-card
[window-height]
@@ -209,9 +209,10 @@
(when same-passwords?
[rn/view {:style style/disclaimer-container}
[quo/disclaimer
{:blur? true
:on-change (partial set-accepts-disclaimer? not)
:checked? accepts-disclaimer?}
{:blur? true
:on-change (partial set-accepts-disclaimer? not)
:checked? accepts-disclaimer?
:customization-color user-color}
(i18n/label :t/password-creation-disclaimer)]])
(when (and (= focused-input :password) (not same-passwords?))
[help

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