Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b778f62e6d | ||
|
|
ac6e3cdb2c | ||
|
|
691c6b67ce | ||
|
|
e26d3a856d | ||
|
|
98cdf00276 | ||
|
|
eb379791c9 | ||
|
|
6bda54266b | ||
|
|
1a8bca56e0 | ||
|
|
296d868797 | ||
|
|
e002fab376 | ||
|
|
4fa9a27cd9 | ||
|
|
dbcdb18818 |
@@ -22,37 +22,37 @@
|
||||
:multi-lhs-hang]
|
||||
|
||||
:fn-map
|
||||
{"reg-sub" :arg1-pair
|
||||
"h/describe" :arg1-body
|
||||
"h/describe-skip" :arg1-body
|
||||
"h/describe-only" :arg1-body
|
||||
"h/test" :arg1-body
|
||||
"h/test-skip" :arg1-body
|
||||
"h/test-only" :arg1-body
|
||||
"test/async" :arg1-body
|
||||
"test/use-fixtures" :arg1-body
|
||||
"global.describe" :arg1-body
|
||||
"global.test" :arg1-body
|
||||
"list-comp" :binding
|
||||
"defview" :arg1-body
|
||||
"letsubs" :binding
|
||||
"with-let" "let"
|
||||
"reg-event-fx" :arg1-pair
|
||||
"reg-fx" :arg1-pair
|
||||
"testing" :arg1-body
|
||||
"deftest-sub" :arg1-body
|
||||
"h/integration-test" :arg1-body
|
||||
"wait-for" :arg1-body
|
||||
"with-deps-check" :arg1-body
|
||||
"schema/=>" :arg1-body
|
||||
"->" [:noarg1-body
|
||||
{:list {:constant-pair? false :force-nl? false}
|
||||
:next-inner-restore [[:list :constant-pair?]]}]
|
||||
"set!" "reset!"
|
||||
"assoc-when" "assoc"
|
||||
"assoc-some" "assoc"
|
||||
"conj-when" "conj"
|
||||
"conj-some" "conj"}
|
||||
{"reg-sub" :arg1-pair
|
||||
"h/describe" :arg1-body
|
||||
"h/describe-skip" :arg1-body
|
||||
"h/describe-only" :arg1-body
|
||||
"h/test" :arg1-body
|
||||
"h/test-skip" :arg1-body
|
||||
"h/test-only" :arg1-body
|
||||
"test/async" :arg1-body
|
||||
"test/use-fixtures" :arg1-body
|
||||
"global.describe" :arg1-body
|
||||
"global.test" :arg1-body
|
||||
"list-comp" :binding
|
||||
"defview" :arg1-body
|
||||
"letsubs" :binding
|
||||
"with-let" "let"
|
||||
"reg-event-fx" :arg1-pair
|
||||
"reg-fx" :arg1-pair
|
||||
"testing" :arg1-body
|
||||
"deftest-sub" :arg1-body
|
||||
"test-async" :arg1-body
|
||||
"wait-for" :arg1-body
|
||||
"with-deps-check" :arg1-body
|
||||
"schema/=>" :arg1-body
|
||||
"->" [:noarg1-body
|
||||
{:list {:constant-pair? false :force-nl? false}
|
||||
:next-inner-restore [[:list :constant-pair?]]}]
|
||||
"set!" "reset!"
|
||||
"assoc-when" "assoc"
|
||||
"assoc-some" "assoc"
|
||||
"conj-when" "conj"
|
||||
"conj-some" "conj"}
|
||||
|
||||
:style-map
|
||||
{:no-comma {:map {:comma? false}}
|
||||
|
||||
+6
-3
@@ -1,6 +1,9 @@
|
||||
module.exports = {
|
||||
presets: ['module:metro-react-native-babel-preset'],
|
||||
plugins: ['react-native-reanimated/plugin', '@babel/plugin-transform-named-capturing-groups-regex'],
|
||||
presets: [
|
||||
"@babel/preset-env"
|
||||
// 'module:metro-react-native-babel-preset'
|
||||
],
|
||||
/*plugins: ['react-native-reanimated/plugin', '@babel/plugin-transform-named-capturing-groups-regex'],
|
||||
env: {
|
||||
test: {
|
||||
presets: [
|
||||
@@ -15,5 +18,5 @@ module.exports = {
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
},*/
|
||||
};
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
"@babel/helper-builder-react-jsx": "^7.20.0",
|
||||
"@babel/plugin-transform-block-scoping": "^7.20.0",
|
||||
"@babel/preset-env": "^7.20.0",
|
||||
"@babel/preset-es2015": "^7.0.0-beta.53",
|
||||
"@babel/preset-react": "^7.18.6",
|
||||
"@babel/register": "7.0.0",
|
||||
"@jest/globals": "^25.1.0",
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
;; To match the folder created by Nix build of JSBundle.
|
||||
:output-dir "result"
|
||||
:init-fn status-im.core/init
|
||||
:build-hooks [(status-im.setup.build-hooks.worklets/transform-output)]
|
||||
;; When false, the Shadow-CLJS watcher won't automatically refresh
|
||||
;; the target files (a.k.a hot reload). When false, you can manually
|
||||
;; reload by calling `shadow.cljs.devtools.api/watch-compile-all!`.
|
||||
|
||||
@@ -4,13 +4,6 @@
|
||||
[clojure.walk :as walk]
|
||||
[status-im.constants :as constants]))
|
||||
|
||||
(defn rpc->channel-permissions
|
||||
[rpc-channels-permissions]
|
||||
(update-vals rpc-channels-permissions
|
||||
(fn [{:keys [viewAndPostPermissions viewOnlyPermissions]}]
|
||||
{:view-only (set/rename-keys viewOnlyPermissions {:satisfied :satisfied?})
|
||||
:view-and-post (set/rename-keys viewAndPostPermissions {:satisfied :satisfied?})})))
|
||||
|
||||
(defn <-revealed-accounts-rpc
|
||||
[accounts]
|
||||
(mapv
|
||||
@@ -34,8 +27,9 @@
|
||||
(assoc acc
|
||||
(name k)
|
||||
(-> v
|
||||
(assoc :can-post? (:canPost v))
|
||||
(dissoc :canPost)
|
||||
(assoc :token-gated? (:tokenGated v)
|
||||
:can-post? (:canPost v))
|
||||
(dissoc :canPost :tokenGated)
|
||||
(update :members walk/stringify-keys))))
|
||||
{}
|
||||
chats))
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
(ns legacy.status-im.data-store.communities-test
|
||||
(:require
|
||||
[cljs.test :refer [deftest is]]
|
||||
[legacy.status-im.data-store.communities :as sut]))
|
||||
|
||||
(def permissions
|
||||
{"community-id-chat-1"
|
||||
{:viewOnlyPermissions {:satisfied false
|
||||
:permissions {:token-permission-id-01 {:criteria [false]}}}
|
||||
:viewAndPostPermissions {:satisfied true :permissions {}}}
|
||||
"community-id-chat-2"
|
||||
{:viewOnlyPermissions {:satisfied true :permissions {}}
|
||||
:viewAndPostPermissions {:satisfied true :permissions {}}}})
|
||||
|
||||
(deftest rpc->channel-permissions-test
|
||||
(is (= {"community-id-chat-1"
|
||||
{:view-only {:satisfied? false
|
||||
:permissions {:token-permission-id-01 {:criteria [false]}}}
|
||||
:view-and-post {:satisfied? true :permissions {}}}
|
||||
"community-id-chat-2"
|
||||
{:view-only {:satisfied? true :permissions {}}
|
||||
:view-and-post {:satisfied? true :permissions {}}}}
|
||||
(sut/rpc->channel-permissions permissions))))
|
||||
@@ -28,8 +28,7 @@
|
||||
[{:keys [db] :as cofx} {:keys [auth-method logout?]}]
|
||||
(let [key-uid (get-in db [:profile/profile :key-uid])]
|
||||
(rf/merge cofx
|
||||
{:set-root :progress
|
||||
:effects.shell/reset-state nil
|
||||
{:effects.shell/reset-state nil
|
||||
:hide-popover nil
|
||||
::logout nil
|
||||
:profile.settings/webview-debug-changed false
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
(ns legacy.status-im.ui.screens.screens
|
||||
(:require
|
||||
[legacy.status-im.ui.components.colors :as colors]
|
||||
[legacy.status-im.ui.components.icons.icons :as icons]
|
||||
[legacy.status-im.ui.screens.about-app.views :as about-app]
|
||||
[legacy.status-im.ui.screens.advanced-settings.views :as advanced-settings]
|
||||
[legacy.status-im.ui.screens.appearance.views :as appearance]
|
||||
@@ -48,10 +47,18 @@
|
||||
[status-im.contexts.chat.group-details.view :as group-details]
|
||||
[utils.i18n :as i18n]))
|
||||
|
||||
(defn right-button-options
|
||||
[id icon]
|
||||
{:id id
|
||||
:icon (icons/icon-source icon)})
|
||||
(defn topbar-options
|
||||
[title]
|
||||
{:elevation 0
|
||||
:title {:color (if (colors/dark?) colors/white colors/black)
|
||||
:text (i18n/label title)}
|
||||
:rightButtonColor (if (colors/dark?) colors/white colors/black)
|
||||
:background {:color (if (colors/dark?) colors/black colors/white)}
|
||||
:backButton {:color (if (colors/dark?) colors/white colors/black)
|
||||
:id :legacy-back-button
|
||||
:testID :back-button
|
||||
:visible true
|
||||
:popStackOnPress false}})
|
||||
|
||||
(defn screens
|
||||
[]
|
||||
@@ -77,7 +84,7 @@
|
||||
|
||||
{:name :stickers
|
||||
:options {:insets {:top? true}
|
||||
:topBar {:title {:text (i18n/label :t/sticker-market)}}}
|
||||
:topBar (topbar-options :t/sticker-market)}
|
||||
:component stickers/packs}
|
||||
|
||||
{:name :stickers-pack
|
||||
@@ -90,7 +97,7 @@
|
||||
:component group-chat/new-group}
|
||||
|
||||
{:name :currency-settings
|
||||
:options {:topBar {:title {:text (i18n/label :t/main-currency)}}
|
||||
:options {:topBar (topbar-options :t/main-currency)
|
||||
:insets {:top? true}}
|
||||
:component currency-settings/currency-settings}
|
||||
|
||||
@@ -100,27 +107,27 @@
|
||||
:options {:topBar {:visible false}}
|
||||
:component profile.user/my-profile}
|
||||
{:name :contacts-list
|
||||
:options {:topBar {:title {:text (i18n/label :t/contacts)}}
|
||||
:options {:topBar (topbar-options :t/contacts)
|
||||
:insets {:top? true}}
|
||||
:component contacts-list/contacts-list}
|
||||
{:name :ens-main
|
||||
:options {:topBar {:title {:text (i18n/label :t/ens-usernames)}}
|
||||
:options {:topBar (topbar-options :t/ens-usernames)
|
||||
:insets {:top? true}}
|
||||
:component ens/main}
|
||||
{:name :ens-search
|
||||
:options {:topBar {:title {:text (i18n/label :t/ens-your-username)}}
|
||||
:options {:topBar (topbar-options :t/ens-your-username)
|
||||
:insets {:top? true}}
|
||||
:component ens/search}
|
||||
{:name :ens-checkout
|
||||
:options {:topBar {:title {:text (i18n/label :t/ens-your-username)}}
|
||||
:options {:topBar (topbar-options :t/ens-your-username)
|
||||
:insets {:top? true}}
|
||||
:component ens/checkout}
|
||||
{:name :ens-confirmation
|
||||
:options {:topBar {:title {:text (i18n/label :t/ens-your-username)}}
|
||||
:options {:topBar (topbar-options :t/ens-your-username)
|
||||
:insets {:top? true}}
|
||||
:component ens/confirmation}
|
||||
{:name :ens-terms
|
||||
:options {:topBar {:title {:text (i18n/label :t/ens-terms-registration)}}
|
||||
:options {:topBar (topbar-options :t/ens-terms-registration)
|
||||
:insets {:top? true}}
|
||||
:component ens/terms}
|
||||
{:name :ens-name-details
|
||||
@@ -128,7 +135,7 @@
|
||||
:options {:insets {:top? true}}
|
||||
:component ens/name-details}
|
||||
{:name :blocked-users-list
|
||||
:options {:topBar {:title {:text (i18n/label :t/blocked-users)}}
|
||||
:options {:topBar (topbar-options :t/blocked-users)
|
||||
:insets {:top? true}}
|
||||
:component contacts-list/blocked-users-list}
|
||||
{:name :wakuv2-settings
|
||||
@@ -144,7 +151,7 @@
|
||||
:options {:insets {:top? true}}
|
||||
:component bootnodes-settings/bootnodes-settings}
|
||||
{:name :installations
|
||||
:options {:topBar {:title {:text (i18n/label :t/devices)}}
|
||||
:options {:topBar (topbar-options (i18n/label :t/devices))
|
||||
:insets {:top? true}}
|
||||
:component pairing/installations}
|
||||
{:name :edit-bootnode
|
||||
@@ -160,63 +167,63 @@
|
||||
:options {:insets {:top? true}}
|
||||
:component edit-mailserver/edit-mailserver}
|
||||
{:name :dapps-permissions
|
||||
:options {:topBar {:title {:text (i18n/label :t/dapps-permissions)}}
|
||||
:options {:topBar (topbar-options :t/dapps-permissions)
|
||||
:insets {:top? true}}
|
||||
:component dapps-permissions/dapps-permissions}
|
||||
{:name :privacy-and-security
|
||||
:options {:topBar {:title {:text (i18n/label :t/privacy-and-security)}}
|
||||
:options {:topBar (topbar-options :t/privacy-and-security)
|
||||
:insets {:top? true}}
|
||||
:component privacy-and-security/privacy-and-security}
|
||||
{:name :messages-from-contacts-only
|
||||
:options {:topBar {:title {:text (i18n/label :t/accept-new-chats-from)}}
|
||||
:options {:topBar (topbar-options :t/accept-new-chats-from)
|
||||
:insets {:top? true}}
|
||||
:component messages-from-contacts-only/messages-from-contacts-only-view}
|
||||
{:name :appearance
|
||||
:options {:topBar {:title {:text (i18n/label :t/appearance)}}
|
||||
:options {:topBar (topbar-options :t/appearance)
|
||||
:insets {:top? true}}
|
||||
:component appearance/appearance-view}
|
||||
{:name :privacy-and-security-profile-pic-show-to
|
||||
:options {:topbar {:title {:text (i18n/label :t/show-profile-pictures-to)}}
|
||||
:options {:topbar (topbar-options :t/show-profile-pictures-to)
|
||||
:insets {:top? true}}
|
||||
:component privacy-and-security/profile-pic-show-to}
|
||||
{:name :privacy-and-security-profile-pic
|
||||
:options {:topBar {:title {:text (i18n/label :t/show-profile-pictures)}}
|
||||
:options {:topBar (topbar-options :t/show-profile-pictures)
|
||||
:insets {:top? true}}
|
||||
:component privacy-and-security/profile-pic}
|
||||
{:name :notifications
|
||||
:options {:topBar {:title {:text (i18n/label :t/notification-settings)}}
|
||||
:options {:topBar (topbar-options :t/notification-settings)
|
||||
:insets {:top? true}}
|
||||
:component notifications-settings/notifications-settings}
|
||||
{:name :sync-settings
|
||||
:options {:topBar {:title {:text (i18n/label :t/sync-settings)}}
|
||||
:options {:topBar (topbar-options :t/sync-settings)
|
||||
:insets {:top? true}}
|
||||
:component sync-settings/sync-settings}
|
||||
{:name :advanced-settings
|
||||
:options {:topBar {:title {:text (i18n/label :t/advanced)}}
|
||||
:options {:topBar (topbar-options :t/advanced)
|
||||
:insets {:top? true}}
|
||||
:component advanced-settings/advanced-settings}
|
||||
{:name :help-center
|
||||
:options {:topBar {:title {:text (i18n/label :t/need-help)}}
|
||||
:options {:topBar (topbar-options :t/need-help)
|
||||
:insets {:top? true}}
|
||||
:component help-center/help-center}
|
||||
{:name :glossary
|
||||
:options {:topBar {:title {:text (i18n/label :t/glossary)}}
|
||||
:options {:topBar (topbar-options :t/glossary)
|
||||
:insets {:top? true}}
|
||||
:component glossary/glossary}
|
||||
{:name :about-app
|
||||
:options {:topBar {:title {:text (i18n/label :t/about-app)}}
|
||||
:options {:topBar (topbar-options :t/about-app)
|
||||
:insets {:top? true}}
|
||||
:component about-app/about-app}
|
||||
{:name :privacy-policy
|
||||
:options {:topBar {:title {:text (i18n/label :t/privacy-policy)}}
|
||||
:options {:topBar (topbar-options :t/privacy-policy)
|
||||
:insets {:top? true}}
|
||||
:component about-app/privacy-policy}
|
||||
{:name :terms-of-service
|
||||
:options {:topBar {:title {:text (i18n/label :t/terms-of-service)}}
|
||||
:options {:topBar (topbar-options :t/terms-of-service)
|
||||
:insets {:top? true}}
|
||||
:component about-app/tos}
|
||||
{:name :principles
|
||||
:options {:topBar {:title {:text (i18n/label :t/principles)}}
|
||||
:options {:topBar (topbar-options :t/principles)
|
||||
:insets {:top? true}}
|
||||
:component about-app/principles}
|
||||
{:name :manage-dapps-permissions
|
||||
@@ -224,27 +231,27 @@
|
||||
:options {:insets {:top? true}}
|
||||
:component dapps-permissions/manage}
|
||||
{:name :rpc-usage-info
|
||||
:options {:topBar {:title {:text (i18n/label :t/rpc-usage-info)}}
|
||||
:options {:topBar (topbar-options :t/rpc-usage-info)
|
||||
:insets {:top? true}}
|
||||
:component rpc-usage-info/usage-info}
|
||||
{:name :peers-stats
|
||||
:options {:topBar {:title {:text (i18n/label :t/peers-stats)}}
|
||||
:options {:topBar (topbar-options :t/peers-stats)
|
||||
:insets {:top? true}}
|
||||
:component peers-stats/peers-stats}
|
||||
{:name :log-level-settings
|
||||
:options {:topBar {:title {:text (i18n/label :t/log-level-settings)}}
|
||||
:options {:topBar (topbar-options :t/log-level-settings)
|
||||
:insets {:top? true}}
|
||||
:component log-level-settings/log-level-settings}
|
||||
{:name :fleet-settings
|
||||
:options {:topBar {:title {:text (i18n/label :t/fleet-settings)}}
|
||||
:options {:topBar (topbar-options :t/fleet-settings)
|
||||
:insets {:top? true}}
|
||||
:component fleet-settings/fleet-settings}
|
||||
{:name :mobile-network-settings
|
||||
:options {:topBar {:title {:text (i18n/label :t/mobile-network-settings)}}
|
||||
:options {:topBar (topbar-options :t/mobile-network-settings)
|
||||
:insets {:top? true}}
|
||||
:component mobile-network-settings/mobile-network-settings}
|
||||
{:name :backup-settings
|
||||
:options {:topBar {:title {:text (i18n/label :t/backup-settings)}}
|
||||
:options {:topBar (topbar-options :t/backup-settings)
|
||||
:insets {:top? true}}
|
||||
:component backup-settings/backup-settings}
|
||||
{:name :backup-seed
|
||||
@@ -252,14 +259,14 @@
|
||||
:options {:insets {:top? true}}
|
||||
:component profile.seed/backup-seed}
|
||||
{:name :reset-password
|
||||
:options {:topBar {:title {:text (i18n/label :t/reset-password)}}
|
||||
:options {:topBar (topbar-options :t/reset-password)
|
||||
:insets {:top? true}}
|
||||
:component reset-password/reset-password}
|
||||
{:name :delete-profile
|
||||
:insets {:bottom? true}
|
||||
:component delete-profile/delete-profile}
|
||||
{:name :default-sync-period-settings
|
||||
:options {:topBar {:title {:text (i18n/label :t/default-sync-period)}}
|
||||
:options {:topBar (topbar-options :t/default-sync-period)
|
||||
:insets {:top? true}}
|
||||
:component default-sync-period-settings/default-sync-period-settings}
|
||||
|
||||
@@ -268,7 +275,7 @@
|
||||
;[Chat] Link preview settings
|
||||
|
||||
{:name :link-previews-settings
|
||||
:options {:topBar {:title {:text (i18n/label :t/chat-link-previews)}}
|
||||
:options {:topBar (topbar-options :t/chat-link-previews)
|
||||
:insets {:top? true}}
|
||||
:component link-previews-settings/link-previews-settings}
|
||||
|
||||
@@ -308,7 +315,7 @@
|
||||
;;TODO WHY MODAL?
|
||||
;[Profile] Notifications settings
|
||||
{:name :notifications-settings
|
||||
:options {:topBar {:title {:text (i18n/label :t/notification-settings)}}
|
||||
:options {:topBar (topbar-options :t/notification-settings)
|
||||
:popGesture false
|
||||
:hardwareBackButton {:dismissModalOnPress false
|
||||
:popStackOnPress false}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
(ns quo.components.colors.color-picker.component-spec
|
||||
(:require
|
||||
[quo.components.colors.color-picker.view :as color-picker]
|
||||
[reagent.core :as reagent]
|
||||
[test-helpers.component :as h]))
|
||||
|
||||
(h/describe "color-picker"
|
||||
@@ -12,7 +11,7 @@
|
||||
(-> (h/expect event)
|
||||
(.toHaveBeenCalled))))
|
||||
(h/test "color picker color changed"
|
||||
(let [selected (reagent/atom nil)]
|
||||
(let [selected (atom nil)]
|
||||
(h/render [color-picker/view {:on-change #(reset! selected %)}])
|
||||
(h/fire-event :press (get (h/get-all-by-label-text :color-picker-item) 0))
|
||||
(-> (h/expect @selected)
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
[quo.foundations.colors :as colors]
|
||||
[quo.theme :as quo.theme]
|
||||
[react-native.core :as rn]
|
||||
[react-native.reanimated :as reanimated]
|
||||
[reagent.core :as reagent]))
|
||||
[react-native.reanimated :as reanimated]))
|
||||
|
||||
(def header-height 56)
|
||||
|
||||
@@ -57,7 +56,7 @@
|
||||
:justify-content :center
|
||||
:align-items :flex-end})
|
||||
|
||||
(defn title-style
|
||||
(defn get-title-style
|
||||
[{:keys [left right]} title-align]
|
||||
(merge
|
||||
absolute-fill
|
||||
@@ -133,76 +132,77 @@
|
||||
:size :large}
|
||||
title])])
|
||||
|
||||
(defn- header-internal
|
||||
[{:keys [left-width right-width]}]
|
||||
(let [layout (reagent/atom {:left {:width (or left-width 8)
|
||||
:height header-height}
|
||||
:right {:width (or right-width 8)
|
||||
:height header-height}
|
||||
:title {:width 0
|
||||
:height header-height}})
|
||||
handle-layout (fn [el get-layout]
|
||||
(fn [evt]
|
||||
(let [width (oget evt "nativeEvent" "layout" "width")
|
||||
height (oget evt "nativeEvent" "layout" "height")]
|
||||
(when get-layout
|
||||
(get-layout el
|
||||
{:width width
|
||||
:height height}))
|
||||
(swap! layout assoc
|
||||
el
|
||||
{:width width
|
||||
:height height}))))]
|
||||
(fn
|
||||
[{:keys [left-accessories left-component border-bottom
|
||||
right-accessories right-component insets get-layout
|
||||
title subtitle title-component style title-align
|
||||
background theme]
|
||||
:or {title-align :center
|
||||
border-bottom false}}]
|
||||
(let [status-bar-height (get insets :top 0)
|
||||
height (+ header-height status-bar-height)]
|
||||
[reanimated/view
|
||||
{:style (header-wrapper-style {:height height
|
||||
:background background
|
||||
:border-bottom border-bottom
|
||||
:theme theme})}
|
||||
[rn/view
|
||||
{:pointer-events :box-none
|
||||
:height status-bar-height}]
|
||||
[rn/view
|
||||
{:style (merge {:height header-height}
|
||||
style)
|
||||
:pointer-events :box-none}
|
||||
[rn/view
|
||||
{:style absolute-fill
|
||||
:pointer-events :box-none}
|
||||
[rn/view
|
||||
{:style content
|
||||
:pointer-events :box-none}
|
||||
[rn/view
|
||||
{:style left-style
|
||||
:on-layout (handle-layout :left get-layout)
|
||||
:pointer-events :box-none}
|
||||
[header-actions
|
||||
{:accessories left-accessories
|
||||
:component left-component}]]
|
||||
|
||||
[rn/view
|
||||
{:style (title-style @layout title-align)
|
||||
:on-layout (handle-layout :title get-layout)
|
||||
:pointer-events :box-none}
|
||||
[header-title
|
||||
{:title title
|
||||
:subtitle subtitle
|
||||
:title-align title-align
|
||||
:component title-component}]]
|
||||
[rn/view
|
||||
{:style right-style
|
||||
:on-layout (handle-layout :right get-layout)
|
||||
:pointer-events :box-none}
|
||||
[header-actions
|
||||
{:accessories right-accessories
|
||||
:component right-component}]]]]]]))))
|
||||
|
||||
(def header (quo.theme/with-theme header-internal))
|
||||
(defn header
|
||||
[{:keys [left-accessories left-component border-bottom left-width right-width
|
||||
right-accessories right-component insets get-layout
|
||||
title subtitle title-component style title-align
|
||||
background]
|
||||
:or {title-align :center
|
||||
border-bottom false}}]
|
||||
(let [theme (quo.theme/use-theme-value)
|
||||
[layout set-layout] (rn/use-state {:left {:width (or left-width 8)
|
||||
:height header-height}
|
||||
:right {:width (or right-width 8)
|
||||
:height header-height}
|
||||
:title {:width 0
|
||||
:height header-height}})
|
||||
handle-layout (rn/use-callback
|
||||
(fn [el get-layout]
|
||||
(fn [evt]
|
||||
(let [width (oget evt "nativeEvent" "layout" "width")
|
||||
height (oget evt "nativeEvent" "layout" "height")]
|
||||
(when get-layout
|
||||
(get-layout el
|
||||
{:width width
|
||||
:height height}))
|
||||
(set-layout
|
||||
(assoc layout
|
||||
el
|
||||
{:width width
|
||||
:height height})))))
|
||||
[layout])
|
||||
status-bar-height (get insets :top 0)
|
||||
height (+ header-height status-bar-height)
|
||||
title-style (rn/use-memo (fn [] (get-title-style layout title-align))
|
||||
[layout title-align])]
|
||||
[reanimated/view
|
||||
{:style (header-wrapper-style {:height height
|
||||
:background background
|
||||
:border-bottom border-bottom
|
||||
:theme theme})}
|
||||
[rn/view
|
||||
{:pointer-events :box-none
|
||||
:height status-bar-height}]
|
||||
[rn/view
|
||||
{:style (merge {:height header-height}
|
||||
style)
|
||||
:pointer-events :box-none}
|
||||
[rn/view
|
||||
{:style absolute-fill
|
||||
:pointer-events :box-none}
|
||||
[rn/view
|
||||
{:style content
|
||||
:pointer-events :box-none}
|
||||
[rn/view
|
||||
{:style left-style
|
||||
:on-layout (handle-layout :left get-layout)
|
||||
:pointer-events :box-none}
|
||||
[header-actions
|
||||
{:accessories left-accessories
|
||||
:component left-component}]]
|
||||
[rn/view
|
||||
{:style title-style
|
||||
:on-layout (handle-layout :title get-layout)
|
||||
:pointer-events :box-none}
|
||||
[header-title
|
||||
{:title title
|
||||
:subtitle subtitle
|
||||
:title-align title-align
|
||||
:component title-component}]]
|
||||
[rn/view
|
||||
{:style right-style
|
||||
:on-layout (handle-layout :right get-layout)
|
||||
:pointer-events :box-none}
|
||||
[header-actions
|
||||
{:accessories right-accessories
|
||||
:component right-component}]]]]]]))
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
[quo.foundations.colors :as colors]))
|
||||
|
||||
(def ^:private themes
|
||||
{:light {:default {:bg colors/white
|
||||
{:light {:default {:bg colors/neutral-5
|
||||
:border colors/neutral-20
|
||||
:icon colors/neutral-50
|
||||
:text colors/neutral-100}
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
[:blur? {:optional true} [:maybe :boolean]]
|
||||
[:customization-color {:optional true} [:maybe :schema.common/customization-color]]
|
||||
[:on-press {:optional true} [:maybe fn?]]
|
||||
[:theme :schema.common/theme]
|
||||
[:title-icon {:optional true} [:maybe :keyword]]
|
||||
[:account-props
|
||||
[:map
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
[quo.foundations.colors :as colors]
|
||||
[quo.theme :as quo.theme]
|
||||
[react-native.core :as rn]
|
||||
[reagent.core :as reagent]
|
||||
[schema.core :as schema]))
|
||||
|
||||
(defn- account-view
|
||||
@@ -97,41 +96,40 @@
|
||||
(colors/resolve-color customization-color theme))}]])
|
||||
|
||||
(defn- internal-view
|
||||
[_]
|
||||
(let [pressed? (reagent/atom false)
|
||||
on-press-in #(reset! pressed? true)
|
||||
on-press-out #(reset! pressed? false)]
|
||||
(fn [{:keys [type state blur? customization-color on-press]
|
||||
:or {customization-color :blue
|
||||
type :default
|
||||
state :default
|
||||
blur? false}
|
||||
:as props}]
|
||||
[rn/pressable
|
||||
{:style (style/container
|
||||
{:state state
|
||||
:blur? blur?
|
||||
:customization-color customization-color
|
||||
:pressed? @pressed?})
|
||||
:on-press-in on-press-in
|
||||
:on-press on-press
|
||||
:on-press-out on-press-out
|
||||
:accessibility-label :container}
|
||||
[account-view props]
|
||||
[rn/view {:style (when (= type :tag) style/token-tag-container)}
|
||||
(cond
|
||||
(#{:balance-neutral :balance-negative :balance-positive} type)
|
||||
[balance-view props]
|
||||
[{:keys [type state blur? customization-color on-press]
|
||||
:or {customization-color :blue
|
||||
type :default
|
||||
state :default
|
||||
blur? false}
|
||||
:as props}]
|
||||
(let [theme (quo.theme/use-theme-value)
|
||||
[pressed? set-pressed] (rn/use-state false)
|
||||
on-press-in (rn/use-callback #(set-pressed true))
|
||||
on-press-out (rn/use-callback #(set-pressed false))
|
||||
props (assoc props :theme theme)]
|
||||
[rn/pressable
|
||||
{:style (style/container
|
||||
{:state state
|
||||
:blur? blur?
|
||||
:customization-color customization-color
|
||||
:pressed? pressed?})
|
||||
:on-press-in on-press-in
|
||||
:on-press on-press
|
||||
:on-press-out on-press-out
|
||||
:accessibility-label :container}
|
||||
[account-view props]
|
||||
[rn/view {:style (when (= type :tag) style/token-tag-container)}
|
||||
(cond
|
||||
(#{:balance-neutral :balance-negative :balance-positive} type)
|
||||
[balance-view props]
|
||||
|
||||
(= type :tag)
|
||||
[token-tag props]
|
||||
(= type :tag)
|
||||
[token-tag props]
|
||||
|
||||
(= type :action)
|
||||
[options-button props]
|
||||
(= type :action)
|
||||
[options-button props]
|
||||
|
||||
(and (= type :default) (= state :selected))
|
||||
[check-icon props])]])))
|
||||
(and (= type :default) (= state :selected))
|
||||
[check-icon props])]]))
|
||||
|
||||
(def view
|
||||
(quo.theme/with-theme
|
||||
(schema/instrument #'internal-view component-schema/?schema)))
|
||||
(def view (schema/instrument #'internal-view component-schema/?schema))
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
[:action {:optional true} [:enum :icon :none]]
|
||||
[:blur? {:optional true} [:maybe :boolean]]
|
||||
[:on-press {:optional true} [:maybe fn?]]
|
||||
[:theme :schema.common/theme]
|
||||
[:account-props
|
||||
[:map {:closed true}
|
||||
[:type [:enum :default :watch-only]]
|
||||
|
||||
@@ -9,38 +9,37 @@
|
||||
[quo.foundations.colors :as colors]
|
||||
[quo.theme :as quo.theme]
|
||||
[react-native.core :as rn]
|
||||
[reagent.core :as reagent]
|
||||
[schema.core :as schema]))
|
||||
|
||||
(defn- internal-view
|
||||
[]
|
||||
(let [state (reagent/atom :default)]
|
||||
(fn [{:keys [action blur? account-props networks on-press on-options-press theme]}]
|
||||
[rn/pressable
|
||||
{:style (style/container {:state @state :blur? blur? :theme theme})
|
||||
:on-press-in #(reset! state :pressed)
|
||||
:on-press-out #(reset! state :default)
|
||||
:on-press on-press
|
||||
:accessibility-label :container}
|
||||
[rn/view {:style style/left-container}
|
||||
[account-avatar/view account-props]
|
||||
[rn/view {:style {:margin-left 8}}
|
||||
[text/text
|
||||
{:weight :semi-bold
|
||||
:size :paragraph-2}
|
||||
(:name account-props)]
|
||||
[address-text/view
|
||||
{:networks networks
|
||||
:address (:address account-props)
|
||||
:format :short}]]]
|
||||
(when (= action :icon)
|
||||
[rn/pressable {:on-press on-options-press}
|
||||
[icon/icon :i/options
|
||||
{:color (if blur?
|
||||
colors/white-opa-70
|
||||
(colors/theme-colors colors/neutral-50 colors/neutral-40 theme))
|
||||
:accessibility-label :icon}]])])))
|
||||
[{:keys [action blur? account-props networks on-press on-options-press]}]
|
||||
(let [theme (quo.theme/use-theme-value)
|
||||
[state set-state] (rn/use-state :default)
|
||||
on-press-in (rn/use-callback #(set-state :pressed))
|
||||
on-press-out (rn/use-callback #(set-state :default))]
|
||||
[rn/pressable
|
||||
{:style (style/container {:state state :blur? blur? :theme theme})
|
||||
:on-press-in on-press-in
|
||||
:on-press-out on-press-out
|
||||
:on-press on-press
|
||||
:accessibility-label :container}
|
||||
[rn/view {:style style/left-container}
|
||||
[account-avatar/view account-props]
|
||||
[rn/view {:style {:margin-left 8}}
|
||||
[text/text
|
||||
{:weight :semi-bold
|
||||
:size :paragraph-2}
|
||||
(:name account-props)]
|
||||
[address-text/view
|
||||
{:networks networks
|
||||
:address (:address account-props)
|
||||
:format :short}]]]
|
||||
(when (= action :icon)
|
||||
[rn/pressable {:on-press on-options-press}
|
||||
[icon/icon :i/options
|
||||
{:color (if blur?
|
||||
colors/white-opa-70
|
||||
(colors/theme-colors colors/neutral-50 colors/neutral-40 theme))
|
||||
:accessibility-label :icon}]])]))
|
||||
|
||||
(def view
|
||||
(quo.theme/with-theme
|
||||
(schema/instrument #'internal-view component-schema/?schema)))
|
||||
(def view (schema/instrument #'internal-view component-schema/?schema))
|
||||
|
||||
@@ -9,6 +9,5 @@
|
||||
[:customization-color {:optional true} [:maybe :schema.common/customization-color]]
|
||||
[:on-press {:optional true} [:maybe fn?]]
|
||||
[:blur? {:optional true} [:maybe :boolean]]
|
||||
[:theme :schema.common/theme]
|
||||
[:active-state? {:optional true} [:maybe :boolean]]]]
|
||||
:any])
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
(ns quo.components.list-items.address.view
|
||||
(:require
|
||||
[quo.components.avatars.wallet-user-avatar.view :as wallet-user-avatar]
|
||||
(quo.components.list-items.address.schema :as component-schema)
|
||||
[quo.components.list-items.address.schema :as component-schema]
|
||||
[quo.components.list-items.address.style :as style]
|
||||
[quo.components.markdown.text :as text]
|
||||
[quo.foundations.colors :as colors]
|
||||
[quo.theme :as quo.theme]
|
||||
[react-native.core :as rn]
|
||||
[reagent.core :as reagent]
|
||||
[schema.core :as schema]
|
||||
[utils.address :as address]))
|
||||
|
||||
@@ -39,34 +38,35 @@
|
||||
(address/get-shortened-key address)]]]])
|
||||
|
||||
(defn- internal-view
|
||||
[]
|
||||
(let [state (reagent/atom :default)
|
||||
active? (atom false)
|
||||
timer (atom nil)
|
||||
on-press-in (fn []
|
||||
(when-not (= @state :selected)
|
||||
(reset! timer (js/setTimeout #(reset! state :pressed) 100))))]
|
||||
(fn [{:keys [networks address customization-color on-press active-state? blur? theme]
|
||||
:or {customization-color :blue}}]
|
||||
(let [on-press-out (fn []
|
||||
(let [new-state (if (or (not active-state?) @active?) :default :active)]
|
||||
(when @timer (js/clearTimeout @timer))
|
||||
(reset! timer nil)
|
||||
(reset! active? (= new-state :active))
|
||||
(reset! state new-state)))]
|
||||
[rn/pressable
|
||||
{:style (style/container @state customization-color blur?)
|
||||
:on-press-in on-press-in
|
||||
:on-press-out on-press-out
|
||||
:on-press on-press
|
||||
:accessibility-label :container}
|
||||
[left-container
|
||||
{:theme theme
|
||||
:networks networks
|
||||
:address address
|
||||
:blur? blur?}]]))))
|
||||
|
||||
(def view
|
||||
(quo.theme/with-theme
|
||||
(schema/instrument #'internal-view component-schema/?schema)))
|
||||
[{:keys [networks address customization-color on-press active-state? blur?]
|
||||
:or {customization-color :blue}}]
|
||||
(let [theme (quo.theme/use-theme-value)
|
||||
[state set-state] (rn/use-state :default)
|
||||
active? (rn/use-ref-atom false)
|
||||
timer (rn/use-ref-atom nil)
|
||||
on-press-in (rn/use-callback
|
||||
(fn []
|
||||
(when-not (= state :selected)
|
||||
(reset! timer (js/setTimeout #(set-state :pressed) 100))))
|
||||
[state])
|
||||
on-press-out (rn/use-callback
|
||||
(fn []
|
||||
(let [new-state (if (or (not active-state?) @active?) :default :active)]
|
||||
(when @timer (js/clearTimeout @timer))
|
||||
(reset! timer nil)
|
||||
(reset! active? (= new-state :active))
|
||||
(set-state new-state)))
|
||||
[active-state?])]
|
||||
[rn/pressable
|
||||
{:style (style/container state customization-color blur?)
|
||||
:on-press-in on-press-in
|
||||
:on-press-out on-press-out
|
||||
:on-press on-press
|
||||
:accessibility-label :container}
|
||||
[left-container
|
||||
{:theme theme
|
||||
:networks networks
|
||||
:address address
|
||||
:blur? blur?}]]))
|
||||
|
||||
(def view (schema/instrument #'internal-view component-schema/?schema))
|
||||
|
||||
@@ -5,11 +5,10 @@
|
||||
[quo.components.icon :as quo.icons]
|
||||
[quo.components.list-items.channel.style :as style]
|
||||
[quo.components.markdown.text :as quo.text]
|
||||
[quo.theme :as theme]
|
||||
[react-native.core :as rn]
|
||||
[reagent.core :as reagent]))
|
||||
[quo.theme]
|
||||
[react-native.core :as rn]))
|
||||
|
||||
(defn- view-internal
|
||||
(defn view
|
||||
"Options:
|
||||
- notification - (nil/:notification/:mention/:mute, default: nil):
|
||||
- :notification - Display a grey dot.
|
||||
@@ -27,38 +26,37 @@
|
||||
- on-press - (function, default: nil) - Function called when the component is pressed.
|
||||
- on-long-press - (function, default: nil) - Function called when the component is long pressed.
|
||||
- theme - Theme value from with-theme HOC"
|
||||
[]
|
||||
(let [pressed? (reagent/atom false)]
|
||||
(fn [{:keys [notification locked? mentions-count customization-color emoji name on-press
|
||||
on-long-press theme]}]
|
||||
[rn/pressable
|
||||
{:style (style/container @pressed? customization-color theme)
|
||||
:accessibility-label :channel-list-item
|
||||
:on-press on-press
|
||||
:on-long-press on-long-press
|
||||
:on-press-in #(reset! pressed? true)
|
||||
:on-press-out #(reset! pressed? false)}
|
||||
[channel-avatar/view
|
||||
{:size :size-32
|
||||
:locked? locked?
|
||||
:full-name name
|
||||
:customization-color customization-color
|
||||
:emoji emoji}]
|
||||
[quo.text/text
|
||||
{:style (style/label notification theme)
|
||||
:weight :medium
|
||||
:size :paragraph-1} (str "# " name)]
|
||||
(when-not locked?
|
||||
(condp = notification
|
||||
:mute [quo.icons/icon :i/muted
|
||||
{:color (style/mute-notification-icon-color theme)}]
|
||||
:mention [counter/view
|
||||
{:customization-color customization-color
|
||||
:container-style (style/counter mentions-count)}
|
||||
mentions-count]
|
||||
:notification [quo.icons/icon :i/notification
|
||||
{:color (style/mute-notification-icon-color theme)
|
||||
:accessibility-label :unviewed-messages-public}]
|
||||
nil))])))
|
||||
|
||||
(def view (theme/with-theme view-internal))
|
||||
[{:keys [notification locked? mentions-count customization-color emoji name on-press on-long-press]}]
|
||||
(let [theme (quo.theme/use-theme-value)
|
||||
[pressed? set-pressed] (rn/use-state false)
|
||||
on-press-in (rn/use-callback #(set-pressed true))
|
||||
on-press-out (rn/use-callback #(set-pressed false))]
|
||||
[rn/pressable
|
||||
{:style (style/container pressed? customization-color theme)
|
||||
:accessibility-label :channel-list-item
|
||||
:on-press on-press
|
||||
:on-long-press on-long-press
|
||||
:on-press-in on-press-in
|
||||
:on-press-out on-press-out}
|
||||
[channel-avatar/view
|
||||
{:size :size-32
|
||||
:locked? locked?
|
||||
:full-name name
|
||||
:customization-color customization-color
|
||||
:emoji emoji}]
|
||||
[quo.text/text
|
||||
{:style (style/label notification theme)
|
||||
:weight :medium
|
||||
:size :paragraph-1} (str "# " name)]
|
||||
(when-not locked?
|
||||
(condp = notification
|
||||
:mute [quo.icons/icon :i/muted
|
||||
{:color (style/mute-notification-icon-color theme)}]
|
||||
:mention [counter/view
|
||||
{:customization-color customization-color
|
||||
:container-style (style/counter mentions-count)}
|
||||
mentions-count]
|
||||
:notification [quo.icons/icon :i/notification
|
||||
{:color (style/mute-notification-icon-color theme)
|
||||
:accessibility-label :unviewed-messages-public}]
|
||||
nil))]))
|
||||
|
||||
@@ -7,8 +7,7 @@
|
||||
[quo.components.markdown.text :as text]
|
||||
[quo.foundations.colors :as colors]
|
||||
[quo.theme :as quo.theme]
|
||||
[react-native.core :as rn]
|
||||
[reagent.core :as reagent]))
|
||||
[react-native.core :as rn]))
|
||||
|
||||
(defn- logo-component
|
||||
[logo]
|
||||
@@ -83,7 +82,7 @@
|
||||
:style {:margin-left 10}}
|
||||
component])))
|
||||
|
||||
(defn- view-internal
|
||||
(defn view
|
||||
"Options:
|
||||
|
||||
:type - :discover/engage/share
|
||||
@@ -116,49 +115,48 @@
|
||||
:unread-count - number - When the info is :mention, it will be used to show
|
||||
the number of unread mentions.
|
||||
"
|
||||
[]
|
||||
(let [pressed? (reagent/atom false)]
|
||||
(fn [{:keys [members type info tokens locked? title subtitle
|
||||
logo blur? customization-color
|
||||
on-press on-long-press on-press-info
|
||||
container-style unread-count theme]}]
|
||||
[rn/pressable
|
||||
{:accessibility-label :container
|
||||
:on-press-in (fn [] (reset! pressed? true))
|
||||
:on-press on-press
|
||||
:on-long-press on-long-press
|
||||
:on-press-out (fn [] (reset! pressed? false))
|
||||
:style (merge (style/container {:blur? blur?
|
||||
:customization-color customization-color
|
||||
:info info
|
||||
:type type
|
||||
:pressed? @pressed?
|
||||
:theme theme})
|
||||
container-style)}
|
||||
[logo-component logo]
|
||||
[rn/view {:style {:flex 1}}
|
||||
[title-component
|
||||
{:blur? blur?
|
||||
:info info
|
||||
:theme theme
|
||||
:title title
|
||||
:type type}]
|
||||
(when (and (= type :share) subtitle)
|
||||
[subtitle-component subtitle blur? theme])
|
||||
(when (and members (= type :discover))
|
||||
[community-view/community-stats-column
|
||||
{:type :list-view
|
||||
:members-count (:members-count members)
|
||||
:active-count (:active-count members)}])]
|
||||
[info-component
|
||||
{:blur? blur?
|
||||
:customization-color customization-color
|
||||
:info info
|
||||
:type type
|
||||
:locked? locked?
|
||||
:on-press-info on-press-info
|
||||
:theme theme
|
||||
:tokens tokens
|
||||
:unread-count unread-count}]])))
|
||||
[{:keys [members type info tokens locked? title subtitle logo blur? customization-color
|
||||
on-press on-long-press on-press-info container-style unread-count]}]
|
||||
(let [theme (quo.theme/use-theme-value)
|
||||
[pressed? set-pressed] (rn/use-state false)
|
||||
on-press-in (rn/use-callback #(set-pressed true))
|
||||
on-press-out (rn/use-callback #(set-pressed false))]
|
||||
[rn/pressable
|
||||
{:accessibility-label :container
|
||||
|
||||
(def view (quo.theme/with-theme view-internal))
|
||||
:on-press on-press
|
||||
:on-long-press on-long-press
|
||||
:on-press-in on-press-in
|
||||
:on-press-out on-press-out
|
||||
:style (merge (style/container {:blur? blur?
|
||||
:customization-color customization-color
|
||||
:info info
|
||||
:type type
|
||||
:pressed? pressed?
|
||||
:theme theme})
|
||||
container-style)}
|
||||
[logo-component logo]
|
||||
[rn/view {:style {:flex 1}}
|
||||
[title-component
|
||||
{:blur? blur?
|
||||
:info info
|
||||
:theme theme
|
||||
:title title
|
||||
:type type}]
|
||||
(when (and (= type :share) subtitle)
|
||||
[subtitle-component subtitle blur? theme])
|
||||
(when (and members (= type :discover))
|
||||
[community-view/community-stats-column
|
||||
{:type :list-view
|
||||
:members-count (:members-count members)
|
||||
:active-count (:active-count members)}])]
|
||||
[info-component
|
||||
{:blur? blur?
|
||||
:customization-color customization-color
|
||||
:info info
|
||||
:type type
|
||||
:locked? locked?
|
||||
:on-press-info on-press-info
|
||||
:theme theme
|
||||
:tokens tokens
|
||||
:unread-count unread-count}]]))
|
||||
|
||||
@@ -6,43 +6,41 @@
|
||||
[quo.foundations.colors :as colors]
|
||||
[quo.theme :as quo.theme]
|
||||
[react-native.core :as rn]
|
||||
[react-native.fast-image :as fast-image]
|
||||
[reagent.core :as reagent]))
|
||||
[react-native.fast-image :as fast-image]))
|
||||
|
||||
(defn- view-internal
|
||||
[]
|
||||
(let [pressed? (reagent/atom false)]
|
||||
(fn [{:keys [dapp action on-press on-press-icon theme] :as props}]
|
||||
[rn/pressable
|
||||
{:style (style/container (assoc props :pressed? @pressed?))
|
||||
:on-press on-press
|
||||
:on-press-in #(reset! pressed? true)
|
||||
:on-press-out #(reset! pressed? false)}
|
||||
[rn/view {:style style/container-info}
|
||||
[fast-image/fast-image
|
||||
{:source (:avatar dapp)
|
||||
:style {:width 32 :height 32}}]
|
||||
[rn/view {:style style/user-info}
|
||||
[text/text
|
||||
{:weight :semi-bold
|
||||
:size :paragraph-1
|
||||
:style (style/style-text-name theme)}
|
||||
(:name dapp)]
|
||||
[text/text
|
||||
{:weight :regular
|
||||
:size :paragraph-2
|
||||
:style (style/style-text-value theme)}
|
||||
(:value dapp)]]]
|
||||
(when (= action :icon)
|
||||
[rn/pressable
|
||||
{:on-press on-press-icon
|
||||
:testID "dapp-component-icon"}
|
||||
[icons/icon :i/options
|
||||
{:color (colors/theme-colors
|
||||
colors/neutral-50
|
||||
colors/neutral-40
|
||||
theme)
|
||||
:accessibility-label :icon}]])])))
|
||||
|
||||
(def view
|
||||
(quo.theme/with-theme view-internal))
|
||||
(defn view
|
||||
[{:keys [dapp action on-press on-press-icon] :as props}]
|
||||
(let [theme (quo.theme/use-theme-value)
|
||||
[pressed? set-pressed] (rn/use-state false)
|
||||
on-press-in (rn/use-callback #(set-pressed true))
|
||||
on-press-out (rn/use-callback #(set-pressed false))]
|
||||
[rn/pressable
|
||||
{:style (style/container (assoc props :pressed? pressed?))
|
||||
:on-press on-press
|
||||
:on-press-in on-press-in
|
||||
:on-press-out on-press-out}
|
||||
[rn/view {:style style/container-info}
|
||||
[fast-image/fast-image
|
||||
{:source (:avatar dapp)
|
||||
:style {:width 32 :height 32}}]
|
||||
[rn/view {:style style/user-info}
|
||||
[text/text
|
||||
{:weight :semi-bold
|
||||
:size :paragraph-1
|
||||
:style (style/style-text-name theme)}
|
||||
(:name dapp)]
|
||||
[text/text
|
||||
{:weight :regular
|
||||
:size :paragraph-2
|
||||
:style (style/style-text-value theme)}
|
||||
(:value dapp)]]]
|
||||
(when (= action :icon)
|
||||
[rn/pressable
|
||||
{:on-press on-press-icon
|
||||
:testID "dapp-component-icon"}
|
||||
[icons/icon :i/options
|
||||
{:color (colors/theme-colors
|
||||
colors/neutral-50
|
||||
colors/neutral-40
|
||||
theme)
|
||||
:accessibility-label :icon}]])]))
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
[quo.components.markdown.text :as text]
|
||||
[quo.theme :as quo.theme]
|
||||
[react-native.core :as rn]
|
||||
[reagent.core :as reagent]
|
||||
[schema.core :as schema]))
|
||||
|
||||
(defn- info
|
||||
@@ -22,18 +21,19 @@
|
||||
(if (string/blank? label) "-" label)]]])
|
||||
|
||||
(defn- values
|
||||
[{:keys [token-value fiat-value theme]}]
|
||||
[rn/view {:style style/values-container}
|
||||
[text/text
|
||||
{:weight :medium
|
||||
:size :paragraph-2
|
||||
:number-of-lines 1}
|
||||
token-value]
|
||||
[text/text
|
||||
{:style (style/fiat-value theme)
|
||||
:size :paragraph-2
|
||||
:number-of-lines 1}
|
||||
fiat-value]])
|
||||
[{:keys [token-value fiat-value]}]
|
||||
(let [theme (quo.theme/use-theme-value)]
|
||||
[rn/view {:style style/values-container}
|
||||
[text/text
|
||||
{:weight :medium
|
||||
:size :paragraph-2
|
||||
:number-of-lines 1}
|
||||
token-value]
|
||||
[text/text
|
||||
{:style (style/fiat-value theme)
|
||||
:size :paragraph-2
|
||||
:number-of-lines 1}
|
||||
fiat-value]]))
|
||||
|
||||
(def ?schema
|
||||
[:=>
|
||||
@@ -51,26 +51,21 @@
|
||||
:any])
|
||||
|
||||
(defn- view-internal
|
||||
[]
|
||||
(let [pressed? (reagent/atom false)
|
||||
on-press-in #(reset! pressed? true)
|
||||
on-press-out #(reset! pressed? false)]
|
||||
(fn [{:keys [on-press state customization-color theme]
|
||||
:as props
|
||||
:or {customization-color :blue}}]
|
||||
(let [internal-state (if @pressed?
|
||||
:pressed
|
||||
state)]
|
||||
[{:keys [on-press state customization-color]
|
||||
:as props
|
||||
:or {customization-color :blue}}]
|
||||
(let [theme (quo.theme/use-theme-value)
|
||||
[pressed? set-pressed] (rn/use-state false)
|
||||
on-press-in (rn/use-callback #(set-pressed true))
|
||||
on-press-out (rn/use-callback #(set-pressed false))
|
||||
internal-state (if pressed? :pressed state)]
|
||||
[rn/pressable
|
||||
{:style (style/container internal-state customization-color theme)
|
||||
:on-press-in on-press-in
|
||||
:on-press-out on-press-out
|
||||
:on-press on-press
|
||||
:accessibility-label :network-list}
|
||||
[info props]
|
||||
[values props]]))
|
||||
|
||||
[rn/pressable
|
||||
{:style (style/container internal-state customization-color theme)
|
||||
:on-press-in on-press-in
|
||||
:on-press-out on-press-out
|
||||
:on-press on-press
|
||||
:accessibility-label :network-list}
|
||||
[info props]
|
||||
[values props]]))))
|
||||
|
||||
(def view
|
||||
(quo.theme/with-theme
|
||||
(schema/instrument #'view-internal ?schema)))
|
||||
(def view (schema/instrument #'view-internal ?schema))
|
||||
|
||||
@@ -7,70 +7,69 @@
|
||||
[quo.foundations.colors :as colors]
|
||||
[quo.theme :as quo.theme]
|
||||
[react-native.core :as rn]
|
||||
[reagent.core :as reagent]
|
||||
[utils.address :as address]))
|
||||
|
||||
(defn- left-container
|
||||
[{:keys [blur? theme name ens address customization-color]}]
|
||||
[rn/view {:style style/left-container}
|
||||
[wallet-user-avatar/wallet-user-avatar
|
||||
{:size :size-32
|
||||
:full-name name
|
||||
:customization-color customization-color}]
|
||||
[rn/view {:style style/account-container}
|
||||
[text/text
|
||||
{:weight :semi-bold
|
||||
:size :paragraph-1
|
||||
:style style/name-text}
|
||||
name]
|
||||
[text/text {:size :paragraph-2}
|
||||
[text/text
|
||||
{:size :paragraph-2
|
||||
:weight :monospace
|
||||
:style (style/account-address blur? theme)}
|
||||
(or ens (address/get-shortened-key address))]]]])
|
||||
[{:keys [blur? name ens address customization-color]}]
|
||||
(let [theme (quo.theme/use-theme-value)]
|
||||
[rn/view {:style style/left-container}
|
||||
[wallet-user-avatar/wallet-user-avatar
|
||||
{:size :size-32
|
||||
:full-name name
|
||||
:customization-color customization-color}]
|
||||
[rn/view {:style style/account-container}
|
||||
[text/text
|
||||
{:weight :semi-bold
|
||||
:size :paragraph-1
|
||||
:style style/name-text}
|
||||
name]
|
||||
[text/text {:size :paragraph-2}
|
||||
[text/text
|
||||
{:size :paragraph-2
|
||||
:weight :monospace
|
||||
:style (style/account-address blur? theme)}
|
||||
(or ens (address/get-shortened-key address))]]]]))
|
||||
|
||||
(defn- internal-view
|
||||
[]
|
||||
(let [state (reagent/atom :default)
|
||||
active? (atom false)
|
||||
timer (atom nil)
|
||||
on-press-in (fn []
|
||||
(when-not (= @state :selected)
|
||||
(reset! timer (js/setTimeout #(reset! state :pressed) 100))))]
|
||||
(fn [{:keys [blur? user-props active-state? customization-color
|
||||
on-press
|
||||
on-options-press
|
||||
theme]
|
||||
:or {customization-color :blue
|
||||
blur? false}}]
|
||||
(let [on-press-out (fn []
|
||||
(let [new-state (if (or (not active-state?) @active?) :default :active)]
|
||||
(when @timer (js/clearTimeout @timer))
|
||||
(reset! timer nil)
|
||||
(reset! active? (= new-state :active))
|
||||
(reset! state new-state)))]
|
||||
[rn/pressable
|
||||
{:style (style/container
|
||||
{:state @state :blur? blur? :customization-color customization-color})
|
||||
:on-press-in on-press-in
|
||||
:on-press-out on-press-out
|
||||
:on-press on-press
|
||||
:accessibility-label :container}
|
||||
[left-container
|
||||
{:blur? blur?
|
||||
:theme theme
|
||||
:name (:name user-props)
|
||||
:ens (:ens user-props)
|
||||
:address (:address user-props)
|
||||
:customization-color (or (:customization-color user-props) :blue)}]
|
||||
(when on-options-press
|
||||
[rn/pressable
|
||||
{:accessibility-label :options-button
|
||||
:on-press on-options-press}
|
||||
[icon/icon :i/options
|
||||
{:color (if blur?
|
||||
colors/white-opa-70
|
||||
(colors/theme-colors colors/neutral-50 colors/neutral-40 theme))}]])]))))
|
||||
|
||||
(def view (quo.theme/with-theme internal-view))
|
||||
(defn view
|
||||
[{:keys [blur? user-props active-state? customization-color on-press on-options-press]
|
||||
:or {customization-color :blue
|
||||
blur? false}}]
|
||||
(let [theme (quo.theme/use-theme-value)
|
||||
[state set-state] (rn/use-state :default)
|
||||
active? (rn/use-ref-atom false)
|
||||
timer (rn/use-ref-atom nil)
|
||||
on-press-in (rn/use-callback
|
||||
(fn []
|
||||
(when-not (= state :selected)
|
||||
(reset! timer (js/setTimeout #(set-state :pressed) 100))))
|
||||
[state])
|
||||
on-press-out (rn/use-callback
|
||||
(fn []
|
||||
(let [new-state (if (or (not active-state?) @active?) :default :active)]
|
||||
(when @timer (js/clearTimeout @timer))
|
||||
(reset! timer nil)
|
||||
(reset! active? (= new-state :active))
|
||||
(set-state new-state)))
|
||||
[active-state?])]
|
||||
[rn/pressable
|
||||
{:style (style/container
|
||||
{:state state :blur? blur? :customization-color customization-color})
|
||||
:on-press-in on-press-in
|
||||
:on-press-out on-press-out
|
||||
:on-press on-press
|
||||
:accessibility-label :container}
|
||||
[left-container
|
||||
{:blur? blur?
|
||||
:theme theme
|
||||
:name (:name user-props)
|
||||
:ens (:ens user-props)
|
||||
:address (:address user-props)
|
||||
:customization-color (or (:customization-color user-props) :blue)}]
|
||||
(when on-options-press
|
||||
[rn/pressable
|
||||
{:accessibility-label :options-button
|
||||
:on-press on-options-press}
|
||||
[icon/icon :i/options
|
||||
{:color (if blur?
|
||||
colors/white-opa-70
|
||||
(colors/theme-colors colors/neutral-50 colors/neutral-40 theme))}]])]))
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
[:contact-props ?contact]
|
||||
[:accounts {:optional true} ?accounts]
|
||||
[:on-press {:optional true} [:maybe fn?]]
|
||||
[:theme :schema.common/theme]
|
||||
[:active-state? {:optional true} [:maybe :boolean]]
|
||||
[:customization-color {:optional true} [:maybe :schema.common/customization-color]]]]
|
||||
:any])
|
||||
|
||||
@@ -9,91 +9,89 @@
|
||||
[quo.foundations.colors :as colors]
|
||||
[quo.theme :as quo.theme]
|
||||
[react-native.core :as rn]
|
||||
[reagent.core :as reagent]
|
||||
[schema.core :as schema]
|
||||
[utils.address :as address]
|
||||
[utils.i18n :as i18n]))
|
||||
|
||||
(defn- account
|
||||
[{:keys [emoji name address customization-color theme]}]
|
||||
[rn/view
|
||||
{:accessibility-label :account-container
|
||||
:style style/account-container}
|
||||
[account-avatar/view
|
||||
{:emoji emoji
|
||||
:size 16
|
||||
:customization-color customization-color}]
|
||||
[text/text
|
||||
{:size :paragraph-2
|
||||
:weight :monospace
|
||||
:style (style/account-name theme)}
|
||||
name]
|
||||
[rn/view {:style (style/dot-divider theme)}]
|
||||
[text/text
|
||||
{:size :paragraph-2
|
||||
:weight :monospace
|
||||
:style (style/account-address theme)}
|
||||
(address/get-shortened-key address)]])
|
||||
[{:keys [emoji name address customization-color]}]
|
||||
(let [theme (quo.theme/use-theme-value)]
|
||||
[rn/view
|
||||
{:accessibility-label :account-container
|
||||
:style style/account-container}
|
||||
[account-avatar/view
|
||||
{:emoji emoji
|
||||
:size 16
|
||||
:customization-color customization-color}]
|
||||
[text/text
|
||||
{:size :paragraph-2
|
||||
:weight :monospace
|
||||
:style (style/account-name theme)}
|
||||
name]
|
||||
[rn/view {:style (style/dot-divider theme)}]
|
||||
[text/text
|
||||
{:size :paragraph-2
|
||||
:weight :monospace
|
||||
:style (style/account-address theme)}
|
||||
(address/get-shortened-key address)]]))
|
||||
|
||||
(defn- internal-view
|
||||
[]
|
||||
(let [state (reagent/atom :default)
|
||||
active? (atom false)
|
||||
timer (atom nil)
|
||||
on-press-in (fn []
|
||||
(when-not (= @state :selected)
|
||||
(reset! timer (js/setTimeout #(reset! state :pressed) 100))))]
|
||||
(fn [{:keys [contact-props accounts active-state? customization-color on-press theme]
|
||||
:or {customization-color :blue
|
||||
accounts []
|
||||
active-state? true}}]
|
||||
(let [accounts-count (count accounts)
|
||||
account-props (when (= accounts-count 1)
|
||||
(first accounts))
|
||||
on-press-out (fn []
|
||||
[{:keys [contact-props accounts active-state? customization-color on-press]
|
||||
:or {customization-color :blue
|
||||
accounts []
|
||||
active-state? true}}]
|
||||
(let [theme (quo.theme/use-theme-value)
|
||||
[state set-state] (rn/use-state :default)
|
||||
active? (rn/use-ref-atom false)
|
||||
timer (rn/use-ref-atom nil)
|
||||
on-press (rn/use-callback #(when on-press (on-press)))
|
||||
on-press-in (rn/use-callback
|
||||
(fn []
|
||||
(when-not (= state :selected)
|
||||
(reset! timer (js/setTimeout #(set-state :pressed) 100))))
|
||||
[state])
|
||||
accounts-count (count accounts)
|
||||
account-props (when (= accounts-count 1) (first accounts))
|
||||
on-press-out (rn/use-callback
|
||||
(fn []
|
||||
(let [new-state (if (or (not active-state?) @active?) :default :active)]
|
||||
(when @timer (js/clearTimeout @timer))
|
||||
(reset! timer nil)
|
||||
(reset! active? (= new-state :active))
|
||||
(reset! state new-state)))]
|
||||
[rn/pressable
|
||||
{:style (style/container
|
||||
{:state @state :customization-color customization-color})
|
||||
:on-press-in on-press-in
|
||||
:on-press-out on-press-out
|
||||
:on-press #(when on-press
|
||||
(on-press))
|
||||
:accessibility-label :container}
|
||||
[rn/view {:style style/left-container}
|
||||
[user-avatar/user-avatar (assoc contact-props :size :small)]
|
||||
[rn/view {:style style/saved-contact-container}
|
||||
[rn/view
|
||||
{:style style/account-title-container}
|
||||
[text/text
|
||||
{:weight :semi-bold
|
||||
:size :paragraph-1}
|
||||
(:full-name contact-props)]
|
||||
[icon/icon :i/contact
|
||||
{:container-style style/contact-icon-container
|
||||
:accessibility-label :contact-icon
|
||||
:size 12
|
||||
:color (colors/theme-colors colors/primary-50 colors/primary-60 theme)
|
||||
:color-2 colors/white}]]
|
||||
(if account-props
|
||||
[account (assoc account-props :theme theme)]
|
||||
[text/text
|
||||
{:accessibility-label :accounts-count
|
||||
:size :paragraph-2
|
||||
:style (style/accounts-count theme)}
|
||||
(i18n/label :t/accounts-count {:count accounts-count})])]]
|
||||
(when (> accounts-count 1)
|
||||
[icon/icon :i/chevron-right
|
||||
{:accessibility-label :check-icon
|
||||
:size 20
|
||||
:color (colors/theme-colors colors/neutral-50
|
||||
colors/neutral-40
|
||||
theme)}])]))))
|
||||
(set-state new-state)))
|
||||
[active-state?])]
|
||||
[rn/pressable
|
||||
{:style (style/container {:state state :customization-color customization-color})
|
||||
:on-press-in on-press-in
|
||||
:on-press-out on-press-out
|
||||
:on-press on-press
|
||||
:accessibility-label :container}
|
||||
[rn/view {:style style/left-container}
|
||||
[user-avatar/user-avatar (assoc contact-props :size :small)]
|
||||
[rn/view {:style style/saved-contact-container}
|
||||
[rn/view
|
||||
{:style style/account-title-container}
|
||||
[text/text
|
||||
{:weight :semi-bold
|
||||
:size :paragraph-1}
|
||||
(:full-name contact-props)]
|
||||
[icon/icon :i/contact
|
||||
{:container-style style/contact-icon-container
|
||||
:accessibility-label :contact-icon
|
||||
:size 12
|
||||
:color (colors/theme-colors colors/primary-50 colors/primary-60 theme)
|
||||
:color-2 colors/white}]]
|
||||
(if account-props
|
||||
[account (assoc account-props :theme theme)]
|
||||
[text/text
|
||||
{:accessibility-label :accounts-count
|
||||
:size :paragraph-2
|
||||
:style (style/accounts-count theme)}
|
||||
(i18n/label :t/accounts-count {:count accounts-count})])]]
|
||||
(when (> accounts-count 1)
|
||||
[icon/icon :i/chevron-right
|
||||
{:accessibility-label :check-icon
|
||||
:size 20
|
||||
:color (colors/theme-colors colors/neutral-50 colors/neutral-40 theme)}])]))
|
||||
|
||||
(def view
|
||||
(quo.theme/with-theme
|
||||
(schema/instrument #'internal-view component-schema/?schema)))
|
||||
(def view (schema/instrument #'internal-view component-schema/?schema))
|
||||
|
||||
@@ -11,6 +11,5 @@
|
||||
[:networks [:* [:map [:source :schema.common/image-source]]]]
|
||||
[:on-press {:optional true} [:maybe fn?]]
|
||||
[:customization-color {:optional true} [:maybe :schema.common/customization-color]]
|
||||
[:state {:optional true} [:enum :default :active :selected]]
|
||||
[:theme :schema.common/theme]]]
|
||||
[:state {:optional true} [:enum :default :active :selected]]]]
|
||||
:any])
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
[quo.components.utilities.token.view :as token]
|
||||
[quo.theme :as quo.theme]
|
||||
[react-native.core :as rn]
|
||||
[reagent.core :as reagent]
|
||||
[schema.core :as schema]))
|
||||
|
||||
(defn- info
|
||||
@@ -31,44 +30,40 @@
|
||||
networks]]])
|
||||
|
||||
(defn- values
|
||||
[{:keys [state token-value fiat-value customization-color theme]}]
|
||||
(if (= state :selected)
|
||||
[icon/icon :i/check
|
||||
{:color (style/check-color customization-color theme)
|
||||
:accessibility-label :check-icon}]
|
||||
[rn/view {:style style/values-container}
|
||||
[text/text
|
||||
{:weight :medium
|
||||
:size :paragraph-2
|
||||
:number-of-lines 1}
|
||||
token-value]
|
||||
[text/text
|
||||
{:style (style/fiat-value theme)
|
||||
:size :paragraph-2
|
||||
:number-of-lines 1}
|
||||
fiat-value]]))
|
||||
[{:keys [state token-value fiat-value customization-color]}]
|
||||
(let [theme (quo.theme/use-theme-value)]
|
||||
(if (= state :selected)
|
||||
[icon/icon :i/check
|
||||
{:color (style/check-color customization-color theme)
|
||||
:accessibility-label :check-icon}]
|
||||
[rn/view {:style style/values-container}
|
||||
[text/text
|
||||
{:weight :medium
|
||||
:size :paragraph-2
|
||||
:number-of-lines 1}
|
||||
token-value]
|
||||
[text/text
|
||||
{:style (style/fiat-value theme)
|
||||
:size :paragraph-2
|
||||
:number-of-lines 1}
|
||||
fiat-value]])))
|
||||
|
||||
(defn- view-internal
|
||||
[]
|
||||
(let [pressed? (reagent/atom false)
|
||||
on-press-in #(reset! pressed? true)
|
||||
on-press-out #(reset! pressed? false)]
|
||||
(fn [{:keys [on-press state customization-color
|
||||
_token _networks _token-value _fiat-value theme]
|
||||
:as props
|
||||
:or {customization-color :blue}}]
|
||||
(let [internal-state (if @pressed?
|
||||
:pressed
|
||||
state)]
|
||||
[rn/pressable
|
||||
{:style (style/container internal-state customization-color theme)
|
||||
:on-press-in on-press-in
|
||||
:on-press-out on-press-out
|
||||
:on-press on-press
|
||||
:accessibility-label :token-network}
|
||||
[info props]
|
||||
[values props]]))))
|
||||
[{:keys [on-press state customization-color _token _networks _token-value _fiat-value]
|
||||
:as props
|
||||
:or {customization-color :blue}}]
|
||||
(let [theme (quo.theme/use-theme-value)
|
||||
[pressed? set-pressed] (rn/use-state false)
|
||||
on-press-in (rn/use-callback #(set-pressed true))
|
||||
on-press-out (rn/use-callback #(set-pressed false))
|
||||
internal-state (if pressed? :pressed state)]
|
||||
[rn/pressable
|
||||
{:style (style/container internal-state customization-color theme)
|
||||
:on-press-in on-press-in
|
||||
:on-press-out on-press-out
|
||||
:on-press on-press
|
||||
:accessibility-label :token-network}
|
||||
[info props]
|
||||
[values props]]))
|
||||
|
||||
(def view
|
||||
(quo.theme/with-theme
|
||||
(schema/instrument #'view-internal component-schema/?schema)))
|
||||
(def view (schema/instrument #'view-internal component-schema/?schema))
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
[:values ?values]
|
||||
[:on-press {:optional true} [:maybe fn?]]
|
||||
[:on-long-press {:optional true} [:maybe fn?]]
|
||||
[:theme :schema.common/theme]
|
||||
[:customization-color {:optional true} [:maybe :schema.common/customization-color]]
|
||||
[:metrics? {:optional true} [:maybe :boolean]]]]
|
||||
:any])
|
||||
|
||||
@@ -9,64 +9,67 @@
|
||||
[quo.foundations.colors :as colors]
|
||||
[quo.theme :as quo.theme]
|
||||
[react-native.core :as rn]
|
||||
[reagent.core :as reagent]
|
||||
[schema.core :as schema]))
|
||||
|
||||
(defn- internal-view
|
||||
[]
|
||||
(let [state (reagent/atom :default)]
|
||||
(fn [{:keys [theme customization-color status token metrics? values on-press on-long-press
|
||||
token-name]}]
|
||||
(let [bg-opacity (case @state
|
||||
:active 10
|
||||
:pressed 5
|
||||
0)
|
||||
{:keys [crypto-value fiat-value percentage-change fiat-change]} values]
|
||||
[rn/pressable
|
||||
{:style (style/container customization-color bg-opacity theme)
|
||||
:on-press-in #(reset! state :pressed)
|
||||
:on-press-out #(reset! state :default)
|
||||
:on-press (fn []
|
||||
(reset! state :active)
|
||||
(js/setTimeout #(reset! state :default) 300)
|
||||
on-press)
|
||||
:on-long-press on-long-press
|
||||
:accessibility-label :container}
|
||||
[rn/view
|
||||
{:style {:flex-direction :row
|
||||
:align-items :center
|
||||
:flex 1}}
|
||||
[token/view {:token token :size :size-32}]
|
||||
[rn/view {:style {:margin-left 8}}
|
||||
[text/text {:weight :semi-bold} token-name]
|
||||
[text/text
|
||||
{:size :paragraph-2
|
||||
:style {:color (colors/theme-colors colors/neutral-50 colors/neutral-40 theme)}}
|
||||
(str crypto-value " " (if token (string/upper-case (clj->js token)) ""))]]]
|
||||
[rn/view
|
||||
{:style {:align-items :flex-end
|
||||
:justify-content :space-between}}
|
||||
[text/text
|
||||
{:weight :medium
|
||||
:size :paragraph-2} fiat-value]
|
||||
(when metrics?
|
||||
[rn/view
|
||||
{:style {:flex-direction :row
|
||||
:align-items :center}}
|
||||
[text/text
|
||||
{:size :paragraph-2
|
||||
:style (style/metric-text status theme)} (str percentage-change "%")]
|
||||
[rn/view {:style (style/dot-divider status theme)}]
|
||||
[text/text
|
||||
{:size :paragraph-2
|
||||
:style (style/metric-text status theme)} fiat-change]
|
||||
(when (not= status :empty)
|
||||
[rn/view
|
||||
{:style {:margin-left 4}
|
||||
:accessibility-label :arrow-icon}
|
||||
[icon/icon (if (= status :positive) :i/positive :i/negative)
|
||||
(style/arrow-icon status theme)]])])]]))))
|
||||
[{:keys [customization-color status token metrics? values on-press on-long-press token-name]}]
|
||||
(let [theme (quo.theme/use-theme-value)
|
||||
[state set-state] (rn/use-state :default)
|
||||
bg-opacity (case state
|
||||
:active 10
|
||||
:pressed 5
|
||||
0)
|
||||
{:keys [crypto-value
|
||||
fiat-value
|
||||
percentage-change
|
||||
fiat-change]} values
|
||||
on-press-in (rn/use-callback #(set-state :pressed))
|
||||
on-press-out (rn/use-callback #(set-state :default))
|
||||
on-press (rn/use-callback
|
||||
(fn []
|
||||
(set-state :active)
|
||||
(js/setTimeout #(set-state :default) 300)
|
||||
on-press))]
|
||||
[rn/pressable
|
||||
{:style (style/container customization-color bg-opacity theme)
|
||||
:on-press-in on-press-in
|
||||
:on-press-out on-press-out
|
||||
:on-press on-press
|
||||
:on-long-press on-long-press
|
||||
:accessibility-label :container}
|
||||
[rn/view
|
||||
{:style {:flex-direction :row
|
||||
:align-items :center
|
||||
:flex 1}}
|
||||
[token/view {:token token :size :size-32}]
|
||||
[rn/view {:style {:margin-left 8}}
|
||||
[text/text {:weight :semi-bold} token-name]
|
||||
[text/text
|
||||
{:size :paragraph-2
|
||||
:style {:color (colors/theme-colors colors/neutral-50 colors/neutral-40 theme)}}
|
||||
(str crypto-value " " (if token (string/upper-case (clj->js token)) ""))]]]
|
||||
[rn/view
|
||||
{:style {:align-items :flex-end
|
||||
:justify-content :space-between}}
|
||||
[text/text
|
||||
{:weight :medium
|
||||
:size :paragraph-2} fiat-value]
|
||||
(when metrics?
|
||||
[rn/view
|
||||
{:style {:flex-direction :row
|
||||
:align-items :center}}
|
||||
[text/text
|
||||
{:size :paragraph-2
|
||||
:style (style/metric-text status theme)} (str percentage-change "%")]
|
||||
[rn/view {:style (style/dot-divider status theme)}]
|
||||
[text/text
|
||||
{:size :paragraph-2
|
||||
:style (style/metric-text status theme)} fiat-change]
|
||||
(when (not= status :empty)
|
||||
[rn/view
|
||||
{:style {:margin-left 4}
|
||||
:accessibility-label :arrow-icon}
|
||||
[icon/icon (if (= status :positive) :i/positive :i/negative)
|
||||
(style/arrow-icon status theme)]])])]]))
|
||||
|
||||
(def view
|
||||
(quo.theme/with-theme
|
||||
(schema/instrument #'internal-view component-schema/?schema)))
|
||||
(def view (schema/instrument #'internal-view component-schema/?schema))
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
[quo.components.markdown.text :as text]
|
||||
[quo.foundations.colors :as colors]
|
||||
[quo.theme :as theme]
|
||||
[react-native.core :as rn]
|
||||
[reagent.core :as reagent]))
|
||||
[react-native.core :as rn]))
|
||||
|
||||
;;; helpers
|
||||
(def themes
|
||||
@@ -137,24 +136,25 @@
|
||||
style
|
||||
on-press
|
||||
warning-label]}]
|
||||
(let [body-height (reagent/atom nil)]
|
||||
(fn []
|
||||
[rn/view
|
||||
{:on-layout #(reset! body-height (oget % "nativeEvent.layout.height"))
|
||||
:overflow :hidden
|
||||
:flex 1}
|
||||
[hborder {:type :top}]
|
||||
[hborder {:type :bottom}]
|
||||
[rn/view
|
||||
(merge {:width "100%"
|
||||
:background-color (get-color :background)
|
||||
:flex-direction :row
|
||||
:padding 20
|
||||
:padding-left 31
|
||||
:margin-vertical 4}
|
||||
style)
|
||||
(let [[body-height set-body-height] (rn/use-state nil)
|
||||
on-layout (rn/use-callback #(set-body-height
|
||||
(oget % "nativeEvent.layout.height")))]
|
||||
[rn/view
|
||||
{:on-layout on-layout
|
||||
:overflow :hidden
|
||||
:flex 1}
|
||||
[hborder {:type :top}]
|
||||
[hborder {:type :bottom}]
|
||||
[rn/view
|
||||
(merge {:width "100%"
|
||||
:background-color (get-color :background)
|
||||
:flex-direction :row
|
||||
:padding 20
|
||||
:padding-left 31
|
||||
:margin-vertical 4}
|
||||
style)
|
||||
|
||||
[timeline]
|
||||
[body timestamp-far timestamp-near on-info-button-pressed on-press warning-label]]
|
||||
[vborder :left body-height]
|
||||
[vborder :right body-height]])))
|
||||
[timeline]
|
||||
[body timestamp-far timestamp-near on-info-button-pressed on-press warning-label]]
|
||||
[vborder :left body-height]
|
||||
[vborder :right body-height]]))
|
||||
|
||||
@@ -4,50 +4,48 @@
|
||||
[quo.components.markdown.text :as text]
|
||||
[quo.components.numbered-keyboard.keyboard-key.style :as style]
|
||||
[quo.theme :as quo.theme]
|
||||
[react-native.core :as rn]
|
||||
[reagent.core :as reagent]))
|
||||
[react-native.core :as rn]))
|
||||
|
||||
(defn- label->accessibility-label
|
||||
[label]
|
||||
(let [label-name (if (keyword? label) (name label) label)]
|
||||
(keyword (str "keyboard-key-" label-name))))
|
||||
|
||||
(defn- view-internal
|
||||
[]
|
||||
(let [pressed? (reagent/atom false)]
|
||||
(fn [{:keys [disabled? theme blur? on-press on-long-press type]} label]
|
||||
(let [label-color (style/get-label-color disabled? theme blur?)
|
||||
background-color (style/toggle-background-color @pressed? blur? theme)]
|
||||
[rn/pressable
|
||||
{:accessibility-label (label->accessibility-label label)
|
||||
:disabled (or disabled? (not label))
|
||||
:on-press (fn []
|
||||
(when on-press
|
||||
(on-press label)))
|
||||
:on-long-press (fn []
|
||||
(when (fn? on-long-press)
|
||||
(on-long-press label)))
|
||||
:allow-multiple-presses? true
|
||||
:on-press-in #(reset! pressed? true)
|
||||
:on-press-out #(reset! pressed? false)
|
||||
:hit-slop {:top 8 :bottom 8 :left 25 :right 25}
|
||||
:style (style/container background-color)}
|
||||
(case type
|
||||
:key [icons/icon
|
||||
label
|
||||
{:color label-color
|
||||
:accessibility-label :icon-label}]
|
||||
:digit [text/text
|
||||
{:accessibility-label :text-label
|
||||
:weight :regular
|
||||
:size :heading-1
|
||||
:style {:color label-color}}
|
||||
label]
|
||||
:derivation-path [icons/icon
|
||||
:i/derivation-path
|
||||
{:color label-color
|
||||
:size 32
|
||||
:accessibility-label :derivation-path-label}]
|
||||
nil)]))))
|
||||
|
||||
(def view (quo.theme/with-theme view-internal))
|
||||
(defn view
|
||||
[{:keys [disabled? blur? on-press on-long-press type]} label]
|
||||
(let [theme (quo.theme/use-theme-value)
|
||||
[pressed? set-pressed?] (rn/use-state false)
|
||||
on-press (rn/use-callback #(when on-press (on-press label)) [label])
|
||||
on-long-press (rn/use-callback #(when (fn? on-long-press) (on-long-press label))
|
||||
[label])
|
||||
on-press-in (rn/use-callback #(set-pressed? true))
|
||||
on-press-out (rn/use-callback #(set-pressed? false))
|
||||
label-color (style/get-label-color disabled? theme blur?)
|
||||
background-color (style/toggle-background-color pressed? blur? theme)]
|
||||
[rn/pressable
|
||||
{:accessibility-label (label->accessibility-label label)
|
||||
:disabled (or disabled? (not label))
|
||||
:on-press on-press
|
||||
:on-long-press on-long-press
|
||||
:allow-multiple-presses? true
|
||||
:on-press-in on-press-in
|
||||
:on-press-out on-press-out
|
||||
:hit-slop {:top 8 :bottom 8 :left 25 :right 25}
|
||||
:style (style/container background-color)}
|
||||
(case type
|
||||
:key [icons/icon
|
||||
label
|
||||
{:color label-color
|
||||
:accessibility-label :icon-label}]
|
||||
:digit [text/text
|
||||
{:accessibility-label :text-label
|
||||
:weight :regular
|
||||
:size :heading-1
|
||||
:style {:color label-color}}
|
||||
label]
|
||||
:derivation-path [icons/icon
|
||||
:i/derivation-path
|
||||
{:color label-color
|
||||
:size 32
|
||||
:accessibility-label :derivation-path-label}]
|
||||
nil)]))
|
||||
|
||||
@@ -3,48 +3,33 @@
|
||||
[quo.components.avatars.user-avatar.view :as user-avatar]
|
||||
[quo.components.markdown.text :as text]
|
||||
[quo.components.profile.select-profile.style :as style]
|
||||
[react-native.core :as rn]
|
||||
[reagent.core :as reagent]))
|
||||
|
||||
(defn- on-change-handler
|
||||
[selected? on-change]
|
||||
(swap! selected? not)
|
||||
(when on-change (on-change @selected?)))
|
||||
[react-native.core :as rn]))
|
||||
|
||||
(defn view
|
||||
"Options
|
||||
- `default-selected?` The default selected state. Use when the component is not controlled.
|
||||
- `selected?` Selected state. Use when the component is controlled.
|
||||
- `name` Full name
|
||||
- `profile-picture` Profile picture
|
||||
- `customization-color` Customization color
|
||||
- `(on-change selected?)` On change event handler
|
||||
"
|
||||
[{:keys [default-selected?]}]
|
||||
(let [internal-selected? (reagent/atom (or default-selected? false))]
|
||||
(fn [{:keys [selected?
|
||||
profile-picture
|
||||
name
|
||||
customization-color
|
||||
on-change]
|
||||
:or {customization-color :turquoise}}]
|
||||
(when (and (not (nil? selected?)) (not= @internal-selected? selected?))
|
||||
(reset! internal-selected? selected?))
|
||||
[rn/touchable-opacity
|
||||
{:style (style/container customization-color @internal-selected?)
|
||||
:on-press #(on-change-handler internal-selected? on-change)
|
||||
:active-opacity 1
|
||||
:accessibility-label :select-profile}
|
||||
[rn/view {:style style/header}
|
||||
[user-avatar/user-avatar
|
||||
{:full-name name
|
||||
:status-indicator? false
|
||||
:size :medium
|
||||
:profile-picture profile-picture}]
|
||||
[rn/view {:style (style/select-radio @internal-selected?)}
|
||||
(when @internal-selected? [rn/view {:style style/select-radio-inner}])]]
|
||||
[text/text
|
||||
{:size :heading-2
|
||||
:weight :semi-bold
|
||||
:style style/profile-name}
|
||||
name]])))
|
||||
[{:keys [selected? profile-picture name customization-color on-change]
|
||||
:or {customization-color :turquoise}}]
|
||||
[rn/touchable-opacity
|
||||
{:style (style/container customization-color selected?)
|
||||
:on-press #(when on-change (on-change (not selected?)))
|
||||
:accessibility-label :select-profile}
|
||||
[rn/view {:style style/header}
|
||||
[user-avatar/user-avatar
|
||||
{:full-name name
|
||||
:status-indicator? false
|
||||
:size :medium
|
||||
:profile-picture profile-picture}]
|
||||
[rn/view {:style (style/select-radio selected?)}
|
||||
(when selected?
|
||||
[rn/view {:style style/select-radio-inner}])]]
|
||||
[text/text
|
||||
{:size :heading-2
|
||||
:weight :semi-bold
|
||||
:style style/profile-name}
|
||||
name]])
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
(ns quo.components.profile.showcase-nav.component-spec
|
||||
(:require
|
||||
[quo.components.profile.showcase-nav.view :as view]
|
||||
[reagent.core :as reagent]
|
||||
[test-helpers.component :as h]))
|
||||
|
||||
(def nav-data
|
||||
@@ -35,7 +34,7 @@
|
||||
(.toHaveBeenCalled))))
|
||||
|
||||
(h/test "active id updated"
|
||||
(let [active-id (reagent/atom :recent)]
|
||||
(let [active-id (atom :recent)]
|
||||
(h/render [view/view
|
||||
{:data nav-data
|
||||
:on-press #(reset! active-id %)}])
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
[quo.components.record-audio.record-audio.helpers :as helpers]
|
||||
[quo.components.record-audio.record-audio.style :as style]
|
||||
[quo.foundations.colors :as colors]
|
||||
[react-native.core :refer [use-effect]]
|
||||
[react-native.core :as rn]
|
||||
[react-native.reanimated :as reanimated]))
|
||||
|
||||
(defn f-delete-button
|
||||
(defn delete-button
|
||||
[recording? ready-to-delete? reviewing-audio? force-show-controls?]
|
||||
(let [opacity (reanimated/use-shared-value (if force-show-controls? 1 0))
|
||||
translate-x (reanimated/use-shared-value (if force-show-controls? 35 20))
|
||||
@@ -17,62 +17,68 @@
|
||||
connector-height (reanimated/use-shared-value 12)
|
||||
border-radius-first-half (reanimated/use-shared-value 8)
|
||||
border-radius-second-half (reanimated/use-shared-value 8)
|
||||
start-x-animation (fn []
|
||||
(helpers/animate-linear-with-delay translate-x 12 50 133.33)
|
||||
(helpers/animate-easing-with-delay connector-opacity 1 0 93.33)
|
||||
(helpers/animate-easing-with-delay connector-width 56 83.33 80)
|
||||
(helpers/animate-easing-with-delay connector-height 56 83.33 80)
|
||||
(helpers/animate-easing-with-delay border-radius-first-half
|
||||
28
|
||||
83.33
|
||||
80)
|
||||
(helpers/animate-easing-with-delay border-radius-second-half
|
||||
28
|
||||
83.33
|
||||
80))
|
||||
reset-x-animation (fn []
|
||||
(helpers/animate-linear translate-x 0 100)
|
||||
(helpers/set-value connector-opacity 0)
|
||||
(helpers/set-value connector-width 24)
|
||||
(helpers/set-value connector-height 12)
|
||||
(helpers/set-value border-radius-first-half 8)
|
||||
(helpers/set-value border-radius-second-half 16))
|
||||
fade-in-animation (fn []
|
||||
(helpers/animate-linear translate-x 0 200)
|
||||
(helpers/animate-linear opacity 1 200))
|
||||
fade-out-animation (fn []
|
||||
(helpers/animate-linear
|
||||
translate-x
|
||||
(if @reviewing-audio? 35 20)
|
||||
200)
|
||||
(if @reviewing-audio?
|
||||
(helpers/animate-linear scale 0.75 200)
|
||||
(helpers/animate-linear opacity 0 200))
|
||||
(helpers/set-value connector-opacity 0)
|
||||
(helpers/set-value connector-width 24)
|
||||
(helpers/set-value connector-height 12)
|
||||
(helpers/set-value border-radius-first-half 8)
|
||||
(helpers/set-value border-radius-second-half 16))
|
||||
fade-out-reset-animation (fn []
|
||||
(helpers/animate-linear opacity 0 200)
|
||||
(helpers/animate-linear-with-delay translate-x 20 0 200)
|
||||
(helpers/animate-linear-with-delay scale 1 0 200))]
|
||||
(use-effect (fn []
|
||||
(if @recording?
|
||||
(fade-in-animation)
|
||||
(fade-out-animation)))
|
||||
[@recording?])
|
||||
(use-effect (fn []
|
||||
(when-not @reviewing-audio?
|
||||
(fade-out-reset-animation)))
|
||||
[@reviewing-audio?])
|
||||
(use-effect (fn []
|
||||
(cond
|
||||
@ready-to-delete?
|
||||
(start-x-animation)
|
||||
@recording?
|
||||
(reset-x-animation)))
|
||||
[@ready-to-delete?])
|
||||
start-x-animation (rn/use-callback
|
||||
(fn []
|
||||
(helpers/animate-linear-with-delay translate-x 12 50 133.33)
|
||||
(helpers/animate-easing-with-delay connector-opacity 1 0 93.33)
|
||||
(helpers/animate-easing-with-delay connector-width 56 83.33 80)
|
||||
(helpers/animate-easing-with-delay connector-height 56 83.33 80)
|
||||
(helpers/animate-easing-with-delay border-radius-first-half
|
||||
28
|
||||
83.33
|
||||
80)
|
||||
(helpers/animate-easing-with-delay border-radius-second-half
|
||||
28
|
||||
83.33
|
||||
80)))
|
||||
reset-x-animation (rn/use-callback
|
||||
(fn []
|
||||
(helpers/animate-linear translate-x 0 100)
|
||||
(helpers/set-value connector-opacity 0)
|
||||
(helpers/set-value connector-width 24)
|
||||
(helpers/set-value connector-height 12)
|
||||
(helpers/set-value border-radius-first-half 8)
|
||||
(helpers/set-value border-radius-second-half 16)))
|
||||
fade-in-animation (rn/use-callback
|
||||
(fn []
|
||||
(helpers/animate-linear translate-x 0 200)
|
||||
(helpers/animate-linear opacity 1 200)))
|
||||
fade-out-animation (rn/use-callback
|
||||
(fn []
|
||||
(helpers/animate-linear
|
||||
translate-x
|
||||
(if reviewing-audio? 35 20)
|
||||
200)
|
||||
(if reviewing-audio?
|
||||
(helpers/animate-linear scale 0.75 200)
|
||||
(helpers/animate-linear opacity 0 200))
|
||||
(helpers/set-value connector-opacity 0)
|
||||
(helpers/set-value connector-width 24)
|
||||
(helpers/set-value connector-height 12)
|
||||
(helpers/set-value border-radius-first-half 8)
|
||||
(helpers/set-value border-radius-second-half 16))
|
||||
[reviewing-audio?])
|
||||
fade-out-reset-animation (rn/use-callback
|
||||
(fn []
|
||||
(helpers/animate-linear opacity 0 200)
|
||||
(helpers/animate-linear-with-delay translate-x 20 0 200)
|
||||
(helpers/animate-linear-with-delay scale 1 0 200)))]
|
||||
(rn/use-effect (fn []
|
||||
(if recording?
|
||||
(fade-in-animation)
|
||||
(fade-out-animation)))
|
||||
[recording?])
|
||||
(rn/use-effect (fn []
|
||||
(when-not reviewing-audio?
|
||||
(fade-out-reset-animation)))
|
||||
[reviewing-audio?])
|
||||
(rn/use-effect (fn []
|
||||
(cond
|
||||
ready-to-delete?
|
||||
(start-x-animation)
|
||||
recording?
|
||||
(reset-x-animation)))
|
||||
[ready-to-delete?])
|
||||
[:<>
|
||||
[reanimated/view {:style (style/delete-button-container opacity)}
|
||||
[reanimated/view
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
[quo.components.record-audio.record-audio.style :as style]
|
||||
[quo.foundations.colors :as colors]
|
||||
[quo.theme :as quo.theme]
|
||||
[react-native.core :refer [use-effect]]
|
||||
[react-native.core :as rn]
|
||||
[react-native.reanimated :as reanimated]))
|
||||
|
||||
(defn f-lock-button
|
||||
(defn lock-button
|
||||
[recording? ready-to-lock? locked?]
|
||||
(let [theme (quo.theme/use-theme-value)
|
||||
translate-x-y (reanimated/use-shared-value 20)
|
||||
@@ -18,54 +18,58 @@
|
||||
height (reanimated/use-shared-value 12)
|
||||
border-radius-first-half (reanimated/use-shared-value 8)
|
||||
border-radius-second-half (reanimated/use-shared-value 8)
|
||||
start-x-y-animation (fn []
|
||||
(helpers/animate-linear-with-delay translate-x-y 8 50 116.66)
|
||||
(helpers/animate-easing-with-delay connector-opacity 1 0 80)
|
||||
(helpers/animate-easing-with-delay width 56 83.33 63.33)
|
||||
(helpers/animate-easing-with-delay height 56 83.33 63.33)
|
||||
(helpers/animate-easing-with-delay border-radius-first-half
|
||||
28
|
||||
83.33
|
||||
63.33)
|
||||
(helpers/animate-easing-with-delay border-radius-second-half
|
||||
28
|
||||
83.33
|
||||
63.33))
|
||||
reset-x-y-animation (fn []
|
||||
(helpers/animate-linear translate-x-y 0 100)
|
||||
(helpers/set-value connector-opacity 0)
|
||||
(helpers/set-value width 24)
|
||||
(helpers/set-value height 12)
|
||||
(helpers/set-value border-radius-first-half 8)
|
||||
(helpers/set-value border-radius-second-half 16))
|
||||
fade-in-animation (fn []
|
||||
(helpers/animate-linear translate-x-y 0 220)
|
||||
(helpers/animate-linear opacity 1 220))
|
||||
fade-out-animation (fn []
|
||||
(helpers/animate-linear translate-x-y 20 200)
|
||||
(helpers/animate-linear opacity 0 200)
|
||||
(helpers/set-value connector-opacity 0)
|
||||
(helpers/set-value width 24)
|
||||
(helpers/set-value height 12)
|
||||
(helpers/set-value border-radius-first-half 8)
|
||||
(helpers/set-value border-radius-second-half 16))]
|
||||
(use-effect (fn []
|
||||
(if @recording?
|
||||
(fade-in-animation)
|
||||
(fade-out-animation)))
|
||||
[@recording?])
|
||||
(use-effect (fn []
|
||||
(cond
|
||||
@ready-to-lock?
|
||||
(start-x-y-animation)
|
||||
(and @recording? (not @locked?))
|
||||
(reset-x-y-animation)))
|
||||
[@ready-to-lock?])
|
||||
(use-effect (fn []
|
||||
(if @locked?
|
||||
(fade-out-animation)
|
||||
(reset-x-y-animation)))
|
||||
[@locked?])
|
||||
start-x-y-animation (rn/use-callback
|
||||
(fn []
|
||||
(helpers/animate-linear-with-delay translate-x-y 8 50 116.66)
|
||||
(helpers/animate-easing-with-delay connector-opacity 1 0 80)
|
||||
(helpers/animate-easing-with-delay width 56 83.33 63.33)
|
||||
(helpers/animate-easing-with-delay height 56 83.33 63.33)
|
||||
(helpers/animate-easing-with-delay border-radius-first-half
|
||||
28
|
||||
83.33
|
||||
63.33)
|
||||
(helpers/animate-easing-with-delay border-radius-second-half
|
||||
28
|
||||
83.33
|
||||
63.33)))
|
||||
reset-x-y-animation (rn/use-callback
|
||||
(fn []
|
||||
(helpers/animate-linear translate-x-y 0 100)
|
||||
(helpers/set-value connector-opacity 0)
|
||||
(helpers/set-value width 24)
|
||||
(helpers/set-value height 12)
|
||||
(helpers/set-value border-radius-first-half 8)
|
||||
(helpers/set-value border-radius-second-half 16)))
|
||||
fade-in-animation (rn/use-callback
|
||||
(fn []
|
||||
(helpers/animate-linear translate-x-y 0 220)
|
||||
(helpers/animate-linear opacity 1 220)))
|
||||
fade-out-animation (rn/use-callback
|
||||
(fn []
|
||||
(helpers/animate-linear translate-x-y 20 200)
|
||||
(helpers/animate-linear opacity 0 200)
|
||||
(helpers/set-value connector-opacity 0)
|
||||
(helpers/set-value width 24)
|
||||
(helpers/set-value height 12)
|
||||
(helpers/set-value border-radius-first-half 8)
|
||||
(helpers/set-value border-radius-second-half 16)))]
|
||||
(rn/use-effect (fn []
|
||||
(if recording?
|
||||
(fade-in-animation)
|
||||
(fade-out-animation)))
|
||||
[recording?])
|
||||
(rn/use-effect (fn []
|
||||
(cond
|
||||
ready-to-lock?
|
||||
(start-x-y-animation)
|
||||
(and recording? (not locked?))
|
||||
(reset-x-y-animation)))
|
||||
[ready-to-lock?])
|
||||
(rn/use-effect (fn []
|
||||
(if locked?
|
||||
(fade-out-animation)
|
||||
(reset-x-y-animation)))
|
||||
[locked?])
|
||||
[:<>
|
||||
[reanimated/view {:style (style/lock-button-container opacity)}
|
||||
[reanimated/view
|
||||
@@ -77,6 +81,6 @@
|
||||
[reanimated/view
|
||||
{:style (style/lock-button translate-x-y opacity)
|
||||
:pointer-events :none}
|
||||
[icons/icon (if @ready-to-lock? :i/locked :i/unlocked)
|
||||
[icons/icon (if ready-to-lock? :i/locked :i/unlocked)
|
||||
{:color (colors/theme-colors colors/black colors/white theme)
|
||||
:size 20}]]]))
|
||||
|
||||
@@ -3,19 +3,17 @@
|
||||
[quo.components.buttons.button.view :as button]
|
||||
[quo.components.record-audio.record-audio.helpers :as helpers]
|
||||
[quo.components.record-audio.record-audio.style :as style]
|
||||
[react-native.core :as rn :refer [use-effect]]
|
||||
[react-native.core :as rn]
|
||||
[react-native.reanimated :as reanimated]))
|
||||
|
||||
(defn f-record-button
|
||||
(defn record-button
|
||||
[recording? reviewing-audio?]
|
||||
(let [opacity (reanimated/use-shared-value 1)
|
||||
show-animation #(helpers/set-value opacity 1)
|
||||
hide-animation #(helpers/set-value opacity 0)]
|
||||
(use-effect (fn []
|
||||
(if (or @recording? @reviewing-audio?)
|
||||
(hide-animation)
|
||||
(show-animation)))
|
||||
[@recording? @reviewing-audio?])
|
||||
(let [opacity (reanimated/use-shared-value 1)]
|
||||
(rn/use-effect (fn []
|
||||
(if (or recording? reviewing-audio?)
|
||||
(helpers/set-value opacity 0)
|
||||
(helpers/set-value opacity 1)))
|
||||
[recording? reviewing-audio?])
|
||||
[reanimated/view {:style (style/record-button-container opacity)}
|
||||
[button/button
|
||||
{:type :outline
|
||||
|
||||
@@ -5,8 +5,9 @@
|
||||
[quo.components.record-audio.record-audio.helpers :as helpers]
|
||||
[quo.components.record-audio.record-audio.style :as style]
|
||||
[quo.foundations.colors :as colors]
|
||||
[quo.theme]
|
||||
[react-native.audio-toolkit :as audio]
|
||||
[react-native.core :as rn :refer [use-effect]]
|
||||
[react-native.core :as rn]
|
||||
[react-native.reanimated :as reanimated]
|
||||
[reagent.core :as reagent]
|
||||
[taoensso.timbre :as log]
|
||||
@@ -28,13 +29,16 @@
|
||||
(reagent/as-element
|
||||
[reanimated/view {:style (style/animated-circle scale opacity color)}]))))))
|
||||
|
||||
(defn f-record-button-big
|
||||
[{:keys [recording? ready-to-send? ready-to-lock? ready-to-delete? record-button-is-animating?
|
||||
record-button-at-initial-position? locked? reviewing-audio? recording-length-ms
|
||||
clear-timeout touch-active? recorder-ref reload-recorder-fn idle? on-send on-cancel theme]}]
|
||||
(let [scale (reanimated/use-shared-value 1)
|
||||
(defn record-button-big
|
||||
[{:keys [recording? set-recording ready-to-send? set-ready-to-send ready-to-lock? set-ready-to-lock
|
||||
ready-to-delete? set-ready-to-delete record-button-is-animating?
|
||||
record-button-at-initial-position? locked? set-locked reviewing-audio? recording-length-ms
|
||||
set-recording-length-ms
|
||||
clear-timeout touch-active? recorder-ref reload-recorder-fn idle? on-send on-cancel]}]
|
||||
(let [theme (quo.theme/use-theme-value)
|
||||
scale (reanimated/use-shared-value 1)
|
||||
opacity (reanimated/use-shared-value 0)
|
||||
opacity-from (if @ready-to-lock? opacity-from-lock opacity-from-default)
|
||||
opacity-from (if ready-to-lock? opacity-from-lock opacity-from-default)
|
||||
animations (map
|
||||
(fn [index]
|
||||
(let [ring-scale (worklets.record-audio/ring-scale scale
|
||||
@@ -46,141 +50,155 @@
|
||||
[opacity-from 0])}))
|
||||
(range 0 5))
|
||||
rings-color (cond
|
||||
@ready-to-lock? (colors/theme-colors colors/neutral-80-opa-5-opaque
|
||||
colors/neutral-80
|
||||
theme)
|
||||
@ready-to-delete? colors/danger-50
|
||||
:else colors/primary-50)
|
||||
ready-to-lock? (colors/theme-colors colors/neutral-80-opa-5-opaque
|
||||
colors/neutral-80
|
||||
theme)
|
||||
ready-to-delete? colors/danger-50
|
||||
:else colors/primary-50)
|
||||
translate-y (reanimated/use-shared-value 0)
|
||||
translate-x (reanimated/use-shared-value 0)
|
||||
button-color colors/primary-50
|
||||
icon-color (if (and (not (= :dark theme)) @ready-to-lock?) colors/black colors/white)
|
||||
icon-color (if (and (not (= :dark theme)) ready-to-lock?) colors/black colors/white)
|
||||
icon-opacity (reanimated/use-shared-value 1)
|
||||
red-overlay-opacity (reanimated/use-shared-value 0)
|
||||
gray-overlay-opacity (reanimated/use-shared-value 0)
|
||||
complete-animation
|
||||
(fn []
|
||||
(cond
|
||||
(and @ready-to-lock? (not @record-button-is-animating?))
|
||||
(do
|
||||
(reset! locked? true)
|
||||
(reset! ready-to-lock? false))
|
||||
(and (not @locked?) (not @reviewing-audio?))
|
||||
(audio/stop-recording
|
||||
@recorder-ref
|
||||
(fn []
|
||||
(cond
|
||||
@ready-to-send?
|
||||
(when on-send
|
||||
(on-send {:file-path (audio/get-recorder-file-path @recorder-ref)
|
||||
:duration @recording-length-ms}))
|
||||
@ready-to-delete?
|
||||
(when on-cancel
|
||||
(on-cancel)))
|
||||
(reload-recorder-fn)
|
||||
(reset! recording? false)
|
||||
(reset! ready-to-send? false)
|
||||
(reset! ready-to-delete? false)
|
||||
(reset! ready-to-lock? false)
|
||||
(reset! idle? true)
|
||||
(js/setTimeout #(reset! idle? false) 1000)
|
||||
(reset! recording-length-ms 0)
|
||||
(log/debug "[record-audio] stop recording - success"))
|
||||
#(log/error "[record-audio] stop recording - error: " %))))
|
||||
start-animation (fn []
|
||||
(helpers/set-value opacity 1)
|
||||
(helpers/animate-linear scale 2.6 signal-anim-duration)
|
||||
;; TODO: Research if we can implement this with withSequence method
|
||||
;; from Reanimated 2
|
||||
;; GitHub issue [#14561]:
|
||||
;; https://github.com/status-im/status-mobile/issues/14561
|
||||
(reset! clear-timeout
|
||||
(js/setTimeout
|
||||
(rn/use-callback
|
||||
(fn []
|
||||
(cond
|
||||
(and ready-to-lock? (not @record-button-is-animating?))
|
||||
(do
|
||||
(set-locked true)
|
||||
(set-ready-to-lock false))
|
||||
(and (not locked?) (not reviewing-audio?))
|
||||
(audio/stop-recording
|
||||
@recorder-ref
|
||||
(fn []
|
||||
(cond
|
||||
ready-to-send?
|
||||
(when on-send
|
||||
(on-send {:file-path (audio/get-recorder-file-path @recorder-ref)
|
||||
:duration recording-length-ms}))
|
||||
ready-to-delete?
|
||||
(when on-cancel
|
||||
(on-cancel)))
|
||||
(reload-recorder-fn)
|
||||
(set-recording false)
|
||||
(set-ready-to-send false)
|
||||
(set-ready-to-delete false)
|
||||
(set-ready-to-lock false)
|
||||
(reset! idle? true)
|
||||
(js/setTimeout #(reset! idle? false) 1000)
|
||||
(set-recording-length-ms 0)
|
||||
(log/debug "[record-audio] stop recording - success"))
|
||||
#(log/error "[record-audio] stop recording - error: " %))))
|
||||
[ready-to-lock? locked? reviewing-audio? ready-to-send? recording-length-ms ready-to-delete?
|
||||
idle?])
|
||||
start-animation (rn/use-callback
|
||||
(fn []
|
||||
(helpers/set-value opacity 1)
|
||||
(helpers/animate-linear scale 2.6 signal-anim-duration)
|
||||
;; TODO: Research if we can implement this with withSequence method
|
||||
;; from Reanimated 2
|
||||
;; GitHub issue [#14561]:
|
||||
;; https://github.com/status-im/status-mobile/issues/14561
|
||||
(reset! clear-timeout
|
||||
(js/setTimeout
|
||||
(fn []
|
||||
(helpers/set-value scale scale-to-each)
|
||||
(helpers/animate-linear-with-delay-loop scale
|
||||
scale-to-total
|
||||
signal-anim-duration-2
|
||||
0))
|
||||
signal-anim-duration))))
|
||||
stop-animation (rn/use-callback
|
||||
(fn []
|
||||
(helpers/set-value opacity 0)
|
||||
(reanimated/cancel-animation scale)
|
||||
(helpers/set-value scale 1)
|
||||
(when @clear-timeout (js/clearTimeout @clear-timeout))))
|
||||
start-y-animation (rn/use-callback
|
||||
(fn []
|
||||
(reset! record-button-at-initial-position? false)
|
||||
(reset! record-button-is-animating? true)
|
||||
(helpers/animate-easing translate-y -64 250)
|
||||
(helpers/animate-linear-with-delay icon-opacity 0 33.33 76.66)
|
||||
(js/setTimeout (fn []
|
||||
(reset! record-button-is-animating? false)
|
||||
(when-not @touch-active? (complete-animation)))
|
||||
250))
|
||||
[complete-animation])
|
||||
reset-y-animation (rn/use-callback
|
||||
(fn []
|
||||
(helpers/animate-easing translate-y 0 300)
|
||||
(helpers/animate-linear icon-opacity 1 500)
|
||||
(js/setTimeout (fn []
|
||||
(reset! record-button-at-initial-position? true))
|
||||
500)))
|
||||
start-x-animation (rn/use-callback
|
||||
(fn []
|
||||
(reset! record-button-at-initial-position? false)
|
||||
(reset! record-button-is-animating? true)
|
||||
(helpers/animate-easing translate-x -64 250)
|
||||
(helpers/animate-linear-with-delay icon-opacity 0 33.33 76.66)
|
||||
(helpers/animate-linear red-overlay-opacity 1 33.33)
|
||||
(js/setTimeout (fn []
|
||||
(reset! record-button-is-animating? false)
|
||||
(when-not @touch-active? (complete-animation)))
|
||||
250))
|
||||
[complete-animation])
|
||||
reset-x-animation (rn/use-callback
|
||||
(fn []
|
||||
(helpers/animate-easing translate-x 0 300)
|
||||
(helpers/animate-linear icon-opacity 1 500)
|
||||
(helpers/animate-linear red-overlay-opacity 0 100)
|
||||
(js/setTimeout (fn []
|
||||
(reset! record-button-at-initial-position? true))
|
||||
500)))
|
||||
start-x-y-animation (rn/use-callback
|
||||
(fn []
|
||||
(helpers/set-value scale scale-to-each)
|
||||
(helpers/animate-linear-with-delay-loop scale
|
||||
scale-to-total
|
||||
signal-anim-duration-2
|
||||
0))
|
||||
signal-anim-duration)))
|
||||
stop-animation (fn []
|
||||
(helpers/set-value opacity 0)
|
||||
(reanimated/cancel-animation scale)
|
||||
(helpers/set-value scale 1)
|
||||
(when @clear-timeout (js/clearTimeout @clear-timeout)))
|
||||
start-y-animation (fn []
|
||||
(reset! record-button-at-initial-position? false)
|
||||
(reset! record-button-is-animating? true)
|
||||
(helpers/animate-easing translate-y -64 250)
|
||||
(helpers/animate-linear-with-delay icon-opacity 0 33.33 76.66)
|
||||
(js/setTimeout (fn []
|
||||
(reset! record-button-is-animating? false)
|
||||
(when-not @touch-active? (complete-animation)))
|
||||
250))
|
||||
reset-y-animation (fn []
|
||||
(helpers/animate-easing translate-y 0 300)
|
||||
(helpers/animate-linear icon-opacity 1 500)
|
||||
(js/setTimeout (fn []
|
||||
(reset! record-button-at-initial-position? true))
|
||||
500))
|
||||
start-x-animation (fn []
|
||||
(reset! record-button-at-initial-position? false)
|
||||
(reset! record-button-is-animating? true)
|
||||
(helpers/animate-easing translate-x -64 250)
|
||||
(helpers/animate-linear-with-delay icon-opacity 0 33.33 76.66)
|
||||
(helpers/animate-linear red-overlay-opacity 1 33.33)
|
||||
(js/setTimeout (fn []
|
||||
(reset! record-button-is-animating? false)
|
||||
(when-not @touch-active? (complete-animation)))
|
||||
250))
|
||||
reset-x-animation (fn []
|
||||
(helpers/animate-easing translate-x 0 300)
|
||||
(helpers/animate-linear icon-opacity 1 500)
|
||||
(helpers/animate-linear red-overlay-opacity 0 100)
|
||||
(js/setTimeout (fn []
|
||||
(reset! record-button-at-initial-position? true))
|
||||
500))
|
||||
start-x-y-animation (fn []
|
||||
(reset! record-button-at-initial-position? false)
|
||||
(reset! record-button-is-animating? true)
|
||||
(helpers/animate-easing translate-y -44 200)
|
||||
(helpers/animate-easing translate-x -44 200)
|
||||
(helpers/animate-linear-with-delay icon-opacity 0 33.33 33.33)
|
||||
(helpers/animate-linear gray-overlay-opacity 1 33.33)
|
||||
(js/setTimeout (fn []
|
||||
(reset! record-button-is-animating? false)
|
||||
(when-not @touch-active? (complete-animation)))
|
||||
200))
|
||||
reset-x-y-animation (fn []
|
||||
(helpers/animate-easing translate-y 0 300)
|
||||
(helpers/animate-easing translate-x 0 300)
|
||||
(helpers/animate-linear icon-opacity 1 500)
|
||||
(helpers/animate-linear gray-overlay-opacity 0 800)
|
||||
(js/setTimeout (fn []
|
||||
(reset! record-button-at-initial-position? true))
|
||||
800))]
|
||||
(use-effect (fn []
|
||||
(cond
|
||||
@recording?
|
||||
(start-animation)
|
||||
(not @ready-to-lock?)
|
||||
(stop-animation)))
|
||||
[@recording?])
|
||||
(use-effect (fn []
|
||||
(if @ready-to-lock?
|
||||
(start-x-y-animation)
|
||||
(reset-x-y-animation)))
|
||||
[@ready-to-lock?])
|
||||
(use-effect (fn []
|
||||
(if @ready-to-send?
|
||||
(start-y-animation)
|
||||
(reset-y-animation)))
|
||||
[@ready-to-send?])
|
||||
(use-effect (fn []
|
||||
(if @ready-to-delete?
|
||||
(start-x-animation)
|
||||
(reset-x-animation)))
|
||||
[@ready-to-delete?])
|
||||
(reset! record-button-at-initial-position? false)
|
||||
(reset! record-button-is-animating? true)
|
||||
(helpers/animate-easing translate-y -44 200)
|
||||
(helpers/animate-easing translate-x -44 200)
|
||||
(helpers/animate-linear-with-delay icon-opacity 0 33.33 33.33)
|
||||
(helpers/animate-linear gray-overlay-opacity 1 33.33)
|
||||
(js/setTimeout (fn []
|
||||
(reset! record-button-is-animating? false)
|
||||
(when-not @touch-active? (complete-animation)))
|
||||
200))
|
||||
[complete-animation])
|
||||
reset-x-y-animation (rn/use-callback
|
||||
(fn []
|
||||
(helpers/animate-easing translate-y 0 300)
|
||||
(helpers/animate-easing translate-x 0 300)
|
||||
(helpers/animate-linear icon-opacity 1 500)
|
||||
(helpers/animate-linear gray-overlay-opacity 0 800)
|
||||
(js/setTimeout (fn []
|
||||
(reset! record-button-at-initial-position? true))
|
||||
800)))]
|
||||
(rn/use-effect (fn []
|
||||
(cond
|
||||
recording?
|
||||
(start-animation)
|
||||
(not ready-to-lock?)
|
||||
(stop-animation)))
|
||||
[recording?])
|
||||
(rn/use-effect (fn []
|
||||
(if ready-to-lock?
|
||||
(start-x-y-animation)
|
||||
(reset-x-y-animation)))
|
||||
[ready-to-lock?])
|
||||
(rn/use-effect (fn []
|
||||
(if ready-to-send?
|
||||
(start-y-animation)
|
||||
(reset-y-animation)))
|
||||
[ready-to-send?])
|
||||
(rn/use-effect (fn []
|
||||
(if ready-to-delete?
|
||||
(start-x-animation)
|
||||
(reset-x-animation)))
|
||||
[ready-to-delete?])
|
||||
[reanimated/view
|
||||
{:style (style/record-button-big-container translate-x translate-y opacity)
|
||||
:pointer-events :none}
|
||||
@@ -197,6 +215,6 @@
|
||||
[reanimated/view {:style (style/record-button-big-red-overlay red-overlay-opacity)}]
|
||||
[reanimated/view {:style (style/record-button-big-gray-overlay gray-overlay-opacity)}]
|
||||
[reanimated/view {:style (style/record-button-big-icon-container icon-opacity)}
|
||||
(if @locked?
|
||||
(if locked?
|
||||
[rn/view {:style style/stop-icon}]
|
||||
[icons/icon :i/audio {:color icon-color}])]]]))
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
[quo.components.record-audio.record-audio.helpers :as helpers]
|
||||
[quo.components.record-audio.record-audio.style :as style]
|
||||
[quo.foundations.colors :as colors]
|
||||
[react-native.core :refer [use-effect]]
|
||||
[react-native.core :as rn]
|
||||
[react-native.reanimated :as reanimated]))
|
||||
|
||||
(defn f-send-button
|
||||
(defn send-button
|
||||
[recording? ready-to-send? reviewing-audio? force-show-controls?]
|
||||
(let [opacity (reanimated/use-shared-value (if force-show-controls? 1 0))
|
||||
translate-y (reanimated/use-shared-value (if force-show-controls? 76 20))
|
||||
@@ -16,65 +16,71 @@
|
||||
height (reanimated/use-shared-value 24)
|
||||
border-radius-first-half (reanimated/use-shared-value 16)
|
||||
border-radius-second-half (reanimated/use-shared-value 8)
|
||||
start-y-animation (fn []
|
||||
(helpers/animate-linear-with-delay translate-y 12 50 133.33)
|
||||
(helpers/animate-easing-with-delay connector-opacity 1 0 93.33)
|
||||
(helpers/animate-easing-with-delay width 56 83.33 80)
|
||||
(helpers/animate-easing-with-delay height 56 83.33 80)
|
||||
(helpers/animate-easing-with-delay border-radius-first-half
|
||||
28
|
||||
83.33
|
||||
80)
|
||||
(helpers/animate-easing-with-delay border-radius-second-half
|
||||
28
|
||||
83.33
|
||||
80))
|
||||
reset-y-animation (fn []
|
||||
(helpers/animate-linear translate-y 0 100)
|
||||
(helpers/set-value connector-opacity 0)
|
||||
(helpers/set-value width 12)
|
||||
(helpers/set-value height 24)
|
||||
(helpers/set-value border-radius-first-half 16)
|
||||
(helpers/set-value border-radius-second-half 8))
|
||||
fade-in-animation (fn []
|
||||
(helpers/animate-linear translate-y 0 200)
|
||||
(helpers/animate-linear opacity 1 200))
|
||||
fade-out-animation (fn []
|
||||
(when-not force-show-controls?
|
||||
(helpers/animate-linear
|
||||
translate-y
|
||||
(if @reviewing-audio? 76 20)
|
||||
200))
|
||||
(when-not @reviewing-audio?
|
||||
(helpers/animate-linear opacity 0 200))
|
||||
(helpers/set-value connector-opacity 0)
|
||||
(helpers/set-value width 24)
|
||||
(helpers/set-value height 12)
|
||||
(helpers/set-value border-radius-first-half 8)
|
||||
(helpers/set-value border-radius-second-half 16))
|
||||
fade-out-reset-animation (fn []
|
||||
(helpers/animate-linear opacity 0 200)
|
||||
(helpers/animate-linear-with-delay translate-y 20 0 200)
|
||||
(helpers/set-value connector-opacity 0)
|
||||
(helpers/set-value width 24)
|
||||
(helpers/set-value height 12)
|
||||
(helpers/set-value border-radius-first-half 8)
|
||||
(helpers/set-value border-radius-second-half 16))]
|
||||
(use-effect (fn []
|
||||
(if @recording?
|
||||
(fade-in-animation)
|
||||
(fade-out-animation)))
|
||||
[@recording?])
|
||||
(use-effect (fn []
|
||||
(when-not @reviewing-audio?
|
||||
(fade-out-reset-animation)))
|
||||
[@reviewing-audio?])
|
||||
(use-effect (fn []
|
||||
(cond
|
||||
@ready-to-send?
|
||||
(start-y-animation)
|
||||
@recording? (reset-y-animation)))
|
||||
[@ready-to-send?])
|
||||
start-y-animation (rn/use-callback
|
||||
(fn []
|
||||
(helpers/animate-linear-with-delay translate-y 12 50 133.33)
|
||||
(helpers/animate-easing-with-delay connector-opacity 1 0 93.33)
|
||||
(helpers/animate-easing-with-delay width 56 83.33 80)
|
||||
(helpers/animate-easing-with-delay height 56 83.33 80)
|
||||
(helpers/animate-easing-with-delay border-radius-first-half
|
||||
28
|
||||
83.33
|
||||
80)
|
||||
(helpers/animate-easing-with-delay border-radius-second-half
|
||||
28
|
||||
83.33
|
||||
80)))
|
||||
reset-y-animation (rn/use-callback
|
||||
(fn []
|
||||
(helpers/animate-linear translate-y 0 100)
|
||||
(helpers/set-value connector-opacity 0)
|
||||
(helpers/set-value width 12)
|
||||
(helpers/set-value height 24)
|
||||
(helpers/set-value border-radius-first-half 16)
|
||||
(helpers/set-value border-radius-second-half 8)))
|
||||
fade-in-animation (rn/use-callback
|
||||
(fn []
|
||||
(helpers/animate-linear translate-y 0 200)
|
||||
(helpers/animate-linear opacity 1 200)))
|
||||
fade-out-animation (rn/use-callback
|
||||
(fn []
|
||||
(when-not force-show-controls?
|
||||
(helpers/animate-linear
|
||||
translate-y
|
||||
(if reviewing-audio? 76 20)
|
||||
200))
|
||||
(when-not reviewing-audio?
|
||||
(helpers/animate-linear opacity 0 200))
|
||||
(helpers/set-value connector-opacity 0)
|
||||
(helpers/set-value width 24)
|
||||
(helpers/set-value height 12)
|
||||
(helpers/set-value border-radius-first-half 8)
|
||||
(helpers/set-value border-radius-second-half 16))
|
||||
[reviewing-audio? force-show-controls?])
|
||||
fade-out-reset-animation (rn/use-callback
|
||||
(fn []
|
||||
(helpers/animate-linear opacity 0 200)
|
||||
(helpers/animate-linear-with-delay translate-y 20 0 200)
|
||||
(helpers/set-value connector-opacity 0)
|
||||
(helpers/set-value width 24)
|
||||
(helpers/set-value height 12)
|
||||
(helpers/set-value border-radius-first-half 8)
|
||||
(helpers/set-value border-radius-second-half 16)))]
|
||||
(rn/use-effect (fn []
|
||||
(if recording?
|
||||
(fade-in-animation)
|
||||
(fade-out-animation)))
|
||||
[recording?])
|
||||
(rn/use-effect (fn []
|
||||
(when-not reviewing-audio?
|
||||
(fade-out-reset-animation)))
|
||||
[reviewing-audio?])
|
||||
(rn/use-effect (fn []
|
||||
(cond
|
||||
ready-to-send?
|
||||
(start-y-animation)
|
||||
recording? (reset-y-animation)))
|
||||
[ready-to-send?])
|
||||
[:<>
|
||||
[reanimated/view {:style (style/send-button-container opacity)}
|
||||
[reanimated/view
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
(:require
|
||||
[quo.components.record-audio.record-audio.view :as record-audio]
|
||||
[react-native.audio-toolkit :as audio]
|
||||
[reagent.core :as reagent]
|
||||
[test-helpers.component :as h]
|
||||
[utils.datetime :as datetime]))
|
||||
|
||||
@@ -11,8 +10,10 @@
|
||||
|
||||
(h/test "renders record-audio"
|
||||
(h/render [record-audio/record-audio])
|
||||
(-> (h/expect (h/get-by-test-id "record-audio"))
|
||||
(.toBeTruthy)))
|
||||
(-> (h/wait-for #(h/get-by-test-id "record-audio"))
|
||||
(.then (fn []
|
||||
(-> (h/expect (h/get-by-test-id "record-audio"))
|
||||
(.toBeTruthy))))))
|
||||
|
||||
(h/test "record-audio on-start-recording works"
|
||||
(let [event (js/jest.fn)]
|
||||
@@ -31,7 +32,7 @@
|
||||
|
||||
(h/test "record-audio on-reviewing-audio works"
|
||||
(let [event (js/jest.fn)
|
||||
on-meter (reagent/atom nil)]
|
||||
on-meter (atom nil)]
|
||||
(h/render [record-audio/record-audio
|
||||
{:on-reviewing-audio event
|
||||
:record-audio-permission-granted true}])
|
||||
@@ -39,7 +40,7 @@
|
||||
(reset! on-meter on-meter-fn))
|
||||
audio/start-recording (fn [_ on-start _]
|
||||
(on-start)
|
||||
(js/setInterval #(@on-meter) 100))
|
||||
(js/setInterval @on-meter 100))
|
||||
audio/get-recorder-file-path (fn [] "file-path")]
|
||||
(h/fire-event
|
||||
:on-start-should-set-responder
|
||||
@@ -48,6 +49,7 @@
|
||||
:locationY 70
|
||||
:timestamp 0
|
||||
:identifier 0}})
|
||||
|
||||
(with-redefs [datetime/timestamp (fn [] (+ (.now js/Date) 1000))]
|
||||
(h/advance-timers-by-time 100)
|
||||
(h/fire-event
|
||||
@@ -63,7 +65,7 @@
|
||||
|
||||
(h/test "record-audio on-send works after reviewing audio"
|
||||
(let [event (js/jest.fn)
|
||||
on-meter (reagent/atom nil)]
|
||||
on-meter (atom nil)]
|
||||
(h/render [record-audio/record-audio
|
||||
{:on-send event
|
||||
:record-audio-permission-granted true}])
|
||||
@@ -114,14 +116,15 @@
|
||||
|
||||
(h/test "record-audio on-send works after sliding to the send button"
|
||||
(let [event (js/jest.fn)
|
||||
on-meter (reagent/atom nil)
|
||||
on-meter (atom nil)
|
||||
last-now-ms (atom nil)
|
||||
duration-ms (atom nil)]
|
||||
(h/render [record-audio/record-audio
|
||||
{:on-send event
|
||||
:record-audio-permission-granted true}])
|
||||
(with-redefs [audio/new-recorder (fn [_ on-meter-fn _]
|
||||
(reset! on-meter on-meter-fn))
|
||||
(reset! on-meter on-meter-fn)
|
||||
#js {:destroy #()})
|
||||
audio/start-recording (fn [_ on-start _]
|
||||
(on-start)
|
||||
(js/setInterval #(@on-meter) 100))
|
||||
@@ -170,15 +173,16 @@
|
||||
|
||||
(h/test "record-audio on-cancel works after reviewing audio"
|
||||
(let [event (js/jest.fn)
|
||||
on-meter (reagent/atom nil)]
|
||||
on-meter (atom nil)]
|
||||
(h/render [record-audio/record-audio
|
||||
{:on-cancel event
|
||||
:record-audio-permission-granted true}])
|
||||
(with-redefs [audio/new-recorder (fn [_ on-meter-fn _]
|
||||
(reset! on-meter on-meter-fn))
|
||||
(reset! on-meter on-meter-fn)
|
||||
#js {:destroy #()})
|
||||
audio/start-recording (fn [_ on-start _]
|
||||
(on-start)
|
||||
(js/setInterval #(@on-meter) 100))]
|
||||
(js/setInterval @on-meter 100))]
|
||||
(h/fire-event
|
||||
:on-start-should-set-responder
|
||||
(h/get-by-test-id "record-audio")
|
||||
@@ -195,6 +199,7 @@
|
||||
:locationY 70
|
||||
:timestamp 200
|
||||
:identifier 0}})
|
||||
(h/advance-timers-by-time 250)
|
||||
(h/fire-event
|
||||
:on-responder-release
|
||||
(h/get-by-test-id "record-audio")
|
||||
@@ -202,13 +207,12 @@
|
||||
:locationY 80
|
||||
:timestamp 200
|
||||
:identifier 0}})
|
||||
(h/advance-timers-by-time 250)
|
||||
(-> (js/expect event)
|
||||
(.toHaveBeenCalledTimes 1))))))
|
||||
|
||||
(h/test "record-audio on-cancel works after sliding to the cancel button"
|
||||
(let [event (js/jest.fn)
|
||||
on-meter (reagent/atom nil)]
|
||||
on-meter (atom nil)]
|
||||
(h/render [record-audio/record-audio
|
||||
{:on-cancel event
|
||||
:record-audio-permission-granted true}])
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
(ns quo.components.record-audio.record-audio.constants
|
||||
(:require [react-native.audio-toolkit :as audio]
|
||||
[react-native.platform :as platform]
|
||||
[utils.datetime :as datetime]))
|
||||
|
||||
(def min-audio-duration-ms 1000)
|
||||
(def max-audio-duration-ms (if platform/ios? 120800 120500))
|
||||
(def metering-interval 25)
|
||||
(def base-filename "am")
|
||||
(def default-format ".aac")
|
||||
|
||||
(def min-touch-duration 150)
|
||||
|
||||
(def record-button-area-big
|
||||
{:width 56
|
||||
:height 56
|
||||
:x 64
|
||||
:y 64})
|
||||
|
||||
(def record-button-area
|
||||
{:width 48
|
||||
:height 48
|
||||
:x 68
|
||||
:y 68})
|
||||
|
||||
(defn delete-button-area
|
||||
[{:keys [active? reviewing-audio?]}]
|
||||
{:width (cond
|
||||
active? 72
|
||||
reviewing-audio? 32
|
||||
:else 82)
|
||||
:height (if reviewing-audio? 32 56)
|
||||
:x (cond
|
||||
active? -16
|
||||
reviewing-audio? 36
|
||||
:else -32)
|
||||
:y (cond
|
||||
active? 64
|
||||
reviewing-audio? 76
|
||||
:else 70)})
|
||||
|
||||
(defn lock-button-area
|
||||
[{:keys [active?]}]
|
||||
{:width (if active? 72 100)
|
||||
:height (if active? 72 102)
|
||||
:x -32
|
||||
:y -32})
|
||||
|
||||
(defn send-button-area
|
||||
[{:keys [active? reviewing-audio?]}]
|
||||
{:width (if reviewing-audio? 32 56)
|
||||
:height (cond
|
||||
active? 72
|
||||
reviewing-audio? 47
|
||||
:else 92)
|
||||
:x (if reviewing-audio? 76 32)
|
||||
:y (cond
|
||||
active? -16
|
||||
reviewing-audio? 76
|
||||
:else -32)})
|
||||
|
||||
(defn touch-inside-area?
|
||||
[{:keys [location-x location-y ignore-min-y? ignore-max-y? ignore-min-x? ignore-max-x?]}
|
||||
{:keys [width height x y]}]
|
||||
(let [max-x (+ x width)
|
||||
max-y (+ y height)]
|
||||
(and
|
||||
(and
|
||||
(or ignore-min-x? (>= location-x x))
|
||||
(or ignore-max-x? (<= location-x max-x)))
|
||||
(and
|
||||
(or ignore-min-y? (>= location-y y))
|
||||
(or ignore-max-y? (<= location-y max-y))))))
|
||||
|
||||
(def rec-options
|
||||
(merge
|
||||
audio/default-recorder-options
|
||||
{:filename (str base-filename
|
||||
(datetime/timestamp)
|
||||
default-format)
|
||||
:meteringInterval metering-interval}))
|
||||
@@ -0,0 +1,345 @@
|
||||
(ns quo.components.record-audio.record-audio.handlers
|
||||
(:require [oops.core :as oops]
|
||||
[quo.components.record-audio.record-audio.constants :as record-audio.constants]
|
||||
[react-native.audio-toolkit :as audio]
|
||||
[taoensso.timbre :as log]
|
||||
[utils.datetime :as datetime]))
|
||||
|
||||
(def get-reload-player
|
||||
(memoize
|
||||
(fn
|
||||
[[player-ref destroy-player set-playing-audio playing-timer set-audio-current-time-ms
|
||||
set-seeking-audio]]
|
||||
(fn [audio-file]
|
||||
(when @player-ref
|
||||
(destroy-player))
|
||||
(reset! player-ref
|
||||
(audio/new-player
|
||||
(or audio-file (:filename record-audio.constants/rec-options))
|
||||
{:autoDestroy false
|
||||
:continuesToPlayInBackground false
|
||||
:category audio/PLAYBACK}
|
||||
(fn []
|
||||
(set-playing-audio false)
|
||||
(when @playing-timer
|
||||
(js/clearInterval @playing-timer)
|
||||
(reset! playing-timer nil)
|
||||
(set-audio-current-time-ms 0)
|
||||
(set-seeking-audio false)))))
|
||||
(audio/prepare-player
|
||||
@player-ref
|
||||
#(log/debug "[record-audio] prepare player - success")
|
||||
#(log/error "[record-audio] prepare player - error: " %))))))
|
||||
|
||||
(def get-recorder-on-meter
|
||||
(memoize
|
||||
(fn
|
||||
[[recording-start-ms set-recording-length-ms reached-max-duration? locked? set-locked
|
||||
set-reviewing-audio
|
||||
idle? set-recording
|
||||
set-ready-to-lock set-ready-to-send set-ready-to-delete recorder-ref output-file reload-player
|
||||
on-reviewing-audio]]
|
||||
(fn []
|
||||
(when @recording-start-ms
|
||||
(let [now-ms (datetime/timestamp)
|
||||
recording-duration (- now-ms @recording-start-ms)]
|
||||
(set-recording-length-ms recording-duration)
|
||||
(when (>= recording-duration record-audio.constants/max-audio-duration-ms)
|
||||
(reset! reached-max-duration? (not locked?))
|
||||
(set-reviewing-audio true)
|
||||
(reset! idle? false)
|
||||
(set-locked false)
|
||||
(set-recording false)
|
||||
(set-ready-to-lock false)
|
||||
(set-ready-to-send false)
|
||||
(set-ready-to-delete false)
|
||||
(audio/stop-recording
|
||||
@recorder-ref
|
||||
(fn []
|
||||
(reset! output-file (audio/get-recorder-file-path @recorder-ref))
|
||||
(reload-player nil)
|
||||
(log/debug "[record-audio] stop recording - success"))
|
||||
#(log/error "[record-audio] stop recording - error: " %))
|
||||
(js/setTimeout #(reset! idle? false) 1000)
|
||||
(set-recording-length-ms 0)
|
||||
(reset! recording-start-ms nil)
|
||||
(when on-reviewing-audio
|
||||
(on-reviewing-audio (audio/get-recorder-file-path @recorder-ref))))
|
||||
(log/debug "[record-audio] new recorder - on meter")))))))
|
||||
|
||||
(def get-reload-recorder
|
||||
(memoize
|
||||
(fn
|
||||
[[recorder-ref recorder-on-meter]]
|
||||
(fn []
|
||||
(when @recorder-ref
|
||||
(audio/destroy-recorder @recorder-ref)
|
||||
(reset! recorder-ref nil))
|
||||
(reset! recorder-ref (audio/new-recorder
|
||||
record-audio.constants/rec-options
|
||||
recorder-on-meter
|
||||
#(log/debug "[record-audio] new recorder - on ended")))))))
|
||||
|
||||
(def get-on-start-should-set-responder
|
||||
(memoize
|
||||
(fn
|
||||
[[locked? idle? disabled? recorder-on-meter touch-timestamp touch-identifier
|
||||
reviewing-audio? record-audio-permission-granted set-recording set-playing-audio output-file
|
||||
recorder-ref recording-start-ms set-audio-current-time-ms on-start-recording
|
||||
on-request-record-audio-permission touch-active?]]
|
||||
(fn [e]
|
||||
(when-not (or locked? @idle? (nil? e) @disabled?)
|
||||
(let [pressed-record-button? (record-audio.constants/touch-inside-area?
|
||||
{:location-x (oops/oget e "nativeEvent.locationX")
|
||||
:location-y (oops/oget e "nativeEvent.locationY")
|
||||
:ignore-min-y? false
|
||||
:ignore-max-y? false
|
||||
:ignore-min-x? false
|
||||
:ignore-max-x? false}
|
||||
record-audio.constants/record-button-area)
|
||||
new-recorder (audio/new-recorder
|
||||
record-audio.constants/rec-options
|
||||
recorder-on-meter
|
||||
#(log/debug "[record-audio] new recorder - on ended"))]
|
||||
(reset! touch-timestamp (oops/oget e "nativeEvent.timestamp"))
|
||||
(reset! touch-identifier (oops/oget e "nativeEvent.identifier"))
|
||||
(when-not reviewing-audio?
|
||||
(if record-audio-permission-granted
|
||||
(do
|
||||
(when (not @idle?)
|
||||
(set-recording pressed-record-button?))
|
||||
(when pressed-record-button?
|
||||
(set-playing-audio false)
|
||||
(reset! output-file nil)
|
||||
(reset! recorder-ref new-recorder)
|
||||
(audio/start-recording
|
||||
new-recorder
|
||||
(fn []
|
||||
(reset! recording-start-ms (datetime/timestamp))
|
||||
(set-audio-current-time-ms 0)
|
||||
(log/debug "[record-audio] start recording - success"))
|
||||
#(log/error "[record-audio] start recording - error: " %))
|
||||
(when on-start-recording
|
||||
(on-start-recording))))
|
||||
(when on-request-record-audio-permission
|
||||
(on-request-record-audio-permission))))
|
||||
(when record-audio-permission-granted
|
||||
(reset! touch-active? true))))
|
||||
(and (not @idle?) (not @disabled?))))))
|
||||
|
||||
(def get-on-responder-move
|
||||
(memoize
|
||||
(fn
|
||||
[[locked? ready-to-send? set-ready-to-send ready-to-delete? set-ready-to-delete ready-to-lock?
|
||||
set-ready-to-lock touch-identifier
|
||||
record-button-at-initial-position? recording?]]
|
||||
(fn [^js e]
|
||||
(when-not locked?
|
||||
(let [location-x (oops/oget e "nativeEvent.locationX")
|
||||
location-y (oops/oget e "nativeEvent.locationY")
|
||||
page-x (oops/oget e "nativeEvent.pageX")
|
||||
page-y (oops/oget e "nativeEvent.pageY")
|
||||
identifier (oops/oget e "nativeEvent.identifier")
|
||||
moved-to-send-button? (record-audio.constants/touch-inside-area?
|
||||
{:location-x location-x
|
||||
:location-y location-y
|
||||
:ignore-min-y? true
|
||||
:ignore-max-y? false
|
||||
:ignore-min-x? false
|
||||
:ignore-max-x? true}
|
||||
(record-audio.constants/send-button-area
|
||||
{:active? ready-to-send?
|
||||
:reviewing-audio? false}))
|
||||
moved-to-delete-button? (record-audio.constants/touch-inside-area?
|
||||
{:location-x location-x
|
||||
:location-y location-y
|
||||
:ignore-min-y? false
|
||||
:ignore-max-y? true
|
||||
:ignore-min-x? true
|
||||
:ignore-max-x? false}
|
||||
(record-audio.constants/delete-button-area
|
||||
{:active? ready-to-delete?
|
||||
:reviewing-audio? false}))
|
||||
moved-to-lock-button? (record-audio.constants/touch-inside-area?
|
||||
{:location-x location-x
|
||||
:location-y location-y
|
||||
:ignore-min-y? false
|
||||
:ignore-max-y? false
|
||||
:ignore-min-x? false
|
||||
:ignore-max-x? false}
|
||||
(record-audio.constants/lock-button-area {:active?
|
||||
ready-to-lock?}))
|
||||
moved-to-record-button? (and
|
||||
(record-audio.constants/touch-inside-area?
|
||||
{:location-x location-x
|
||||
:location-y location-y
|
||||
:ignore-min-y? false
|
||||
:ignore-max-y? false
|
||||
:ignore-min-x? false
|
||||
:ignore-max-x? false}
|
||||
record-audio.constants/record-button-area-big)
|
||||
(not= location-x page-x)
|
||||
(not= location-y page-y))]
|
||||
(when (= identifier @touch-identifier)
|
||||
(cond
|
||||
(and
|
||||
(or
|
||||
(and moved-to-record-button? ready-to-lock?)
|
||||
(and (not locked?) moved-to-lock-button? @record-button-at-initial-position?))
|
||||
(not ready-to-delete?)
|
||||
(not ready-to-send?)
|
||||
recording?)
|
||||
(set-ready-to-lock moved-to-lock-button?)
|
||||
(and
|
||||
(or
|
||||
(and moved-to-record-button? ready-to-delete?)
|
||||
(and moved-to-delete-button? @record-button-at-initial-position?))
|
||||
(not ready-to-lock?)
|
||||
(not ready-to-send?)
|
||||
recording?)
|
||||
(set-ready-to-delete moved-to-delete-button?)
|
||||
(and
|
||||
(or
|
||||
(and moved-to-record-button? ready-to-send?)
|
||||
(and moved-to-send-button? @record-button-at-initial-position?))
|
||||
(not ready-to-lock?)
|
||||
(not ready-to-delete?)
|
||||
recording?)
|
||||
(set-ready-to-send moved-to-send-button?)))))))))
|
||||
|
||||
(def get-on-responder-release
|
||||
(memoize
|
||||
(fn
|
||||
[[idle? reached-max-duration? touch-timestamp recording-length-ms set-recording-length-ms
|
||||
reviewing-audio? set-reviewing-audio
|
||||
set-audio-current-time-ms set-force-show-controls on-send output-file player-ref
|
||||
destroy-player on-cancel record-button-is-animating? ready-to-lock? set-ready-to-lock
|
||||
locked? set-locked on-lock ready-to-delete? set-ready-to-delete ready-to-send? set-ready-to-send
|
||||
disabled? on-reviewing-audio recorder-ref set-recording reload-player recording-start-ms
|
||||
touch-active?]]
|
||||
(fn [^js e]
|
||||
(when (and
|
||||
(not @idle?)
|
||||
(not @reached-max-duration?))
|
||||
(let [touch-area {:location-x (oops/oget e "nativeEvent.locationX")
|
||||
:location-y (oops/oget e "nativeEvent.locationY")
|
||||
:ignore-min-y? false
|
||||
:ignore-max-y? false
|
||||
:ignore-min-x? false
|
||||
:ignore-max-x? false}
|
||||
on-record-button? (record-audio.constants/touch-inside-area?
|
||||
touch-area
|
||||
record-audio.constants/record-button-area-big)
|
||||
on-send-button? (record-audio.constants/touch-inside-area?
|
||||
touch-area
|
||||
(record-audio.constants/send-button-area
|
||||
{:active? false
|
||||
:reviewing-audio? true}))
|
||||
on-delete-button? (record-audio.constants/touch-inside-area?
|
||||
touch-area
|
||||
(record-audio.constants/delete-button-area
|
||||
{:active? false
|
||||
:reviewing-audio? true}))
|
||||
release-touch-timestamp (oops/oget e "nativeEvent.timestamp")
|
||||
touch-timestamp-diff (- release-touch-timestamp @touch-timestamp)
|
||||
audio-length recording-length-ms]
|
||||
(cond
|
||||
(and reviewing-audio? on-send-button?)
|
||||
(do
|
||||
(set-reviewing-audio false)
|
||||
(set-audio-current-time-ms 0)
|
||||
(set-force-show-controls false)
|
||||
(when on-send
|
||||
(on-send {:file-path @output-file
|
||||
:duration (min record-audio.constants/max-audio-duration-ms
|
||||
(int (audio/get-player-duration @player-ref)))}))
|
||||
(when @player-ref
|
||||
(audio/stop-playing
|
||||
@player-ref
|
||||
(fn []
|
||||
(destroy-player)
|
||||
(log/debug "[record-audio] stop playing - success"))
|
||||
#(log/error "[record-audio] stop playing - error: " %))))
|
||||
|
||||
(and reviewing-audio? on-delete-button?)
|
||||
(do
|
||||
(set-reviewing-audio false)
|
||||
(set-audio-current-time-ms 0)
|
||||
(set-force-show-controls false)
|
||||
(destroy-player)
|
||||
(when on-cancel
|
||||
(on-cancel)))
|
||||
|
||||
(and ready-to-lock? (not @record-button-is-animating?))
|
||||
(do
|
||||
(set-locked true)
|
||||
(set-ready-to-lock false)
|
||||
(when on-lock
|
||||
(on-lock)))
|
||||
|
||||
(and (not reviewing-audio?)
|
||||
(or on-record-button?
|
||||
(and (not ready-to-delete?)
|
||||
(not ready-to-lock?)
|
||||
(not ready-to-send?))))
|
||||
(do
|
||||
(reset! disabled? (<= touch-timestamp-diff record-audio.constants/min-touch-duration))
|
||||
(js/setTimeout
|
||||
(fn []
|
||||
(if (>= recording-length-ms record-audio.constants/min-audio-duration-ms)
|
||||
(do (set-reviewing-audio true)
|
||||
(reset! idle? false)
|
||||
(when on-reviewing-audio
|
||||
(on-reviewing-audio (audio/get-recorder-file-path @recorder-ref))))
|
||||
(do (when on-cancel
|
||||
(on-cancel))
|
||||
(reset! idle? true)))
|
||||
(set-locked false)
|
||||
(set-recording false)
|
||||
(set-ready-to-lock false)
|
||||
(audio/stop-recording
|
||||
@recorder-ref
|
||||
(fn []
|
||||
(reset! output-file (audio/get-recorder-file-path @recorder-ref))
|
||||
(when (>= audio-length record-audio.constants/min-audio-duration-ms)
|
||||
(reload-player nil))
|
||||
(log/debug "[record-audio] stop recording - success"))
|
||||
#(log/error "[record-audio] stop recording - error: " %))
|
||||
(js/setTimeout #(reset! idle? false) 1000)
|
||||
(set-recording-length-ms 0)
|
||||
(reset! recording-start-ms nil)
|
||||
(reset! disabled? false))
|
||||
(if (> touch-timestamp-diff record-audio.constants/min-touch-duration) 0 250)))
|
||||
|
||||
(and (not locked?) (not reviewing-audio?) (not @record-button-is-animating?))
|
||||
(do
|
||||
(reset! disabled? (<= touch-timestamp-diff record-audio.constants/min-touch-duration))
|
||||
(js/setTimeout
|
||||
(fn []
|
||||
(audio/stop-recording
|
||||
@recorder-ref
|
||||
(fn []
|
||||
(cond
|
||||
ready-to-send?
|
||||
(when on-send
|
||||
(on-send {:file-path (audio/get-recorder-file-path @recorder-ref)
|
||||
:duration recording-length-ms}))
|
||||
ready-to-delete?
|
||||
(when on-cancel
|
||||
(on-cancel)))
|
||||
(set-recording false)
|
||||
(set-ready-to-send false)
|
||||
(set-ready-to-delete false)
|
||||
(set-ready-to-lock false)
|
||||
(reset! idle? true)
|
||||
(js/setTimeout #(reset! idle? false) 1000)
|
||||
(set-recording-length-ms 0)
|
||||
(reset! recording-start-ms nil)
|
||||
(reset! disabled? false)
|
||||
(log/debug "[record-audio] stop recording - success"))
|
||||
#(log/error "[record-audio] stop recording - error: " %)))
|
||||
(if (> touch-timestamp-diff record-audio.constants/min-touch-duration) 0 250)))))
|
||||
(reset! touch-active? false))
|
||||
(when @reached-max-duration?
|
||||
(reset! reached-max-duration? false))
|
||||
(reset! touch-timestamp nil)))))
|
||||
@@ -2,7 +2,6 @@
|
||||
(:require
|
||||
[clojure.string :as string]
|
||||
[goog.string :as gstring]
|
||||
[oops.core :as oops]
|
||||
[quo.components.icon :as icons]
|
||||
[quo.components.markdown.text :as text]
|
||||
[quo.components.record-audio.record-audio.buttons.delete-button :as delete-button]
|
||||
@@ -10,93 +9,23 @@
|
||||
[quo.components.record-audio.record-audio.buttons.record-button :as record-button]
|
||||
[quo.components.record-audio.record-audio.buttons.record-button-big :as record-button-big]
|
||||
[quo.components.record-audio.record-audio.buttons.send-button :as send-button]
|
||||
[quo.components.record-audio.record-audio.constants :as record-audio.constants]
|
||||
[quo.components.record-audio.record-audio.handlers :as handlers]
|
||||
[quo.components.record-audio.record-audio.style :as style]
|
||||
[quo.components.record-audio.soundtrack.view :as soundtrack]
|
||||
[quo.foundations.colors :as colors]
|
||||
[quo.theme :as quo.theme]
|
||||
[react-native.audio-toolkit :as audio]
|
||||
[react-native.core :as rn]
|
||||
[react-native.platform :as platform]
|
||||
[reagent.core :as reagent]
|
||||
[taoensso.timbre :as log]
|
||||
[utils.datetime :as datetime]))
|
||||
|
||||
(def ^:private min-audio-duration-ms 1000)
|
||||
(def ^:private max-audio-duration-ms (if platform/ios? 120800 120500))
|
||||
(def ^:private metering-interval 25)
|
||||
(def ^:private base-filename "am")
|
||||
(def ^:private default-format ".aac")
|
||||
|
||||
(def min-touch-duration 150)
|
||||
|
||||
(def ^:private record-button-area-big
|
||||
{:width 56
|
||||
:height 56
|
||||
:x 64
|
||||
:y 64})
|
||||
|
||||
(def ^:private record-button-area
|
||||
{:width 48
|
||||
:height 48
|
||||
:x 68
|
||||
:y 68})
|
||||
|
||||
(defn- delete-button-area
|
||||
[{:keys [active? reviewing-audio?]}]
|
||||
{:width (cond
|
||||
active? 72
|
||||
reviewing-audio? 32
|
||||
:else 82)
|
||||
:height (if reviewing-audio? 32 56)
|
||||
:x (cond
|
||||
active? -16
|
||||
reviewing-audio? 36
|
||||
:else -32)
|
||||
:y (cond
|
||||
active? 64
|
||||
reviewing-audio? 76
|
||||
:else 70)})
|
||||
|
||||
(defn- lock-button-area
|
||||
[{:keys [active?]}]
|
||||
{:width (if active? 72 100)
|
||||
:height (if active? 72 102)
|
||||
:x -32
|
||||
:y -32})
|
||||
|
||||
(defn- send-button-area
|
||||
[{:keys [active? reviewing-audio?]}]
|
||||
{:width (if reviewing-audio? 32 56)
|
||||
:height (cond
|
||||
active? 72
|
||||
reviewing-audio? 47
|
||||
:else 92)
|
||||
:x (if reviewing-audio? 76 32)
|
||||
:y (cond
|
||||
active? -16
|
||||
reviewing-audio? 76
|
||||
:else -32)})
|
||||
|
||||
(defn touch-inside-area?
|
||||
[{:keys [location-x location-y ignore-min-y? ignore-max-y? ignore-min-x? ignore-max-x?]}
|
||||
{:keys [width height x y]}]
|
||||
(let [max-x (+ x width)
|
||||
max-y (+ y height)]
|
||||
(and
|
||||
(and
|
||||
(or ignore-min-x? (>= location-x x))
|
||||
(or ignore-max-x? (<= location-x max-x)))
|
||||
(and
|
||||
(or ignore-min-y? (>= location-y y))
|
||||
(or ignore-max-y? (<= location-y max-y))))))
|
||||
|
||||
(defn- f-recording-bar
|
||||
(defn- recording-bar
|
||||
[recording-length-ms ready-to-delete?]
|
||||
(let [fill-percentage (/ (* recording-length-ms 100) max-audio-duration-ms)]
|
||||
(let [fill-percentage (/ (* recording-length-ms 100) record-audio.constants/max-audio-duration-ms)]
|
||||
[rn/view {:style (style/recording-bar-container)}
|
||||
[rn/view {:style (style/recording-bar fill-percentage ready-to-delete?)}]]))
|
||||
|
||||
(defn- f-time-counter
|
||||
(defn- time-counter
|
||||
[recording? recording-length-ms ready-to-delete? reviewing-audio? audio-current-time-ms]
|
||||
(let [s (quot (if recording? recording-length-ms audio-current-time-ms) 1000)
|
||||
time-str (gstring/format "%02d:%02d" (quot s 60) (mod s 60))]
|
||||
@@ -111,10 +40,12 @@
|
||||
{:style (style/timer-text)}))
|
||||
time-str]]))
|
||||
|
||||
(defn- f-play-button
|
||||
[playing-audio? player-ref playing-timer audio-current-time-ms seeking-audio? max-duration-ms]
|
||||
(defn- play-button
|
||||
[playing-audio? set-playing-audio player-ref playing-timer set-audio-current-time-ms seeking-audio?
|
||||
set-seeking-audio
|
||||
max-duration-ms]
|
||||
(let [on-play (fn []
|
||||
(reset! playing-audio? true)
|
||||
(set-playing-audio true)
|
||||
(reset! playing-timer
|
||||
(js/setInterval
|
||||
(fn []
|
||||
@@ -122,24 +53,24 @@
|
||||
player-state (audio/get-state @player-ref)
|
||||
playing? (= player-state audio/PLAYING)]
|
||||
(when (and playing?
|
||||
(not @seeking-audio?)
|
||||
(not seeking-audio?)
|
||||
(> current-time 0)
|
||||
(< current-time max-duration-ms))
|
||||
(reset! audio-current-time-ms current-time))
|
||||
(set-audio-current-time-ms current-time))
|
||||
(when (>= current-time max-duration-ms)
|
||||
(audio/stop-playing
|
||||
@player-ref
|
||||
(fn []
|
||||
(reset! playing-audio? false)
|
||||
(set-playing-audio false)
|
||||
(when @playing-timer
|
||||
(js/clearInterval @playing-timer)
|
||||
(reset! playing-timer nil)
|
||||
(reset! audio-current-time-ms 0)
|
||||
(reset! seeking-audio? false)))
|
||||
(set-audio-current-time-ms 0)
|
||||
(set-seeking-audio false)))
|
||||
#(log/error "[record-audio] stop player - error: " %)))))
|
||||
100)))
|
||||
on-pause (fn []
|
||||
(reset! playing-audio? false)
|
||||
(set-playing-audio false)
|
||||
(when @playing-timer
|
||||
(js/clearInterval @playing-timer)
|
||||
(reset! playing-timer nil))
|
||||
@@ -154,445 +85,186 @@
|
||||
{:style (style/play-button)
|
||||
:on-press on-press}
|
||||
[icons/icon
|
||||
(if @playing-audio? :i/pause :i/play)
|
||||
(if playing-audio? :i/pause :i/play)
|
||||
{:color (colors/theme-colors colors/neutral-100 colors/white)}]]))
|
||||
|
||||
(defn- record-audio-internal
|
||||
[{:keys [on-init on-start-recording on-send on-cancel on-reviewing-audio
|
||||
record-audio-permission-granted
|
||||
on-request-record-audio-permission on-check-audio-permissions
|
||||
audio-file on-lock max-duration-ms theme]}]
|
||||
[:f>
|
||||
;; TODO we need to refactor this, and use :f> with defined function, currenly state is reseted each
|
||||
;; time parent component
|
||||
;; is re-rendered
|
||||
(fn []
|
||||
(let [recording? (reagent/atom false)
|
||||
locked? (reagent/atom false)
|
||||
ready-to-send? (reagent/atom false)
|
||||
ready-to-lock? (reagent/atom false)
|
||||
ready-to-delete? (reagent/atom false)
|
||||
reviewing-audio? (reagent/atom (some? audio-file))
|
||||
playing-audio? (reagent/atom false)
|
||||
recording-length-ms (reagent/atom 0)
|
||||
audio-current-time-ms (reagent/atom 0)
|
||||
seeking-audio? (reagent/atom false)
|
||||
force-show-controls? (reagent/atom (some? audio-file))
|
||||
recording-start-ms (atom (datetime/timestamp))
|
||||
clear-timeout (atom nil)
|
||||
record-button-at-initial-position? (atom true)
|
||||
record-button-is-animating? (atom false)
|
||||
idle? (atom false)
|
||||
recorder-ref (atom nil)
|
||||
player-ref (atom nil)
|
||||
touch-active? (atom false)
|
||||
playing-timer (atom nil)
|
||||
output-file (atom audio-file)
|
||||
reached-max-duration? (atom false)
|
||||
touch-timestamp (atom nil)
|
||||
touch-identifier (atom nil)
|
||||
disabled? (atom false)
|
||||
app-state-listener (atom nil)
|
||||
rec-options
|
||||
(merge
|
||||
audio/default-recorder-options
|
||||
{:filename (str base-filename (datetime/timestamp) default-format)
|
||||
:meteringInterval metering-interval})
|
||||
destroy-player
|
||||
(fn []
|
||||
(audio/destroy-player @player-ref)
|
||||
(reset! player-ref nil))
|
||||
reload-player
|
||||
(fn [audio-file]
|
||||
(when @player-ref
|
||||
(destroy-player))
|
||||
(reset! player-ref
|
||||
(audio/new-player
|
||||
(or audio-file (:filename rec-options))
|
||||
{:autoDestroy false
|
||||
:continuesToPlayInBackground false
|
||||
:category audio/PLAYBACK}
|
||||
(fn []
|
||||
(reset! playing-audio? false)
|
||||
(when @playing-timer
|
||||
(js/clearInterval @playing-timer)
|
||||
(reset! playing-timer nil)
|
||||
(reset! audio-current-time-ms 0)
|
||||
(reset! seeking-audio? false)))))
|
||||
(audio/prepare-player
|
||||
@player-ref
|
||||
#(log/debug "[record-audio] prepare player - success")
|
||||
#(log/error "[record-audio] prepare player - error: " %)))
|
||||
destroy-recorder
|
||||
(fn []
|
||||
(audio/destroy-recorder @recorder-ref)
|
||||
(reset! recorder-ref nil))
|
||||
recorder-on-meter
|
||||
(fn []
|
||||
(when @recording-start-ms
|
||||
(let [now-ms (datetime/timestamp)
|
||||
recording-duration (- now-ms
|
||||
@recording-start-ms)]
|
||||
(reset! recording-length-ms recording-duration)
|
||||
(when (>= recording-duration max-audio-duration-ms)
|
||||
(reset! reached-max-duration? (not @locked?))
|
||||
(reset! reviewing-audio? true)
|
||||
(reset! idle? false)
|
||||
(reset! locked? false)
|
||||
(reset! recording? false)
|
||||
(reset! ready-to-lock? false)
|
||||
(reset! ready-to-send? false)
|
||||
(reset! ready-to-delete? false)
|
||||
(audio/stop-recording
|
||||
@recorder-ref
|
||||
(fn []
|
||||
(reset! output-file (audio/get-recorder-file-path
|
||||
@recorder-ref))
|
||||
(reload-player nil)
|
||||
(log/debug "[record-audio] stop recording - success"))
|
||||
#(log/error "[record-audio] stop recording - error: " %))
|
||||
(js/setTimeout #(reset! idle? false) 1000)
|
||||
(reset! recording-length-ms 0)
|
||||
(reset! recording-start-ms nil)
|
||||
(when on-reviewing-audio
|
||||
(on-reviewing-audio (audio/get-recorder-file-path
|
||||
@recorder-ref))))
|
||||
(log/debug "[record-audio] new recorder - on meter"))))
|
||||
reload-recorder
|
||||
(fn []
|
||||
(when @recorder-ref
|
||||
(destroy-recorder))
|
||||
(reset! recorder-ref (audio/new-recorder
|
||||
rec-options
|
||||
recorder-on-meter
|
||||
#(log/debug "[record-audio] new recorder - on ended"))))
|
||||
reset-recorder
|
||||
(fn []
|
||||
(reset! recording? false)
|
||||
(reset! reviewing-audio? false)
|
||||
(reset! locked? false)
|
||||
(reset! ready-to-send? false)
|
||||
(reset! ready-to-lock? false)
|
||||
(reset! ready-to-delete? false)
|
||||
(reset! audio-current-time-ms 0)
|
||||
(reset! recording-length-ms 0)
|
||||
(reset! seeking-audio? false)
|
||||
(reset! playing-audio? false)
|
||||
(reset! touch-active? false)
|
||||
(reset! reached-max-duration? false)
|
||||
(reset! output-file nil)
|
||||
(reset! idle? false)
|
||||
(reset! record-button-is-animating? false)
|
||||
(reset! record-button-at-initial-position? true)
|
||||
(when @clear-timeout
|
||||
(js/clearTimeout @clear-timeout)
|
||||
(reset! clear-timeout nil))
|
||||
(when @playing-timer
|
||||
(js/clearInterval @playing-timer)
|
||||
(reset! playing-timer nil))
|
||||
(reload-recorder))
|
||||
on-start-should-set-responder
|
||||
(fn [^js e]
|
||||
(when-not (or @locked? @idle? (nil? e) @disabled?)
|
||||
(let [pressed-record-button? (touch-inside-area?
|
||||
{:location-x (oops/oget e "nativeEvent.locationX")
|
||||
:location-y (oops/oget e "nativeEvent.locationY")
|
||||
:ignore-min-y? false
|
||||
:ignore-max-y? false
|
||||
:ignore-min-x? false
|
||||
:ignore-max-x? false}
|
||||
record-button-area)
|
||||
new-recorder (audio/new-recorder
|
||||
rec-options
|
||||
recorder-on-meter
|
||||
#(log/debug "[record-audio] new recorder - on ended"))]
|
||||
(reset! touch-timestamp (oops/oget e "nativeEvent.timestamp"))
|
||||
(reset! touch-identifier (oops/oget e "nativeEvent.identifier"))
|
||||
(when-not @reviewing-audio?
|
||||
(if record-audio-permission-granted
|
||||
(do
|
||||
(when (not @idle?)
|
||||
(reset! recording? pressed-record-button?))
|
||||
(when pressed-record-button?
|
||||
(reset! playing-audio? false)
|
||||
(reset! output-file nil)
|
||||
(reset! recorder-ref new-recorder)
|
||||
(audio/start-recording
|
||||
new-recorder
|
||||
(fn []
|
||||
(reset! recording-start-ms (datetime/timestamp))
|
||||
(reset! audio-current-time-ms 0)
|
||||
(log/debug "[record-audio] start recording - success"))
|
||||
#(log/error "[record-audio] start recording - error: " %))
|
||||
(when on-start-recording
|
||||
(on-start-recording))))
|
||||
(when on-request-record-audio-permission
|
||||
(on-request-record-audio-permission))))
|
||||
(when record-audio-permission-granted
|
||||
(reset! touch-active? true))))
|
||||
(and (not @idle?) (not @disabled?)))
|
||||
on-responder-move
|
||||
(fn [^js e]
|
||||
(when-not @locked?
|
||||
(let [location-x (oops/oget e "nativeEvent.locationX")
|
||||
location-y (oops/oget e "nativeEvent.locationY")
|
||||
page-x (oops/oget e "nativeEvent.pageX")
|
||||
page-y (oops/oget e "nativeEvent.pageY")
|
||||
identifier (oops/oget e "nativeEvent.identifier")
|
||||
moved-to-send-button? (touch-inside-area?
|
||||
{:location-x location-x
|
||||
:location-y location-y
|
||||
:ignore-min-y? true
|
||||
:ignore-max-y? false
|
||||
:ignore-min-x? false
|
||||
:ignore-max-x? true}
|
||||
(send-button-area
|
||||
{:active? @ready-to-send?
|
||||
:reviewing-audio? false}))
|
||||
moved-to-delete-button? (touch-inside-area?
|
||||
{:location-x location-x
|
||||
:location-y location-y
|
||||
:ignore-min-y? false
|
||||
:ignore-max-y? true
|
||||
:ignore-min-x? true
|
||||
:ignore-max-x? false}
|
||||
(delete-button-area
|
||||
{:active? @ready-to-delete?
|
||||
:reviewing-audio? false}))
|
||||
moved-to-lock-button? (touch-inside-area?
|
||||
{:location-x location-x
|
||||
:location-y location-y
|
||||
:ignore-min-y? false
|
||||
:ignore-max-y? false
|
||||
:ignore-min-x? false
|
||||
:ignore-max-x? false}
|
||||
(lock-button-area {:active? @ready-to-lock?}))
|
||||
moved-to-record-button? (and
|
||||
(touch-inside-area?
|
||||
{:location-x location-x
|
||||
:location-y location-y
|
||||
:ignore-min-y? false
|
||||
:ignore-max-y? false
|
||||
:ignore-min-x? false
|
||||
:ignore-max-x? false}
|
||||
record-button-area-big)
|
||||
(not= location-x page-x)
|
||||
(not= location-y page-y))]
|
||||
(when (= identifier @touch-identifier)
|
||||
(cond
|
||||
(and
|
||||
(or
|
||||
(and moved-to-record-button? @ready-to-lock?)
|
||||
(and (not @locked?) moved-to-lock-button? @record-button-at-initial-position?))
|
||||
(not @ready-to-delete?)
|
||||
(not @ready-to-send?)
|
||||
@recording?)
|
||||
(reset! ready-to-lock? moved-to-lock-button?)
|
||||
(and
|
||||
(or
|
||||
(and moved-to-record-button? @ready-to-delete?)
|
||||
(and moved-to-delete-button? @record-button-at-initial-position?))
|
||||
(not @ready-to-lock?)
|
||||
(not @ready-to-send?)
|
||||
@recording?)
|
||||
(reset! ready-to-delete? moved-to-delete-button?)
|
||||
(and
|
||||
(or
|
||||
(and moved-to-record-button? @ready-to-send?)
|
||||
(and moved-to-send-button? @record-button-at-initial-position?))
|
||||
(not @ready-to-lock?)
|
||||
(not @ready-to-delete?)
|
||||
@recording?)
|
||||
(reset! ready-to-send? moved-to-send-button?))))))
|
||||
on-responder-release
|
||||
(fn [^js e]
|
||||
(when (and
|
||||
(not @idle?)
|
||||
(not @reached-max-duration?))
|
||||
(let [touch-area {:location-x (oops/oget e "nativeEvent.locationX")
|
||||
:location-y (oops/oget e "nativeEvent.locationY")
|
||||
:ignore-min-y? false
|
||||
:ignore-max-y? false
|
||||
:ignore-min-x? false
|
||||
:ignore-max-x? false}
|
||||
on-record-button? (touch-inside-area?
|
||||
touch-area
|
||||
record-button-area-big)
|
||||
on-send-button? (touch-inside-area?
|
||||
touch-area
|
||||
(send-button-area
|
||||
{:active? false
|
||||
:reviewing-audio? true}))
|
||||
on-delete-button? (touch-inside-area?
|
||||
touch-area
|
||||
(delete-button-area
|
||||
{:active? false
|
||||
:reviewing-audio? true}))
|
||||
release-touch-timestamp (oops/oget e "nativeEvent.timestamp")
|
||||
touch-timestamp-diff (- release-touch-timestamp @touch-timestamp)
|
||||
audio-length @recording-length-ms]
|
||||
(cond
|
||||
(and @reviewing-audio? on-send-button?)
|
||||
(do
|
||||
(reset! reviewing-audio? false)
|
||||
(reset! audio-current-time-ms 0)
|
||||
(reset! force-show-controls? false)
|
||||
(when on-send
|
||||
(on-send {:file-path @output-file
|
||||
:duration (min max-audio-duration-ms
|
||||
(int (audio/get-player-duration @player-ref)))}))
|
||||
(when @player-ref
|
||||
(audio/stop-playing
|
||||
@player-ref
|
||||
(fn []
|
||||
(destroy-player)
|
||||
(log/debug "[record-audio] stop playing - success"))
|
||||
#(log/error "[record-audio] stop playing - error: " %))))
|
||||
(and @reviewing-audio? on-delete-button?)
|
||||
(do
|
||||
(reset! reviewing-audio? false)
|
||||
(reset! audio-current-time-ms 0)
|
||||
(reset! force-show-controls? false)
|
||||
(destroy-player)
|
||||
(when on-cancel
|
||||
(on-cancel)))
|
||||
(and @ready-to-lock? (not @record-button-is-animating?))
|
||||
(do
|
||||
(reset! locked? true)
|
||||
(reset! ready-to-lock? false)
|
||||
(when on-lock
|
||||
(on-lock)))
|
||||
(and (not @reviewing-audio?)
|
||||
(or on-record-button?
|
||||
(and (not @ready-to-delete?)
|
||||
(not @ready-to-lock?)
|
||||
(not @ready-to-send?))))
|
||||
(do
|
||||
(reset! disabled? (<= touch-timestamp-diff min-touch-duration))
|
||||
(js/setTimeout
|
||||
(fn []
|
||||
(if (>= @recording-length-ms min-audio-duration-ms)
|
||||
(do (reset! reviewing-audio? true)
|
||||
(reset! idle? false)
|
||||
(when on-reviewing-audio
|
||||
(on-reviewing-audio (audio/get-recorder-file-path @recorder-ref))))
|
||||
(do (when on-cancel
|
||||
(on-cancel))
|
||||
(reset! idle? true)))
|
||||
(reset! locked? false)
|
||||
(reset! recording? false)
|
||||
(reset! ready-to-lock? false)
|
||||
(audio/stop-recording
|
||||
@recorder-ref
|
||||
(fn []
|
||||
(reset! output-file (audio/get-recorder-file-path @recorder-ref))
|
||||
(when (>= audio-length min-audio-duration-ms)
|
||||
(reload-player nil))
|
||||
(log/debug "[record-audio] stop recording - success"))
|
||||
#(log/error "[record-audio] stop recording - error: " %))
|
||||
(js/setTimeout #(reset! idle? false) 1000)
|
||||
(reset! recording-length-ms 0)
|
||||
(reset! recording-start-ms nil)
|
||||
(reset! disabled? false))
|
||||
(if (> touch-timestamp-diff min-touch-duration) 0 250)))
|
||||
(and (not @locked?) (not @reviewing-audio?) (not @record-button-is-animating?))
|
||||
(do
|
||||
(reset! disabled? (<= touch-timestamp-diff min-touch-duration))
|
||||
(js/setTimeout
|
||||
(fn []
|
||||
(audio/stop-recording
|
||||
@recorder-ref
|
||||
(fn []
|
||||
(cond
|
||||
@ready-to-send?
|
||||
(when on-send
|
||||
(on-send {:file-path (audio/get-recorder-file-path @recorder-ref)
|
||||
:duration @recording-length-ms}))
|
||||
@ready-to-delete?
|
||||
(when on-cancel
|
||||
(on-cancel)))
|
||||
(reset! recording? false)
|
||||
(reset! ready-to-send? false)
|
||||
(reset! ready-to-delete? false)
|
||||
(reset! ready-to-lock? false)
|
||||
(reset! idle? true)
|
||||
(js/setTimeout #(reset! idle? false) 1000)
|
||||
(reset! recording-length-ms 0)
|
||||
(reset! recording-start-ms nil)
|
||||
(reset! disabled? false)
|
||||
(log/debug "[record-audio] stop recording - success"))
|
||||
#(log/error "[record-audio] stop recording - error: " %)))
|
||||
(if (> touch-timestamp-diff min-touch-duration) 0 250)))))
|
||||
(reset! touch-active? false))
|
||||
(when @reached-max-duration?
|
||||
(reset! reached-max-duration? false))
|
||||
(reset! touch-timestamp nil))]
|
||||
(fn []
|
||||
(rn/use-mount (fn []
|
||||
(when on-check-audio-permissions
|
||||
(on-check-audio-permissions))
|
||||
(when on-init
|
||||
(on-init reset-recorder))
|
||||
(when audio-file
|
||||
(let [filename (last (string/split audio-file "/"))]
|
||||
(reload-player filename)))
|
||||
(reset! app-state-listener
|
||||
(.addEventListener rn/app-state
|
||||
"change"
|
||||
#(when (= % "background")
|
||||
(reset! playing-audio? false))))
|
||||
#(.remove @app-state-listener)))
|
||||
[rn/view
|
||||
{:style style/bar-container
|
||||
:pointer-events :box-none}
|
||||
(when @reviewing-audio?
|
||||
[:<>
|
||||
[:f> f-play-button playing-audio? player-ref playing-timer audio-current-time-ms
|
||||
seeking-audio? max-duration-ms]
|
||||
[:f> soundtrack/f-soundtrack
|
||||
{:audio-current-time-ms audio-current-time-ms
|
||||
:player-ref @player-ref
|
||||
:seeking-audio? seeking-audio?
|
||||
:max-audio-duration-ms max-duration-ms}]])
|
||||
(when (or @recording? @reviewing-audio?)
|
||||
[:f> f-time-counter @recording? @recording-length-ms @ready-to-delete? @reviewing-audio?
|
||||
@audio-current-time-ms])
|
||||
(when @recording?
|
||||
[:f> f-recording-bar @recording-length-ms @ready-to-delete?])
|
||||
[rn/view
|
||||
{:test-ID "record-audio"
|
||||
:style style/button-container
|
||||
:hit-slop {:top -70
|
||||
:bottom 0
|
||||
:left 0
|
||||
:right 0}
|
||||
:pointer-events :box-only
|
||||
:on-start-should-set-responder on-start-should-set-responder
|
||||
:on-responder-move on-responder-move
|
||||
:on-responder-release on-responder-release}
|
||||
[:f> delete-button/f-delete-button recording? ready-to-delete? reviewing-audio?
|
||||
@force-show-controls?]
|
||||
[:f> lock-button/f-lock-button recording? ready-to-lock? locked?]
|
||||
[:f> send-button/f-send-button recording? ready-to-send? reviewing-audio?
|
||||
@force-show-controls?]
|
||||
[:f> record-button-big/f-record-button-big
|
||||
{:recording? recording?
|
||||
:ready-to-send? ready-to-send?
|
||||
:ready-to-lock? ready-to-lock?
|
||||
:ready-to-delete? ready-to-delete?
|
||||
:record-button-is-animating? record-button-is-animating?
|
||||
:record-button-at-initial-position? record-button-at-initial-position?
|
||||
:locked? locked?
|
||||
:reviewing-audio? reviewing-audio?
|
||||
:recording-length-ms recording-length-ms
|
||||
:clear-timeout clear-timeout
|
||||
:touch-active? touch-active?
|
||||
:recorder-ref recorder-ref
|
||||
:reload-recorder-fn reload-recorder
|
||||
:idle? idle?
|
||||
:on-send on-send
|
||||
:on-cancel on-cancel
|
||||
:theme theme}]
|
||||
[:f> record-button/f-record-button recording? reviewing-audio?]]])))])
|
||||
(defn record-audio
|
||||
[{:keys [on-init on-start-recording on-send on-cancel on-reviewing-audio audio-file on-lock
|
||||
max-duration-ms on-check-audio-permissions record-audio-permission-granted
|
||||
on-request-record-audio-permission]}]
|
||||
(let [;;STATE
|
||||
[recording? set-recording] (rn/use-state false)
|
||||
[locked? set-locked] (rn/use-state false)
|
||||
[ready-to-send? set-ready-to-send] (rn/use-state false)
|
||||
[ready-to-lock? set-ready-to-lock] (rn/use-state false)
|
||||
[ready-to-delete?
|
||||
set-ready-to-delete] (rn/use-state false)
|
||||
[reviewing-audio?
|
||||
set-reviewing-audio] (rn/use-state (some? audio-file))
|
||||
[playing-audio?
|
||||
set-playing-audio] (rn/use-state false)
|
||||
[recording-length-ms
|
||||
set-recording-length-ms] (rn/use-state 0)
|
||||
[audio-current-time-ms
|
||||
set-audio-current-time-ms] (rn/use-state 0)
|
||||
[seeking-audio? set-seeking-audio] (rn/use-state false)
|
||||
[force-show-controls?
|
||||
set-force-show-controls] (rn/use-state (some? audio-file))
|
||||
;;ATOMS
|
||||
recording-start-ms (rn/use-ref-atom (datetime/timestamp))
|
||||
clear-timeout (rn/use-ref-atom nil)
|
||||
record-button-at-initial-position? (rn/use-ref-atom true)
|
||||
record-button-is-animating? (rn/use-ref-atom false)
|
||||
idle? (rn/use-ref-atom false)
|
||||
recorder-ref (rn/use-ref-atom nil)
|
||||
player-ref (rn/use-ref-atom nil)
|
||||
touch-active? (rn/use-ref-atom false)
|
||||
playing-timer (rn/use-ref-atom nil)
|
||||
output-file (rn/use-ref-atom audio-file)
|
||||
reached-max-duration? (rn/use-ref-atom false)
|
||||
touch-timestamp (rn/use-ref-atom nil)
|
||||
touch-identifier (rn/use-ref-atom nil)
|
||||
disabled? (rn/use-ref-atom false)
|
||||
app-state-listener (rn/use-ref-atom nil)
|
||||
;;HANDLERS
|
||||
destroy-player (rn/use-callback
|
||||
(fn []
|
||||
(audio/destroy-player @player-ref)
|
||||
(reset! player-ref nil)))
|
||||
reload-player (handlers/get-reload-player
|
||||
[player-ref destroy-player set-playing-audio playing-timer
|
||||
set-audio-current-time-ms set-seeking-audio])
|
||||
recorder-on-meter (handlers/get-recorder-on-meter
|
||||
[recording-start-ms set-recording-length-ms
|
||||
reached-max-duration?
|
||||
locked? set-locked set-reviewing-audio idle? set-recording
|
||||
set-ready-to-lock set-ready-to-send set-ready-to-delete
|
||||
recorder-ref
|
||||
output-file reload-player
|
||||
on-reviewing-audio])
|
||||
reload-recorder (handlers/get-reload-recorder [recorder-ref
|
||||
recorder-on-meter])
|
||||
on-start-should-set-responder (handlers/get-on-start-should-set-responder
|
||||
[locked? idle? disabled? recorder-on-meter touch-timestamp
|
||||
touch-identifier
|
||||
reviewing-audio? record-audio-permission-granted
|
||||
set-recording set-playing-audio output-file
|
||||
recorder-ref recording-start-ms set-audio-current-time-ms
|
||||
on-start-recording
|
||||
on-request-record-audio-permission touch-active?])
|
||||
on-responder-move (handlers/get-on-responder-move
|
||||
[locked? ready-to-send? set-ready-to-send
|
||||
ready-to-delete? set-ready-to-delete
|
||||
ready-to-lock? set-ready-to-lock
|
||||
touch-identifier
|
||||
record-button-at-initial-position?
|
||||
recording?])
|
||||
on-responder-release (handlers/get-on-responder-release
|
||||
[idle? reached-max-duration? touch-timestamp
|
||||
recording-length-ms set-recording-length-ms
|
||||
reviewing-audio? set-reviewing-audio
|
||||
set-audio-current-time-ms set-force-show-controls on-send
|
||||
output-file player-ref
|
||||
destroy-player on-cancel record-button-is-animating?
|
||||
ready-to-lock? set-ready-to-lock
|
||||
locked? set-locked on-lock ready-to-delete?
|
||||
set-ready-to-delete ready-to-send? set-ready-to-send
|
||||
disabled? on-reviewing-audio
|
||||
recorder-ref set-recording reload-player recording-start-ms
|
||||
touch-active?])]
|
||||
|
||||
(def record-audio (quo.theme/with-theme record-audio-internal))
|
||||
;;ON MOUNT
|
||||
(rn/use-mount
|
||||
(fn []
|
||||
(when on-check-audio-permissions (on-check-audio-permissions))
|
||||
(when on-init
|
||||
(on-init
|
||||
(fn reset-recorder []
|
||||
(set-recording false)
|
||||
(set-reviewing-audio false)
|
||||
(set-locked false)
|
||||
(set-ready-to-send false)
|
||||
(set-ready-to-lock false)
|
||||
(set-ready-to-delete false)
|
||||
(set-audio-current-time-ms 0)
|
||||
(set-recording-length-ms 0)
|
||||
(set-seeking-audio false)
|
||||
(set-playing-audio false)
|
||||
(reset! touch-active? false)
|
||||
(reset! reached-max-duration? false)
|
||||
(reset! output-file nil)
|
||||
(reset! idle? false)
|
||||
(reset! record-button-is-animating? false)
|
||||
(reset! record-button-at-initial-position? true)
|
||||
(when @clear-timeout
|
||||
(js/clearTimeout @clear-timeout)
|
||||
(reset! clear-timeout nil))
|
||||
(when @playing-timer
|
||||
(js/clearInterval @playing-timer)
|
||||
(reset! playing-timer nil))
|
||||
(reload-recorder))))
|
||||
(when audio-file
|
||||
(let [filename (last (string/split audio-file "/"))]
|
||||
(reload-player filename)))
|
||||
(reset! app-state-listener
|
||||
(.addEventListener rn/app-state
|
||||
"change"
|
||||
#(when (= % "background")
|
||||
(set-playing-audio false))))))
|
||||
;;ON UNMOUNT
|
||||
(rn/use-unmount #(.remove @app-state-listener))
|
||||
|
||||
[rn/view
|
||||
{:style style/bar-container
|
||||
:pointer-events :box-none}
|
||||
(when reviewing-audio?
|
||||
[:<>
|
||||
[play-button playing-audio? set-playing-audio player-ref playing-timer set-audio-current-time-ms
|
||||
seeking-audio? set-seeking-audio max-duration-ms]
|
||||
[soundtrack/soundtrack
|
||||
{:audio-current-time-ms audio-current-time-ms
|
||||
:set-audio-current-time-ms set-audio-current-time-ms
|
||||
:player-ref @player-ref
|
||||
:seeking-audio? seeking-audio?
|
||||
:set-seeking-audio set-seeking-audio
|
||||
:max-audio-duration-ms max-duration-ms}]])
|
||||
(when (or recording? reviewing-audio?)
|
||||
[time-counter recording? recording-length-ms ready-to-delete? reviewing-audio?
|
||||
audio-current-time-ms])
|
||||
(when recording?
|
||||
[recording-bar recording-length-ms ready-to-delete?])
|
||||
[rn/view
|
||||
{:test-ID "record-audio"
|
||||
:style style/button-container
|
||||
:hit-slop {:top -70 :bottom 0 :left 0 :right 0}
|
||||
:pointer-events :box-only
|
||||
:on-start-should-set-responder on-start-should-set-responder
|
||||
:on-responder-move on-responder-move
|
||||
:on-responder-release on-responder-release}
|
||||
[delete-button/delete-button recording? ready-to-delete? reviewing-audio? force-show-controls?]
|
||||
[lock-button/lock-button recording? ready-to-lock? locked?]
|
||||
[send-button/send-button recording? ready-to-send? reviewing-audio? force-show-controls?]
|
||||
[record-button-big/record-button-big
|
||||
{:recording? recording?
|
||||
:set-recording set-recording
|
||||
:ready-to-send? ready-to-send?
|
||||
:set-ready-to-send set-ready-to-send
|
||||
:ready-to-lock? ready-to-lock?
|
||||
:set-ready-to-lock set-ready-to-lock
|
||||
:ready-to-delete? ready-to-delete?
|
||||
:set-ready-to-delete set-ready-to-delete
|
||||
:record-button-is-animating? record-button-is-animating?
|
||||
:record-button-at-initial-position? record-button-at-initial-position?
|
||||
:locked? locked?
|
||||
:set-locked set-locked
|
||||
:reviewing-audio? reviewing-audio?
|
||||
:recording-length-ms recording-length-ms
|
||||
:set-recording-length-ms set-recording-length-ms
|
||||
:clear-timeout clear-timeout
|
||||
:touch-active? touch-active?
|
||||
:recorder-ref recorder-ref
|
||||
:reload-recorder-fn reload-recorder
|
||||
:idle? idle?
|
||||
:on-send on-send
|
||||
:on-cancel on-cancel}]
|
||||
[record-button/record-button recording? reviewing-audio?]]]))
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
(:require
|
||||
[quo.components.record-audio.soundtrack.view :as soundtrack]
|
||||
[react-native.audio-toolkit :as audio]
|
||||
[reagent.core :as reagent]
|
||||
[test-helpers.component :as h]))
|
||||
|
||||
(h/describe "soundtrack component"
|
||||
@@ -10,23 +9,21 @@
|
||||
|
||||
(h/test "renders soundtrack"
|
||||
(with-redefs [audio/get-player-duration (fn [] 2000)]
|
||||
(let [player-ref (reagent/atom {})
|
||||
audio-current-time-ms (reagent/atom 0)]
|
||||
(h/render [:f> soundtrack/f-soundtrack
|
||||
{:player-ref @player-ref
|
||||
:audio-current-time-ms audio-current-time-ms}])
|
||||
(-> (h/expect (h/get-by-test-id "soundtrack"))
|
||||
(.toBeTruthy)))))
|
||||
(h/render [soundtrack/soundtrack
|
||||
{:player-ref {}
|
||||
:audio-current-time-ms 0}])
|
||||
(-> (h/expect (h/get-by-test-id "soundtrack"))
|
||||
(.toBeTruthy))))
|
||||
|
||||
(h/test "soundtrack on-sliding-start works"
|
||||
(with-redefs [audio/get-player-duration (fn [] 2000)]
|
||||
(let [seeking-audio? (reagent/atom false)
|
||||
player-ref (reagent/atom {})
|
||||
audio-current-time-ms (reagent/atom 0)]
|
||||
(h/render [:f> soundtrack/f-soundtrack
|
||||
{:seeking-audio? seeking-audio?
|
||||
:player-ref @player-ref
|
||||
:audio-current-time-ms audio-current-time-ms}])
|
||||
(let [seeking-audio? (atom false)]
|
||||
(h/render [soundtrack/soundtrack
|
||||
{:seeking-audio? seeking-audio?
|
||||
:set-seeking-audio #(reset! seeking-audio? %)
|
||||
:player-ref {}
|
||||
:audio-current-time-ms 0
|
||||
:set-audio-current-time-ms #()}])
|
||||
(h/fire-event
|
||||
:on-sliding-start
|
||||
(h/get-by-test-id "soundtrack"))
|
||||
@@ -36,13 +33,13 @@
|
||||
(h/test "soundtrack on-sliding-complete works"
|
||||
(with-redefs [audio/get-player-duration (fn [] 2000)
|
||||
audio/seek-player (js/jest.fn)]
|
||||
(let [seeking-audio? (reagent/atom false)
|
||||
player-ref (reagent/atom {})
|
||||
audio-current-time-ms (reagent/atom 0)]
|
||||
(h/render [:f> soundtrack/f-soundtrack
|
||||
{:seeking-audio? seeking-audio?
|
||||
:player-ref @player-ref
|
||||
:audio-current-time-ms audio-current-time-ms}])
|
||||
(let [seeking-audio? (atom false)]
|
||||
(h/render [soundtrack/soundtrack
|
||||
{:seeking-audio? seeking-audio?
|
||||
:set-seeking-audio #(reset! seeking-audio? %)
|
||||
:player-ref {}
|
||||
:audio-current-time-ms 0
|
||||
:set-audio-current-time-ms #()}])
|
||||
(h/fire-event
|
||||
:on-sliding-start
|
||||
(h/get-by-test-id "soundtrack"))
|
||||
@@ -58,13 +55,14 @@
|
||||
(h/test "soundtrack on-value-change when seeking audio works"
|
||||
(with-redefs [audio/get-player-duration (fn [] 2000)
|
||||
audio/seek-player (js/jest.fn)]
|
||||
(let [seeking-audio? (reagent/atom false)
|
||||
player-ref (reagent/atom {})
|
||||
audio-current-time-ms (reagent/atom 0)]
|
||||
(h/render [:f> soundtrack/f-soundtrack
|
||||
{:seeking-audio? seeking-audio?
|
||||
:player-ref @player-ref
|
||||
:audio-current-time-ms audio-current-time-ms}])
|
||||
(let [seeking-audio? (atom false)
|
||||
audio-current-time-ms (atom 0)]
|
||||
(h/render [soundtrack/soundtrack
|
||||
{:seeking-audio? seeking-audio?
|
||||
:set-seeking-audio #(reset! seeking-audio? %)
|
||||
:player-ref {}
|
||||
:audio-current-time-ms audio-current-time-ms
|
||||
:set-audio-current-time-ms #(reset! audio-current-time-ms %)}])
|
||||
(h/fire-event
|
||||
:on-sliding-start
|
||||
(h/get-by-test-id "soundtrack"))
|
||||
|
||||
@@ -1,42 +1,47 @@
|
||||
(ns quo.components.record-audio.soundtrack.view
|
||||
(:require
|
||||
[quo.components.icons.icons :as icons]
|
||||
[quo.components.record-audio.soundtrack.style :as style]
|
||||
[quo.foundations.colors :as colors]
|
||||
[quo.theme :as quo.theme]
|
||||
[react-native.audio-toolkit :as audio]
|
||||
[react-native.core :as rn]
|
||||
[react-native.platform :as platform]
|
||||
[react-native.slider :as slider]
|
||||
[taoensso.timbre :as log]))
|
||||
|
||||
(def ^:private thumb-light (js/require "../resources/images/icons2/12x12/thumb-light.png"))
|
||||
(def ^:private thumb-dark (js/require "../resources/images/icons2/12x12/thumb-dark.png"))
|
||||
(def ^:private thumb-light (icons/icon-source :thumb-light12))
|
||||
(def ^:private thumb-dark (icons/icon-source :thumb-dark12))
|
||||
|
||||
(defn f-soundtrack
|
||||
[{:keys [audio-current-time-ms player-ref style seeking-audio? max-audio-duration-ms]}]
|
||||
(let [audio-duration-ms (min max-audio-duration-ms (audio/get-player-duration player-ref))
|
||||
theme (quo.theme/use-theme-value)]
|
||||
[:<>
|
||||
[slider/slider
|
||||
{:test-ID "soundtrack"
|
||||
:style (merge
|
||||
(style/player-slider-container)
|
||||
(or style {}))
|
||||
:minimum-value 0
|
||||
:maximum-value audio-duration-ms
|
||||
:value @audio-current-time-ms
|
||||
:on-sliding-start #(reset! seeking-audio? true)
|
||||
:on-sliding-complete (fn [seek-time]
|
||||
(reset! seeking-audio? false)
|
||||
(audio/seek-player
|
||||
player-ref
|
||||
seek-time
|
||||
#(log/debug "[record-audio] on seek - seek time: " seek-time)
|
||||
#(log/error "[record-audio] on seek - error: " %)))
|
||||
:on-value-change #(when @seeking-audio?
|
||||
(reset! audio-current-time-ms %))
|
||||
:thumb-image (quo.theme/theme-value thumb-light thumb-dark theme)
|
||||
:minimum-track-tint-color (colors/theme-colors colors/primary-50 colors/primary-60 theme)
|
||||
:maximum-track-tint-color (colors/theme-colors
|
||||
(if platform/ios? colors/neutral-20 colors/neutral-40)
|
||||
(if platform/ios? colors/neutral-80 colors/neutral-60)
|
||||
theme)}]]))
|
||||
(defn soundtrack
|
||||
[{:keys [audio-current-time-ms set-audio-current-time-ms player-ref style
|
||||
seeking-audio? set-seeking-audio max-audio-duration-ms]}]
|
||||
(let [audio-duration-ms (min max-audio-duration-ms (audio/get-player-duration player-ref))
|
||||
theme (quo.theme/use-theme-value)
|
||||
on-sliding-start (rn/use-callback #(set-seeking-audio true))
|
||||
on-sliding-complete (rn/use-callback
|
||||
(fn [seek-time]
|
||||
(set-seeking-audio false)
|
||||
(audio/seek-player
|
||||
player-ref
|
||||
seek-time
|
||||
#(log/debug "[record-audio] on seek - seek time: " seek-time)
|
||||
#(log/error "[record-audio] on seek - error: " %)))
|
||||
[player-ref])
|
||||
on-value-change (rn/use-callback #(when seeking-audio? (set-audio-current-time-ms %))
|
||||
[seeking-audio?])]
|
||||
[slider/slider
|
||||
{:test-ID "soundtrack"
|
||||
:style (merge (style/player-slider-container) style)
|
||||
:minimum-value 0
|
||||
:maximum-value audio-duration-ms
|
||||
:value audio-current-time-ms
|
||||
:on-sliding-start on-sliding-start
|
||||
:on-sliding-complete on-sliding-complete
|
||||
:on-value-change on-value-change
|
||||
:thumb-image (quo.theme/theme-value thumb-light thumb-dark theme)
|
||||
:minimum-track-tint-color (colors/theme-colors colors/primary-50 colors/primary-60 theme)
|
||||
:maximum-track-tint-color (colors/theme-colors
|
||||
(if platform/ios? colors/neutral-20 colors/neutral-40)
|
||||
(if platform/ios? colors/neutral-80 colors/neutral-60)
|
||||
theme)}]))
|
||||
|
||||
@@ -7,17 +7,23 @@
|
||||
[quo.foundations.colors :as colors]
|
||||
[quo.theme :as quo.theme]
|
||||
[react-native.core :as rn]
|
||||
[react-native.draggable-flatlist :as draggable-flatlist]
|
||||
[reagent.core :as reagent]))
|
||||
[react-native.draggable-flatlist :as draggable-flatlist]))
|
||||
|
||||
(defn on-drag-end-fn
|
||||
[data atom-data]
|
||||
(reset! atom-data data)
|
||||
(reagent/flush))
|
||||
(defn key-fn [item index] (str (:title item) index))
|
||||
|
||||
(defn- reorder-category-internal
|
||||
[{:keys [label data blur? theme container-style]}]
|
||||
(reagent/with-let [atom-data (reagent/atom data)]
|
||||
(defn reorder-category
|
||||
[{:keys [label data blur? container-style]}]
|
||||
(let [theme (quo.theme/use-theme-value)
|
||||
[atom-data set-atom-data] (rn/use-state data)
|
||||
render-fn (rn/use-callback
|
||||
(fn [item _ _ _ _ drag]
|
||||
[reorder-item/reorder-item item types/item
|
||||
{:blur? blur? :drag drag}])
|
||||
[blur?])
|
||||
on-drag-end-fn (rn/use-callback (fn [_ _ data] (set-atom-data data)))
|
||||
separator (rn/use-memo (fn [] [rn/view
|
||||
{:style (style/reorder-separator blur? theme)}])
|
||||
[blur? theme])]
|
||||
[rn/view {:style (merge (style/container label) container-style)}
|
||||
[text/text
|
||||
{:weight :medium
|
||||
@@ -25,14 +31,9 @@
|
||||
:style {:color (colors/theme-colors colors/neutral-50 colors/neutral-40 theme)}}
|
||||
label]
|
||||
[draggable-flatlist/draggable-flatlist
|
||||
{:data @atom-data
|
||||
:key-fn (fn [item index] (str (:title item) index))
|
||||
{:data atom-data
|
||||
:key-fn key-fn
|
||||
:style style/reorder-items
|
||||
:render-fn (fn [item _ _ _ _ drag] [reorder-item/reorder-item item types/item
|
||||
{:blur? blur? :drag drag}])
|
||||
:on-drag-end-fn (fn [_ _ data]
|
||||
(on-drag-end-fn data atom-data))
|
||||
:separator [rn/view
|
||||
{:style (style/reorder-separator blur? theme)}]}]]))
|
||||
|
||||
(def reorder-category (quo.theme/with-theme reorder-category-internal))
|
||||
:render-fn render-fn
|
||||
:on-drag-end-fn on-drag-end-fn
|
||||
:separator separator}]]))
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
[quo.foundations.colors :as colors]
|
||||
[quo.theme]
|
||||
[react-native.core :as rn]
|
||||
[reagent.core :as reagent]
|
||||
[schema.core :as schema]
|
||||
[utils.i18n :as i18n]))
|
||||
|
||||
@@ -190,20 +189,20 @@
|
||||
|
||||
(defn- view-internal
|
||||
[props]
|
||||
(reagent/with-let [component-width (reagent/atom nil)
|
||||
container-component [rn/view {:background-color style/overlay-color}]]
|
||||
(let [[component-width
|
||||
set-component-width] (rn/use-state nil)
|
||||
on-layout (rn/use-callback #(set-component-width
|
||||
(oops/oget % "nativeEvent.layout.width")))
|
||||
props (-> props
|
||||
(assoc :component-width component-width)
|
||||
(clojure.set/rename-keys {:type :share-qr-type}))]
|
||||
[quo.theme/provider {:theme :dark}
|
||||
[rn/view
|
||||
{:accessibility-label :share-qr-code
|
||||
:style style/outer-container
|
||||
:on-layout #(reset! component-width (oops/oget % "nativeEvent.layout.width"))}
|
||||
(conj container-component
|
||||
(when @component-width
|
||||
[share-qr-code
|
||||
(-> props
|
||||
(assoc :component-width @component-width)
|
||||
(clojure.set/rename-keys {:type :share-qr-type}))]))]]))
|
||||
:on-layout on-layout}
|
||||
[rn/view {:style {:background-color style/overlay-color}}
|
||||
(when component-width
|
||||
[share-qr-code props])]]]))
|
||||
|
||||
(def view
|
||||
(quo.theme/with-theme
|
||||
(schema/instrument #'view-internal component-schema/?schema)))
|
||||
(def view (schema/instrument #'view-internal component-schema/?schema))
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
[quo.components.tabs.tab.view :as tab]
|
||||
[quo.foundations.colors :as colors]
|
||||
[quo.theme :as quo.theme]
|
||||
[react-native.core :as rn]
|
||||
[reagent.core :as reagent]))
|
||||
[react-native.core :as rn]))
|
||||
|
||||
(def themes-for-blur
|
||||
{:light {:background-color colors/neutral-80-opa-5}
|
||||
@@ -14,41 +13,41 @@
|
||||
{:light {:background-color colors/neutral-10}
|
||||
:dark {:background-color colors/neutral-90}})
|
||||
|
||||
(defn- segmented-control-internal
|
||||
[{:keys [default-active on-change]}]
|
||||
(let [active-tab-id (reagent/atom default-active)]
|
||||
(fn [{:keys [data size theme blur? container-style item-container-style
|
||||
active-item-container-style]}]
|
||||
(let [active-id @active-tab-id]
|
||||
[rn/view
|
||||
(merge
|
||||
{:flex-direction :row
|
||||
:background-color (get-in (if blur? themes-for-blur themes)
|
||||
[theme :background-color])
|
||||
:border-radius (case size
|
||||
32 10
|
||||
28 10
|
||||
24 8
|
||||
20 6)
|
||||
:padding 2}
|
||||
container-style)
|
||||
(for [[indx {:keys [label id]}] (map-indexed vector data)]
|
||||
^{:key id}
|
||||
[rn/view
|
||||
{:margin-left (if (= 0 indx) 0 2)
|
||||
:flex 1}
|
||||
[tab/view
|
||||
{:id id
|
||||
:active-item-container-style active-item-container-style
|
||||
:item-container-style item-container-style
|
||||
:segmented? true
|
||||
:size size
|
||||
:blur? blur?
|
||||
:active (= id active-id)
|
||||
:on-press (fn [tab-id]
|
||||
(reset! active-tab-id tab-id)
|
||||
(when on-change
|
||||
(on-change tab-id)))}
|
||||
label]])]))))
|
||||
|
||||
(def segmented-control (quo.theme/with-theme segmented-control-internal))
|
||||
(defn segmented-control
|
||||
[{:keys [data size blur? container-style item-container-style
|
||||
active-item-container-style default-active on-change]}]
|
||||
(let [theme (quo.theme/use-theme-value)
|
||||
[active-tab-id
|
||||
set-active-tab-id] (rn/use-state default-active)
|
||||
on-press (rn/use-callback
|
||||
(fn [tab-id]
|
||||
(set-active-tab-id tab-id)
|
||||
(when on-change (on-change tab-id)))
|
||||
[on-change])]
|
||||
[rn/view
|
||||
(merge
|
||||
{:flex-direction :row
|
||||
:background-color (get-in (if blur? themes-for-blur themes)
|
||||
[theme :background-color])
|
||||
:border-radius (case size
|
||||
32 10
|
||||
28 10
|
||||
24 8
|
||||
20 6)
|
||||
:padding 2}
|
||||
container-style)
|
||||
(for [[indx {:keys [label id]}] (map-indexed vector data)]
|
||||
^{:key id}
|
||||
[rn/view
|
||||
{:margin-left (if (= 0 indx) 0 2)
|
||||
:flex 1}
|
||||
[tab/view
|
||||
{:id id
|
||||
:active-item-container-style active-item-container-style
|
||||
:item-container-style item-container-style
|
||||
:segmented? true
|
||||
:size size
|
||||
:blur? blur?
|
||||
:active (= id active-tab-id)
|
||||
:on-press on-press}
|
||||
label]])]))
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
(into [:<>] children)))
|
||||
|
||||
(defn- on-scroll-handler
|
||||
[{:keys [on-scroll fading fade-end-percentage fade-end?]} ^js e]
|
||||
[{:keys [on-scroll fading set-fading fade-end-percentage fade-end?]} ^js e]
|
||||
(when fade-end?
|
||||
(let [offset-x (oget e "nativeEvent.contentOffset.x")
|
||||
content-width (oget e "nativeEvent.contentSize.width")
|
||||
@@ -52,11 +52,8 @@
|
||||
:layout-width layout-width
|
||||
:max-fade-percentage fade-end-percentage})]
|
||||
;; Avoid unnecessary re-rendering.
|
||||
(when (not= new-percentage
|
||||
(get @fading :fade-end-percentage))
|
||||
(swap! fading assoc
|
||||
:fade-end-percentage
|
||||
new-percentage))))
|
||||
(when (not= new-percentage (get fading :fade-end-percentage))
|
||||
(set-fading (assoc fading :fade-end-percentage new-percentage)))))
|
||||
(when on-scroll
|
||||
(on-scroll e)))
|
||||
|
||||
@@ -64,6 +61,7 @@
|
||||
[{:keys [id label notification-dot? accessibility-label]}
|
||||
index _
|
||||
{:keys [active-tab-id
|
||||
set-active-tab-id
|
||||
blur?
|
||||
customization-color
|
||||
flat-list-ref
|
||||
@@ -85,9 +83,9 @@
|
||||
:accessibility-label accessibility-label
|
||||
:size size
|
||||
:blur? blur?
|
||||
:active (= id @active-tab-id)
|
||||
:active (= id active-tab-id)
|
||||
:on-press (fn [id]
|
||||
(reset! active-tab-id id)
|
||||
(set-active-tab-id id)
|
||||
(when (and scroll-on-press? @flat-list-ref)
|
||||
(.scrollToIndex ^js @flat-list-ref
|
||||
#js
|
||||
@@ -118,87 +116,81 @@
|
||||
- `scroll-on-press?` When non-nil, clicking on a tag centers it the middle
|
||||
(with animation enabled).
|
||||
"
|
||||
[{:keys [default-active fade-end-percentage]
|
||||
:or {fade-end-percentage 0.8}}]
|
||||
(let [active-tab-id (reagent/atom default-active)
|
||||
fading (reagent/atom {:fade-end-percentage fade-end-percentage})
|
||||
flat-list-ref (atom nil)]
|
||||
(fn
|
||||
[{:keys [data
|
||||
fade-end-percentage
|
||||
fade-end?
|
||||
on-change
|
||||
on-scroll
|
||||
scroll-on-press?
|
||||
scrollable?
|
||||
style
|
||||
container-style
|
||||
size
|
||||
blur?
|
||||
in-scroll-view?
|
||||
customization-color]
|
||||
:or {fade-end-percentage fade-end-percentage
|
||||
fade-end? false
|
||||
scrollable? false
|
||||
scroll-on-press? false
|
||||
size default-tab-size}
|
||||
:as props}]
|
||||
(if scrollable?
|
||||
[rn/view {:style {:margin-top (- (dec unread-count-offset))}}
|
||||
[masked-view-wrapper
|
||||
{:fade-end-percentage (get @fading :fade-end-percentage) :fade-end? fade-end?}
|
||||
[(if in-scroll-view?
|
||||
gesture/flat-list
|
||||
rn/flat-list)
|
||||
(merge
|
||||
(dissoc props
|
||||
:default-active
|
||||
:fade-end-percentage
|
||||
:fade-end?
|
||||
:on-change
|
||||
:scroll-on-press?
|
||||
:size)
|
||||
(when scroll-on-press?
|
||||
{:initial-scroll-index (utils.collection/first-index #(= @active-tab-id (:id %)) data)})
|
||||
{:ref #(reset! flat-list-ref %)
|
||||
:style style
|
||||
;; The padding-top workaround is needed because on Android
|
||||
;; {:overflow :visible} doesn't work on components inheriting
|
||||
;; from ScrollView (e.g. FlatList). There are open issues, here's
|
||||
;; just one about this topic:
|
||||
;; https://github.com/facebook/react-native/issues/31218
|
||||
:content-container-style
|
||||
(assoc container-style :padding-top (dec unread-count-offset))
|
||||
:horizontal true
|
||||
:scroll-event-throttle 64
|
||||
:shows-horizontal-scroll-indicator false
|
||||
:data data
|
||||
:key-fn (comp str :id)
|
||||
:on-scroll-to-index-failed identity
|
||||
:on-scroll (partial on-scroll-handler
|
||||
{:fade-end-percentage fade-end-percentage
|
||||
:fade-end? fade-end?
|
||||
:fading fading
|
||||
:on-scroll on-scroll})
|
||||
:render-fn tab-view
|
||||
:render-data {:active-tab-id active-tab-id
|
||||
:blur? blur?
|
||||
:customization-color customization-color
|
||||
:flat-list-ref flat-list-ref
|
||||
:number-of-items (count data)
|
||||
:on-change on-change
|
||||
:scroll-on-press? scroll-on-press?
|
||||
:size size
|
||||
:style style}})]]]
|
||||
[rn/view (merge style {:flex-direction :row})
|
||||
(map-indexed (fn [index item]
|
||||
^{:key (:id item)}
|
||||
[tab-view item index nil
|
||||
{:active-tab-id active-tab-id
|
||||
:blur? blur?
|
||||
:customization-color customization-color
|
||||
:number-of-items (count data)
|
||||
:on-change on-change
|
||||
:size size
|
||||
:style style}])
|
||||
data)]))))
|
||||
[{:keys [default-active data fade-end-percentage fade-end? on-change on-scroll scroll-on-press?
|
||||
scrollable? style container-style size blur? in-scroll-view? customization-color]
|
||||
:or {fade-end-percentage 0.8
|
||||
fade-end? false
|
||||
scrollable? false
|
||||
scroll-on-press? false
|
||||
size default-tab-size}
|
||||
:as props}]
|
||||
(let [[active-tab-id
|
||||
set-active-tab-id] (rn/use-state default-active)
|
||||
[fading set-fading] (rn/use-state {:fade-end-percentage fade-end-percentage})
|
||||
flat-list-ref (rn/use-ref-atom nil)
|
||||
clean-props (dissoc props
|
||||
:default-active
|
||||
:fade-end-percentage
|
||||
:fade-end?
|
||||
:on-change
|
||||
:scroll-on-press?
|
||||
:size)
|
||||
on-scroll (rn/use-callback
|
||||
(partial on-scroll-handler
|
||||
{:fade-end-percentage fade-end-percentage
|
||||
:fade-end? fade-end?
|
||||
:fading fading
|
||||
:set-fading set-fading
|
||||
:on-scroll on-scroll})
|
||||
[fade-end? fading])]
|
||||
(if scrollable?
|
||||
[rn/view {:style {:margin-top (- (dec unread-count-offset))}}
|
||||
[masked-view-wrapper
|
||||
{:fade-end-percentage (get fading :fade-end-percentage) :fade-end? fade-end?}
|
||||
[(if in-scroll-view?
|
||||
gesture/flat-list
|
||||
rn/flat-list)
|
||||
(merge
|
||||
clean-props
|
||||
(when scroll-on-press?
|
||||
{:initial-scroll-index (utils.collection/first-index #(= active-tab-id (:id %)) data)})
|
||||
{:ref #(reset! flat-list-ref %)
|
||||
:style style
|
||||
;; The padding-top workaround is needed because on Android
|
||||
;; {:overflow :visible} doesn't work on components inheriting
|
||||
;; from ScrollView (e.g. FlatList). There are open issues, here's
|
||||
;; just one about this topic:
|
||||
;; https://github.com/facebook/react-native/issues/31218
|
||||
:content-container-style
|
||||
(assoc container-style :padding-top (dec unread-count-offset))
|
||||
:horizontal true
|
||||
:scroll-event-throttle 64
|
||||
:shows-horizontal-scroll-indicator false
|
||||
:data data
|
||||
:key-fn (comp str :id)
|
||||
:on-scroll-to-index-failed identity
|
||||
:on-scroll on-scroll
|
||||
:render-fn tab-view
|
||||
:render-data {:active-tab-id active-tab-id
|
||||
:set-active-tab-id set-active-tab-id
|
||||
:blur? blur?
|
||||
:customization-color customization-color
|
||||
:flat-list-ref flat-list-ref
|
||||
:number-of-items (count data)
|
||||
:on-change on-change
|
||||
:scroll-on-press? scroll-on-press?
|
||||
:size size
|
||||
:style style}})]]]
|
||||
[rn/view (merge style {:flex-direction :row})
|
||||
(map-indexed (fn [index item]
|
||||
^{:key (:id item)}
|
||||
[tab-view item index nil
|
||||
{:active-tab-id active-tab-id
|
||||
:set-active-tab-id set-active-tab-id
|
||||
:blur? blur?
|
||||
:customization-color customization-color
|
||||
:number-of-items (count data)
|
||||
:on-change on-change
|
||||
:size size
|
||||
:style style}])
|
||||
data)])))
|
||||
|
||||
+1
-1
@@ -357,7 +357,7 @@
|
||||
|
||||
;;;; Record Audio
|
||||
(def record-audio quo.components.record-audio.record-audio.view/record-audio)
|
||||
(def soundtrack quo.components.record-audio.soundtrack.view/f-soundtrack)
|
||||
(def soundtrack quo.components.record-audio.soundtrack.view/soundtrack)
|
||||
|
||||
;;;; Selectors
|
||||
(def disclaimer quo.components.selectors.disclaimer.view/view)
|
||||
|
||||
@@ -38,7 +38,8 @@
|
||||
(when on-meter
|
||||
(.on ^js recorder "meter" on-meter))
|
||||
(when on-ended
|
||||
(.on ^js recorder "ended" on-ended))))
|
||||
(.on ^js recorder "ended" on-ended))
|
||||
recorder))
|
||||
|
||||
(defn new-player
|
||||
[audio options on-ended]
|
||||
@@ -46,7 +47,8 @@
|
||||
audio
|
||||
(clj->js options))]
|
||||
(when on-ended
|
||||
(.on ^js player "ended" on-ended))))
|
||||
(.on ^js player "ended" on-ended))
|
||||
player))
|
||||
|
||||
(defn prepare-player
|
||||
[player on-prepared on-error]
|
||||
@@ -157,20 +159,16 @@
|
||||
(on-error {:error (.-err err) :message (.-message err)})
|
||||
(on-seek))))))
|
||||
|
||||
(defn can-play?
|
||||
[player]
|
||||
(and player (.-canPlay ^js player)))
|
||||
|
||||
(defn destroy-recorder
|
||||
[recorder]
|
||||
(stop-recording recorder
|
||||
#(when (and recorder (not= (get-state recorder) DESTROYED))
|
||||
#(when (and recorder (.-destroy ^js recorder) (not= (get-state recorder) DESTROYED))
|
||||
(.destroy ^js recorder))
|
||||
#()))
|
||||
|
||||
(defn destroy-player
|
||||
[player]
|
||||
(stop-playing player
|
||||
#(when (and player (not= (get-state player) IDLE))
|
||||
#(when (and player (.-destroy ^js player) (not= (get-state player) IDLE))
|
||||
(.destroy ^js player))
|
||||
#()))
|
||||
|
||||
@@ -20,7 +20,8 @@
|
||||
LinearTransition
|
||||
enableLayoutAnimations
|
||||
useAnimatedScrollHandler
|
||||
runOnJS)]
|
||||
runOnJS)
|
||||
:as reanimated*]
|
||||
["react-native-redash" :refer (withPause)]
|
||||
[react-native.flat-list :as rn-flat-list]
|
||||
[react-native.utils :as rn.utils]
|
||||
@@ -107,6 +108,8 @@
|
||||
(when (and anim (some? v))
|
||||
(set! (.-value anim) v)))
|
||||
|
||||
(def interpolate* reanimated*/interpolate)
|
||||
|
||||
(defn interpolate
|
||||
([shared-value input-range output-range]
|
||||
(interpolate shared-value input-range output-range nil))
|
||||
|
||||
@@ -1,31 +1,36 @@
|
||||
(ns status-im.common.scalable-avatar.view
|
||||
(ns ^:workletize status-im.common.scalable-avatar.view
|
||||
(:require [quo.core :as quo]
|
||||
[react-native.reanimated :as reanimated]
|
||||
[status-im.common.scalable-avatar.style :as style]))
|
||||
(def scroll-animation-input-range [0 50])
|
||||
(def header-extrapolation-option
|
||||
{:extrapolateLeft "clamp"
|
||||
:extrapolateRight "clamp"})
|
||||
;;[status-im.common.scalable-avatar.style :as style]
|
||||
[status-im.contexts.profile.settings.header.style :as style]))
|
||||
|
||||
(defn f-avatar
|
||||
[{:keys [scroll-y full-name online? profile-picture customization-color border-color]}]
|
||||
(let [image-scale-animation (reanimated/interpolate scroll-y
|
||||
scroll-animation-input-range
|
||||
[1 0.4]
|
||||
header-extrapolation-option)
|
||||
image-top-margin-animation (reanimated/interpolate scroll-y
|
||||
scroll-animation-input-range
|
||||
[0 20]
|
||||
header-extrapolation-option)
|
||||
image-side-margin-animation (reanimated/interpolate scroll-y
|
||||
scroll-animation-input-range
|
||||
[-4 -20]
|
||||
header-extrapolation-option)]
|
||||
[reanimated/view
|
||||
{:style (style/wrapper {:scale image-scale-animation
|
||||
:margin-top image-top-margin-animation
|
||||
:margin image-side-margin-animation
|
||||
:border-color border-color})}
|
||||
[{:keys [scroll-y full-name online? profile-picture customization-color]}]
|
||||
(let [avatar-total-size 88
|
||||
border-height 4
|
||||
translation-to-bottom (fn [scale inverse-scale]
|
||||
(js* "'worklet'")
|
||||
(let [current-avatar-size (* avatar-total-size (- 1 scale))
|
||||
current-center (/ current-avatar-size 2)]
|
||||
(* current-center inverse-scale)))
|
||||
transform (reanimated/use-animated-style
|
||||
(fn []
|
||||
(js* "'worklet'")
|
||||
(let [scale (reanimated/interpolate*
|
||||
(.-value scroll-y)
|
||||
#js [0 48]
|
||||
#js [1 0.4]
|
||||
"clamp")
|
||||
inverse-scale (/ 1 scale)
|
||||
translation (translation-to-bottom scale inverse-scale)
|
||||
bottom (* border-height inverse-scale (- 1 scale))]
|
||||
#js
|
||||
{:transform #js
|
||||
[#js {:scale scale}
|
||||
#js {:translateX (- translation)}
|
||||
#js {:translateY (- translation bottom)}]})))
|
||||
theme (quo.theme/get-theme)]
|
||||
[reanimated/view {:style [transform (style/avatar-container theme)]}
|
||||
[quo/user-avatar
|
||||
{:full-name full-name
|
||||
:online? online?
|
||||
@@ -35,6 +40,34 @@
|
||||
:customization-color customization-color
|
||||
:size :big}]]))
|
||||
|
||||
#_(defn f-avatar
|
||||
[{:keys [scroll-y full-name online? profile-picture customization-color border-color]}]
|
||||
(let [image-scale-animation (reanimated/interpolate scroll-y
|
||||
scroll-animation-input-range
|
||||
[1 0.4]
|
||||
header-extrapolation-option)
|
||||
image-top-margin-animation (reanimated/interpolate scroll-y
|
||||
scroll-animation-input-range
|
||||
[0 20]
|
||||
header-extrapolation-option)
|
||||
image-side-margin-animation (reanimated/interpolate scroll-y
|
||||
scroll-animation-input-range
|
||||
[-4 -20]
|
||||
header-extrapolation-option)]
|
||||
[reanimated/view
|
||||
{:style (style/wrapper {:scale image-scale-animation
|
||||
:margin-top image-top-margin-animation
|
||||
:margin image-side-margin-animation
|
||||
:border-color border-color})}
|
||||
[quo/user-avatar
|
||||
{:full-name full-name
|
||||
:online? online?
|
||||
:profile-picture profile-picture
|
||||
:status-indicator? true
|
||||
:ring? true
|
||||
:customization-color customization-color
|
||||
:size :big}]]))
|
||||
|
||||
(defn view
|
||||
[props]
|
||||
[:f> f-avatar props])
|
||||
|
||||
@@ -210,12 +210,14 @@
|
||||
:else :i/play-audio)
|
||||
{:size 20
|
||||
:color colors/white}]]
|
||||
[:f> quo/soundtrack
|
||||
{:style style/slider-container
|
||||
:audio-current-time-ms progress
|
||||
:player-ref (@active-players player-key)
|
||||
:seeking-audio? seeking-audio?
|
||||
:max-audio-duration-ms constants/audio-max-duration-ms}]
|
||||
[quo/soundtrack
|
||||
{:style style/slider-container
|
||||
:audio-current-time-ms @progress
|
||||
:set-audio-current-time-ms #(reset! progress %)
|
||||
:player-ref (@active-players player-key)
|
||||
:seeking-audio? @seeking-audio?
|
||||
:set-seeking-audio? #(reset! seeking-audio? %)
|
||||
:max-audio-duration-ms constants/audio-max-duration-ms}]
|
||||
[quo/text
|
||||
{:style style/timestamp
|
||||
:accessibility-label :audio-duration-label
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
:canManageUsers :can-manage-users?
|
||||
:categoryID :category-id
|
||||
:canPost :can-post?
|
||||
:tokenGated :token-gated?
|
||||
:isControlNode :is-control-node?
|
||||
:pinMessageAllMembersEnabled :pin-message-all-members-enabled
|
||||
:isMember :is-member?
|
||||
|
||||
@@ -21,28 +21,21 @@
|
||||
(defn handle-community
|
||||
[{:keys [db]} [community-js]]
|
||||
(when community-js
|
||||
(let [{:keys [token-permissions
|
||||
(let [{:keys [clock
|
||||
token-permissions-check joined id last-opened-at]
|
||||
:as community} (data-store.communities/<-rpc community-js)
|
||||
has-channel-perm? (fn [id-perm-tuple]
|
||||
(let [{:keys [type]} (second id-perm-tuple)]
|
||||
(or (= type constants/community-token-permission-can-view-channel)
|
||||
(=
|
||||
type
|
||||
constants/community-token-permission-can-view-and-post-channel))))
|
||||
:as community} (data-store.communities/<-rpc community-js)
|
||||
previous-last-opened-at (get-in db [:communities id :last-opened-at])]
|
||||
{:db (assoc-in db
|
||||
[:communities id]
|
||||
(assoc community :last-opened-at (max last-opened-at previous-last-opened-at)))
|
||||
:fx [[:dispatch [:communities/initialize-permission-addresses id]]
|
||||
(when (not joined)
|
||||
[:dispatch [:chat.ui/spectate-community id]])
|
||||
(when (nil? token-permissions-check)
|
||||
[:dispatch [:communities/check-permissions-to-join-community id]])
|
||||
(when (some has-channel-perm? token-permissions)
|
||||
[:dispatch [:communities/check-all-community-channels-permissions id]])
|
||||
(when joined
|
||||
[:dispatch [:communities/get-revealed-accounts id]])]})))
|
||||
(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)))
|
||||
:fx [[:dispatch [:communities/initialize-permission-addresses id]]
|
||||
(when (not joined)
|
||||
[:dispatch [:chat.ui/spectate-community id]])
|
||||
(when (nil? token-permissions-check)
|
||||
[:dispatch [:communities/check-permissions-to-join-community id]])
|
||||
(when joined
|
||||
[:dispatch [:communities/get-revealed-accounts id]])]}))))
|
||||
|
||||
(rf/reg-event-fx :communities/handle-community handle-community)
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
(:require [cljs.test :refer [deftest is testing]]
|
||||
[legacy.status-im.mailserver.core :as mailserver]
|
||||
matcher-combinators.test
|
||||
[status-im.constants :as constants]
|
||||
[status-im.contexts.chat.messenger.messages.link-preview.events :as link-preview.events]
|
||||
[status-im.contexts.communities.events :as events]))
|
||||
|
||||
@@ -262,7 +261,7 @@
|
||||
(-> effects :json-rpc/call first (select-keys [:method :params]))))))))
|
||||
|
||||
(deftest handle-community
|
||||
(let [community {:id community-id}]
|
||||
(let [community {:id community-id :clock 2}]
|
||||
(testing "given a unjoined community"
|
||||
(let [effects (events/handle-community {} [community])]
|
||||
(is (match? community-id
|
||||
@@ -287,29 +286,10 @@
|
||||
[[:dispatch [:communities/initialize-permission-addresses community-id]]
|
||||
[:dispatch [:chat.ui/spectate-community community-id]]]
|
||||
(filter some? (:fx effects))))))
|
||||
(testing "given a community with view channel permission"
|
||||
(let [community (assoc community
|
||||
:token-permissions
|
||||
[["perm-id" {:type constants/community-token-permission-can-view-channel}]])
|
||||
(testing "given a community with lower clock"
|
||||
(let [effects (events/handle-community {:db {:communities {community-id {:clock 3}}}} [community])]
|
||||
(is (nil? effects))))
|
||||
(testing "given a community without clock"
|
||||
(let [community (dissoc community :clock)
|
||||
effects (events/handle-community {} [community])]
|
||||
(is (match?
|
||||
[[:dispatch [:communities/initialize-permission-addresses community-id]]
|
||||
[:dispatch [:chat.ui/spectate-community community-id]]
|
||||
[:dispatch [:communities/check-permissions-to-join-community community-id]]
|
||||
[:dispatch
|
||||
[:communities/check-all-community-channels-permissions community-id]]]
|
||||
(filter some? (:fx effects))))))
|
||||
|
||||
(testing "given a community with post in channel permission"
|
||||
(let [community (assoc community
|
||||
:token-permissions
|
||||
[["perm-id"
|
||||
{:type constants/community-token-permission-can-view-and-post-channel}]])
|
||||
effects (events/handle-community {} [community])]
|
||||
(is (match?
|
||||
[[:dispatch [:communities/initialize-permission-addresses community-id]]
|
||||
[:dispatch [:chat.ui/spectate-community community-id]]
|
||||
[:dispatch [:communities/check-permissions-to-join-community community-id]]
|
||||
[:dispatch
|
||||
[:communities/check-all-community-channels-permissions community-id]]]
|
||||
(filter some? (:fx effects))))))))
|
||||
(is (nil? effects))))))
|
||||
|
||||
@@ -1,37 +1,10 @@
|
||||
(ns status-im.contexts.communities.overview.events
|
||||
(:require
|
||||
[legacy.status-im.data-store.communities :as data-store]
|
||||
[taoensso.timbre :as log]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(rf/reg-event-fx :communities/check-all-community-channels-permissions-success
|
||||
(fn [{:keys [db]} [community-id response]]
|
||||
{:db (-> db
|
||||
(assoc-in [:community-channels-permissions community-id]
|
||||
(data-store/rpc->channel-permissions (:channels response)))
|
||||
(assoc-in [:communities/channel-permissions-check community-id] false))}))
|
||||
|
||||
(rf/reg-event-fx :communities/check-all-community-channels-permissions-failed
|
||||
(fn [{:keys [db]} [community-id]]
|
||||
{:db (assoc-in db [:communities/channel-permissions-check community-id] false)}))
|
||||
|
||||
(rf/reg-event-fx :communities/check-all-community-channels-permissions
|
||||
(fn [{:keys [db]} [community-id]]
|
||||
(when (get-in db [:communities community-id])
|
||||
{:db (assoc-in db [:communities/channel-permissions-check community-id] true)
|
||||
:fx [[:json-rpc/call
|
||||
[{:method "wakuext_checkAllCommunityChannelsPermissions"
|
||||
:params [{:CommunityID community-id}]
|
||||
:on-success [:communities/check-all-community-channels-permissions-success community-id]
|
||||
:on-error (fn [error]
|
||||
(rf/dispatch [:communities/check-all-community-channels-permissions-failed
|
||||
community-id])
|
||||
(log/error "failed to check channels permissions"
|
||||
{:error error
|
||||
:community-id community-id
|
||||
:event
|
||||
:communities/check-all-community-channels-permissions}))}]]]})))
|
||||
|
||||
(rf/reg-event-fx :communities/check-permissions-to-join-community-success
|
||||
(fn [{:keys [db]} [community-id based-on-client-selection? result]]
|
||||
|
||||
@@ -48,15 +48,16 @@
|
||||
:emoji emoji
|
||||
:customization-color community-color
|
||||
:mentions-count mentions-count
|
||||
;; NOTE: this is a troolean, nil/true/false have different meaning
|
||||
:locked? locked?
|
||||
:notification notification}
|
||||
channel-sheet-data {:selected-item (fn [] [quo/channel channel-options])
|
||||
:content (fn [] sheet-content)}]
|
||||
[rn/view {:key id}
|
||||
[quo/channel
|
||||
(merge channel-options
|
||||
{:on-press on-press
|
||||
:on-long-press #(rf/dispatch [:show-bottom-sheet channel-sheet-data])})]]))
|
||||
(assoc channel-options
|
||||
:on-press on-press
|
||||
:on-long-press #(rf/dispatch [:show-bottom-sheet channel-sheet-data]))]]))
|
||||
|
||||
(defn- channel-list-component
|
||||
[{:keys [on-category-layout community-id community-color on-first-channel-height-changed]}
|
||||
@@ -253,8 +254,7 @@
|
||||
:description-accessibility-label :community-description}])
|
||||
|
||||
(defn- community-content
|
||||
[id]
|
||||
(rf/dispatch [:communities/check-all-community-channels-permissions id])
|
||||
[_]
|
||||
(fn [id
|
||||
{:keys [on-category-layout
|
||||
collapsed?
|
||||
|
||||
@@ -5,21 +5,28 @@
|
||||
[status-im.common.not-implemented :as not-implemented]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.contexts.profile.contact.add-nickname.view :as add-nickname]
|
||||
[status-im.contexts.profile.contact.block-contact.view :as block-contact]
|
||||
[status-im.contexts.profile.utils :as profile.utils]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(defn on-add-nickname
|
||||
[]
|
||||
(rf/dispatch [:show-bottom-sheet
|
||||
{:content
|
||||
(fn [] [add-nickname/view])}]))
|
||||
|
||||
(defn on-block-contact
|
||||
[]
|
||||
(rf/dispatch [:show-bottom-sheet
|
||||
{:content
|
||||
(fn [] [block-contact/view])}]))
|
||||
|
||||
(defn view
|
||||
[]
|
||||
(let [{:keys [nickname
|
||||
public-key
|
||||
contact-request-state]
|
||||
(let [{:keys [nickname public-key contact-request-state blocked?]
|
||||
:as contact} (rf/sub [:contacts/current-contact])
|
||||
|
||||
full-name (profile.utils/displayed-name contact)
|
||||
on-add-nickname (rn/use-callback #(rf/dispatch [:show-bottom-sheet
|
||||
{:content
|
||||
(fn [] [add-nickname/view])}]))
|
||||
on-remove-nickname (rn/use-callback
|
||||
(fn []
|
||||
(rf/dispatch [:hide-bottom-sheet])
|
||||
@@ -55,41 +62,43 @@
|
||||
(rf/dispatch [:contact.ui/remove-contact-pressed contact]))
|
||||
[public-key full-name])]
|
||||
[quo/action-drawer
|
||||
[[{:icon :i/edit
|
||||
:label (if has-nickname?
|
||||
(i18n/label :t/edit-nickname)
|
||||
(i18n/label :t/add-nickname-title))
|
||||
:on-press on-add-nickname
|
||||
:accessibility-label (if nickname :edit-nickname :add-nickname)}
|
||||
{:icon :i/qr-code
|
||||
:label (i18n/label :t/show-qr)
|
||||
:on-press on-show-qr
|
||||
:accessibility-label :show-qr-code}
|
||||
{:icon :i/share
|
||||
:label (i18n/label :t/share-profile)
|
||||
:on-press on-share-profile
|
||||
:accessibility-label :share-profile}
|
||||
(when has-nickname?
|
||||
{:icon :i/delete
|
||||
:label (i18n/label :t/remove-nickname)
|
||||
:on-press on-remove-nickname
|
||||
:add-divider? true
|
||||
:accessibility-label :remove-nickname
|
||||
:danger? true})
|
||||
{:icon :i/untrustworthy
|
||||
:label (i18n/label :t/mark-untrustworthy)
|
||||
:on-press not-implemented/alert
|
||||
:accessibility-label :mark-untrustworthy
|
||||
:add-divider? (when-not has-nickname? true)
|
||||
:danger? true}
|
||||
(when (= constants/contact-request-state-mutual contact-request-state)
|
||||
{:icon :i/remove-user
|
||||
:label (i18n/label :t/remove-contact)
|
||||
:on-press on-remove-contact
|
||||
:accessibility-label :remove-contact
|
||||
:danger? true})
|
||||
{:icon :i/block
|
||||
:label (i18n/label :t/block-user)
|
||||
:on-press not-implemented/alert
|
||||
:accessibility-label :block-user
|
||||
:danger? true}]]]))
|
||||
[(concat
|
||||
[{:icon :i/edit
|
||||
:label (if has-nickname?
|
||||
(i18n/label :t/edit-nickname)
|
||||
(i18n/label :t/add-nickname-title))
|
||||
:on-press on-add-nickname
|
||||
:accessibility-label (if nickname :edit-nickname :add-nickname)}
|
||||
{:icon :i/qr-code
|
||||
:label (i18n/label :t/show-qr)
|
||||
:on-press on-show-qr
|
||||
:accessibility-label :show-qr-code}
|
||||
{:icon :i/share
|
||||
:label (i18n/label :t/share-profile)
|
||||
:on-press on-share-profile
|
||||
:accessibility-label :share-profile}
|
||||
(when has-nickname?
|
||||
{:icon :i/delete
|
||||
:label (i18n/label :t/remove-nickname)
|
||||
:on-press on-remove-nickname
|
||||
:add-divider? true
|
||||
:accessibility-label :remove-nickname
|
||||
:danger? true})]
|
||||
(when-not blocked?
|
||||
[{:icon :i/untrustworthy
|
||||
:label (i18n/label :t/mark-untrustworthy)
|
||||
:on-press not-implemented/alert
|
||||
:accessibility-label :mark-untrustworthy
|
||||
:add-divider? (when-not has-nickname? true)
|
||||
:danger? true}
|
||||
(when (= constants/contact-request-state-mutual contact-request-state)
|
||||
{:icon :i/remove-user
|
||||
:label (i18n/label :t/remove-contact)
|
||||
:on-press on-remove-contact
|
||||
:accessibility-label :remove-contact
|
||||
:danger? true})
|
||||
{:icon :i/block
|
||||
:label (i18n/label :t/block-user)
|
||||
:on-press on-block-contact
|
||||
:accessibility-label :block-user
|
||||
:danger? true}]))]]))
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
[]
|
||||
(let [{:keys [public-key primary-name nickname customization-color]
|
||||
:as profile} (rf/sub [:contacts/current-contact])
|
||||
;; TODO(@mohsen): remove :blue, https://github.com/status-im/status-mobile/issues/18733
|
||||
customization-color (or customization-color :blue)
|
||||
;; TODO(@mohsen): remove default color, https://github.com/status-im/status-mobile/issues/18733
|
||||
customization-color (or customization-color constants/profile-default-color)
|
||||
full-name (profile.utils/displayed-name profile)
|
||||
profile-picture (profile.utils/photo profile)
|
||||
[unsaved-nickname set-unsaved-nickname] (rn/use-state nickname)
|
||||
@@ -54,15 +54,15 @@
|
||||
:customization-color customization-color}]
|
||||
[rn/view {:style style/input-wrapper}
|
||||
[quo/input
|
||||
{:type :text
|
||||
:char-limit constants/profile-name-max-length
|
||||
:max-length constants/profile-name-max-length
|
||||
:auto-focus true
|
||||
:default-value unsaved-nickname
|
||||
:error? (not (string/blank? error-msg))
|
||||
:label (i18n/label :t/nickname)
|
||||
:on-change-text on-nickname-change}
|
||||
:on-submit-editing on-nickname-submit]
|
||||
{:type :text
|
||||
:char-limit constants/profile-name-max-length
|
||||
:max-length constants/profile-name-max-length
|
||||
:auto-focus true
|
||||
:default-value unsaved-nickname
|
||||
:error? (not (string/blank? error-msg))
|
||||
:label (i18n/label :t/nickname)
|
||||
:on-change-text on-nickname-change
|
||||
:on-submit-editing on-nickname-submit}]
|
||||
[quo/info-message
|
||||
{:icon :i/info
|
||||
:size :default
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
(ns status-im.contexts.profile.contact.block-contact.style)
|
||||
|
||||
(def content-wrapper
|
||||
{:padding-vertical 8
|
||||
:padding-horizontal 20
|
||||
:gap 12})
|
||||
|
||||
(def checkbox-wrapper
|
||||
{:flex-direction :row
|
||||
:align-items :center
|
||||
:gap 8})
|
||||
@@ -0,0 +1,70 @@
|
||||
(ns status-im.contexts.profile.contact.block-contact.view
|
||||
(:require [quo.core :as quo]
|
||||
[react-native.core :as rn]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.contexts.profile.contact.block-contact.style :as style]
|
||||
[status-im.contexts.profile.utils :as profile.utils]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(defn on-close [] (rf/dispatch [:hide-bottom-sheet]))
|
||||
|
||||
(defn view
|
||||
[]
|
||||
(let [{:keys [customization-color contact-request-state public-key]
|
||||
:as contact} (rf/sub [:contacts/current-contact])
|
||||
;; TODO(@mohsen): remove default color, https://github.com/status-im/status-mobile/issues/18733
|
||||
customization-color (or customization-color constants/profile-default-color)
|
||||
full-name (profile.utils/displayed-name contact)
|
||||
profile-picture (profile.utils/photo contact)
|
||||
[remove-contact? set-remove-contact?] (rn/use-state false)
|
||||
on-remove-toggle (rn/use-callback #(set-remove-contact? not) [])
|
||||
on-block-press (rn/use-callback
|
||||
(fn []
|
||||
(rf/dispatch [:toasts/upsert
|
||||
{:id :user-blocked
|
||||
:type :positive
|
||||
:text (i18n/label :t/user-blocked
|
||||
{:username
|
||||
full-name})}])
|
||||
(rf/dispatch [:contact.ui/block-contact-confirmed
|
||||
public-key])
|
||||
(when remove-contact?
|
||||
(rf/dispatch [:contact.ui/remove-contact-pressed
|
||||
{:public-key public-key}]))
|
||||
(on-close))
|
||||
[remove-contact? public-key full-name])]
|
||||
[:<>
|
||||
[quo/drawer-top
|
||||
{:type :context-tag
|
||||
:context-tag-type :default
|
||||
:title (i18n/label :t/block)
|
||||
:full-name full-name
|
||||
:profile-picture profile-picture
|
||||
:customization-color customization-color}]
|
||||
[rn/view {:style style/content-wrapper}
|
||||
[quo/text
|
||||
{:weight :medium
|
||||
:size :paragraph-1}
|
||||
(i18n/label :t/block-user-title-message {:username full-name})]
|
||||
[quo/information-box
|
||||
{:icon :i/info
|
||||
:type :default}
|
||||
(i18n/label :t/blocking-a-user-message {:username full-name})]
|
||||
(when (= constants/contact-request-state-mutual contact-request-state)
|
||||
[rn/pressable
|
||||
{:style style/checkbox-wrapper
|
||||
:on-press on-remove-toggle}
|
||||
[quo/selectors
|
||||
{:type :checkbox
|
||||
:checked? remove-contact?
|
||||
:on-change on-remove-toggle}]
|
||||
[quo/text (i18n/label :t/remove-contact)]])]
|
||||
[quo/bottom-actions
|
||||
{:actions :two-actions
|
||||
:button-one-label (i18n/label :t/block)
|
||||
:button-one-props {:type :danger
|
||||
:on-press on-block-press}
|
||||
:button-two-label (i18n/label :t/cancel)
|
||||
:button-two-props {:type :grey
|
||||
:on-press on-close}}]]))
|
||||
@@ -2,6 +2,7 @@
|
||||
(:require [clojure.string :as string]
|
||||
[quo.core :as quo]
|
||||
[react-native.core :as rn]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.contexts.profile.contact.contact-request.style :as style]
|
||||
[status-im.contexts.profile.utils :as profile.utils]
|
||||
[utils.i18n :as i18n]
|
||||
@@ -11,8 +12,8 @@
|
||||
[]
|
||||
(let [{:keys [public-key customization-color]
|
||||
:as profile} (rf/sub [:contacts/current-contact])
|
||||
;; TODO: remove :blue when #18733 merged.
|
||||
customization-color (or customization-color :blue)
|
||||
;; TODO: remove default color when #18733 merged.
|
||||
customization-color (or customization-color constants/profile-default-color)
|
||||
full-name (profile.utils/displayed-name profile)
|
||||
profile-picture (profile.utils/photo profile)
|
||||
[message set-message] (rn/use-state "")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
(ns status-im.contexts.profile.contact.contact-review.view
|
||||
(:require [quo.core :as quo]
|
||||
[react-native.core :as rn]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.contexts.profile.contact.contact-review.style :as style]
|
||||
[status-im.contexts.profile.utils :as profile.utils]
|
||||
[utils.i18n :as i18n]
|
||||
@@ -14,7 +15,7 @@
|
||||
:keys [message]} (rf/sub [:activity-center/pending-contact-request-from-contact-id
|
||||
public-key])
|
||||
;; TODO(@seanstrom): https://github.com/status-im/status-mobile/issues/18733
|
||||
customization-color (or customization-color :blue)
|
||||
customization-color (or customization-color constants/profile-default-color)
|
||||
full-name (profile.utils/displayed-name profile)
|
||||
profile-picture (profile.utils/photo profile)
|
||||
on-contact-accept (rn/use-callback
|
||||
|
||||
@@ -13,24 +13,41 @@
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(defn on-contact-request
|
||||
[]
|
||||
(rf/dispatch [:show-bottom-sheet
|
||||
{:content (fn [] [contact-request/view])}]))
|
||||
|
||||
(defn on-contact-review
|
||||
[]
|
||||
(rf/dispatch [:show-bottom-sheet
|
||||
{:content (fn [] [contact-review/view])}]))
|
||||
|
||||
(defn view
|
||||
[{:keys [scroll-y]}]
|
||||
(let [{:keys [public-key customization-color ens-name
|
||||
emoji-hash bio contact-request-state]
|
||||
:as profile} (rf/sub [:contacts/current-contact])
|
||||
customization-color (or customization-color :blue)
|
||||
full-name (profile.utils/displayed-name profile)
|
||||
profile-picture (profile.utils/photo profile)
|
||||
emoji-hash bio blocked? contact-request-state]
|
||||
:as contact} (rf/sub [:contacts/current-contact])
|
||||
;; TODO(@mohsen): remove default color, https://github.com/status-im/status-mobile/issues/18733
|
||||
customization-color (or customization-color constants/profile-default-color)
|
||||
full-name (profile.utils/displayed-name contact)
|
||||
profile-picture (profile.utils/photo contact)
|
||||
online? (rf/sub [:visibility-status-updates/online? public-key])
|
||||
theme (quo.theme/use-theme-value)
|
||||
on-contact-request (rn/use-callback #(rf/dispatch [:show-bottom-sheet
|
||||
{:content (fn [] [contact-request/view])}]))
|
||||
on-contact-review (rn/use-callback #(rf/dispatch [:show-bottom-sheet
|
||||
{:content (fn [] [contact-review/view])}]))
|
||||
on-start-chat (rn/use-callback #(rf/dispatch [:chat.ui/start-chat
|
||||
public-key
|
||||
ens-name])
|
||||
[ens-name public-key])]
|
||||
[ens-name public-key])
|
||||
on-unblock-press (rn/use-callback (fn []
|
||||
(rf/dispatch [:contact.ui/unblock-contact-pressed
|
||||
public-key])
|
||||
(rf/dispatch [:toasts/upsert
|
||||
{:id :user-unblocked
|
||||
:type :positive
|
||||
:text (i18n/label :t/user-unblocked
|
||||
{:username
|
||||
full-name})}]))
|
||||
[public-key full-name])]
|
||||
[rn/view {:style style/header-container}
|
||||
[rn/view {:style style/header-top-wrapper}
|
||||
[rn/view {:style style/avatar-wrapper}
|
||||
@@ -53,10 +70,20 @@
|
||||
:description-text bio
|
||||
:emoji-dash emoji-hash}]
|
||||
|
||||
(when blocked?
|
||||
[quo/button
|
||||
{:container-style style/button-wrapper
|
||||
:on-press on-unblock-press
|
||||
:icon-left :i/block}
|
||||
(i18n/label :t/unblock)])
|
||||
|
||||
(cond
|
||||
(or (not contact-request-state)
|
||||
(= contact-request-state constants/contact-request-state-none)
|
||||
(= contact-request-state constants/contact-request-state-dismissed))
|
||||
(and (not blocked?)
|
||||
(or
|
||||
(not contact-request-state)
|
||||
(= contact-request-state constants/contact-request-state-none)
|
||||
(= contact-request-state constants/contact-request-state-dismissed)))
|
||||
|
||||
[quo/button
|
||||
{:container-style style/button-wrapper
|
||||
:on-press on-contact-request
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
[react-native.core :as rn]
|
||||
[react-native.reanimated :as reanimated]
|
||||
[status-im.common.scroll-page.view :as scroll-page]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.contexts.profile.contact.actions.view :as actions]
|
||||
[status-im.contexts.profile.contact.header.view :as contact-header]
|
||||
[utils.re-frame :as rf]))
|
||||
@@ -21,8 +22,8 @@
|
||||
{:navigate-back? true
|
||||
:height 148
|
||||
:on-scroll #(reanimated/set-shared-value scroll-y %)
|
||||
;; TODO(@mohsen): remove colors/primary-50, https://github.com/status-im/status-mobile/issues/18733
|
||||
:cover-color (or customization-color colors/primary-50)
|
||||
;; TODO(@mohsen): remove default color, https://github.com/status-im/status-mobile/issues/18733
|
||||
:cover-color (or customization-color constants/profile-default-color)
|
||||
:background-color (colors/theme-colors colors/white colors/neutral-95 theme)
|
||||
:page-nav-props {:right-side [{:icon-name :i/options
|
||||
:on-press on-action-press}]}}
|
||||
|
||||
@@ -170,20 +170,18 @@
|
||||
(defn password-input
|
||||
[]
|
||||
(let [auth-method (rf/sub [:auth-method])
|
||||
on-press-biometrics (rn/use-callback
|
||||
(fn []
|
||||
(when (= auth-method constants/auth-method-biometric)
|
||||
(rf/dispatch [:biometric/authenticate
|
||||
{:on-success #(rf/dispatch
|
||||
[:profile.login/biometric-success])
|
||||
:on-fail #(rf/dispatch
|
||||
[:profile.login/biometric-auth-fail
|
||||
%])}])))
|
||||
[auth-method])]
|
||||
on-press-biometrics (fn []
|
||||
(rf/dispatch [:biometric/authenticate
|
||||
{:on-success #(rf/dispatch
|
||||
[:profile.login/biometric-success])
|
||||
:on-fail #(rf/dispatch
|
||||
[:profile.login/biometric-auth-fail
|
||||
%])}]))
|
||||
]
|
||||
[standard-authentication/password-input
|
||||
{:shell? true
|
||||
:blur? true
|
||||
:on-press-biometrics on-press-biometrics}]))
|
||||
:on-press-biometrics (when (= auth-method constants/auth-method-biometric) on-press-biometrics)}]))
|
||||
|
||||
(defn login-section
|
||||
[{:keys [show-profiles]}]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
(ns status-im.contexts.profile.settings.header.header-shape
|
||||
(ns ^:workletize status-im.contexts.profile.settings.header.header-shape
|
||||
(:require [quo.foundations.colors :as colors]
|
||||
[react-native.core :as rn]
|
||||
[react-native.reanimated :as reanimated]
|
||||
@@ -21,13 +21,17 @@
|
||||
|
||||
(defn f-view
|
||||
[{:keys [scroll-y customization-color theme]}]
|
||||
(let [background-color (colors/resolve-color customization-color theme 40)
|
||||
opacity-animation (reanimated/interpolate scroll-y
|
||||
[0 45 50]
|
||||
[1 1 0])]
|
||||
(let [background-color (colors/resolve-color customization-color theme 40)
|
||||
opacity (reanimated/use-animated-style
|
||||
(fn []
|
||||
(js* "'worklet'")
|
||||
#js
|
||||
{:opacity (reanimated/interpolate* (.-value scroll-y)
|
||||
#js [0 45 50]
|
||||
#js [1 1 0])}))]
|
||||
[:<>
|
||||
[rn/view {:style (style/header-middle-shape background-color)}]
|
||||
[reanimated/view {:style (style/radius-container opacity-animation)}
|
||||
[reanimated/view {:style [opacity style/radius-container]}
|
||||
[left-radius background-color]
|
||||
[right-radius background-color]]]))
|
||||
|
||||
|
||||
@@ -27,19 +27,13 @@
|
||||
:height 48
|
||||
:flex-grow 1})
|
||||
|
||||
(defn radius-container
|
||||
[opacity-animation]
|
||||
{:opacity opacity-animation
|
||||
:flex-direction :row
|
||||
(def radius-container
|
||||
{:flex-direction :row
|
||||
:justify-content :space-between})
|
||||
|
||||
(defn avatar-container
|
||||
[theme scale-animation top-margin-animation side-margin-animation]
|
||||
[{:transform [{:scale scale-animation}]
|
||||
:margin-top top-margin-animation
|
||||
:margin-left side-margin-animation
|
||||
:margin-bottom side-margin-animation}
|
||||
{:align-items :flex-start
|
||||
:border-width 4
|
||||
:border-color (colors/theme-colors colors/border-avatar-light colors/neutral-80-opa-80 theme)
|
||||
:border-radius 100}])
|
||||
[theme]
|
||||
{:align-items :flex-start
|
||||
:border-width 4
|
||||
:border-color (colors/theme-colors colors/border-avatar-light colors/neutral-80-opa-80 theme)
|
||||
:border-radius 44})
|
||||
|
||||
@@ -153,9 +153,10 @@
|
||||
(reagent/flush))))
|
||||
handle-delete (fn [loading-routes? current-limit-amount]
|
||||
(when-not loading-routes?
|
||||
(let [{:keys [start end]} @input-selection]
|
||||
(reset-input-error @input-value current-limit-amount input-error)
|
||||
(let [{:keys [start end]} @input-selection
|
||||
new-value (delete-from-string @input-value start)]
|
||||
(when (= start end)
|
||||
(reset-input-error new-value current-limit-amount input-error)
|
||||
(swap! input-value delete-from-string start)
|
||||
(move-input-cursor input-selection (dec start)))
|
||||
(reagent/flush))))
|
||||
@@ -213,16 +214,16 @@
|
||||
crypto-limit (or default-limit-crypto
|
||||
(utils/get-standard-crypto-format token token-balance))
|
||||
fiat-limit (.toFixed (* token-balance conversion-rate) 2)
|
||||
current-limit (if @crypto-currency? crypto-limit fiat-limit)
|
||||
current-limit #(if @crypto-currency? crypto-limit fiat-limit)
|
||||
current-currency (if @crypto-currency? token-symbol fiat-currency)
|
||||
limit-label (make-limit-label {:amount current-limit
|
||||
limit-label (make-limit-label {:amount (current-limit)
|
||||
:currency current-currency})
|
||||
input-num-value (parse-double @input-value)
|
||||
confirm-disabled? (or (nil? route)
|
||||
(empty? route)
|
||||
(empty? @input-value)
|
||||
(<= input-num-value 0)
|
||||
(> input-num-value current-limit))
|
||||
(> input-num-value (current-limit)))
|
||||
amount-text (str @input-value " " token-symbol)
|
||||
native-currency-symbol (when-not confirm-disabled?
|
||||
(get-in route [:from :native-currency-symbol]))
|
||||
@@ -248,7 +249,7 @@
|
||||
app-keyboard-listener (.addEventListener rn/app-state "change" dismiss-keyboard-fn)]
|
||||
#(.remove app-keyboard-listener))))
|
||||
(rn/use-effect
|
||||
#(fetch-routes input-num-value current-limit)
|
||||
#(fetch-routes input-num-value (current-limit))
|
||||
[@input-value])
|
||||
[rn/view
|
||||
{:style style/screen
|
||||
@@ -269,7 +270,7 @@
|
||||
:show-keyboard? false
|
||||
:value @input-value
|
||||
:selection @input-selection
|
||||
:on-change-text #(handle-on-change % current-limit)
|
||||
:on-change-text #(handle-on-change % (current-limit))
|
||||
:on-selection-change selection-change
|
||||
:on-swap #(handle-swap
|
||||
{:crypto? %
|
||||
@@ -282,7 +283,7 @@
|
||||
:routes suggested-routes
|
||||
:token token
|
||||
:input-value @input-value
|
||||
:fetch-routes #(fetch-routes % current-limit)}]
|
||||
:fetch-routes #(fetch-routes % (current-limit))}]
|
||||
(when (or loading-routes? (seq route))
|
||||
[estimated-fees
|
||||
{:loading-suggested-routes? loading-routes?
|
||||
@@ -299,8 +300,8 @@
|
||||
{:container-style (style/keyboard-container bottom)
|
||||
:left-action :dot
|
||||
:delete-key? true
|
||||
:on-press #(handle-keyboard-press % loading-routes? current-limit)
|
||||
:on-delete #(handle-delete loading-routes? current-limit)
|
||||
:on-press #(handle-keyboard-press % loading-routes? (current-limit))
|
||||
:on-delete #(handle-delete loading-routes? (current-limit))
|
||||
:on-long-press-delete #(on-long-press-delete loading-routes?)}]]))))
|
||||
|
||||
(defn- view-internal
|
||||
|
||||
@@ -56,7 +56,9 @@
|
||||
(rf/dispatch event))
|
||||
(effects/dismiss-modal))
|
||||
(when-let [handler (get-in views/screens [(keyword id) :right-handler])]
|
||||
(handler)))))
|
||||
(handler)))
|
||||
(when (= "legacy-back-button" id)
|
||||
(rf/dispatch [:navigate-back]))))
|
||||
|
||||
(navigation/reg-modal-dismissed-listener
|
||||
(fn []
|
||||
@@ -92,35 +94,16 @@
|
||||
(navigation/register-component
|
||||
"bottom-sheet"
|
||||
(fn [] (gesture/gesture-handler-root-hoc views/bottom-sheet))
|
||||
(fn [] views/bottom-sheet)))
|
||||
(fn [] views/bottom-sheet))
|
||||
|
||||
;;;; Alert Banner
|
||||
(navigation/register-component
|
||||
"alert-banner"
|
||||
(fn [] (gesture/gesture-handler-root-hoc views/alert-banner #js {:flex 0}))
|
||||
(fn [] views/alert-banner))
|
||||
;;;; Alert Banner
|
||||
(navigation/register-component
|
||||
"alert-banner"
|
||||
(fn [] (gesture/gesture-handler-root-hoc views/alert-banner #js {:flex 0}))
|
||||
(fn [] views/alert-banner))
|
||||
|
||||
|
||||
;; LEGACY (should be removed in status 2.0)
|
||||
|
||||
(defonce
|
||||
_
|
||||
[(navigation/register-component
|
||||
"popover"
|
||||
(fn [] (gesture/gesture-handler-root-hoc views/popover-comp))
|
||||
(fn [] views/popover-comp))
|
||||
|
||||
(navigation/register-component
|
||||
"visibility-status-popover"
|
||||
(fn [] (gesture/gesture-handler-root-hoc views/visibility-status-popover-comp))
|
||||
(fn [] views/visibility-status-popover-comp))
|
||||
|
||||
(navigation/register-component
|
||||
"bottom-sheet-old"
|
||||
(fn [] (gesture/gesture-handler-root-hoc views/sheet-comp-old))
|
||||
(fn [] views/sheet-comp-old))
|
||||
|
||||
(navigation/register-component
|
||||
"signing-sheet"
|
||||
(fn [] (gesture/gesture-handler-root-hoc views/signing-comp))
|
||||
(fn [] views/signing-comp))])
|
||||
;;;; LEGACY (should be removed in status 2.0)
|
||||
(navigation/register-component
|
||||
"bottom-sheet-old"
|
||||
(fn [] (gesture/gesture-handler-root-hoc views/sheet-comp-old))
|
||||
(fn [] views/sheet-comp-old)))
|
||||
|
||||
@@ -63,12 +63,8 @@
|
||||
(name @state/root-id)
|
||||
{:component {:id component
|
||||
:name component
|
||||
:options (merge (options/default-root)
|
||||
(options/statusbar-and-navbar)
|
||||
options
|
||||
(if (:topBar options)
|
||||
(options/merge-top-bar (options/topbar-options) options)
|
||||
{:topBar {:visible false}}))}})))
|
||||
:options (merge (options/root-options {:theme (:theme options)})
|
||||
options)}})))
|
||||
|
||||
(rf/reg-fx :navigate-to navigate)
|
||||
|
||||
@@ -81,11 +77,9 @@
|
||||
(name comp-id)
|
||||
{:component {:id component
|
||||
:name component
|
||||
:options (merge (options/statusbar-and-navbar)
|
||||
options
|
||||
(if (:topBar options)
|
||||
(options/merge-top-bar (options/topbar-options) options)
|
||||
{:topBar {:visible false}}))}})))
|
||||
:options (merge
|
||||
(options/root-options {:theme (:theme options)})
|
||||
options)}})))
|
||||
|
||||
(rf/reg-fx :navigate-to-within-stack navigate-to-within-stack)
|
||||
|
||||
@@ -140,8 +134,7 @@
|
||||
{:stack {:children [{:component
|
||||
{:name component
|
||||
:id component
|
||||
:options (merge (options/default-root)
|
||||
(options/statusbar-and-navbar)
|
||||
:options (merge (options/root-options {:theme (:theme options)})
|
||||
options
|
||||
(when sheet?
|
||||
options/sheet-options))}}]}})))))
|
||||
@@ -157,7 +150,7 @@
|
||||
(navigation/show-overlay
|
||||
{:component {:name component
|
||||
:id component
|
||||
:options (merge (options/statusbar)
|
||||
:options (merge (options/statusbar-and-navbar-options (:theme opts) nil nil)
|
||||
{:layout {:componentBackgroundColor :transparent
|
||||
:orientation ["portrait"]}
|
||||
:overlay {:interceptTouchOutside true}}
|
||||
@@ -201,9 +194,8 @@
|
||||
(let [{:keys [options]} (get views/screens component)]
|
||||
{:component {:id component
|
||||
:name component
|
||||
:options (merge (options/statusbar-and-navbar)
|
||||
options
|
||||
(options/merge-top-bar (options/topbar-options) options))}}))
|
||||
:options (merge (options/statusbar-and-navbar-options (:theme options) nil nil)
|
||||
options)}}))
|
||||
|
||||
(rf/reg-fx :set-stack-root-fx
|
||||
(fn [[stack component]]
|
||||
|
||||
@@ -155,22 +155,6 @@
|
||||
#(when-not hide?
|
||||
(if (seq sheets) (hide-bottom-sheet %) {:show-bottom-sheet nil})))))
|
||||
|
||||
;; LEGACY (should be removed in status 2.0)
|
||||
(rf/defn hide-signing-sheet
|
||||
{:events [:hide-signing-sheet]}
|
||||
[_]
|
||||
{:hide-signing-sheet nil})
|
||||
|
||||
(rf/defn set-multiaccount-root
|
||||
{:events [:set-multiaccount-root]}
|
||||
[{:keys [db]}]
|
||||
(let [key-uid (get-in db [:profile/login :key-uid])
|
||||
keycard-account? (boolean (get-in db
|
||||
[:profile/profiles-overview
|
||||
key-uid
|
||||
:keycard-pairing]))]
|
||||
{:set-root (if keycard-account? :multiaccounts-keycard :multiaccounts)}))
|
||||
|
||||
(rf/defn dismiss-all-overlays
|
||||
{:events [:dismiss-all-overlays]}
|
||||
[_]
|
||||
@@ -178,7 +162,6 @@
|
||||
[:hide-visibility-status-popover]
|
||||
[:hide-bottom-sheet]
|
||||
[:bottom-sheet-hidden]
|
||||
[:hide-signing-sheet]
|
||||
[:bottom-sheet/hide-old-navigation-overlay]
|
||||
[:toasts/close-all-toasts]]})
|
||||
|
||||
|
||||
@@ -10,23 +10,23 @@
|
||||
{:layout {:orientation ["portrait"]}
|
||||
:topBar {:visible false}})
|
||||
|
||||
;; Note: Currently, the status bar style provided while setting the root has a high preference,
|
||||
;; and even if we change the status bar style later dynamically, the style gets restored to this
|
||||
;; set root style while navigating
|
||||
;; https://github.com/status-im/status-mobile/pull/15596
|
||||
(defn statusbar-and-navbar-root
|
||||
[& [status-bar-theme]]
|
||||
(if platform/android?
|
||||
{:navigationBar {:backgroundColor colors/neutral-100}
|
||||
:statusBar {:translucent true
|
||||
:backgroundColor :transparent
|
||||
:style (or status-bar-theme :light)
|
||||
:drawBehind true}}
|
||||
{:statusBar {:style (or status-bar-theme :light)}}))
|
||||
(defn statusbar-and-navbar-options
|
||||
[theme status-bar-theme nav-bar-color]
|
||||
(let [[status-bar-theme nav-bar-color]
|
||||
(if (= :dark (or theme (quo.theme/get-theme)))
|
||||
[(or status-bar-theme :light) (or nav-bar-color colors/neutral-100)]
|
||||
[(or status-bar-theme :dark) (or nav-bar-color colors/white)])]
|
||||
(if platform/android?
|
||||
{:navigationBar {:backgroundColor nav-bar-color}
|
||||
:statusBar {:translucent true
|
||||
:backgroundColor :transparent
|
||||
:style (or status-bar-theme :light)
|
||||
:drawBehind true}}
|
||||
{:statusBar {:style (or status-bar-theme :light)}})))
|
||||
|
||||
(defn default-root
|
||||
[& [status-bar-theme background-color]]
|
||||
(merge (statusbar-and-navbar-root status-bar-theme)
|
||||
(defn root-options
|
||||
[{:keys [background-color theme status-bar-theme nav-bar-color]}]
|
||||
(merge (statusbar-and-navbar-options theme status-bar-theme nav-bar-color)
|
||||
{:topBar {:visible false}
|
||||
:layout {:componentBackgroundColor (or background-color
|
||||
(colors/theme-colors colors/white colors/neutral-100))
|
||||
@@ -35,6 +35,15 @@
|
||||
(colors/theme-colors colors/white
|
||||
colors/neutral-100))}}))
|
||||
|
||||
(defn dark-root-options
|
||||
[]
|
||||
(root-options
|
||||
{:background-color colors/neutral-100
|
||||
:theme :dark
|
||||
:status-bar-theme :light
|
||||
:nav-bar-color colors/neutral-100}))
|
||||
|
||||
;;;; Screen Specific Options
|
||||
(def onboarding-layout
|
||||
{:componentBackgroundColor colors/neutral-80-opa-80-blur
|
||||
:orientation ["portrait"]
|
||||
@@ -45,49 +54,12 @@
|
||||
:orientation ["portrait"]
|
||||
:backgroundColor :transparent})
|
||||
|
||||
(defn navbar
|
||||
([dark?]
|
||||
{:navigationBar {:backgroundColor (if (or dark? (= :dark (quo.theme/get-theme)))
|
||||
colors/neutral-100
|
||||
colors/white)}})
|
||||
([] (navbar nil)))
|
||||
|
||||
(defn statusbar
|
||||
([dark?]
|
||||
(let [style (if (or dark? (= :dark (quo.theme/get-theme))) :light :dark)]
|
||||
(if platform/android?
|
||||
{:statusBar {:translucent true
|
||||
:backgroundColor :transparent
|
||||
:drawBehind true
|
||||
:style style}}
|
||||
{:statusBar {:style style}})))
|
||||
([] (statusbar nil)))
|
||||
|
||||
(defn statusbar-and-navbar
|
||||
([dark?]
|
||||
(merge (navbar dark?) (statusbar dark?)))
|
||||
([] (statusbar-and-navbar nil)))
|
||||
|
||||
(defn topbar-options
|
||||
[]
|
||||
{:noBorder true
|
||||
:scrollEdgeAppearance {:active false
|
||||
:noBorder true}
|
||||
:elevation 0
|
||||
:title {:color (colors/theme-colors colors/neutral-100 colors/white)}
|
||||
:rightButtonColor (colors/theme-colors colors/neutral-100 colors/white)
|
||||
:background {:color (colors/theme-colors colors/white colors/neutral-100)}
|
||||
:backButton {:color (colors/theme-colors colors/neutral-100 colors/white)
|
||||
:testID :back-button}})
|
||||
|
||||
(def transparent-screen-options
|
||||
(merge
|
||||
(statusbar-and-navbar-root)
|
||||
{:modalPresentationStyle :overCurrentContext
|
||||
:theme :dark
|
||||
:layout {:componentBackgroundColor :transparent
|
||||
:orientation ["portrait"]
|
||||
:backgroundColor :transparent}}))
|
||||
{:modalPresentationStyle :overCurrentContext
|
||||
:theme :dark
|
||||
:layout {:componentBackgroundColor :transparent
|
||||
:orientation ["portrait"]
|
||||
:backgroundColor :transparent}})
|
||||
|
||||
(def transparent-modal-screen-options
|
||||
(merge
|
||||
@@ -109,11 +81,10 @@
|
||||
{})})
|
||||
|
||||
(def dark-screen
|
||||
(merge (statusbar-and-navbar-root)
|
||||
{:theme :dark
|
||||
:layout {:componentBackgroundColor colors/neutral-95
|
||||
:orientation ["portrait"]
|
||||
:backgroundColor colors/neutral-95}}))
|
||||
{:theme :dark
|
||||
:layout {:componentBackgroundColor colors/neutral-95
|
||||
:orientation ["portrait"]
|
||||
:backgroundColor colors/neutral-95}})
|
||||
|
||||
(def lightbox
|
||||
{:topBar {:visible false}
|
||||
@@ -136,20 +107,3 @@
|
||||
:interpolation {:type
|
||||
:decelerate
|
||||
:factor 1.5}}]}}})
|
||||
|
||||
(defn merge-top-bar
|
||||
[root-options options]
|
||||
(let [options (:topBar options)]
|
||||
{:topBar
|
||||
(merge root-options
|
||||
options
|
||||
(when (or (:title root-options) (:title options))
|
||||
{:title (merge (:title root-options) (:title options))})
|
||||
(when (or (:background root-options) (:background options))
|
||||
{:background (merge (:background root-options) (:background options))})
|
||||
(when (or (:backButton root-options) (:backButton options))
|
||||
{:backButton (merge (:backButton root-options) (:backButton options))})
|
||||
(when (or (:leftButtons root-options) (:leftButtons options))
|
||||
{:leftButtons (merge (:leftButtons root-options) (:leftButtons options))})
|
||||
(when (or (:rightButtons root-options) (:rightButtons options))
|
||||
{:rightButtons (merge (:rightButtons root-options) (:rightButtons options))}))}))
|
||||
|
||||
@@ -1,74 +1,8 @@
|
||||
(ns status-im.navigation.roots
|
||||
(:require
|
||||
[quo.foundations.colors :as colors]
|
||||
[quo.theme :as quo.theme]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.navigation.options :as options]
|
||||
[status-im.navigation.view :as views]))
|
||||
|
||||
(defn get-screen-options
|
||||
[screen]
|
||||
(merge (get-in views/screens [screen :options])
|
||||
(options/statusbar-and-navbar-root)
|
||||
(options/merge-top-bar (options/topbar-options)
|
||||
(get-in views/screens [screen :options]))))
|
||||
|
||||
(defn old-roots
|
||||
[]
|
||||
{;; ONBOARDING
|
||||
:onboarding
|
||||
{:root {:stack {:id :onboarding
|
||||
:children [{:component {:name :get-your-keys
|
||||
:id :get-your-keys
|
||||
:options (options/statusbar-and-navbar-root)}}]
|
||||
:options (merge (options/default-root)
|
||||
(options/statusbar-and-navbar-root)
|
||||
{:topBar (assoc (options/topbar-options)
|
||||
:elevation 0
|
||||
:noBorder true
|
||||
:animate false)})}}}
|
||||
|
||||
;;PROGRESS
|
||||
:progress
|
||||
{:root {:stack {:children [{:component {:name :progress
|
||||
:id :progress
|
||||
:options (options/statusbar-and-navbar-root)}}]
|
||||
:options (merge (options/default-root)
|
||||
{:topBar (assoc (options/topbar-options) :visible false)})}}}
|
||||
|
||||
;;LOGIN
|
||||
:multiaccounts
|
||||
{:root {:stack {:id :multiaccounts-stack
|
||||
:children [{:component {:name :multiaccounts
|
||||
:id :multiaccounts
|
||||
:options (get-screen-options :multiaccounts)}}
|
||||
{:component {:name :login
|
||||
:id :login
|
||||
:options (get-screen-options :login)}}]
|
||||
:options (merge (options/default-root)
|
||||
(options/statusbar-and-navbar-root)
|
||||
{:topBar (options/topbar-options)})}}}
|
||||
|
||||
:multiaccounts-keycard
|
||||
{:root {:stack {:id :multiaccounts-stack
|
||||
:children [{:component {:name :profiles
|
||||
:id :profiles
|
||||
:options (get-screen-options :multiaccounts)}}
|
||||
{:component {:name :keycard-login-pin
|
||||
:id :keycard-login-pin
|
||||
:options (get-screen-options :keycard-login-pin)}}]
|
||||
:options (merge (options/default-root)
|
||||
(options/statusbar-and-navbar-root)
|
||||
{:topBar (options/topbar-options)})}}}
|
||||
|
||||
;;NOTIFICATIONS
|
||||
:onboarding-notification
|
||||
{:root {:stack {:children [{:component {:name :onboarding-notification
|
||||
:id :onboarding-notification
|
||||
:options (options/statusbar-and-navbar-root)}}]
|
||||
:options (merge (options/default-root)
|
||||
(options/statusbar-and-navbar-root)
|
||||
{:topBar (assoc (options/topbar-options) :visible false)})}}}})
|
||||
[status-im.navigation.options :as options]))
|
||||
|
||||
;; Theme Order for navigation roots
|
||||
;; 1. Themes hardcoded in below map
|
||||
@@ -82,39 +16,36 @@
|
||||
|
||||
(defn roots
|
||||
[]
|
||||
(merge
|
||||
(old-roots)
|
||||
{:intro
|
||||
{:root
|
||||
{:stack {:id :intro
|
||||
:children [{:component {:name :intro
|
||||
:id :intro
|
||||
:options (options/dark-root-options)}}]}}}
|
||||
:shell-stack
|
||||
{:root
|
||||
{:stack {:id :shell-stack
|
||||
:children [{:component {:name :shell-stack
|
||||
:id :shell-stack
|
||||
:options (options/root-options
|
||||
{:nav-bar-color colors/neutral-100})}}]}}}
|
||||
:profiles
|
||||
{:root
|
||||
{:stack {:id :profiles
|
||||
:children [{:component {:name :profiles
|
||||
:id :profiles
|
||||
:options (options/dark-root-options)}}]}}}
|
||||
|
||||
{:intro
|
||||
{:root
|
||||
{:stack {:id :intro
|
||||
:children [{:component {:name :intro
|
||||
:id :intro
|
||||
:options (options/default-root nil colors/neutral-100)}}]}}}
|
||||
:shell-stack
|
||||
{:root
|
||||
{:stack {:id :shell-stack
|
||||
:children [{:component {:name :shell-stack
|
||||
:id :shell-stack
|
||||
:options (options/default-root
|
||||
(if (= :dark (quo.theme/get-theme)) :light :dark))}}]}}}
|
||||
:profiles
|
||||
{:root
|
||||
{:stack {:id :profiles
|
||||
:children [{:component {:name :profiles
|
||||
:id :profiles
|
||||
:options (options/default-root)}}]}}}
|
||||
:enable-notifications
|
||||
{:root {:stack {:children [{:component {:name :enable-notifications
|
||||
:id :enable-notifications
|
||||
:options (options/dark-root-options)}}]}}}
|
||||
|
||||
:enable-notifications
|
||||
{:root {:stack {:children [{:component {:name :enable-notifications
|
||||
:id :enable-notifications
|
||||
:options (options/default-root)}}]}}}
|
||||
|
||||
:welcome
|
||||
{:root {:stack {:children [{:component {:name :welcome
|
||||
:id :welcome
|
||||
:options (options/default-root)}}]}}}
|
||||
:syncing-results
|
||||
{:root {:stack {:children [{:component {:name :syncing-results
|
||||
:id :syncing-results
|
||||
:options (options/default-root)}}]}}}}))
|
||||
:welcome
|
||||
{:root {:stack {:children [{:component {:name :welcome
|
||||
:id :welcome
|
||||
:options (options/dark-root-options)}}]}}}
|
||||
:syncing-results
|
||||
{:root {:stack {:children [{:component {:name :syncing-results
|
||||
:id :syncing-results
|
||||
:options (options/dark-root-options)}}]}}}})
|
||||
|
||||
@@ -130,7 +130,7 @@
|
||||
:component lightbox/lightbox}
|
||||
|
||||
{:name :photo-selector
|
||||
:options (merge {:sheet? true} (options/statusbar-and-navbar-root))
|
||||
:options {:sheet? true}
|
||||
:component photo-selector/photo-selector}
|
||||
|
||||
{:name :camera-screen
|
||||
@@ -230,17 +230,15 @@
|
||||
:component create-password/create-password}
|
||||
|
||||
{:name :enable-biometrics
|
||||
:options (merge
|
||||
(options/statusbar-and-navbar-root)
|
||||
{:theme :dark
|
||||
:layout options/onboarding-transparent-layout
|
||||
:animations (merge
|
||||
transitions/new-to-status-modal-animations
|
||||
transitions/push-animations-for-transparent-background)
|
||||
:popGesture false
|
||||
:modalPresentationStyle :overCurrentContext
|
||||
:hardwareBackButton {:dismissModalOnPress false
|
||||
:popStackOnPress false}})
|
||||
:options {:theme :dark
|
||||
:layout options/onboarding-transparent-layout
|
||||
:animations (merge
|
||||
transitions/new-to-status-modal-animations
|
||||
transitions/push-animations-for-transparent-background)
|
||||
:popGesture false
|
||||
:modalPresentationStyle :overCurrentContext
|
||||
:hardwareBackButton {:dismissModalOnPress false
|
||||
:popStackOnPress false}}
|
||||
:component enable-biometrics/view}
|
||||
|
||||
{:name :generating-keys
|
||||
@@ -260,14 +258,12 @@
|
||||
:component enter-seed-phrase/enter-seed-phrase}
|
||||
|
||||
{:name :enable-notifications
|
||||
:options (merge
|
||||
(options/statusbar-and-navbar-root)
|
||||
{:theme :dark
|
||||
:layout options/onboarding-transparent-layout
|
||||
:animations (merge
|
||||
transitions/new-to-status-modal-animations
|
||||
transitions/push-animations-for-transparent-background)
|
||||
:modalPresentationStyle :overCurrentContext})
|
||||
:options {:theme :dark
|
||||
:layout options/onboarding-transparent-layout
|
||||
:animations (merge
|
||||
transitions/new-to-status-modal-animations
|
||||
transitions/push-animations-for-transparent-background)
|
||||
:modalPresentationStyle :overCurrentContext}
|
||||
:component enable-notifications/view}
|
||||
|
||||
{:name :identifiers
|
||||
@@ -298,11 +294,9 @@
|
||||
:component sign-in/view}
|
||||
|
||||
{:name :syncing-progress
|
||||
:options (merge
|
||||
(options/statusbar-and-navbar-root)
|
||||
{:theme :dark
|
||||
:layout options/onboarding-layout
|
||||
:popGesture false})
|
||||
:options {:theme :dark
|
||||
:layout options/onboarding-layout
|
||||
:popGesture false}
|
||||
:component syncing-devices/view}
|
||||
|
||||
{:name :syncing-progress-intro
|
||||
@@ -317,11 +311,9 @@
|
||||
:component syncing-results/view}
|
||||
|
||||
{:name :welcome
|
||||
:options (merge
|
||||
(options/statusbar-and-navbar-root)
|
||||
{:theme :dark
|
||||
:layout options/onboarding-transparent-layout
|
||||
:animations transitions/push-animations-for-transparent-background})
|
||||
:options {:theme :dark
|
||||
:layout options/onboarding-transparent-layout
|
||||
:animations transitions/push-animations-for-transparent-background}
|
||||
:component welcome/view}
|
||||
|
||||
{:name :emoji-picker
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
(ns status-im.navigation.view
|
||||
(:require
|
||||
[legacy.status-im.bottom-sheet.sheets :as bottom-sheets-old]
|
||||
[legacy.status-im.ui.screens.popover.views :as popover]
|
||||
[legacy.status-im.ui.screens.profile.visibility-status.views :as visibility-status-views]
|
||||
[legacy.status-im.ui.screens.signing.views :as signing]
|
||||
[quo.foundations.colors :as colors]
|
||||
[quo.theme :as theme]
|
||||
[react-native.core :as rn]
|
||||
@@ -111,29 +108,15 @@
|
||||
|
||||
(def toasts (reagent/reactify-component toasts/toasts))
|
||||
|
||||
(def alert-banner
|
||||
(reagent/reactify-component
|
||||
(fn []
|
||||
^{:key (str "alert-banner" @reloader/cnt)}
|
||||
[theme/provider {:theme :dark}
|
||||
[alert-banner/view]])
|
||||
functional-compiler))
|
||||
|
||||
;; LEGACY (should be removed in status 2.0)
|
||||
(def popover-comp
|
||||
(reagent/reactify-component
|
||||
(fn []
|
||||
^{:key (str "popover" @reloader/cnt)}
|
||||
[:<>
|
||||
[inactive]
|
||||
[popover/popover]
|
||||
(when js/goog.DEBUG
|
||||
[reloader/reload-view])])
|
||||
functional-compiler))
|
||||
|
||||
(def visibility-status-popover-comp
|
||||
(reagent/reactify-component
|
||||
(fn []
|
||||
^{:key (str "visibility-status-popover" @reloader/cnt)}
|
||||
[rn/view
|
||||
[inactive]
|
||||
[visibility-status-views/visibility-status-popover]
|
||||
(when js/goog.DEBUG
|
||||
[reloader/reload-view])])
|
||||
functional-compiler))
|
||||
|
||||
(def sheet-comp-old
|
||||
(reagent/reactify-component
|
||||
(fn []
|
||||
@@ -142,22 +125,3 @@
|
||||
[inactive]
|
||||
[bottom-sheets-old/bottom-sheet]])
|
||||
functional-compiler))
|
||||
|
||||
(def signing-comp
|
||||
(reagent/reactify-component
|
||||
(fn []
|
||||
^{:key (str "signing-sheet" @reloader/cnt)}
|
||||
[:<>
|
||||
[inactive]
|
||||
[signing/signing]
|
||||
(when js/goog.DEBUG
|
||||
[reloader/reload-view])])
|
||||
functional-compiler))
|
||||
|
||||
(def alert-banner
|
||||
(reagent/reactify-component
|
||||
(fn []
|
||||
^{:key (str "alert-banner" @reloader/cnt)}
|
||||
[theme/provider {:theme :dark}
|
||||
[alert-banner/view]])
|
||||
functional-compiler))
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
(ns status-im.setup.build-hooks.worklets
|
||||
(:require [clojure.set]
|
||||
[clojure.java.io :as io]
|
||||
[cljs.build.api :as b])
|
||||
(:import (java.io BufferedReader InputStreamReader PrintWriter)
|
||||
(javax.script ScriptEngineManager)))
|
||||
|
||||
(def engine
|
||||
(doto (.getEngineByName (ScriptEngineManager.) "nashorn")
|
||||
(.eval (io/reader (io/file "babel.min.js")))))
|
||||
|
||||
(defn- update-js-output
|
||||
[build-state code-processed]
|
||||
(reduce-kv (fn [prev-build-state ns-key new-code]
|
||||
(assoc-in prev-build-state [:output ns-key :js] new-code))
|
||||
build-state
|
||||
code-processed))
|
||||
|
||||
|
||||
(defn- gen-workletized-code!
|
||||
[code-seq store-atom]
|
||||
(doall
|
||||
(pmap (fn [[[_ filepath :as ns-key] js-code]]
|
||||
(println "Workletizing:" filepath) ;; TODO: debug remove
|
||||
(def js-code* js-code)
|
||||
(try
|
||||
(let [command "node workletize-code.js"
|
||||
process (.exec (Runtime/getRuntime) command)
|
||||
output-stream (.getOutputStream process)
|
||||
writer (PrintWriter. output-stream)
|
||||
reader (BufferedReader. (InputStreamReader. (.getInputStream process)))]
|
||||
(.println writer js-code)
|
||||
(.flush writer)
|
||||
(.close writer)
|
||||
|
||||
(let [output (apply str (interleave (line-seq reader) (repeat "\n")))
|
||||
_exit-code (.waitFor process)]
|
||||
(swap! store-atom assoc ns-key output)))
|
||||
|
||||
(catch Exception e
|
||||
(println (str "Exception while workletizing: " filepath "\n")
|
||||
(.getMessage e)))))
|
||||
code-seq)))
|
||||
|
||||
(defn- get-js-code-with-ns
|
||||
[build-state-output file-path]
|
||||
(let [ns-key [:shadow.build.classpath/resource file-path]
|
||||
js-code (get-in build-state-output [ns-key :js])]
|
||||
[ns-key js-code]))
|
||||
|
||||
(defn- get-files-to-workletize
|
||||
[{:keys [shadow.build/build-info compiler-env] :as _build-state}]
|
||||
(let [files-compiled (->> build-info
|
||||
:compiled
|
||||
(map second)
|
||||
(set))
|
||||
namespaces-metadata (->> compiler-env
|
||||
:cljs.analyzer/namespaces
|
||||
vals
|
||||
(map :meta))
|
||||
files-marked (->> namespaces-metadata
|
||||
(filter :workletize)
|
||||
(map :file)
|
||||
(set))]
|
||||
(clojure.set/intersection files-compiled files-marked)))
|
||||
|
||||
(defn transform-output
|
||||
{:shadow.build/stage :compile-finish}
|
||||
[{:keys [output] :as build-state}]
|
||||
(let [files-to-workletize (get-files-to-workletize build-state)
|
||||
js-code-with-ns (map #(get-js-code-with-ns output %) files-to-workletize)
|
||||
code-processed-store (atom {})]
|
||||
(gen-workletized-code! js-code-with-ns code-processed-store)
|
||||
(update-js-output build-state @code-processed-store)))
|
||||
@@ -181,15 +181,16 @@
|
||||
(chat.events/public-chat? current-chat)
|
||||
(assoc :able-to-send-message? true)
|
||||
|
||||
(and (chat.events/community-chat? current-chat)
|
||||
(get-in community [:chats (subs (:chat-id current-chat) 68) :can-post?]))
|
||||
(assoc :able-to-send-message? true)
|
||||
|
||||
|
||||
(and (chat.events/group-chat? current-chat)
|
||||
(group-chats.db/member? my-public-key current-chat))
|
||||
(assoc :able-to-send-message? true
|
||||
:member? true)
|
||||
|
||||
(and (chat.events/community-chat? current-chat)
|
||||
(get-in community [:chats (subs (:chat-id current-chat) 68) :can-post?]))
|
||||
(assoc :able-to-send-message? true)
|
||||
|
||||
(not group-chat)
|
||||
(assoc
|
||||
:contact-request-state (get-in contacts [chat-id :contact-request-state])
|
||||
|
||||
@@ -229,42 +229,13 @@
|
||||
(sort-by :position)
|
||||
(into []))))
|
||||
|
||||
(defn- get-chat-lock-state
|
||||
"Returns the chat lock state.
|
||||
|
||||
- Nil: no lock (there are no permissions for the chat)
|
||||
- True: locked (there are permissions and can-post? is false)
|
||||
- False: unlocked (there are permissions and can-post? is true)"
|
||||
[community-id channels-permissions {chat-id :id}]
|
||||
(let [composite-key (keyword (str community-id chat-id))
|
||||
permissions (get channels-permissions composite-key)
|
||||
{view-only-satisfied? :satisfied?
|
||||
view-only-permissions :permissions} (:view-only permissions)
|
||||
{view-and-post-satisfied? :satisfied?
|
||||
view-and-post-permissions :permissions} (:view-and-post permissions)
|
||||
can-access? (or (and (seq view-only-permissions)
|
||||
view-only-satisfied?)
|
||||
(and (seq view-and-post-permissions)
|
||||
view-and-post-satisfied?))]
|
||||
(if (and (empty? view-only-permissions)
|
||||
(empty? view-and-post-permissions))
|
||||
nil
|
||||
(not can-access?))))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:communities/community-channels-permissions
|
||||
:<- [:communities/channels-permissions]
|
||||
(fn [channel-permissions [_ community-id]]
|
||||
(get channel-permissions community-id)))
|
||||
|
||||
(defn- reduce-over-categories
|
||||
[community-id
|
||||
categories
|
||||
collapsed-categories
|
||||
full-chats-data
|
||||
channels-permissions]
|
||||
full-chats-data]
|
||||
(fn [acc
|
||||
[_ {:keys [name categoryID position id emoji] :as chat}]]
|
||||
[_ {:keys [name categoryID position id emoji can-post? token-gated?]}]]
|
||||
(let [category-id (if (seq categoryID) categoryID constants/empty-category-id)
|
||||
{:keys [unviewed-messages-count
|
||||
unviewed-mentions-count
|
||||
@@ -286,9 +257,11 @@
|
||||
:unread-messages? (pos? unviewed-messages-count)
|
||||
:position position
|
||||
:mentions-count (or unviewed-mentions-count 0)
|
||||
:locked? (get-chat-lock-state community-id
|
||||
channels-permissions
|
||||
chat)
|
||||
:can-post? can-post?
|
||||
;; NOTE: this is a troolean nil->no permissions, true->no access, false ->
|
||||
;; has access
|
||||
:locked? (when token-gated?
|
||||
(not can-post?))
|
||||
:id id}]
|
||||
(update-in acc-with-category [category-id :chats] conj categorized-chat))))
|
||||
|
||||
@@ -297,17 +270,14 @@
|
||||
(fn [[_ community-id]]
|
||||
[(re-frame/subscribe [:communities/community community-id])
|
||||
(re-frame/subscribe [:chats/chats])
|
||||
(re-frame/subscribe [:communities/collapsed-categories-for-community community-id])
|
||||
(re-frame/subscribe [:communities/community-channels-permissions community-id])])
|
||||
(fn [[{:keys [categories chats]} full-chats-data collapsed-categories
|
||||
channels-permissions]
|
||||
(re-frame/subscribe [:communities/collapsed-categories-for-community community-id])])
|
||||
(fn [[{:keys [categories chats]} full-chats-data collapsed-categories]
|
||||
[_ community-id]]
|
||||
(let [reduce-fn (reduce-over-categories
|
||||
community-id
|
||||
categories
|
||||
collapsed-categories
|
||||
full-chats-data
|
||||
channels-permissions)
|
||||
full-chats-data)
|
||||
categories-and-chats
|
||||
(->> chats
|
||||
(reduce reduce-fn {})
|
||||
|
||||
@@ -84,26 +84,29 @@
|
||||
:communities
|
||||
{"0x1" {:id "0x1"
|
||||
:chats {"0x1"
|
||||
{:id "0x1"
|
||||
:position 1
|
||||
:name "chat1"
|
||||
:muted? nil
|
||||
:categoryID "1"
|
||||
:can-post? true}
|
||||
{:id "0x1"
|
||||
:position 1
|
||||
:name "chat1"
|
||||
:muted? nil
|
||||
:categoryID "1"
|
||||
:token-gated? false
|
||||
:can-post? true}
|
||||
"0x2"
|
||||
{:id "0x2"
|
||||
:position 2
|
||||
:name "chat2"
|
||||
:muted? nil
|
||||
:categoryID "1"
|
||||
:can-post? false}
|
||||
{:id "0x2"
|
||||
:position 2
|
||||
:name "chat2"
|
||||
:muted? nil
|
||||
:categoryID "1"
|
||||
:token-gated? true
|
||||
:can-post? false}
|
||||
"0x3"
|
||||
{:id "0x3"
|
||||
:position 3
|
||||
:name "chat3"
|
||||
:muted? nil
|
||||
:categoryID "2"
|
||||
:can-post? true}}
|
||||
{:id "0x3"
|
||||
:position 3
|
||||
:name "chat3"
|
||||
:muted? nil
|
||||
:categoryID "2"
|
||||
:token-gated? true
|
||||
:can-post? true}}
|
||||
:categories {"1" {:id "1"
|
||||
:position 2
|
||||
:name "category1"}
|
||||
@@ -112,141 +115,41 @@
|
||||
:name "category2"}}
|
||||
:joined true}})
|
||||
(is
|
||||
(= [["2"
|
||||
{:id "2"
|
||||
:name "category2"
|
||||
:collapsed? nil
|
||||
:position 1
|
||||
:chats [{:name "chat3"
|
||||
:position 3
|
||||
:emoji nil
|
||||
:muted? nil
|
||||
:locked? nil
|
||||
:id "0x3"
|
||||
:unread-messages? false
|
||||
:mentions-count 0}]}]
|
||||
["1"
|
||||
{:id "1"
|
||||
:name "category1"
|
||||
:collapsed? nil
|
||||
:position 2
|
||||
:chats [{:name "chat1"
|
||||
:emoji nil
|
||||
:position 1
|
||||
:muted? nil
|
||||
:locked? nil
|
||||
:id "0x1"
|
||||
:unread-messages? false
|
||||
:mentions-count 0}
|
||||
{:name "chat2"
|
||||
:emoji nil
|
||||
:position 2
|
||||
:muted? nil
|
||||
:locked? nil
|
||||
:id "0x2"
|
||||
:unread-messages? false
|
||||
:mentions-count 0}]}]]
|
||||
(rf/sub [sub-name "0x1"]))))
|
||||
|
||||
(testing "Channels with categories and token permissions"
|
||||
(swap! rf-db/app-db assoc
|
||||
:community-channels-permissions
|
||||
{community-id
|
||||
{(keyword (str community-id "0x100"))
|
||||
{:view-only {:satisfied? false
|
||||
:permissions {:token-permission-id-01 {:criteria [false]}}}
|
||||
:view-and-post {:satisfied? true :permissions {}}}
|
||||
(keyword (str community-id "0x200"))
|
||||
{:view-only {:satisfied? true :permissions {}}
|
||||
:view-and-post {:satisfied? true :permissions {}}}
|
||||
(keyword (str community-id "0x300"))
|
||||
{:view-only {:satisfied? false :permissions {}}
|
||||
:view-and-post {:satisfied? true
|
||||
:permissions {:token-permission-id-03 {:criteria [true]}}}}
|
||||
(keyword (str community-id "0x400"))
|
||||
{:view-only {:satisfied? true
|
||||
:permissions {}}
|
||||
:view-and-post {:satisfied? false
|
||||
:permissions {:token-permission-id-04 {:criteria [false]}}}}}}
|
||||
|
||||
:communities
|
||||
{community-id {:id community-id
|
||||
:chats {"0x100" {:id "0x100"
|
||||
:position 1
|
||||
:name "chat1"
|
||||
:muted? nil
|
||||
:categoryID "1"
|
||||
:can-post? false}
|
||||
"0x200" {:id "0x200"
|
||||
:position 2
|
||||
:name "chat2"
|
||||
:muted? nil
|
||||
:categoryID "1"
|
||||
:can-post? false}
|
||||
"0x300" {:id "0x300"
|
||||
:position 3
|
||||
:name "chat3"
|
||||
:muted? nil
|
||||
:categoryID "2"
|
||||
:can-post? true}
|
||||
"0x400" {:id "0x400"
|
||||
:position 4
|
||||
:name "chat4"
|
||||
:muted? nil
|
||||
:categoryID "2"
|
||||
:can-post? true}}
|
||||
:categories {"1" {:id "1"
|
||||
:position 2
|
||||
:name "category1"}
|
||||
"2" {:id "2"
|
||||
:position 1
|
||||
:name "category2"}}
|
||||
:joined true}})
|
||||
(is
|
||||
(= [["2"
|
||||
{:id "2"
|
||||
:name "category2"
|
||||
:collapsed? nil
|
||||
:position 1
|
||||
:chats [{:name "chat3"
|
||||
:position 3
|
||||
:emoji nil
|
||||
:muted? nil
|
||||
:locked? false
|
||||
:id "0x300"
|
||||
:unread-messages? false
|
||||
:mentions-count 0}
|
||||
{:name "chat4"
|
||||
:position 4
|
||||
:emoji nil
|
||||
:muted? nil
|
||||
:locked? true
|
||||
:id "0x400"
|
||||
:unread-messages? false
|
||||
:mentions-count 0}]}]
|
||||
["1"
|
||||
{:id "1"
|
||||
:name "category1"
|
||||
:collapsed? nil
|
||||
:position 2
|
||||
:chats [{:name "chat1"
|
||||
:emoji nil
|
||||
:position 1
|
||||
:muted? nil
|
||||
:locked? true
|
||||
:id "0x100"
|
||||
:unread-messages? false
|
||||
:mentions-count 0}
|
||||
{:name "chat2"
|
||||
:emoji nil
|
||||
:position 2
|
||||
:muted? nil
|
||||
:locked? nil
|
||||
:id "0x200"
|
||||
:unread-messages? false
|
||||
:mentions-count 0}]}]]
|
||||
(rf/sub [sub-name "0x1"]))))
|
||||
|
||||
(match? [["2"
|
||||
{:id "2"
|
||||
:name "category2"
|
||||
:collapsed? nil
|
||||
:position 1
|
||||
:chats [{:name "chat3"
|
||||
:position 3
|
||||
:emoji nil
|
||||
:muted? nil
|
||||
:locked? false
|
||||
:id "0x3"
|
||||
:unread-messages? false
|
||||
:mentions-count 0}]}]
|
||||
["1"
|
||||
{:id "1"
|
||||
:name "category1"
|
||||
:collapsed? nil
|
||||
:position 2
|
||||
:chats [{:name "chat1"
|
||||
:emoji nil
|
||||
:position 1
|
||||
:muted? nil
|
||||
:locked? nil
|
||||
:id "0x1"
|
||||
:unread-messages? false
|
||||
:mentions-count 0}
|
||||
{:name "chat2"
|
||||
:emoji nil
|
||||
:position 2
|
||||
:muted? nil
|
||||
:locked? true
|
||||
:id "0x2"
|
||||
:unread-messages? false
|
||||
:mentions-count 0}]}]]
|
||||
(rf/sub [sub-name "0x1"]))))
|
||||
(testing "Channels without categories"
|
||||
(swap! rf-db/app-db assoc
|
||||
:communities
|
||||
@@ -274,7 +177,7 @@
|
||||
:name "category2"}}
|
||||
:joined true}})
|
||||
(is
|
||||
(=
|
||||
(match?
|
||||
[[constants/empty-category-id
|
||||
{:name (i18n/label :t/none)
|
||||
:collapsed? nil
|
||||
@@ -323,27 +226,27 @@
|
||||
{"0x10x1" {:unviewed-messages-count 1 :unviewed-mentions-count 2}
|
||||
"0x10x2" {:unviewed-messages-count 0 :unviewed-mentions-count 0}})
|
||||
(is
|
||||
(= [["1"
|
||||
{:name "category1"
|
||||
:id "1"
|
||||
:collapsed? nil
|
||||
:chats [{:name "chat1"
|
||||
:emoji nil
|
||||
:position 1
|
||||
:locked? nil
|
||||
:id "0x1"
|
||||
:muted? nil
|
||||
:unread-messages? true
|
||||
:mentions-count 2}
|
||||
{:name "chat2"
|
||||
:emoji nil
|
||||
:position 2
|
||||
:locked? nil
|
||||
:muted? nil
|
||||
:id "0x2"
|
||||
:unread-messages? false
|
||||
:mentions-count 0}]}]]
|
||||
(rf/sub [sub-name "0x1"])))))
|
||||
(match? [["1"
|
||||
{:name "category1"
|
||||
:id "1"
|
||||
:collapsed? nil
|
||||
:chats [{:name "chat1"
|
||||
:emoji nil
|
||||
:position 1
|
||||
:locked? nil
|
||||
:id "0x1"
|
||||
:muted? nil
|
||||
:unread-messages? true
|
||||
:mentions-count 2}
|
||||
{:name "chat2"
|
||||
:emoji nil
|
||||
:position 2
|
||||
:locked? nil
|
||||
:muted? nil
|
||||
:id "0x2"
|
||||
:unread-messages? false
|
||||
:mentions-count 0}]}]]
|
||||
(rf/sub [sub-name "0x1"])))))
|
||||
|
||||
(h/deftest-sub :communities/my-pending-requests-to-join
|
||||
[sub-name]
|
||||
|
||||
@@ -141,7 +141,6 @@
|
||||
(reg-root-key-sub :communities :communities)
|
||||
(reg-root-key-sub :communities/create :communities/create)
|
||||
(reg-root-key-sub :communities/create-channel :communities/create-channel)
|
||||
(reg-root-key-sub :communities/channels-permissions :community-channels-permissions)
|
||||
(reg-root-key-sub :communities/requests-to-join :communities/requests-to-join)
|
||||
(reg-root-key-sub :communities/community-id-input :communities/community-id-input)
|
||||
(reg-root-key-sub :communities/fetching-community :communities/fetching-community)
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
(ns test-helpers.integration
|
||||
(:require [day8.re-frame.test :as rf-test]
|
||||
[re-frame.core :as rf]))
|
||||
|
||||
(defmacro with-app-initialized
|
||||
[& body]
|
||||
`(do
|
||||
(tests.test-utils/init!)
|
||||
(if (test-helpers.integration/app-initialized)
|
||||
(do ~@body)
|
||||
(do
|
||||
(rf/dispatch [:app-started])
|
||||
(rf-test/wait-for [:profile/get-profiles-overview-success]
|
||||
~@body)))))
|
||||
|
||||
(defmacro with-account
|
||||
[& body]
|
||||
`(if (test-helpers.integration/messenger-started)
|
||||
(do ~@body)
|
||||
(do
|
||||
(test-helpers.integration/create-multiaccount!)
|
||||
(rf-test/wait-for [:messenger-started]
|
||||
(test-helpers.integration/assert-messenger-started)
|
||||
~@body))))
|
||||
|
||||
(defmacro with-recovered-account
|
||||
[& body]
|
||||
`(if (test-helpers.integration/messenger-started)
|
||||
(do ~@body)
|
||||
(do
|
||||
(test-helpers.integration/recover-multiaccount!)
|
||||
(rf-test/wait-for
|
||||
[:messenger-started]
|
||||
(test-helpers.integration/enable-testnet!)
|
||||
(test-helpers.integration/assert-messenger-started)
|
||||
~@body))))
|
||||
@@ -1,5 +1,4 @@
|
||||
(ns test-helpers.integration
|
||||
(:require-macros [test-helpers.integration])
|
||||
(:require
|
||||
[cljs.test :refer [is] :as test]
|
||||
legacy.status-im.events
|
||||
@@ -20,14 +19,16 @@
|
||||
[utils.transforms :as transforms]))
|
||||
|
||||
(defn validate-mnemonic
|
||||
[mnemonic on-success on-error]
|
||||
(native-module/validate-mnemonic
|
||||
(security/safe-unmask-data mnemonic)
|
||||
(fn [result]
|
||||
(let [{:keys [error keyUID]} (transforms/json->clj result)]
|
||||
(if (seq error)
|
||||
(when on-error (on-error error))
|
||||
(on-success mnemonic keyUID))))))
|
||||
[mnemonic]
|
||||
(p/create
|
||||
(fn [p-resolve p-reject]
|
||||
(native-module/validate-mnemonic
|
||||
(security/safe-unmask-data mnemonic)
|
||||
(fn [result]
|
||||
(let [{:keys [error keyUID]} (transforms/json->clj result)]
|
||||
(if (seq error)
|
||||
(p-reject error)
|
||||
(p-resolve [mnemonic keyUID]))))))))
|
||||
|
||||
(def default-re-frame-wait-for-timeout-ms
|
||||
"Controls the maximum time allowed to wait for all events to be processed by
|
||||
@@ -153,7 +154,7 @@
|
||||
(-> (wait-for [:messenger-started])
|
||||
(.then #(assert-messenger-started))))))
|
||||
|
||||
(defn integration-test
|
||||
(defn test-async
|
||||
"Runs `f` inside `cljs.test/async` macro in a restorable re-frame checkpoint.
|
||||
|
||||
`f` will be called with one argument, the `done` function exposed by the
|
||||
@@ -175,10 +176,11 @@
|
||||
fail more often.
|
||||
"
|
||||
([test-name f]
|
||||
(integration-test test-name
|
||||
{:fail-fast? true
|
||||
:timeout-ms default-integration-test-timeout-ms}
|
||||
f))
|
||||
(test-async
|
||||
test-name
|
||||
{:fail-fast? true
|
||||
:timeout-ms default-integration-test-timeout-ms}
|
||||
f))
|
||||
([test-name {:keys [fail-fast? timeout-ms]} f]
|
||||
(test/async
|
||||
done
|
||||
@@ -194,6 +196,29 @@
|
||||
(restore-fn)
|
||||
(done))))))))
|
||||
|
||||
(defn test-app-initialization
|
||||
[]
|
||||
(test-async ::initialize-app
|
||||
(fn []
|
||||
(p/do
|
||||
(test-utils/init!)
|
||||
(rf/dispatch [:app-started])
|
||||
;; Use initialize-view because it has the longest avg. time and is
|
||||
;; dispatched by initialize-multiaccounts (last non-view event).
|
||||
(wait-for [:profile/get-profiles-overview-success
|
||||
:font/init-font-file-for-initials-avatar])
|
||||
(assert-app-initialized)))))
|
||||
|
||||
(defn test-account-creation
|
||||
[]
|
||||
(test-async ::create-account
|
||||
(fn []
|
||||
(p/do
|
||||
(setup-app)
|
||||
(setup-account)
|
||||
(logout)
|
||||
(wait-for [::logout/logout-method])))))
|
||||
|
||||
;;;; Fixtures
|
||||
|
||||
(defn fixture-session
|
||||
@@ -246,19 +271,14 @@
|
||||
|
||||
(defn enable-testnet!
|
||||
[]
|
||||
(rf/dispatch [:profile.settings/profile-update :test-networks-enabled?
|
||||
true {}])
|
||||
(rf/dispatch [:profile.settings/profile-update :test-networks-enabled? true {}])
|
||||
(rf/dispatch [:wallet/initialize]))
|
||||
|
||||
(defn recover-multiaccount!
|
||||
[]
|
||||
(let [masked-seed-phrase (security/mask-data (:seed-phrase constants/recovery-account))]
|
||||
(validate-mnemonic
|
||||
masked-seed-phrase
|
||||
(fn [mnemonic key-uid]
|
||||
(rf/dispatch [:onboarding/seed-phrase-validated
|
||||
(security/mask-data mnemonic) key-uid])
|
||||
(rf/dispatch [:pop-to-root :profiles])
|
||||
(rf/dispatch [:profile/profile-selected key-uid])
|
||||
(recover-and-login mnemonic))
|
||||
#())))
|
||||
(p/let [masked-seed-phrase (security/mask-data (:seed-phrase constants/recovery-account))
|
||||
[mnemonic key-uid] (validate-mnemonic masked-seed-phrase)]
|
||||
(rf/dispatch [:onboarding/seed-phrase-validated (security/mask-data mnemonic) key-uid])
|
||||
(rf/dispatch [:pop-to-root :profiles])
|
||||
(rf/dispatch [:profile/profile-selected key-uid])
|
||||
(recover-and-login mnemonic)))
|
||||
|
||||
@@ -1,34 +1,15 @@
|
||||
(ns tests.contract-test.core-test
|
||||
(:require
|
||||
[cljs.test :refer [deftest]]
|
||||
[day8.re-frame.test :as rf-test]
|
||||
legacy.status-im.events
|
||||
[legacy.status-im.multiaccounts.logout.core :as logout]
|
||||
legacy.status-im.subs.root
|
||||
[re-frame.core :as rf]
|
||||
status-im.events
|
||||
status-im.navigation.core
|
||||
status-im.subs.root
|
||||
[test-helpers.integration :as h]
|
||||
[tests.test-utils :as utils.test]))
|
||||
[test-helpers.integration :as h]))
|
||||
|
||||
(deftest initialize-app-test
|
||||
(h/log-headline :initialize-app-test)
|
||||
(rf-test/run-test-async
|
||||
(utils.test/init!)
|
||||
(rf/dispatch [:app-started])
|
||||
(rf-test/wait-for
|
||||
;; use initialize-view because it has the longest avg. time and
|
||||
;; is dispatched by initialize-multiaccounts (last non-view event)
|
||||
[:profile/get-profiles-overview-success]
|
||||
(rf-test/wait-for
|
||||
[:font/init-font-file-for-initials-avatar]
|
||||
(h/assert-app-initialized)))))
|
||||
(h/test-app-initialization))
|
||||
|
||||
(deftest create-account-test
|
||||
(h/log-headline :create-account-test)
|
||||
(rf-test/run-test-async
|
||||
(h/with-app-initialized
|
||||
(h/with-account
|
||||
(h/logout)
|
||||
(rf-test/wait-for [::logout/logout-method])))))
|
||||
(h/test-account-creation))
|
||||
|
||||
@@ -1,25 +1,19 @@
|
||||
(ns tests.contract-test.profile-test
|
||||
(:require
|
||||
[cljs.test :refer [deftest is]]
|
||||
[day8.re-frame.test :as rf-test]
|
||||
[cljs.test :refer [deftest is use-fixtures]]
|
||||
legacy.status-im.events
|
||||
[legacy.status-im.multiaccounts.logout.core :as logout]
|
||||
legacy.status-im.subs.root
|
||||
[promesa.core :as p]
|
||||
status-im.events
|
||||
status-im.navigation.core
|
||||
status-im.subs.root
|
||||
[test-helpers.integration :as h]
|
||||
[tests.contract-test.utils :as contract-utils]))
|
||||
|
||||
(use-fixtures :each (h/fixture-session))
|
||||
|
||||
(deftest profile-set-bio-contract
|
||||
(h/log-headline :contract/wakuext_setBio)
|
||||
(rf-test/run-test-async
|
||||
(h/with-app-initialized
|
||||
(h/with-account
|
||||
(contract-utils/call-rpc-endpoint
|
||||
{:rpc-endpoint "wakuext_setBio"
|
||||
:params ["new bio"]
|
||||
:on-error #(is (nil? %) "Set bio RPC call should have succeeded")})
|
||||
(h/logout)
|
||||
(rf-test/wait-for
|
||||
[::logout/logout-method])))))
|
||||
(h/test-async :contract/wakuext_setBio
|
||||
(fn []
|
||||
(-> (contract-utils/call-rpc "wakuext_setBio" "new bio")
|
||||
(p/catch #(is (nil? %) "Set bio RPC call should have succeeded"))))))
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
(ns tests.contract-test.utils
|
||||
(:require
|
||||
[promesa.core :as p]
|
||||
[status-im.common.json-rpc.events :as rpc-events]
|
||||
[utils.number]))
|
||||
|
||||
(defn call-rpc-endpoint
|
||||
[{:keys [rpc-endpoint
|
||||
params
|
||||
action
|
||||
on-error]}]
|
||||
(rpc-events/call {:method rpc-endpoint
|
||||
:params params
|
||||
:on-success action
|
||||
:on-error on-error}))
|
||||
(defn call-rpc
|
||||
[method & args]
|
||||
(p/create
|
||||
(fn [p-resolve p-reject]
|
||||
(rpc-events/call {:method method
|
||||
:params args
|
||||
:on-success p-resolve
|
||||
:on-error p-reject}))))
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
(ns tests.contract-test.wallet-test
|
||||
(:require
|
||||
[cljs.test :refer [deftest is]]
|
||||
[day8.re-frame.test :as rf-test]
|
||||
[cljs.test :refer [deftest is use-fixtures]]
|
||||
legacy.status-im.events
|
||||
[legacy.status-im.multiaccounts.logout.core :as logout]
|
||||
legacy.status-im.subs.root
|
||||
[native-module.core :as native-module]
|
||||
[promesa.core :as p]
|
||||
[status-im.common.emoji-picker.utils :as emoji-picker.utils]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.contexts.wallet.data-store :as data-store]
|
||||
@@ -16,23 +15,19 @@
|
||||
[tests.contract-test.utils :as contract-utils]
|
||||
[tests.integration-test.constants :as integration-constants]))
|
||||
|
||||
(use-fixtures :each (h/fixture-session))
|
||||
|
||||
(defn assert-accounts-get-accounts
|
||||
[result]
|
||||
(is (true? (some :wallet result)))
|
||||
(is (true? (some :chat result)))
|
||||
(is (= 2 (count result))))
|
||||
|
||||
(deftest accounts-get-accounts-contract
|
||||
(h/log-headline :contract/accounts-get-accounts)
|
||||
(rf-test/run-test-async
|
||||
(h/with-app-initialized
|
||||
(h/with-account
|
||||
(contract-utils/call-rpc-endpoint
|
||||
{:rpc-endpoint "accounts_getAccounts"
|
||||
:action assert-accounts-get-accounts})
|
||||
(h/logout)
|
||||
(rf-test/wait-for
|
||||
[::logout/logout-method])))))
|
||||
(deftest accounts-get-accounts-contract-test
|
||||
(h/test-async :contract/accounts-get-accounts
|
||||
(fn []
|
||||
(p/let [result (contract-utils/call-rpc "accounts_getAccounts")]
|
||||
(assert-accounts-get-accounts result)))))
|
||||
|
||||
(defn get-default-account
|
||||
[accounts]
|
||||
@@ -44,24 +39,16 @@
|
||||
(is (= (:emoji default-account) test-emoji))))
|
||||
|
||||
(deftest accounts-save-accounts-contract
|
||||
(h/log-headline :contract/accounts-save-account)
|
||||
(rf-test/run-test-async
|
||||
(h/with-app-initialized
|
||||
(h/with-account
|
||||
(let [test-emoji (emoji-picker.utils/random-emoji)
|
||||
account (contract-utils/call-rpc-endpoint
|
||||
{:rpc-endpoint "accounts_getAccounts"
|
||||
:action get-default-account})]
|
||||
(contract-utils/call-rpc-endpoint
|
||||
{:rpc-endpoint "accounts_saveAccount"
|
||||
:action identity
|
||||
:params [(data-store/<-account (merge account {:emoji test-emoji}))]})
|
||||
(contract-utils/call-rpc-endpoint
|
||||
{:rpc-endpoint "accounts_getAccounts"
|
||||
:action #(check-emoji-is-updated test-emoji %)})
|
||||
(h/logout)
|
||||
(rf-test/wait-for
|
||||
[::logout/logout-method]))))))
|
||||
(h/test-async :contract/accounts-save-account
|
||||
(fn []
|
||||
(p/let [test-emoji (emoji-picker.utils/random-emoji)
|
||||
account (contract-utils/call-rpc "accounts_getAccounts")
|
||||
default-account (get-default-account account)
|
||||
_ (contract-utils/call-rpc
|
||||
"accounts_saveAccount"
|
||||
(data-store/<-account (merge default-account {:emoji test-emoji})))
|
||||
accounts (contract-utils/call-rpc "accounts_getAccounts")]
|
||||
(check-emoji-is-updated test-emoji accounts)))))
|
||||
|
||||
(def number-of-networks 3)
|
||||
|
||||
@@ -76,16 +63,10 @@
|
||||
(is (some #(= constants/optimism-sepolia-chain-id (get-in % [:Test :chainId])) response)))
|
||||
|
||||
(deftest accounts-get-chains-contract
|
||||
(h/log-headline :contract/wallet_get-ethereum-chains)
|
||||
(rf-test/run-test-async
|
||||
(h/with-app-initialized
|
||||
(h/with-account
|
||||
(contract-utils/call-rpc-endpoint
|
||||
{:rpc-endpoint "wallet_getEthereumChains"
|
||||
:action assert-ethereum-chains})
|
||||
(h/logout)
|
||||
(rf-test/wait-for
|
||||
[::logout/logout-method])))))
|
||||
(h/test-async :contract/wallet_get-ethereum-chains
|
||||
(fn []
|
||||
(p/let [response (contract-utils/call-rpc "wallet_getEthereumChains")]
|
||||
(assert-ethereum-chains response)))))
|
||||
|
||||
(defn get-main-account
|
||||
[accounts]
|
||||
@@ -99,18 +80,18 @@
|
||||
(is (= (:public-key response) (:public-key response)))
|
||||
(is (= "m/43'/60'/1581'/0'/0" (:path (first response)))))
|
||||
|
||||
(deftest wallet-get-derived-addressess-contract
|
||||
(h/log-headline :wallet/create-derived-addresses)
|
||||
(rf-test/run-test-async
|
||||
(h/with-app-initialized
|
||||
(h/with-recovered-account
|
||||
(let [sha3-pwd (native-module/sha3 test-password)
|
||||
derivation-path ["m/43'/60'/1581'/0'/0"]
|
||||
main-account (contract-utils/call-rpc-endpoint
|
||||
{:rpc-endpoint "accounts_getAccounts"
|
||||
:action get-main-account})]
|
||||
|
||||
(contract-utils/call-rpc-endpoint
|
||||
{:rpc-endpoint "wallet_getDerivedAddresses"
|
||||
:params [sha3-pwd main-account derivation-path]
|
||||
:action assert-derived-account}))))))
|
||||
(deftest wallet-get-derived-addressess-contract-test
|
||||
(h/test-async :wallet/create-derived-addresses
|
||||
(fn []
|
||||
(p/let [_ (h/enable-testnet!)
|
||||
_ (h/recover-multiaccount!)
|
||||
sha3-pwd (native-module/sha3 test-password)
|
||||
derivation-path ["m/43'/60'/1581'/0'/0"]
|
||||
accounts (contract-utils/call-rpc "accounts_getAccounts")
|
||||
main-account (get-main-account accounts)
|
||||
response (contract-utils/call-rpc
|
||||
"wallet_getDerivedAddresses"
|
||||
sha3-pwd
|
||||
main-account
|
||||
derivation-path)]
|
||||
(assert-derived-account response)))))
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"0x0402905bed83f0bbf993cee8239012ccb1a8bc86907ead834c1e38476a0eda71414eed0e25f525f270592a2eebb01c9119a4ed6429ba114e51f5cb0a28dae1adfd")
|
||||
|
||||
(deftest one-to-one-chat-test
|
||||
(h/integration-test ::one-to-one-chat
|
||||
(h/test-async ::one-to-one-chat
|
||||
(fn []
|
||||
(p/do
|
||||
(rf/dispatch-sync [:chat.ui/start-chat chat-id])
|
||||
@@ -26,7 +26,7 @@
|
||||
(is (= chat-id @(rf/subscribe [:chats/current-chat-id])))))))
|
||||
|
||||
(deftest delete-chat-test
|
||||
(h/integration-test ::delete-chat
|
||||
(h/test-async ::delete-chat
|
||||
(fn []
|
||||
(p/do
|
||||
(rf/dispatch-sync [:chat.ui/start-chat chat-id])
|
||||
@@ -38,7 +38,7 @@
|
||||
(rf/dispatch-sync [:chat.ui/remove-chat chat-id])))))
|
||||
|
||||
(deftest mute-chat-test
|
||||
(h/integration-test ::mute-chat
|
||||
(h/test-async ::mute-chat
|
||||
(fn []
|
||||
(p/do
|
||||
(rf/dispatch-sync [:chat.ui/start-chat chat-id])
|
||||
@@ -57,7 +57,7 @@
|
||||
(is (not @(rf/subscribe [:chats/muted chat-id])))))))
|
||||
|
||||
(deftest add-contact-test
|
||||
(h/integration-test ::add-contact
|
||||
(h/test-async ::add-contact
|
||||
(fn []
|
||||
(let [compressed-key "zQ3shMwgSMKHVznoowceZMxWde9HUnkQEVSGvvex8UFpFNErL"
|
||||
public-key (str "0x0407e9dc435fe366cb0b4c4f35cbd925438c0f46fe0"
|
||||
|
||||
@@ -2,33 +2,14 @@
|
||||
(:require
|
||||
[cljs.test :refer [deftest]]
|
||||
legacy.status-im.events
|
||||
[legacy.status-im.multiaccounts.logout.core :as logout]
|
||||
legacy.status-im.subs.root
|
||||
[promesa.core :as p]
|
||||
[re-frame.core :as rf]
|
||||
status-im.events
|
||||
status-im.navigation.core
|
||||
status-im.subs.root
|
||||
[test-helpers.integration :as h]
|
||||
[tests.test-utils :as test-utils]))
|
||||
[test-helpers.integration :as h]))
|
||||
|
||||
(deftest initialize-app-test
|
||||
(h/integration-test ::initialize-app
|
||||
(fn []
|
||||
(p/do
|
||||
(test-utils/init!)
|
||||
(rf/dispatch [:app-started])
|
||||
;; Use initialize-view because it has the longest avg. time and is
|
||||
;; dispatched by initialize-multiaccounts (last non-view event).
|
||||
(h/wait-for [:profile/get-profiles-overview-success
|
||||
:font/init-font-file-for-initials-avatar])
|
||||
(h/assert-app-initialized)))))
|
||||
(h/test-app-initialization))
|
||||
|
||||
(deftest create-account-test
|
||||
(h/integration-test ::create-account
|
||||
(fn []
|
||||
(p/do
|
||||
(h/setup-app)
|
||||
(h/setup-account)
|
||||
(h/logout)
|
||||
(h/wait-for [::logout/logout-method])))))
|
||||
(h/test-account-creation))
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
(use-fixtures :each (h/fixture-session))
|
||||
|
||||
(deftest edit-profile-name-test
|
||||
(h/integration-test ::edit-profile-name
|
||||
(h/test-async ::edit-profile-name
|
||||
(fn []
|
||||
(let [new-name "John Doe"]
|
||||
(p/do
|
||||
@@ -21,7 +21,7 @@
|
||||
(is (= new-name display-name))))))))
|
||||
|
||||
(deftest edit-profile-picture-test
|
||||
(h/integration-test ::edit-profile-picture
|
||||
(h/test-async ::edit-profile-picture
|
||||
(fn []
|
||||
(let [mock-image "resources/images/mock2/monkey.png"
|
||||
absolute-path (.resolve test-utils/path mock-image)]
|
||||
@@ -32,7 +32,7 @@
|
||||
(is (not (nil? (:images profile))))))))))
|
||||
|
||||
(deftest delete-profile-picture-test
|
||||
(h/integration-test ::delete-profile-picture
|
||||
(h/test-async ::delete-profile-picture
|
||||
(fn []
|
||||
(p/do
|
||||
(rf/dispatch [:profile/delete-picture])
|
||||
@@ -41,7 +41,7 @@
|
||||
(is (nil? (:image profile))))))))
|
||||
|
||||
(deftest edit-profile-bio-test
|
||||
(h/integration-test ::edit-profile-bio
|
||||
(h/test-async ::edit-profile-bio
|
||||
(fn []
|
||||
(let [new-bio "New bio text"]
|
||||
(p/do
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"_comment": "Instead use: scripts/update-status-go.sh <rev>",
|
||||
"owner": "status-im",
|
||||
"repo": "status-go",
|
||||
"version": "v0.176.8",
|
||||
"commit-sha1": "8c0e24dc260031a822604f7a2771f22daea79aa4",
|
||||
"src-sha256": "1c6r5gizcvixawl1c0lxd022wg54m5qxw8rr1q1i9221pzbfmmsl"
|
||||
"version": "v0.176.9",
|
||||
"commit-sha1": "a3ad05db58b8af20bf6b7ab852f8603212abac4e",
|
||||
"src-sha256": "10c05kgihczbams3i8fkfm1mh5m61bnsjh255c22m4awc073rg12"
|
||||
}
|
||||
|
||||
@@ -94,6 +94,8 @@
|
||||
"blank-keycard-text": "You can proceed with your keycard once you've generated your keys and name",
|
||||
"blank-keycard-title": "Looks like you’ve tapped \na blank keycard",
|
||||
"block": "Block",
|
||||
"user-blocked": "{{username}} blocked",
|
||||
"user-unblocked": "{{username}} unblocked",
|
||||
"unblock": "Unblock",
|
||||
"block-contact": "Block this user",
|
||||
"block-contact-details": "Blocking will delete this user's previous messages and stop new ones from reaching you",
|
||||
@@ -2065,6 +2067,8 @@
|
||||
"rename": "Rename",
|
||||
"mark-untrustworthy": "Mark as Untrustworthy",
|
||||
"block-user": "Block user",
|
||||
"block-user-title-message": "You will not see {{username}}'s messages, but {{username}} still can see your messages in mutual group chats and communities. {{username}} will be unable to message you.",
|
||||
"blocking-a-user-message": "Blocking a user purges the database of all messages that you’ve previously received from {{username}} in all contexts.",
|
||||
"group-details": "Group details",
|
||||
"edit-name-and-image": "Edit name and image",
|
||||
"change-group-privacy": "Change group privacy",
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user