Compare commits

..
Author SHA1 Message Date
frank f9fdd5c671 fix_: random empty account 2024-09-11 19:31:03 +08:00
152 changed files with 2232 additions and 2353 deletions
-1
View File
@@ -36,4 +36,3 @@ 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,4 +40,3 @@ 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,4 +37,3 @@ 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,4 +24,3 @@ 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,4 +21,3 @@ 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.30.0
2.29.0
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env groovy
library 'status-jenkins-lib@v1.9.10'
library 'status-jenkins-lib@v1.9.9'
/* 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.10'
library 'status-jenkins-lib@v1.9.9'
import groovy.json.JsonBuilder
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env groovy
library 'status-jenkins-lib@v1.9.10'
library 'status-jenkins-lib@v1.9.9'
pipeline {
agent { label 'linux' }
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env groovy
library 'status-jenkins-lib@v1.9.10'
library 'status-jenkins-lib@v1.9.9'
/* 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.10'
library 'status-jenkins-lib@v1.9.9'
/* 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.10'
library 'status-jenkins-lib@v1.9.9'
pipeline {
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env groovy
library 'status-jenkins-lib@v1.9.10'
library 'status-jenkins-lib@v1.9.9'
pipeline {
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env groovy
library 'status-jenkins-lib@v1.9.10'
library 'status-jenkins-lib@v1.9.9'
pipeline {
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env groovy
library 'status-jenkins-lib@v1.9.10'
library 'status-jenkins-lib@v1.9.9'
pipeline {
agent { label 'macos' }
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env groovy
library 'status-jenkins-lib@v1.9.10'
library 'status-jenkins-lib@v1.9.9'
pipeline {
agent { label params.AGENT_LABEL }
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env groovy
library 'status-jenkins-lib@v1.9.10'
library 'status-jenkins-lib@v1.9.9'
pipeline {
agent { label 'linux' }
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env groovy
library 'status-jenkins-lib@v1.9.10'
library 'status-jenkins-lib@v1.9.9'
pipeline {
agent {
@@ -25,18 +25,13 @@ class LogManager(private val reactContext: ReactApplicationContext) : ReactConte
override fun getName() = "LogManager"
private fun getRequestLogFile(): File {
val pubDirectory = utils.getPublicStorageDirectory()
return File(pubDirectory, requestsLogFileName)
}
private fun getGethLogFile(): File {
private fun getLogsFile(): File {
val pubDirectory = utils.getPublicStorageDirectory()
return File(pubDirectory, gethLogFileName)
}
fun prepareLogsFile(context: Context): File? {
val logFile = getGethLogFile()
val logFile = utils.getLogsFile()
try {
logFile.setReadable(true)
@@ -154,8 +149,7 @@ class LogManager(private val reactContext: ReactApplicationContext) : ReactConte
val zipFile = File(logsTempDir, logsZipFileName)
val statusLogFile = File(logsTempDir, statusLogFileName)
val gethLogFile = getGethLogFile()
val requestLogFile = getRequestLogFile()
val gethLogFile = getLogsFile()
try {
if (zipFile.exists() || zipFile.createNewFile()) {
@@ -171,11 +165,7 @@ class LogManager(private val reactContext: ReactApplicationContext) : ReactConte
dumpAdbLogsTo(FileOutputStream(statusLogFile))
val errorList = Stack<String>()
val filesToZip = mutableListOf(dbFile, gethLogFile, statusLogFile)
if (requestLogFile.exists()) {
filesToZip.add(requestLogFile)
}
val zipped = zip(filesToZip.toTypedArray(), zipFile, errorList)
val zipped = zip(arrayOf(dbFile, gethLogFile, statusLogFile), zipFile, errorList)
if (zipped && zipFile.exists()) {
zipFile.setReadable(true, false)
val extUri = FileProvider.getUriForFile(context, "${context.packageName}.provider", zipFile)
@@ -196,14 +186,12 @@ class LogManager(private val reactContext: ReactApplicationContext) : ReactConte
}
@ReactMethod
fun initLogging(enabled: Boolean, mobileSystem: Boolean, logLevel: String, logRequestGo: Boolean, callback: Callback) {
fun initLogging(enabled: Boolean, mobileSystem: Boolean, logLevel: String, callback: Callback) {
val jsonConfig = JSONObject().apply {
put("Enabled", enabled)
put("MobileSystem", mobileSystem)
put("Level", logLevel)
put("File", getGethLogFile().absolutePath)
put("LogRequestGo", logRequestGo)
put("LogRequestFile", getRequestLogFile().absolutePath)
put("File", getLogsFile().absolutePath)
}
val config = jsonConfig.toString()
utils.executeRunnableStatusGoMethod({ Statusgo.initLogging(config) }, callback)
@@ -218,7 +206,6 @@ 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,6 +17,7 @@ import statusgo.Statusgo
class Utils(private val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
companion object {
private const val gethLogFileName = "geth.log"
private const val TAG = "Utils"
}
@@ -39,6 +40,11 @@ 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,8 +57,6 @@ 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];
@@ -67,10 +65,6 @@ 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];
@@ -81,20 +75,17 @@ 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];
Binary file not shown.

Before

Width:  |  Height:  |  Size: 722 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

+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/${1}" | cut -f1)
STATUS_GO_TAG_SHA1=$(echo "${STATUS_GO_MATCHING_REFS}" | grep 'refs/tags' | cut -f1)
STATUS_GO_BRANCH_SHA1=$(echo "${STATUS_GO_MATCHING_REFS}" | grep 'refs/heads' | cut -f1)
# Prefer tag over branch if both are found
+44 -36
View File
@@ -1,7 +1,8 @@
(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]
@@ -58,20 +59,18 @@
(defn- transfer-mention-result
[result]
(let [{:keys [input-segments mentionable-users state chat-id new-text call-id]}
(let [{:keys [input-segments mentionable-users state chat-id new-text]}
(set/rename-keys result
{:InputSegments :input-segments
:MentionSuggestions :mentionable-users
:MentionState :state
:ChatID :chat-id
:NewText :new-text
:CallID :call-id})]
:NewText :new-text})]
{: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
:call-id call-id}))
:new-text new-text}))
(rf/defn on-error
{:events [:mention/on-error]}
@@ -92,25 +91,23 @@
: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 [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]}))
(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))}))
(rf/defn on-change-text
{:events [:mention/on-change-text]}
[{:keys [db]} text]
(let [chat-id (:current-chat-id db)
call-id (inc (get-in db [:chat/inputs chat-id :current-mentions-call-id] 0))
params [chat-id text call-id]
params [chat-id text]
method "wakuext_chatMentionOnChangeText"]
(log/debug "[mentions] on-change-text" {:params params})
{:db (assoc-in db [:chat/inputs chat-id :current-mentions-call-id] call-id)
:json-rpc/call [{:method method
{:json-rpc/call [{:method method
:params params
:on-success #(rf/dispatch [:mention/on-change-text-success %])
:on-error #(rf/dispatch [:mention/on-error
@@ -121,38 +118,49 @@
{: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 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))})))
(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))}))
(rf/defn on-select-mention-success
{:events [:mention/on-select-mention-success]}
[{:keys [db]} result primary-name match searched-text public-key]
[{:keys [db] :as cofx} 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]} (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]}))
(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)}))
(rf/defn clear-mentions
[{:keys [db]}]
[{:keys [db] :as cofx}]
(log/debug "[mentions] clear mentions")
(let [chat-id (:current-chat-id db)]
{: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/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))))
(rf/defn select-mention
{:events [:chat.ui/select-mention]}
@@ -62,7 +62,6 @@
: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,24 +117,20 @@
(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/new-installation-received 22
notification-types/new-installation-created 23}
(= {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}
(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/new-installation-received)) 22
(keyword (str notification-types/new-installation-created)) 23
{(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
;; Unsupported type in the response is ignored
:999 100}))))
:999 100}))))
-11
View File
@@ -57,11 +57,6 @@
(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]
@@ -169,12 +164,6 @@
[_ 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,19 +13,17 @@
(native-module/logout)))
(rf/defn initialize-app-db
[{{:keys [keycard initials-avatar-font-file biometrics]
:network/keys [type status expensive?]
:centralized-metrics/keys [user-confirmed?]}
[{{:keys [keycard initials-avatar-font-file biometrics]
:network/keys [type status expensive?]}
:db}]
{:db (assoc db/app-db
: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)})
: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,23 +243,6 @@
{}
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,6 +5,7 @@
[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]))
@@ -16,6 +17,7 @@
(defn- normal-mode-settings-data
[{:keys [current-log-level
telemetry-enabled?
light-client-enabled?
store-confirmations-enabled?
current-fleet
@@ -58,6 +60,14 @@
: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
@@ -99,6 +109,7 @@
[]
(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?]]
@@ -112,6 +123,7 @@
[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 log-request-go? callback]}]
(.initLogging ^js (log-manager) enable? mobile-system? log-level log-request-go? callback))
[{:keys [enable? mobile-system? log-level callback]}]
(.initLogging ^js (log-manager) enable? mobile-system? log-level callback))
(defn get-random-mnemonic
[callback]
@@ -81,20 +81,12 @@
: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)
unread-messages? (pos? unviewed-messages-count)]
(let [theme (quo.theme/use-theme)]
[rn/touchable-highlight
(merge {:underlay-color (colors/theme-colors
colors/neutral-5
@@ -116,7 +108,11 @@
:ellipsize-mode :tail
:weight :semi-bold
:size :paragraph-1
:style {:color (title-color unread-messages? muted theme)}}
:style (when muted
{:color (colors/theme-colors
colors/neutral-40
colors/neutral-60
theme)})}
name]]
[rn/view
@@ -132,4 +128,4 @@
:customization-color customization-color
:muted? muted
:unread-mentions-count unviewed-mentions-count
:unread-messages? unread-messages?}])]]]))
:unread-messages? (pos? unviewed-messages-count)}])]]]))
+1 -4
View File
@@ -46,14 +46,11 @@
(let [theme (quo.theme/use-theme)]
[rn/view {:style (style/container container-style)}
[rn/view {:style style/index}
(case type
:step
(if (= 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
@@ -5,7 +5,9 @@
(def container
{:padding-horizontal 12
:padding-top 12
:padding-bottom 14})
:padding-bottom 14
:flex-direction :row
:justify-content :space-between})
(defn left-sub-container
[{:keys [tag description]}]
@@ -108,25 +108,20 @@
nil)])
(defn view
[{:keys [title on-press action-props accessibility-label blur? container-style content] :as props}]
[{:keys [title on-press action-props accessibility-label blur? container-style] :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 {: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)])
[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]]])
+2 -22
View File
@@ -1,10 +1,7 @@
(ns react-native.permissions
(:require
["react-native-permissions" :refer
[check openSettings PERMISSIONS requestMultiple requestNotifications
RESULTS]]
[react-native.platform :as platform]
[taoensso.timbre :as log]))
["react-native-permissions" :refer (check openSettings requestMultiple PERMISSIONS RESULTS)]
[react-native.platform :as platform]))
(def permissions-map
{:read-external-storage (cond
@@ -48,20 +45,3 @@
(.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,11 +14,7 @@
[state]
(:value state))
(defn value-numeric
[state]
(or (parse-double (input-value state)) 0))
(defn value-bn
(defn numeric-value
[state]
(money/bignumber (input-value state)))
@@ -31,32 +27,28 @@
(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]
(and (upper-limit state)
(when (money/bignumber? (value-bn state))
(money/greater-than (value-bn state) (upper-limit-bn 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))))))
(defn- lower-limit-exceeded?
[state]
(and (lower-limit state)
(when (money/bignumber? (value-bn state))
(money/less-than (value-bn state) (lower-limit-bn 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))))))
(defn- recheck-errorness
[state]
@@ -70,7 +62,7 @@
(assoc :value value)
recheck-errorness))
(defn set-value-numeric
(defn set-numeric-value
[state value]
(set-input-value state (str value)))
@@ -89,11 +81,11 @@
(defn increase
[state]
(set-input-value state (str (money/add (value-bn state) 1))))
(set-input-value state (str (money/add (numeric-value state) 1))))
(defn decrease
[state]
(set-input-value state (str (money/add (value-bn state) -1))))
(set-input-value state (str (money/add (numeric-value state) -1))))
(def ^:private not-digits-or-dot-pattern
#"[^0-9+\.]")
@@ -130,12 +122,10 @@
state))
(defn delete-last
([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)))))
[state]
(let [value (input-value state)
new-value (subs value 0 (dec (count value)))]
(set-input-value state new-value)))
(defn delete-all
[state]
@@ -143,7 +133,7 @@
(defn empty-value?
[state]
(or (string/blank? (:value state)) (<= (value-numeric state) 0)))
(string/blank? (:value state)))
(defn- fiat->crypto
[value conversion-rate]
-12
View File
@@ -2,7 +2,6 @@
(: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]
@@ -90,17 +89,6 @@
(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]
+4 -6
View File
@@ -4,7 +4,6 @@
[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]))
@@ -25,11 +24,10 @@
(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
:log-request-go? config/log-request-go
:callback handle-error}]
logging-params {:enable? true
:mobile-system? false
:log-level level
: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,16 +17,14 @@
[]
(rf/dispatch [:hide-bottom-sheet]))
(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])
(defn- toggle-metrics
[enabled?]
(rf/dispatch [:centralized-metrics/toggle-centralized-metrics enabled?]))
(def ^:private will-receive-for-all-points
[:t/action-logs
:t/ip-addresses-uuid])
(def ^:private will-receive-points
[:t/ip-address
:t/universally-unique-identifiers-of-device
:t/logs-of-actions-withing-the-app])
(def ^:private not-receive-points
[:t/your-profile-information
@@ -34,7 +32,7 @@
:t/information-you-input-and-send])
(defn- bullet-points
[{:keys [title points lock?]}]
[{:keys [title points]}]
[rn/view
[quo/text {:weight :semi-bold}
title]
@@ -43,36 +41,37 @@
[quo/markdown-list
{:description (i18n/label label)
:blur? true
:type (when lock? :lock)
:container-style style/item-text}])])
(defn- on-share-usage
[]
(rf/dispatch [:centralized-metrics/toggle-centralized-metrics true true])
(toggle-metrics true)
(hide-bottom-sheet))
(defn- on-do-not-share
[]
(rf/dispatch [:centralized-metrics/toggle-centralized-metrics false true])
(toggle-metrics false)
(hide-bottom-sheet))
(declare view)
(defn- on-privacy-policy-press
[]
[settings?]
(rf/dispatch
[:show-bottom-sheet
{:content privacy/privacy-statement
{:content (fn []
[quo.theme/provider :dark
[privacy/privacy-statement]])
:on-close (fn []
(rf/dispatch [:show-bottom-sheet
{:content view
:theme :dark
{:content (fn []
[quo.theme/provider :dark
[view {:settings? settings?}]])
:shell? true}]))
:theme :dark
:shell? true}]))
(defn- privacy-policy-text
[]
[settings?]
[rn/view {:style style/privacy-policy}
[quo/text
[quo/text
@@ -82,38 +81,39 @@
[quo/text
{:size :paragraph-2
:weight :bold
:on-press on-privacy-policy-press}
:on-press #(on-privacy-policy-press settings?)}
(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/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)]]]
[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)])]
[quo/bottom-actions
{:actions :two-actions
:blur? true
:button-one-label (i18n/label :t/help-us-improve-status)
:button-one-label (i18n/label :t/share-usage-data)
: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]])
[privacy-policy-text settings?]])
@@ -1,23 +0,0 @@
(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})
@@ -1,75 +0,0 @@
(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,7 +28,6 @@
: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,7 +76,6 @@
;; 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"))
@@ -7,10 +7,17 @@
[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 (:centralized-metrics/user-confirmed? db))
(:centralized-metrics/enabled? db)))
(or (not (user-confirmed-key db))
(enabled-key db)))
(defn centralized-metrics-interceptor
[context]
@@ -26,16 +33,17 @@
:after centralized-metrics-interceptor))
(rf/reg-event-fx :centralized-metrics/toggle-centralized-metrics
(fn [{:keys [db]} [enabled? onboarding?]]
(fn [{:keys [db]} [enabled?]]
{:fx [[:effects.centralized-metrics/toggle-metrics enabled?]]
:db (-> db
(assoc :centralized-metrics/user-confirmed? true)
(assoc :centralized-metrics/enabled? enabled?)
(assoc :centralized-metrics/onboarding-enabled? (and onboarding? enabled?)))}))
:db (assoc db
user-confirmed-key
true
enabled-key
enabled?)}))
(rf/reg-event-fx :centralized-metrics/check-modal
(fn [{:keys [db]} [modal-view]]
(when-not (:centralized-metrics/user-confirmed? db)
(when (show-confirmation-modal? db)
{:fx [[:dispatch
[:show-bottom-sheet
{:content (fn [] [modal-view])
@@ -6,6 +6,11 @@
[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,7 +37,6 @@
: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
@@ -146,16 +146,9 @@
"")]
(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} muted unread-messages?]
[group-chat {:keys [deleted? outgoing from deleted-for-me?] :as message}]
(let [theme (quo.theme/use-theme)
[primary-name _] (rf/sub [:contacts/contact-two-names-by-identity from])
preview-text (if deleted-for-me?
@@ -169,7 +162,9 @@
(preview-text-from-content group-chat primary-name message)))]
[quo/text
{:size :paragraph-2
:style {:color (last-message-color unread-messages? muted theme)
:style {:color (colors/theme-colors colors/neutral-50
colors/neutral-40
theme)
:flex 1}
:number-of-lines 1
:ellipsize-mode :tail
@@ -236,16 +231,14 @@
unviewed-messages-count]])))
(defn chat-item
[{:keys [chat-id group-chat color name last-message timestamp muted image
unviewed-messages-count]
[{:keys [chat-id group-chat color name last-message timestamp muted image]
: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]))
unread-messages? (pos? unviewed-messages-count)]
(rf/sub [:contacts/contact-by-address chat-id]))]
[rn/view {:style {:flex-direction :row}}
[avatar-view
{:contact contact
@@ -265,7 +258,7 @@
:muted? muted
:time-str (datetime/to-short-str timestamp)
:style {:flex-shrink 1}}]
[last-message-preview group-chat last-message muted unread-messages?]]]
[last-message-preview group-chat last-message muted]]]
[notification item]]))
(defn chat-user
@@ -1,54 +0,0 @@
(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,14 +7,26 @@
{:height constants/actions-container-height
:justify-content :space-between
:align-items :center
:z-index 2
:flex-direction :row})
(defn send-button
[opacity]
[opacity z-index]
(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,60 +2,246 @@
(: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
[input-ref edit btn-opacity]
(when @input-ref
(.clear ^js @input-ref))
(reanimated/animate btn-opacity 0)
"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)
(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 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])))
(defn f-send-button
[props state animations window-height images? btn-opacity z-index edit]
(let [{:keys [text-value]} state
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]))
on-press (rn/use-callback #(send-message input-ref edit btn-opacity) [edit])]
chat-id]))]
(rn/use-effect (fn []
;; Handle send button opacity animation and z-index when input content changes
(if (or (seq input-text) images?)
(when (not= (reanimated/get-shared-value btn-opacity) 1)
(if (or (seq @text-value) images?)
(when (or (not= @z-index 1) (not= (reanimated/get-shared-value btn-opacity) 1))
(reset! z-index 1)
(js/setTimeout #(reanimated/animate btn-opacity 1) 50))
(when (not= (reanimated/get-shared-value btn-opacity) 0)
(reanimated/animate btn-opacity 0))))
[(and (empty? input-text) (not images?))])
(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?))])
[reanimated/view
{:style (style/send-button btn-opacity)}
{:style (style/send-button btn-opacity @z-index)}
[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 on-press}
:on-press #(send-message props state animations window-height edit)}
: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
[input-ref]
(let [edit (rf/sub [:chats/edit-message])]
[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])]
[rn/view {:style style/actions-container}
[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]]))
[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])]))
@@ -13,6 +13,10 @@
(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
@@ -29,6 +33,16 @@
(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,13 +7,12 @@
[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
[input-ref]
[{:keys [text-value input-ref input-height]}]
(let [theme (quo.theme/use-theme)]
[rn/view
{:style style/container
@@ -33,16 +32,18 @@
{:size 24
:icon-only? true
:accessibility-label :edit-cancel-button
:on-press #(utils/cancel-edit-message input-ref)
:on-press #(utils/cancel-edit-message text-value input-ref input-height)
:type :outline}
:i/close]]))
(defn view
[input-ref]
(defn- f-view
[props]
(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 input-ref])]))
(when edit [edit-message props])]))
(defn view
[props]
[:f> f-view props])
@@ -1,20 +1,225 @@
(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]
[utils.number]))
[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)))
(defn use-edit
[input-ref edit]
(rn/use-effect
(fn []
(when (and edit @input-ref)
(js/setTimeout #(.focus ^js @input-ref) 600)))
[(:message-id 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)])))
(defn use-reply
[input-ref reply]
[{:keys [input-ref]}
{:keys [reply]}
chat-screen-layout-calculations-complete?]
(rn/use-effect
(fn []
(when (and reply @input-ref)
(when (and reply @input-ref (reanimated/get-shared-value chat-screen-layout-calculations-complete?))
(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,7 +6,6 @@
[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]
@@ -22,12 +21,6 @@
(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]
@@ -76,14 +69,15 @@
[{: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 [: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-n [[:chat.ui/set-chat-input-text nil current-chat-id]
[:mention/to-input-field text current-chat-id]]}))
(rf/defn cancel-message-reply
"Cancels stage message reply"
@@ -263,13 +257,6 @@
[{{: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)})))))
@@ -0,0 +1,112 @@
(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,17 +3,139 @@
[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]
[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))
(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 images-list
(defn f-images-list
[]
(let [theme (quo.theme/use-theme)
images (rf/sub [:chats/sending-image])
@@ -48,3 +48,7 @@
:horizontal true
:shows-horizontal-scroll-indicator false
:keyboard-should-persist-taps :handled}]]))
(defn images-list
[]
[:f> f-images-list])
@@ -0,0 +1,87 @@
(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 view
(defn f-view
[]
(let [previews (rf/sub [:chats/link-previews-unfurled])
height (use-animated-height (boolean (seq previews)))]
@@ -48,3 +48,7 @@
:thumbnail (:data-uri thumbnail)
:url url})
previews)}]]))
(defn view
[]
[:f> f-view])
@@ -5,6 +5,7 @@
[react-native.reanimated :as reanimated]
[status-im.contexts.chat.messenger.composer.constants :as constants]))
(defn shadow
[theme]
(if platform/ios?
@@ -15,12 +16,12 @@
{:elevation 10}))
(defn container
[opacity top theme]
[opacity bottom theme]
(reanimated/apply-animations-to-style
{:opacity opacity}
(merge
{:position :absolute
:top (- (+ 8 top))
:bottom bottom
:left 8
:right 8
:border-radius 16
@@ -2,45 +2,70 @@
(: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.messages.constants :as messages.constants]
[status-im.contexts.chat.messenger.composer.utils :as utils]
[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]
[user _ _ render-data]
[contact-list-item/contact-list-item
{:on-press #(rf/dispatch [:chat.ui/select-mention user])}
{:on-press (fn []
(rf/dispatch [:chat.ui/select-mention user])
(update-cursor user render-data))}
user])
(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)))]
(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?)]
(rn/use-effect
(fn []
(if suggestions?
(set-suggestions-state suggestions)
(js/setTimeout #(set-suggestions-state suggestions) 300))
(reanimated/animate opacity (if suggestions? 1 0)))
[suggestions?])
(if (seq suggestions)
(reset! suggestions-atom suggestions)
(js/setTimeout #(reset! suggestions-atom suggestions) 300))
(reanimated/animate opacity (if (seq suggestions) 1 0)))
[(seq suggestions)])
[reanimated/view
{:style (style/container opacity top theme)}
{:style (style/container opacity mentions-pos theme)}
[rn/flat-list
{:keyboard-should-persist-taps :always
:data (vals suggestions-state)
:data (vals @suggestions-atom)
: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,7 +9,6 @@
[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]
@@ -165,12 +164,14 @@
:end {:x 0.7 :y 0}
:style style/gradient}])]))
(defn view
[input-ref]
(defn- f-view
[recording? 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 false input-ref])]))
(when reply [quoted-message reply true false recording? input-ref])]))
(defn view
[{:keys [recording?]} input-ref]
[:f> f-view @recording? input-ref])
@@ -2,6 +2,7 @@
(: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]
@@ -9,6 +10,34 @@
(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
@@ -25,14 +54,65 @@
: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
[theme]
[{:keys [saved-emoji-kb-extra-height]}
{:keys [focused? maximized?]}
{:keys [max-height 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
:max-height 150))
: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})
(defn shell-button
[translate-y opacity]
@@ -0,0 +1,48 @@
(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,8 +1,52 @@
(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.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)))
(defn calc-top-content-height
[reply? edit?]
@@ -16,6 +60,35 @@
(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
@@ -28,17 +101,36 @@
(rf/dispatch [:chat.ui/cancel-message-reply]))
(defn cancel-edit-message
[input-ref]
[text-value input-ref input-height]
(reset! text-value "")
;; 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))
(blur-input input-ref)
(reanimated/set-shared-value input-height constants/input-height))
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?]}
@@ -61,3 +153,85 @@
(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,56 +1,183 @@
(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.safe-area :as safe-area]
[react-native.gesture :as gesture]
[react-native.hooks :as hooks]
[react-native.platform :as platform]
[react-native.reanimated :as reanimated]
[reagent.core :as reagent]
[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 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 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 view
(defn f-composer
[props]
(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]]]))
(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}]))))
@@ -26,7 +26,9 @@
[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))
@@ -334,7 +336,8 @@
(defn list-group-chat-header
[{:keys [chat-id invitation-admin]}]
[chat.group/group-chat-footer chat-id invitation-admin])
[rn/view
[chat.group/group-chat-footer chat-id invitation-admin]])
(defn render-fn
[{:keys [type value] :as message-data} _ _
@@ -407,8 +410,10 @@
{:key-fn list-key-fn
:ref list-ref
:bounces false
:header (when (= (:chat-type chat) constants/private-group-chat-type)
[list-group-chat-header chat])
:header [:<>
[list-header insets able-to-send-message?]
(when (= (:chat-type chat) constants/private-group-chat-type)
[list-group-chat-header chat])]
:footer [list-footer
{:theme theme
:chat chat
@@ -428,11 +433,15 @@
: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 (fn []
(rf/dispatch [:chat.ui/set-input-focused false])
(rn/dismiss-keyboard!))
:on-scroll-begin-drag #(do
(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
@@ -454,5 +463,4 @@
:chat-screen-layout-calculations-complete?
chat-screen-layout-calculations-complete?})
:scroll-enabled (not recording?)
:content-inset-adjustment-behavior :never
:scroll-indicator-insets {:right 1}}]]]))
:content-inset-adjustment-behavior :never}]]]))
@@ -5,13 +5,12 @@
(defn navigation-view
[navigation-view-height pinned-banner-height]
{:top 0
:left 0
:right 0
:position :absolute
:pointer-events :box-none
:height (+ navigation-view-height pinned-banner-height)
:z-index 1})
{:top 0
:left 0
:right 0
:position :absolute
:height (+ navigation-view-height pinned-banner-height)
:z-index 1})
(defn animated-background-view
[background-opacity navigation-view-height]
@@ -1,11 +0,0 @@
(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})
@@ -1,20 +0,0 @@
(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,32 +1,19 @@
(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]
[status-im.contexts.chat.messenger.messages.contact-requests.bottom-drawer.view :as
contact-requests.bottom-drawer]
[status-im.contexts.chat.messenger.composer.view :as composer.view]
[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)
@@ -36,11 +23,9 @@
[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]
[scroll-to-bottom/button props]]
[list.view/messages-list-content props]
[messages.navigation/view props]
[footer props]])))
[composer.view/composer props]])))
(defn lazy-chat-screen
[chat-screen-layout-calculations-complete? *screen-loaded?*]
@@ -8,8 +8,7 @@
(h/test "joined options - Non token Gated"
(h/setup-subs {:communities/my-pending-request-to-join nil
:communities/community {:joined true}
:profile/test-networks-enabled? false})
:communities/community {:joined true}})
(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))
@@ -24,8 +23,7 @@
(h/test "joined options - Token Gated"
(h/setup-subs {:communities/my-pending-request-to-join nil
:communities/community {:joined true
:role-permissions? true}
:profile/test-networks-enabled? false})
:role-permissions? true}})
(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))
@@ -40,8 +38,7 @@
(h/test "admin options - Non token Gated"
(h/setup-subs {:communities/my-pending-request-to-join nil
:communities/community {:admin true}
:profile/test-networks-enabled? false})
:communities/community {:admin true}})
(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))
@@ -55,8 +52,7 @@
(h/test "admin options - Token Gated"
(h/setup-subs {:communities/my-pending-request-to-join nil
:communities/community {:admin true
:role-permissions? true}
:profile/test-networks-enabled? false})
:role-permissions? true}})
(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))
@@ -69,8 +65,7 @@
(h/test "request sent options - Non token Gated"
(h/setup-subs {:communities/my-pending-request-to-join "mock-id"
:communities/community {}
:profile/test-networks-enabled? false})
:communities/community {}})
(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))
@@ -81,8 +76,7 @@
(h/test "request sent options - Token Gated"
(h/setup-subs {:communities/my-pending-request-to-join "mock-id"
:communities/community {:role-permissions? true}
:profile/test-networks-enabled? false})
:communities/community {:role-permissions? true}})
(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))
@@ -92,8 +86,7 @@
(h/test "banned options - Non token Gated"
(h/setup-subs {:communities/my-pending-request-to-join nil
:communities/community {:banList true}
:profile/test-networks-enabled? false})
:communities/community {:banList true}})
(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))
@@ -104,8 +97,7 @@
(h/test "banned options - Token Gated"
(h/setup-subs {:communities/my-pending-request-to-join nil
:communities/community {:banList 100
:role-permissions? true}
:profile/test-networks-enabled? false})
:role-permissions? true}})
(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))
@@ -115,8 +107,7 @@
(h/test "banned options - Token Gated"
(h/setup-subs {:communities/my-pending-request-to-join nil
:communities/community {:banList 100
:role-permissions? true}
:profile/test-networks-enabled? false})
:role-permissions? true}})
(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))
@@ -127,7 +118,6 @@
(h/setup-subs {:communities/my-pending-request-to-join nil
:communities/community {:joined true
:muted true
:role-permissions? true}
:profile/test-networks-enabled? false})
:role-permissions? true}})
(h/render [options/community-options-bottom-sheet {:id "test"}])
(h/is-truthy (h/get-by-translation-text :t/unmute-community))))
@@ -36,13 +36,14 @@
(defn view-token-gating
[id]
{: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)})
(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)}))
(defn edit-shared-addresses
[id]
@@ -55,7 +56,7 @@
(defn mark-as-read
[id]
{:icon :i/mark-as-read
{:icon :i/up-to-date
: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])})
@@ -135,27 +136,23 @@
request-id])}])})
(defn not-joined-options
[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)]))
[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)]])
(defn join-request-sent-options
[id token-gated? request-id intro-message test-networks-enabled?]
[(conj (first (not-joined-options id token-gated? intro-message test-networks-enabled?))
[id token-gated? request-id intro-message]
[(conj (first (not-joined-options id token-gated? intro-message))
(assoc (cancel-request-to-join id request-id) :add-divider? true))])
(defn banned-options
[id token-gated? intro-message test-networks-enabled?]
(not-joined-options id token-gated? intro-message test-networks-enabled?))
[id token-gated? intro-message]
(not-joined-options id token-gated? intro-message))
(defn joined-options
[id token-gated? muted? muted-till color intro-message]
@@ -189,19 +186,13 @@
(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])
test-networks? (rf/sub [:profile/test-networks-enabled?])]
request-id (rf/sub [:communities/my-pending-request-to-join id])]
(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 test-networks?)
banList
(banned-options id role-permissions? intro-message test-networks?)
:else
(not-joined-options id role-permissions? intro-message test-networks?))))
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))))
(defn community-options-bottom-sheet
[id]
+29 -36
View File
@@ -2,7 +2,6 @@
(: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]
@@ -26,7 +25,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 (and id (>= clock (get-in db [:communities id :clock])))
(when (>= 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)))
@@ -224,14 +223,14 @@
:fx [[:dispatch [:communities/check-permissions-to-join-community community-id]]]})))
(defn community-fetched
[{:keys [db]} [community-id community-js]]
(when community-js
[{:keys [db]} [community-id community]]
(when community
{:db (update db :communities/fetching-communities dissoc community-id)
:fx [[:dispatch [:communities/handle-community community-js]]
:fx [[:dispatch [:communities/handle-community community]]
[:dispatch [:chat.ui/spectate-community community-id]]
[:dispatch
[:chat.ui/cache-link-preview-data (link-preview.events/community-link community-id)
(data-store.communities/<-rpc community-js)]]]}))
community]]]}))
(rf/reg-event-fx :chat.ui/community-fetched community-fetched)
@@ -254,19 +253,18 @@
(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}]
: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)}]}))
: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)}]}))
(schema/=> fetch-community
[:=>
@@ -285,15 +283,11 @@
(rf/reg-event-fx :communities/fetch-community fetch-community)
(defn spectate-community-success
[{: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]]]}))))
[{:keys [db]} [{:keys [communities]}]]
(when-let [community (first communities)]
{:db (-> db
(assoc-in [:communities (:id community) :spectated] true))
:fx [[:dispatch [:communities/handle-community community]]]}))
(rf/reg-event-fx :chat.ui/spectate-community-success spectate-community-success)
@@ -301,14 +295,13 @@
[{: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]
:js-response true
: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]
:on-success [:chat.ui/spectate-community-success]
:on-error (fn [err]
(log/error {:message
"Failed to spectate community"
:error err}))}]})))
(schema/=> spectate-community
[:=>
@@ -1,6 +1,5 @@
(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]))
@@ -52,7 +51,7 @@
[:dispatch [:chat.ui/spectate-community community-id]]
[:dispatch
[:chat.ui/cache-link-preview-data "community-link+community-id"
(data-store.communities/<-rpc #js {"id" community-id})]]]}
{:id community-id}]]]}
(events/community-fetched cofx arg))))))
(testing "given a joined community"
(let [cofx {:db {:communities/fetching-communities {community-id true}}}
@@ -63,7 +62,7 @@
[:dispatch [:chat.ui/spectate-community community-id]]
[:dispatch
[:chat.ui/cache-link-preview-data "community-link+community-id"
(data-store.communities/<-rpc #js {"id" community-id})]]]}
{:id community-id}]]]}
(events/community-fetched cofx arg))))))
(testing "given a token-gated community"
(let [cofx {:db {:communities/fetching-communities {community-id true}}}
@@ -74,7 +73,7 @@
[:dispatch [:chat.ui/spectate-community community-id]]
[:dispatch
[:chat.ui/cache-link-preview-data "community-link+community-id"
(data-store.communities/<-rpc #js {"id" community-id})]]]}
{:id community-id}]]]}
(events/community-fetched cofx arg))))))
(testing "given nil community"
(testing "do nothing"
@@ -104,38 +103,26 @@
(events/spectate-community {:db {:communities {community-id {}}}} [community-id]))))))
(deftest spectate-community-success-test
(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 []))))))))
(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 {} []))))))
(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,10 +209,9 @@
(when same-passwords?
[rn/view {:style style/disclaimer-container}
[quo/disclaimer
{:blur? true
:on-change (partial set-accepts-disclaimer? not)
:checked? accepts-disclaimer?
:customization-color user-color}
{:blur? true
:on-change (partial set-accepts-disclaimer? not)
:checked? accepts-disclaimer?}
(i18n/label :t/password-creation-disclaimer)]])
(when (and (= focused-input :password) (not same-passwords?))
[help
@@ -7,6 +7,7 @@
[status-im.contexts.onboarding.enable-notifications.style :as style]
[status-im.contexts.shell.jump-to.constants :as shell.constants]
[status-im.contexts.shell.jump-to.utils :as shell.utils]
[taoensso.timbre :as log]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
@@ -19,36 +20,39 @@
:description (i18n/label :t/enable-notifications-sub-title)
:description-accessibility-label :notifications-sub-title}])
(defn- finish-onboarding
[notifications-enabled?]
(rf/dispatch [:push-notifications/switch notifications-enabled?])
(shell.utils/change-selected-stack-id shell.constants/default-selected-stack true nil)
(rf/dispatch [:update-theme-and-init-root :shell-stack])
(rf/dispatch [:profile/show-testnet-mode-banner-if-enabled])
(rf/dispatch [:universal-links/process-stored-event]))
(defn enable-notification-buttons
[{:keys [insets]}]
(let [profile-color (rf/sub [:onboarding/customization-color])
ask-permission (fn []
(rf/dispatch [:request-notifications
{:on-allowed (fn []
(js/setTimeout #(finish-onboarding true)
300))
:on-denied (fn []
(js/setTimeout #(finish-onboarding false)
300))}]))
skip-permission #(finish-onboarding false)]
(let [profile-color (rf/sub [:onboarding/customization-color])]
[rn/view {:style (style/buttons insets)}
[quo/button
{:on-press ask-permission
{:on-press (fn []
(shell.utils/change-selected-stack-id
shell.constants/default-selected-stack
true
nil)
(rf/dispatch
[:request-permissions
{:permissions [:post-notifications]
:on-allowed #(log/info "push notification permissions were allowed")
:on-denied #(log/error "user denied push notification permissions")}])
(rf/dispatch [:push-notifications/switch true])
(rf/dispatch [:navigate-to-within-stack
[:screen/onboarding.welcome
:screen/onboarding.enable-notifications]]))
:type :primary
:icon-left :i/notifications
:accessibility-label :enable-notifications-button
:customization-color profile-color}
(i18n/label :t/intro-wizard-title6)]
[quo/button
{:on-press skip-permission
{:on-press (fn []
(shell.utils/change-selected-stack-id
shell.constants/default-selected-stack
true
nil)
(rf/dispatch [:navigate-to-within-stack
[:screen/onboarding.welcome
:screen/onboarding.enable-notifications]]))
:accessibility-label :enable-notifications-later-button
:type :grey
:background :blur
+19 -32
View File
@@ -71,20 +71,17 @@
{:events [:onboarding/create-account-and-login]}
[{:keys [db] :as cofx}]
(let [{:keys [display-name seed-phrase password image-path color] :as profile}
(:onboarding/profile db)
loading-screen (if (seq (:syncing/key-uid db))
:screen/onboarding.preparing-status
:screen/onboarding.generating-keys)]
(:onboarding/profile db)]
(rf/merge cofx
{:dispatch [:navigate-to-within-stack
[loading-screen
[:screen/onboarding.generating-keys
(get db
:onboarding/navigated-to-enter-seed-phrase-from-screen
:screen/onboarding.new-to-status)]]
:dispatch-later [{:ms constants/onboarding-generating-keys-animation-duration-ms
:dispatch [:navigate-to-within-stack
[:screen/onboarding.enable-notifications
loading-screen]]}]
:screen/onboarding.generating-keys]]}]
:db (-> db
(dissoc :profile/login)
(dissoc :auth-method)
@@ -137,32 +134,22 @@
(rf/defn seed-phrase-validated
{:events [:onboarding/seed-phrase-validated]}
[{:keys [db]} seed-phrase key-uid]
(let [syncing-account-recovered? (and (seq (:syncing/key-uid db))
(= (:syncing/key-uid db) key-uid))
next-screen (if syncing-account-recovered?
:screen/onboarding.create-profile-password
:screen/onboarding.create-profile)]
(if (contains? (:profile/profiles-overview db) key-uid)
{:effects.utils/show-confirmation
{:title (i18n/label :t/multiaccount-exists-title)
:content (i18n/label :t/multiaccount-exists-content)
:confirm-button-text (i18n/label :t/unlock)
:on-accept (fn []
(re-frame/dispatch [:pop-to-root :screen/profile.profiles])
(re-frame/dispatch
[:profile/profile-selected key-uid]))
:on-cancel #(re-frame/dispatch [:pop-to-root :multiaccounts])}}
{:db (-> db
(assoc-in [:onboarding/profile :seed-phrase] seed-phrase)
(assoc-in [:onboarding/profile :color] constants/profile-default-color))
:fx [[:dispatch
[:navigate-to-within-stack
[next-screen
(get db
:onboarding/navigated-to-enter-seed-phrase-from-screen
:screen/onboarding.new-to-status)]]]
(when-not syncing-account-recovered?
[:dispatch [:syncing/clear-syncing-data]])]})))
(if (contains? (:profile/profiles-overview db) key-uid)
{:effects.utils/show-confirmation
{:title (i18n/label :t/multiaccount-exists-title)
:content (i18n/label :t/multiaccount-exists-content)
:confirm-button-text (i18n/label :t/unlock)
:on-accept (fn []
(re-frame/dispatch [:pop-to-root :screen/profile.profiles])
(re-frame/dispatch
[:profile/profile-selected key-uid]))
:on-cancel #(re-frame/dispatch [:pop-to-root :multiaccounts])}}
{:db (assoc-in db [:onboarding/profile :seed-phrase] seed-phrase)
:dispatch [:navigate-to-within-stack
[:screen/onboarding.create-profile
(get db
:onboarding/navigated-to-enter-seed-phrase-from-screen
:screen/onboarding.new-to-status)]]}))
(rf/defn navigate-to-create-profile
{:events [:onboarding/navigate-to-create-profile]}
@@ -1,11 +0,0 @@
(ns status-im.contexts.onboarding.preparing-status.style)
(defn page-container
[insets]
{:flex 1
:padding-top (:top insets)})
(defn page-illustration
[width]
{:flex 1
:width width})
@@ -1,36 +0,0 @@
(ns status-im.contexts.onboarding.preparing-status.view
(:require
[quo.core :as quo]
[react-native.core :as rn]
[react-native.safe-area :as safe-area]
[status-im.common.resources :as resources]
[status-im.contexts.onboarding.generating-keys.style :as style]
[utils.i18n :as i18n]))
(defn title
[]
[rn/view
{:style {:margin-top 56
:height 56
:margin-bottom 10}}
[quo/text-combinations
{:container-style {:margin-horizontal 20
:margin-vertical 12}
:title (i18n/label :t/preparing-app-for-you)
:description (i18n/label :t/hang-in-there)}]])
(defn content
[]
(let [width (:width (rn/get-window))]
[rn/image
{:resize-mode :stretch
:style (style/page-illustration width)
:source (resources/get-image :preparing-status)}]))
(defn view
[]
(let [insets (safe-area/get-insets)]
[rn/view {:style (style/page-container insets)}
[:<>
[title]
[content]]]))
@@ -27,3 +27,7 @@
:align-items :center
:align-self :center
:justify-content :center})
(def try-again-button
{:margin-top 20
:padding-horizontal 20})
@@ -5,7 +5,6 @@
[status-im.common.resources :as resources]
[status-im.contexts.onboarding.common.background.view :as background]
[status-im.contexts.onboarding.syncing.progress.style :as style]
[utils.debounce :as debounce]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
@@ -26,40 +25,17 @@
:title-accessibility-label :progress-screen-title
:description-accessibility-label :progress-screen-sub-title}])
(defn- navigate-to-enter-seed-phrase
[]
(debounce/debounce-and-dispatch
[:onboarding/navigate-to-sign-in-by-seed-phrase :screen/onboarding.sync-or-recover-profile]
500))
(defn- try-again
[logged-in?]
(rf/dispatch [:syncing/clear-states])
(if logged-in?
(rf/dispatch [:navigate-back])
(rf/dispatch [:navigate-back-to :screen/onboarding.sync-or-recover-profile])))
(defn try-again-button
[profile-color logged-in?]
[quo/bottom-actions
{:actions (if logged-in? :one-action :two-actions)
:blur? true
:button-one-label (i18n/label :t/recovery-phrase)
:button-one-props {:type :primary
:accessibility-label :try-seed-phrase-button
:customization-color profile-color
:container-style {:flex 1}
:size 40
:on-press navigate-to-enter-seed-phrase}
(if logged-in? :button-one-label :button-two-label)
(i18n/label :t/try-again)
(if logged-in? :button-one-props :button-two-props)
{:type (if logged-in? :primary :grey)
:accessibility-label :try-again-later-button
:customization-color profile-color
:container-style {:flex 1}
:size 40
:on-press #(try-again logged-in?)}}])
[profile-color]
[quo/button
{:on-press (fn []
(rf/dispatch [:syncing/clear-states])
(rf/dispatch [:navigate-back]))
:accessibility-label :try-again-later-button
:customization-color profile-color
:size 40
:container-style style/try-again-button}
(i18n/label :t/try-again)])
(defn- illustration
[pairing-progress?]
@@ -71,7 +47,6 @@
(defn view
[in-onboarding?]
(let [pairing-status (rf/sub [:pairing/pairing-status])
logged-in? (rf/sub [:multiaccount/logged-in?])
pairing-progress? (pairing-progress pairing-status)
profile-color (or (:color (rf/sub [:onboarding/profile]))
(rf/sub [:profile/customization-color]))]
@@ -83,7 +58,7 @@
[page-title pairing-progress?]
[illustration pairing-progress?]
(when-not (pairing-progress pairing-status)
[try-again-button profile-color logged-in?])]))
[try-again-button profile-color])]))
(defn view-onboarding
[]
@@ -0,0 +1,36 @@
(ns status-im.contexts.onboarding.welcome.style)
(def default-margin 20)
(defn page-container
[insets]
{:flex 1
:padding-top (:top insets)})
(defn page-illustration
[width]
{:width width
:aspect-ratio 0.7
:height :auto})
(def page-title
{:margin-top 12
:margin-horizontal 20
:margin-bottom 8})
(defn buttons
[insets]
{:position :absolute
:left 0
:right 0
:padding-left default-margin
:padding-right default-margin
:bottom (+ 12 (:bottom insets))})
(def bottom-shadow
{:position :absolute
:height 64
:top 0
:bottom 0
:left 0
:right 0})
@@ -0,0 +1,54 @@
(ns status-im.contexts.onboarding.welcome.view
(:require
[quo.core :as quo]
[quo.foundations.colors :as colors]
[react-native.core :as rn]
[react-native.linear-gradient :as linear-gradient]
[react-native.safe-area :as safe-area]
[status-im.common.resources :as resources]
[status-im.contexts.onboarding.welcome.style :as style]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn page-title
[]
(let [new-account? (rf/sub [:onboarding/new-account?])]
[quo/text-combinations
{:container-style style/page-title
:title (i18n/label (if new-account?
:t/welcome-to-web3
:t/welcome-back))
:title-accessibility-label :welcome-title
:description (i18n/label :t/welcome-to-web3-sub-title)
:description-accessibility-label :welcome-sub-title}]))
(defn view
[]
(let [profile-color (rf/sub [:onboarding/customization-color])
window (rf/sub [:dimensions/window])
insets (safe-area/get-insets)]
[rn/view {:style (style/page-container insets)}
[quo/page-nav
{:type :no-title
:background :blur
:icon-name :i/arrow-left
:on-press #(rf/dispatch [:navigate-back])}]
[page-title]
[rn/image
{:style (style/page-illustration (:width window))
:resize-mode :contain
:source (resources/get-image :welcome-illustration)}]
[rn/view {:style (style/buttons insets)}
(when rn/small-screen?
[linear-gradient/linear-gradient
{:style style/bottom-shadow
:colors [colors/neutral-100-opa-0 colors/neutral-100-opa-80]}])
[quo/button
{:on-press (fn []
(rf/dispatch [:update-theme-and-init-root :shell-stack])
(rf/dispatch [:profile/show-testnet-mode-banner-if-enabled])
(rf/dispatch [:universal-links/process-stored-event]))
:type :primary
:accessibility-label :welcome-button
:customization-color profile-color}
(i18n/label :t/start-using-status)]]]))
@@ -15,8 +15,7 @@
{:key :type
:type :select
:options [{:key :bullet}
{:key :step}
{:key :lock}]}
{:key :step}]}
(preview/customization-color-option)])
(defn view
+16 -19
View File
@@ -34,25 +34,22 @@
(defn create
[]
(let [log-enabled? (boolean (not-empty config/log-level))]
(assoc
(login)
:deviceName (native-module/get-installation-name)
:rootDataDir (native-module/backup-disabled-data-dir)
:rootKeystoreDir (native-module/keystore-dir)
:logLevel (when log-enabled? config/log-level)
:logEnabled log-enabled?
:logFilePath (native-module/log-file-directory)
:verifyTransactionURL config/verify-transaction-url
:verifyENSURL config/verify-ens-url
:verifyENSContractAddress config/verify-ens-contract-address
:verifyTransactionChainID config/verify-transaction-chain-id
:wakuV2LightClient true
:wakuV2Fleet config/fleet
;; NOTE: https://github.com/status-im/status-go/pull/5570#discussion_r1690794119
:wakuV2EnableMissingMessageVerification true
:wakuV2EnableStoreConfirmationForMessagesSent false
:previewPrivacy config/blank-preview?
:testNetworksEnabled config/test-networks-enabled?)))
(assoc (login)
:deviceName (native-module/get-installation-name)
:rootDataDir (native-module/backup-disabled-data-dir)
:rootKeystoreDir (native-module/keystore-dir)
:logLevel (when log-enabled? config/log-level)
:logEnabled log-enabled?
:logFilePath (native-module/log-file-directory)
:verifyTransactionURL config/verify-transaction-url
:verifyENSURL config/verify-ens-url
:verifyENSContractAddress config/verify-ens-contract-address
:verifyTransactionChainID config/verify-transaction-chain-id
:wakuV2LightClient true
:wakuV2Fleet config/fleet
:wakuV2EnableStoreConfirmationForMessagesSent false
:previewPrivacy config/blank-preview?
:testNetworksEnabled config/test-networks-enabled?)))
(defn strip-file-prefix
[path]
@@ -48,8 +48,7 @@
:profile/profile (merge profile-overview
settings
{:log-level log-level}))
(assoc-in [:activity-center :loading?] true)
(dissoc :centralized-metrics/onboarding-enabled?))
(assoc-in [:activity-center :loading?] true))
pairing-completed?
(dissoc :syncing))
:fx (into [[:json-rpc/call
@@ -63,9 +62,6 @@
;; loading chats and communities. This globally helps alleviate
;; stuttering immediately after login.
[:dispatch-later [{:ms 500 :dispatch [:wallet/initialize]}]]
;; Fetching the currencies along with wallet initialization as
;; we rely on it for displaying currency symbol
[:dispatch-later [{:ms 500 :dispatch [:settings/get-currencies]}]]
[:logs/set-level log-level]
@@ -73,10 +69,7 @@
;; messenger has started and has processed all chats because
;; the whole process can take a handful of seconds.
(when-not (:universal-links/handling db)
[:effects.chat/open-last-chat (:key-uid profile-overview)])
(when (:centralized-metrics/onboarding-enabled? db)
[:dispatch [:profile.settings/toggle-telemetry true]])]
[:effects.chat/open-last-chat (:key-uid profile-overview)])]
(cond
pairing-completed?
@@ -102,10 +95,9 @@
[:dispatch-later [{:ms 1500 :dispatch [:profile.login/non-critical-initialization]}]]
[:dispatch [:network/check-expensive-connection]]
[:profile.settings/get-profile-picture key-uid]
[:settings/get-currencies]
(when (ff/enabled? ::ff/wallet.wallet-connect)
[:dispatch [:wallet-connect/init]])
(when (ff/enabled? ::ff/wallet.swap)
[:dispatch [:wallet.tokens/get-token-list]])
(when notifications-enabled?
[:effects/push-notifications-enable])]})))
@@ -142,8 +134,6 @@
(rf/dispatch [:chats-list/load-success result])
(rf/dispatch [:communities/get-user-requests-to-join])
(rf/dispatch [:profile.login/get-chats-callback]))}]
(when (:syncing/installation-id db)
[:dispatch [:pairing/finish-seed-phrase-fallback-syncing]])
(when-not new-account?
[:dispatch [:universal-links/process-stored-event]])]})))
@@ -1,7 +1,6 @@
(ns status-im.contexts.profile.recover.events
(:require
[native-module.core :as native-module]
[status-im.constants :as constants]
[status-im.contexts.profile.config :as profile.config]
status-im.contexts.profile.recover.effects
[utils.re-frame :as rf]
@@ -17,10 +16,10 @@
(assoc-in [:syncing :login-sha3-password] login-sha3-password))
:effects.profile/restore-and-login
(assoc (profile.config/create)
:displayName display-name
:mnemonic (security/safe-unmask-data seed-phrase)
:password login-sha3-password
:imagePath (profile.config/strip-file-prefix image-path)
:customizationColor (or color constants/profile-default-color)
:fetchBackup true)}))
(merge (profile.config/create)
{:displayName display-name
:mnemonic (security/safe-unmask-data seed-phrase)
:password login-sha3-password
:imagePath (profile.config/strip-file-prefix image-path)
:customizationColor color
:fetchBackup true})}))
@@ -87,11 +87,9 @@
:on-error #(log/error "failed to toggle peer syncing" new-value %)}]]]})))
(rf/reg-event-fx :profile.settings/toggle-telemetry
(fn [{:keys [db]} [enable?]]
(let [enable? (if (nil? enable?)
(string/blank? (get-in db [:profile/profile :telemetry-server-url]))
enable?)
new-value (if enable? constants/default-telemetry-server-url "")]
(fn [{:keys [db]}]
(let [value (get-in db [:profile/profile :telemetry-server-url])
new-value (if (string/blank? value) constants/default-telemetry-server-url "")]
{:dispatch [:profile.settings/profile-update :telemetry-server-url new-value]})))
(rf/reg-event-fx :profile.settings/change-appearance
@@ -1,5 +1,6 @@
(ns status-im.contexts.settings.language-and-currency.events
(:require [status-im.contexts.settings.language-and-currency.data-store :as data-store]
(:require [status-im.common.json-rpc.events :as json-rpc]
[status-im.contexts.settings.language-and-currency.data-store :as data-store]
[utils.collection]
[utils.re-frame :as rf]))
@@ -10,9 +11,8 @@
:currencies
(utils.collection/index-by :id all-currencies))})))
(rf/reg-event-fx :settings/get-currencies
(rf/reg-fx :settings/get-currencies
(fn []
{:fx [[:json-rpc/call
[{:method "appgeneral_getCurrencies"
:on-success [:settings/get-currencies-success]
:on-error [:log-rpc-error {:event :settings/get-currencies}]}]]]}))
(json-rpc/call {:method "appgeneral_getCurrencies"
:on-success [:settings/get-currencies-success]
:on-error [:log-rpc-error {:event :settings/get-currencies}]})))
@@ -1,115 +0,0 @@
(ns status-im.contexts.settings.privacy-and-security.share-usage.view
(:require
[quo.core :as quo]
[quo.foundations.colors :as colors]
[quo.theme]
[react-native.core :as rn]
[react-native.safe-area :as safe-area]
[status-im.common.events-helper :as events-helper]
[status-im.common.privacy.view :as privacy]
[status-im.contexts.settings.privacy-and-security.style :as privacy-and-security.style]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(def ^:private network-behavior-points
[:t/number-of-messages-sent
:t/connected-peers
:t/successful-messages-rate
:t/connection-type
:t/os-app-version-bandwidth])
(def ^:private app-interactions-points
[:t/action-logs
:t/ip-addresses-uuid])
(def ^:private not-receive-points
[:t/your-profile-information
:t/your-addresses
:t/information-you-input-and-send])
(defn- get-category-data
[{:keys [title description points on-toggle toggle-checked? lock?]}]
{:title title
:description (when description :text)
:description-props (when description {:text description})
:blur? true
:action (when on-toggle :selector)
:action-props (when on-toggle
{:on-change on-toggle
:checked? toggle-checked?})
:content [rn/view {:style {:margin-top 8}}
(for [label points]
^{:key label}
[quo/markdown-list
{:description (i18n/label label)
:blur? true
:type (when lock? :lock)
:container-style {:padding-top 8}}])]})
(defn- on-privacy-policy-press
[]
(rf/dispatch
[:show-bottom-sheet
{:content (fn []
[quo.theme/provider :dark
[privacy/privacy-statement]])
:shell? true}]))
(defn- privacy-policy-text
[]
[quo/text {:style {:text-align :center}}
[quo/text
{:style {:color colors/white-opa-50}
:size :paragraph-2}
(i18n/label :t/more-details-in-privacy-policy-settings-1)]
[quo/text
{:size :paragraph-2
:on-press on-privacy-policy-press}
(i18n/label :t/more-details-in-privacy-policy-2)]])
(defn view
[]
(let [insets (safe-area/get-insets)
telemetry-enabled? (rf/sub [:profile/telemetry-enabled?])
centralized-metrics-enabled? (rf/sub [:centralized-metrics/enabled?])]
[quo/overlay
{:type :shell
:container-style (privacy-and-security.style/page-wrapper (:top insets))}
[quo/page-nav
{:key :header
:background :blur
:icon-name :i/arrow-left
:on-press events-helper/navigate-back}]
[quo/page-top
{:title (i18n/label :t/share-usage-data)
:title-accessibility-label :title-label
:description :text
:description-text (i18n/label :t/collecting-usage-data)}]
[rn/scroll-view {:style {:flex 1}}
[quo/category
{:data [(get-category-data
{:toggle-checked? telemetry-enabled?
:on-toggle #(rf/dispatch [:profile.settings/toggle-telemetry])
:title (i18n/label :t/network-behavior)
:description (i18n/label :t/will-be-shared-from-the-current-profile)
:points network-behavior-points})
(get-category-data {:toggle-checked? centralized-metrics-enabled?
:on-toggle #(rf/dispatch
[:centralized-metrics/toggle-centralized-metrics
(not centralized-metrics-enabled?)])
:title (i18n/label :t/app-interactions)
:description (i18n/label :t/will-be-shared-from-all-profiles)
:points app-interactions-points})]
:blur? true
:list-type :settings}]
[quo/category
{:data [(get-category-data {:title (i18n/label :t/what-we-wont-receive)
:points not-receive-points
:lock? true})]
:blur? true
:list-type :settings}]]
[rn/view
{:align-items :center
:padding-horizontal 20
:padding-bottom (:bottom insets)}
[privacy-policy-text]]]))
@@ -6,6 +6,7 @@
[react-native.platform :as platform]
[react-native.safe-area :as safe-area]
[status-im.common.events-helper :as events-helper]
[status-im.common.metrics-confirmation-modal.view :as metrics-modal]
[status-im.contexts.settings.privacy-and-security.profile-picture.view :as profile-picture.view]
[status-im.contexts.settings.privacy-and-security.style :as style]
[status-im.feature-flags :as ff]
@@ -24,9 +25,19 @@
:id :preview-privacy
:customization-color customization-color}})
(defn- on-share-usage-data-press
[]
(rf/dispatch
[:show-bottom-sheet
{:content (fn []
[quo.theme/provider :dark
[metrics-modal/view {:settings? true}]])
:shell? true}]))
(defn view
[]
(let [insets (safe-area/get-insets)
centralized-metrics-enabled? (rf/sub [:centralized-metrics/enabled?])
customization-color (rf/sub [:profile/customization-color])
preview-privacy? (rf/sub [:profile/preview-privacy?])
@@ -84,12 +95,13 @@
show-profile-pictures-to
open-show-profile-pictures-to-options))
(setting-preview-privacy preview-privacy? customization-color toggle-preview-privacy)
{:title (i18n/label :t/share-usage-data)
{:title (i18n/label :t/share-usage-data-with-status)
:description :text
:description-props {:text (i18n/label :t/from-all-profiles-on-device)}
:blur? true
:action :arrow
:action-props {:on-change #(rf/dispatch [:open-modal
:screen/settings.share-usage-data])}}]
:action :selector
:action-props {:on-change on-share-usage-data-press
:customization-color customization-color
:checked? centralized-metrics-enabled?}}]
:blur? true
:list-type :settings}]]))
@@ -1,78 +0,0 @@
(ns status-im.contexts.shell.activity-center.notification.syncing.view
(:require
[quo.core :as quo]
quo.theme
[react-native.core :as rn]
[status-im.common.new-device-sheet.view :as new-device-sheet]
[status-im.contexts.shell.activity-center.notification.common.view :as common]
[utils.datetime :as datetime]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn- swipeable
[{:keys [extra-fn]} child]
[common/swipeable
{:left-button common/swipe-button-read-or-unread
:left-on-press common/swipe-on-press-toggle-read
:right-button common/swipe-button-delete
:right-on-press common/swipe-on-press-delete
:extra-fn extra-fn}
child])
(defn installation-created-view
[{:keys [notification extra-fn]}]
(let [{:keys [installation-id read timestamp]} notification
customization-color (rf/sub [:profile/customization-color])
theme (quo.theme/use-theme)
more-details (rn/use-callback
(fn []
(rf/dispatch [:show-bottom-sheet
{:content
(fn []
[new-device-sheet/installation-request-creator-view
installation-id])}]))
[installation-id])]
[swipeable {:extra-fn extra-fn}
[quo/activity-log
{:title (i18n/label :t/sync-your-profile)
:customization-color customization-color
:icon :i/mobile
:timestamp (datetime/timestamp->relative timestamp)
:unread? (not read)
:context [(i18n/label :t/check-other-device-for-pairing)]
:items [{:type :button
:subtype :positive
:key :review-pairing-request
:blur? true
:label (i18n/label :t/more-details)
:theme theme
:on-press more-details}]}]]))
(defn installation-received-view
[{:keys [notification extra-fn]}]
(let [{:keys [installation-id read timestamp]} notification
customization-color (rf/sub [:profile/customization-color])
theme (quo.theme/use-theme)
review-pairing-request (rn/use-callback
(fn []
(rf/dispatch [:show-bottom-sheet
{:content
(fn []
[new-device-sheet/installation-request-receiver-view
installation-id])}]))
[installation-id])]
[swipeable {:extra-fn extra-fn}
[quo/activity-log
{:title (i18n/label :t/new-device-detected)
:customization-color customization-color
:icon :i/mobile
:timestamp (datetime/timestamp->relative timestamp)
:unread? (not read)
:context [(i18n/label :t/new-device-detected)]
:items [{:type :button
:subtype :positive
:key :review-pairing-request
:blur? true
:label (i18n/label :t/review-pairing-request)
:theme theme
:on-press review-pairing-request}]}]]))
@@ -10,8 +10,6 @@
(def ^:const admin 8)
(def ^:const community-kicked 9)
(def ^:const contact-verification 10)
(def ^:const new-installation-received 23)
(def ^:const new-installation-created 24)
(def ^:const all-supported
#{one-to-one-chat
@@ -22,9 +20,7 @@
community-request
admin
community-kicked
contact-verification
new-installation-received
new-installation-created})
contact-verification})
;; TODO: Replace with correct enum values once status-go implements them.
(def ^:const tx 66612)
@@ -19,7 +19,6 @@
[status-im.contexts.shell.activity-center.notification.membership.view :as membership]
[status-im.contexts.shell.activity-center.notification.mentions.view :as mentions]
[status-im.contexts.shell.activity-center.notification.reply.view :as reply]
[status-im.contexts.shell.activity-center.notification.syncing.view :as syncing]
[status-im.contexts.shell.activity-center.style :as style]
[status-im.contexts.shell.activity-center.tabs.empty-tab.view :as empty-tab]
[utils.re-frame :as rf]))
@@ -49,12 +48,6 @@
(= notification-type types/admin)
[admin/view props]
(= notification-type types/new-installation-received)
[syncing/installation-received-view props]
(= notification-type types/new-installation-created)
[syncing/installation-created-view props]
(types/membership notification-type)
(condp = notification-type
types/private-group-chat [membership/view props]
@@ -84,7 +84,7 @@
(load-and-show-profile scanned-text)
(utils-address/supported-address? scanned-text)
(when-let [address (utils-address/supported-address->eth-address scanned-text)]
(when-let [address (utils-address/supported-address->status-address scanned-text)]
(debounce/debounce-and-dispatch [:generic-scanner/scan-success address] 300)
(debounce/debounce-and-dispatch [:navigate-change-tab :wallet-stack] 300))
+3 -32
View File
@@ -2,7 +2,6 @@
(:require
[clojure.string :as string]
[native-module.core :as native-module]
[re-frame.core :as re-frame]
[react-native.platform :as platform]
[status-im.constants :as constants]
[status-im.contexts.profile.config :as profile.config]
@@ -22,45 +21,18 @@
{:events [:syncing/clear-states]}
[{:keys [db]} role]
{:db (dissoc db :syncing)})
(defn- input-connection-string-callback
[res]
(log/info "[local-pairing] input-connection-string-for-bootstrapping callback"
{:response res
:event :syncing/input-connection-string-for-bootstrapping})
(let [response (transforms/json->clj res)
installation-id (:installationId response)
key-uid (:keyUID response)
error (:error response)]
(when (seq installation-id)
(rf/dispatch [:syncing/set-syncing-installation-id installation-id key-uid]))
(when (seq error)
(let [error (when (sync-utils/extract-error res)
(str "generic-error: " res))]
(when (some? error)
(rf/dispatch [:toasts/upsert
{:type :negative
:text error}]))))
(rf/defn initiate-pairing-process
{:events [:syncing/initiate-pairing-process]}
[{:keys [db]}]
{:db (assoc db :syncing/pairing-process-initiated? true)})
(rf/defn set-syncing-installation-id
{:events [:syncing/set-syncing-installation-id]}
[{:keys [db]} installation-id key-uid]
{:db (assoc db
:syncing/key-uid key-uid
:syncing/installation-id installation-id)})
(defn clear-syncing-data
[{:keys [db]}]
{:db (dissoc
db
:syncing/key-uid
:syncing/installation-id
:syncing/pairing-process-initiated?)})
(re-frame/reg-event-fx :syncing/clear-syncing-data clear-syncing-data)
(rf/defn preflight-outbound-check-for-local-pairing
{:events [:syncing/preflight-outbound-check]}
[_ set-checks-passed]
@@ -95,7 +67,6 @@
(when (sync-utils/valid-connection-string? response)
(on-valid-connection-string response)
(rf/dispatch [:syncing/update-role constants/local-pairing-role-sender])
(rf/dispatch [:syncing/initiate-pairing-process])
(rf/dispatch [:hide-bottom-sheet])))]
(when-not (and error (string/blank? error))
(let [key-uid (get-in db [:profile/profile :key-uid])

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