diff --git a/.env b/.env index bf57c6f301..28a2ed5a02 100644 --- a/.env +++ b/.env @@ -17,3 +17,4 @@ PFS_ENCRYPTION_ENABLED=1 DEV_BUILD=1 ERC20_CONTRACT_WARNINGS=1 MAILSERVER_CONFIRMATIONS_ENABLED=0 +STICKERS_ENABLED=1 diff --git a/.env.e2e b/.env.e2e index 84c095388a..6d796bc34c 100644 --- a/.env.e2e +++ b/.env.e2e @@ -13,3 +13,4 @@ EXTENSIONS=1 PFS_ENCRYPTION_ENABLED=1 ERC20_CONTRACT_WARNINGS=1 MAILSERVER_CONFIRMATIONS_ENABLED=0 +STICKERS_ENABLED=0 \ No newline at end of file diff --git a/.env.jenkins b/.env.jenkins index 39efc88bd8..aec02d6ebd 100644 --- a/.env.jenkins +++ b/.env.jenkins @@ -17,3 +17,4 @@ PAIRING_ENABLED=1 ERC20_CONTRACT_WARNINGS=1 MAILSERVER_CONFIRMATIONS_ENABLED=0 HARDWALLET_ENABLED=0 +STICKERS_ENABLED=1 diff --git a/.env.nightly b/.env.nightly index 8afa36234c..eec6d6a11b 100644 --- a/.env.nightly +++ b/.env.nightly @@ -15,3 +15,4 @@ EXTENSIONS=1 PFS_ENCRYPTION_ENABLED=1 ERC20_CONTRACT_WARNINGS=1 MAILSERVER_CONFIRMATIONS_ENABLED=0 +STICKERS_ENABLED=0 diff --git a/.env.nightly.staging.fleet b/.env.nightly.staging.fleet index e7c76eaba1..d415e7f2cf 100644 --- a/.env.nightly.staging.fleet +++ b/.env.nightly.staging.fleet @@ -14,3 +14,4 @@ EXTENSIONS=1 PFS_ENCRYPTION_ENABLED=1 ERC20_CONTRACT_WARNINGS=1 MAILSERVER_CONFIRMATIONS_ENABLED=0 +STICKERS_ENABLED=0 diff --git a/.env.prod b/.env.prod index d3b1de8bcf..b65108bc69 100644 --- a/.env.prod +++ b/.env.prod @@ -15,3 +15,4 @@ MAINNET_WARNING_ENABLED=1 EXTENSIONS=1 PFS_ENCRYPTION_ENABLED=1 ERC20_CONTRACT_WARNINGS=0 +STICKERS_ENABLED=0 diff --git a/resources/icons/clock.svg b/resources/icons/clock.svg new file mode 100644 index 0000000000..41da8c8f61 --- /dev/null +++ b/resources/icons/clock.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/icons/stickers.svg b/resources/icons/stickers.svg new file mode 100644 index 0000000000..3dc70ca074 --- /dev/null +++ b/resources/icons/stickers.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/icons/stickers_big.svg b/resources/icons/stickers_big.svg new file mode 100644 index 0000000000..1ef6966d96 --- /dev/null +++ b/resources/icons/stickers_big.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/status_im/accounts/core.cljs b/src/status_im/accounts/core.cljs index 3c74fcf02d..4694c5ee5c 100644 --- a/src/status_im/accounts/core.cljs +++ b/src/status_im/accounts/core.cljs @@ -82,3 +82,13 @@ (accounts.update/update-settings cofx (assoc settings :web3-opt-in? opt-in) {}))) + +(fx/defn update-recent-stickers [cofx stickers] + (accounts.update/account-update cofx + {:recent-stickers stickers} + {})) + +(fx/defn update-stickers [cofx stickers] + (accounts.update/account-update cofx + {:stickers stickers} + {})) diff --git a/src/status_im/chat/models/input.cljs b/src/status_im/chat/models/input.cljs index f90b99927f..8f992e7d1a 100644 --- a/src/status_im/chat/models/input.cljs +++ b/src/status_im/chat/models/input.cljs @@ -147,6 +147,15 @@ :content (cond-> {:chat-id current-chat-id :text message-text})}))) +(fx/defn send-sticker-fx + [{:keys [db] :as cofx} uri current-chat-id] + (when-not (string/blank? uri) + (chat.message/send-message cofx {:chat-id current-chat-id + :content-type constants/content-type-sticker + :content (cond-> {:chat-id current-chat-id + :uri uri + :text "Update to latest version to see a nice sticker here!"})}))) + (fx/defn send-current-message "Sends message from current chat input" [{{:keys [current-chat-id id->command access-scope->command-id] :as db} :db :as cofx}] diff --git a/src/status_im/constants.cljs b/src/status_im/constants.cljs index 493bcdb5bf..2774d735fa 100644 --- a/src/status_im/constants.cljs +++ b/src/status_im/constants.cljs @@ -7,6 +7,7 @@ (def ethereum-rpc-url "http://localhost:8545") (def content-type-text "text/plain") +(def content-type-sticker "sticker") (def content-type-status "status") (def content-type-command "command") (def content-type-command-request "command-request") diff --git a/src/status_im/data_store/accounts.cljs b/src/status_im/data_store/accounts.cljs index 8fb1189992..c480da7be6 100644 --- a/src/status_im/data_store/accounts.cljs +++ b/src/status_im/data_store/accounts.cljs @@ -56,7 +56,9 @@ (update :settings core/serialize) (update :extensions serialize-extensions) (update :bootnodes serialize-bootnodes) - (update :networks serialize-networks))) + (update :networks serialize-networks) + (update :recent-stickers #(if (nil? %) [] %)) + (update :stickers #(if (nil? %) [] %)))) (defn save-account-tx "Returns tx function for saving account" diff --git a/src/status_im/data_store/realm/schemas/account/core.cljs b/src/status_im/data_store/realm/schemas/account/core.cljs index 2466b017bc..b79431a247 100644 --- a/src/status_im/data_store/realm/schemas/account/core.cljs +++ b/src/status_im/data_store/realm/schemas/account/core.cljs @@ -14,8 +14,7 @@ [status-im.data-store.realm.schemas.account.membership-update :as membership-update] [status-im.data-store.realm.schemas.account.installation :as installation] [status-im.data-store.realm.schemas.account.contact-recovery :as contact-recovery] - [status-im.data-store.realm.schemas.account.migrations :as migrations] - [taoensso.timbre :as log])) + [status-im.data-store.realm.schemas.account.migrations :as migrations])) (def v1 [chat/v1 transport/v1 @@ -471,4 +470,4 @@ :migration (constantly nil)} {:schema v33 :schemaVersion 33 - :migration (constantly nil)}]) + :migration (constantly nil)}]) \ No newline at end of file diff --git a/src/status_im/data_store/realm/schemas/base/account.cljs b/src/status_im/data_store/realm/schemas/base/account.cljs index 74be175125..008b465d83 100644 --- a/src/status_im/data_store/realm/schemas/base/account.cljs +++ b/src/status_im/data_store/realm/schemas/base/account.cljs @@ -225,3 +225,8 @@ (def v18 (assoc-in v17 [:properties :installation-name] {:type :string :optional true})) + +(def v19 (update v18 :properties merge {:stickers + {:type "string[]" :optional true} + :recent-stickers + {:type "string[]" :optional true}})) \ No newline at end of file diff --git a/src/status_im/data_store/realm/schemas/base/core.cljs b/src/status_im/data_store/realm/schemas/base/core.cljs index 013d4a9e7d..b18990a9d6 100644 --- a/src/status_im/data_store/realm/schemas/base/core.cljs +++ b/src/status_im/data_store/realm/schemas/base/core.cljs @@ -86,6 +86,11 @@ extension/v12 account/v18]) +(def v23 [network/v1 + bootnode/v4 + extension/v12 + account/v19]) + ;; put schemas ordered by version (def schemas [{:schema v1 :schemaVersion 1 @@ -152,4 +157,7 @@ :migration migrations/v21} {:schema v22 :schemaVersion 22 + :migration (constantly nil)} + {:schema v23 + :schemaVersion 23 :migration (constantly nil)}]) diff --git a/src/status_im/events.cljs b/src/status_im/events.cljs index e188f37410..aaad93951a 100644 --- a/src/status_im/events.cljs +++ b/src/status_im/events.cljs @@ -48,7 +48,9 @@ [status-im.utils.datetime :as time] [status-im.chat.commands.core :as commands] [status-im.chat.models.loading :as chat-loading] - [status-im.node.core :as node])) + [status-im.node.core :as node] + [cljs.reader :as edn] + [status-im.stickers.core :as stickers])) ;; init module @@ -775,6 +777,14 @@ (fn [{{:keys [current-chat-id]} :db :as cofx} [_ message-text]] (chat.input/send-plain-text-message-fx cofx message-text current-chat-id))) +(handlers/register-handler-fx + :chat/send-sticker + (fn [{{:keys [current-chat-id] :account/keys [account]} :db :as cofx} [_ {:keys [uri]}]] + (fx/merge + cofx + (accounts/update-recent-stickers (conj (remove #(= uri %) (:recent-stickers account)) uri)) + (chat.input/send-sticker-fx uri current-chat-id)))) + (handlers/register-handler-fx :chat/disable-cooldown (fn [cofx _] @@ -1573,3 +1583,30 @@ [(re-frame/inject-cofx :random-id-generator)] (fn [cofx [_ public-key]] (contact-recovery/show-contact-recovery-message cofx public-key))) + +(handlers/register-handler-fx + :stickers/load-sticker-pack-success + (fn [{:keys [db]} [_ edn-string]] + (let [{{:keys [id] :as pack} 'meta} (edn/read-string edn-string)] + {:db (-> db (assoc-in [:stickers/packs id] (assoc pack :edn edn-string)))}))) + +(handlers/register-handler-fx + :stickers/install-pack + (fn [cofx [_ id]] + (stickers/install-stickers-pack cofx id))) + +(handlers/register-handler-fx + :stickers/load-packs + (fn [_ _] + {;;TODO request list of packs from contract + :http-get-n (mapv (fn [uri] {:url uri + :success-event-creator (fn [o] + [:stickers/load-sticker-pack-success o]) + :failure-event-creator (fn [o] nil)}) + ;;TODO for testing ONLY + ["https://ipfs.infura.io/ipfs/QmbgsCFEz4ubLFzF3SFfCxDEeXeMxe4yypxC3W1Ro9rLXS/"])})) + +(handlers/register-handler-fx + :stickers/select-pack + (fn [{:keys [db]} [_ id]] + {:db (assoc db :stickers/selected-pack id)})) \ No newline at end of file diff --git a/src/status_im/init/core.cljs b/src/status_im/init/core.cljs index 1fde99c652..abe5467926 100644 --- a/src/status_im/init/core.cljs +++ b/src/status_im/init/core.cljs @@ -31,7 +31,8 @@ [taoensso.timbre :as log] [status-im.utils.fx :as fx] [status-im.chat.models :as chat-model] - [status-im.accounts.db :as accounts.db])) + [status-im.accounts.db :as accounts.db] + [status-im.stickers.core :as stickers])) (defn init-store! "Try to decrypt the database, move on if successful otherwise go back to @@ -231,9 +232,9 @@ #(when (dev-mode? %) (models.dev-server/start)) (browser/initialize-browsers) - (browser/initialize-dapp-permissions) (extensions.registry/initialize) + (stickers/init-stickers-packs) (accounts.update/update-sign-in-time) #(when-not (or (creating-account? %) (finishing-hardwallet-setup? %)) diff --git a/src/status_im/stickers/core.cljs b/src/status_im/stickers/core.cljs new file mode 100644 index 0000000000..3aee21f0cc --- /dev/null +++ b/src/status_im/stickers/core.cljs @@ -0,0 +1,17 @@ +(ns status-im.stickers.core + (:require [status-im.utils.fx :as fx] + [cljs.reader :as edn] + [status-im.accounts.core :as accounts])) + +(fx/defn init-stickers-packs [{:keys [db]}] + (let [sticker-packs (map #(get (edn/read-string %) 'meta) (get-in db [:account/account :stickers]))] + {:db (assoc db :stickers/packs-installed (into {} (map #(vector (:id %) %) sticker-packs)))})) + +(fx/defn install-stickers-pack [{{:account/keys [account] :as db} :db :as cofx} id] + (let [pack (get-in db [:stickers/packs id])] + (fx/merge + cofx + {:db (-> db + (assoc-in [:stickers/packs-installed id] pack) + (assoc :stickers/selected-pack id))} + (accounts/update-stickers (conj (:stickers account) (:edn pack)))))) \ No newline at end of file diff --git a/src/status_im/transport/db.cljs b/src/status_im/transport/db.cljs index 43ec7ff2db..70cde0f136 100644 --- a/src/status_im/transport/db.cljs +++ b/src/status_im/transport/db.cljs @@ -3,7 +3,8 @@ (:require [cljs.spec.alpha :as spec] [clojure.string :as s] status-im.contact.db - [status-im.utils.clocks :as utils.clocks])) + [status-im.utils.clocks :as utils.clocks] + [status-im.constants :as constants])) ;; required (spec/def ::ack (spec/coll-of string? :kind vector?)) @@ -60,9 +61,11 @@ (spec/def :message.content/response-to string?) (spec/def :message.content/response-to-v2 string?) (spec/def :message.content/command-path (spec/tuple string? (spec/coll-of (spec/or :scope keyword? :chat-id string?) :kind set? :min-count 1))) +(spec/def :message.content/uri (spec/and string? (complement s/blank?))) (spec/def :message.content/params (spec/map-of keyword? any?)) -(spec/def ::content-type #{"text/plain" "command" "command-request"}) +(spec/def ::content-type #{constants/content-type-text constants/content-type-command + constants/content-type-command-request constants/content-type-sticker}) (spec/def ::message-type #{:group-user-message :public-group-user-message :user-message}) (spec/def ::clock-value (spec/and pos-int? utils.clocks/safe-timestamp?)) @@ -95,16 +98,22 @@ :req-opt [:message.content/response-to])) (spec/def :message.command/content (spec/keys :req-un [:message.content/command-path :message.content/params])) +(spec/def :message.sticker/content (spec/keys :req-un [:message.content/uri])) + (defmulti content-type :content-type) -(defmethod content-type "command" [_] +(defmethod content-type constants/content-type-command [_] (spec/merge :message/message-common (spec/keys :req-un [:message.command/content]))) -(defmethod content-type "command-request" [_] +(defmethod content-type constants/content-type-command-request [_] (spec/merge :message/message-common (spec/keys :req-un [:message.command/content]))) +(defmethod content-type constants/content-type-sticker [_] + (spec/merge :message/message-common + (spec/keys :req-un [:message.sticker/content]))) + (defmethod content-type :default [_] (spec/merge :message/message-common (spec/keys :req-un [:message.text/content]))) diff --git a/src/status_im/transport/message/protocol.cljs b/src/status_im/transport/message/protocol.cljs index 685e2034ac..d78133459d 100644 --- a/src/status_im/transport/message/protocol.cljs +++ b/src/status_im/transport/message/protocol.cljs @@ -122,7 +122,7 @@ (validate [this] (if (spec/valid? :message/message this) this - (log/warn "failed to validate Message" (spec/explain :message/message this))))) + (log/warn "failed to validate Message" (spec/explain-str :message/message this))))) (defrecord MessagesSeen [message-ids] StatusMessage diff --git a/src/status_im/ui/components/icons/vector_icons.cljs b/src/status_im/ui/components/icons/vector_icons.cljs index c1c98c2722..7e5b12b53f 100644 --- a/src/status_im/ui/components/icons/vector_icons.cljs +++ b/src/status_im/ui/components/icons/vector_icons.cljs @@ -104,7 +104,8 @@ :icons/reply (js/require "./resources/icons/reply.svg") :icons/indicator-big (js/require "./resources/icons/indicator-big.svg") :icons/indicator-middle (js/require "./resources/icons/indicator-middle.svg") - :icons/indicator-small (js/require "./resources/icons/indicator-small.svg")} + :icons/indicator-small (js/require "./resources/icons/indicator-small.svg") + :icons/stickers (js/require "./resources/icons/stickers.svg")} {:icons/discover (components.svg/slurp-svg "./resources/icons/bottom/discover_gray.svg") :icons/contacts (components.svg/slurp-svg "./resources/icons/bottom/contacts_gray.svg") :icons/home (components.svg/slurp-svg "./resources/icons/bottom/home_gray.svg") @@ -185,7 +186,10 @@ :icons/reply (components.svg/slurp-svg "./resources/icons/reply.svg") :icons/indicator-big (components.svg/slurp-svg "./resources/icons/indicator-big.svg") :icons/indicator-middle (components.svg/slurp-svg "./resources/icons/indicator-middle.svg") - :icons/indicator-small (components.svg/slurp-svg "./resources/icons/indicator-small.svg")})) + :icons/indicator-small (components.svg/slurp-svg "./resources/icons/indicator-small.svg") + :icons/stickers (components.svg/slurp-svg "./resources/icons/stickers.svg") + :icons/stickers-big (components.svg/slurp-svg "./resources/icons/stickers_big.svg") + :icons/clock (components.svg/slurp-svg "./resources/icons/clock.svg")})) (defn normalize-property-name [n] (if (= n :icons/options) diff --git a/src/status_im/ui/screens/chat/input/input.cljs b/src/status_im/ui/screens/chat/input/input.cljs index 979c4e2e0c..c75f8e12b3 100644 --- a/src/status_im/ui/screens/chat/input/input.cljs +++ b/src/status_im/ui/screens/chat/input/input.cljs @@ -18,7 +18,9 @@ [status-im.ui.components.icons.vector-icons :as vector-icons] [status-im.utils.platform :as platform] [status-im.utils.gfycat.core :as gfycat] - [status-im.utils.utils :as utils])) + [status-im.utils.utils :as utils] + [status-im.utils.config :as config] + [status-im.ui.screens.chat.stickers.views :as stickers])) (defview basic-text-input [{:keys [set-container-width-fn height single-line-input?]}] (letsubs [{:keys [input-text]} [:chats/current-chat] @@ -32,6 +34,7 @@ :editable (not cooldown-enabled?) :blur-on-submit false :on-focus #(re-frame/dispatch [:chat.ui/set-chat-ui-props {:input-focused? true + :show-stickers? false :messages-focused? false}]) :on-blur #(re-frame/dispatch [:chat.ui/set-chat-ui-props {:input-focused? false}]) :on-submit-editing #(when single-line-input? @@ -130,7 +133,8 @@ (defview input-container [] (letsubs [margin [:chats/input-margin] {:keys [input-text]} [:chats/current-chat] - result-box [:chats/current-chat-ui-prop :result-box]] + result-box [:chats/current-chat-ui-prop :result-box] + show-stickers? [:chats/current-chat-ui-prop :show-stickers?]] (let [single-line-input? (:singleLineInput result-box)] [react/view {:style (style/root margin) :on-layout #(let [h (-> (.-nativeEvent %) @@ -141,6 +145,8 @@ [reply-message-view] [react/view {:style style/input-container} [input-view {:single-line-input? single-line-input?}] + (when (and config/stickers-enabled? (string/blank? input-text)) + [stickers/button show-stickers?]) (if (string/blank? input-text) [commands-button] [send-button/send-button-view])]]))) diff --git a/src/status_im/ui/screens/chat/message/message.cljs b/src/status_im/ui/screens/chat/message/message.cljs index 63f8858058..1c98bf5eb9 100644 --- a/src/status_im/ui/screens/chat/message/message.cljs +++ b/src/status_im/ui/screens/chat/message/message.cljs @@ -137,6 +137,12 @@ [wrapper message] [wrapper message [emoji-message message]]) +(defmethod message-content constants/content-type-sticker + [wrapper {:keys [content] :as message}] + [wrapper message + [react/image {:style {:margin 10 :width 100 :height 100} + :source {:uri (:uri content)}}]]) + (defmethod message-content :default [wrapper {:keys [content-type] :as message}] [wrapper message diff --git a/src/status_im/ui/screens/chat/stickers/styles.cljs b/src/status_im/ui/screens/chat/stickers/styles.cljs new file mode 100644 index 0000000000..cbfefa79ee --- /dev/null +++ b/src/status_im/ui/screens/chat/stickers/styles.cljs @@ -0,0 +1,13 @@ +(ns status-im.ui.screens.chat.stickers.styles) + +(def stickers-panel {:flex 1 :margin 5 :flex-direction :row :justify-content :flex-start :flex-wrap :wrap}) + +(defn pack-icon [background-color icon-size] + {:background-color background-color + :margin-vertical 5 + :margin-horizontal 8 + :height icon-size + :width icon-size + :border-radius (/ icon-size 2) + :align-items :center + :justify-content :center}) \ No newline at end of file diff --git a/src/status_im/ui/screens/chat/stickers/subs.cljs b/src/status_im/ui/screens/chat/stickers/subs.cljs new file mode 100644 index 0000000000..61575e37e5 --- /dev/null +++ b/src/status_im/ui/screens/chat/stickers/subs.cljs @@ -0,0 +1,9 @@ +(ns status-im.ui.screens.chat.stickers.subs + (:require [re-frame.core :as re-frame] + status-im.ui.screens.extensions.add.subs)) + +(re-frame/reg-sub + :stickers/selected-pack + (fn [db] + (get db :stickers/selected-pack))) + diff --git a/src/status_im/ui/screens/chat/stickers/views.cljs b/src/status_im/ui/screens/chat/stickers/views.cljs new file mode 100644 index 0000000000..ec4ce62c5d --- /dev/null +++ b/src/status_im/ui/screens/chat/stickers/views.cljs @@ -0,0 +1,88 @@ +(ns status-im.ui.screens.chat.stickers.views + (:require-macros [status-im.utils.views :refer [defview letsubs]]) + (:require [re-frame.core :as re-frame] + [status-im.ui.components.react :as react] + [status-im.ui.components.icons.vector-icons :as vector-icons] + [status-im.ui.components.colors :as colors] + [status-im.i18n :as i18n] + [status-im.ui.screens.chat.stickers.styles :as styles])) + +(defn button [show-stickers?] + [react/touchable-highlight + {:on-press (fn [_] + (re-frame/dispatch [:chat.ui/set-chat-ui-props {:show-stickers? (not show-stickers?)}]) + (react/dismiss-keyboard!))} + [vector-icons/icon :icons/stickers {:container-style {:margin 14 :margin-right 6} + :color (if show-stickers? colors/blue colors/gray)}]]) + +(defn- no-stickers-yet-panel [] + [react/view {:style {:flex 1 :align-items :center :justify-content :center}} + [vector-icons/icon :icons/stickers-big {:color colors/gray}] + [react/text {:style {:margin-top 8 :font-size 17}} (i18n/label :t/you-dont-have-stickers)] + [react/touchable-highlight {:on-press #(do + (re-frame/dispatch [:stickers/load-packs]) + (re-frame/dispatch [:navigate-to :stickers]))} + [react/text {:style {:margin-top 17 :font-size 15 :color colors/blue}} + (i18n/label :t/get-stickers)]]]) + +(defn- on-sticker-click [sticker] + (re-frame/dispatch [:chat.ui/set-chat-ui-props {:show-stickers? false}]) + (re-frame/dispatch [:chat/send-sticker sticker]) + (react/dismiss-keyboard!)) + +(defn- stickers-panel [stickers] + [react/scroll-view {:style {:flex 1} :condtent-container-style {:flex 1}} + [react/view {:style styles/stickers-panel} + (for [{:keys [uri] :as sticker} stickers] + ^{:key uri} + [react/touchable-highlight {:style {:height 75 :width 75 :margin 5} + :on-press #(on-sticker-click sticker)} + [react/image {:style {:resize-mode :cover :width "100%" :height "100%"} :source {:uri uri}}]])]]) + +(defview recent-stickers-panel [] + (letsubs [stickers [:stickers/recent]] + (if (seq stickers) + [stickers-panel (map #(hash-map :uri %) stickers)] + [react/view {:style {:flex 1 :align-items :center :justify-content :center}} + [vector-icons/icon :icons/stickers-big {:color colors/gray}] + [react/text {:style {:margin-top 8 :font-size 17}} (i18n/label :t/recently-used-stickers)]]))) + +(def icon-size 28) + +(defn pack-icon [{:keys [id on-press selected? background-color] + :or {background-color colors/gray + on-press #(re-frame/dispatch [:stickers/select-pack id])}} icon] + [react/touchable-highlight {:on-press on-press} + [react/view {:style {:align-items :center}} + [react/view {:style (styles/pack-icon background-color icon-size)} + icon] + [react/view {:style {:margin-bottom 5 :height 2 :width 16 + :background-color (if selected? colors/blue colors/white)}}]]]) + +(defn pack-for [packs id] + (some #(when (= id (:id %)) %) packs)) + +(defview stickers-view [] + (letsubs [selected-pack [:stickers/selected-pack] + installed-packs [:stickers/installed-packs-vals]] + [react/view {:style {:background-color :white :height "40%"}} + (cond + (= selected-pack :recent) [recent-stickers-panel] + (not (seq installed-packs)) [no-stickers-yet-panel] + (nil? selected-pack) [recent-stickers-panel] + :else [stickers-panel (:stickers (pack-for installed-packs selected-pack))]) + [react/view {:style {:flex-direction :row :padding-horizontal 4}} + [pack-icon {:on-press #(do + (re-frame/dispatch [:stickers/load-packs]) + (re-frame/dispatch [:navigate-to :stickers])) + :selected? false :background-color colors/blue} + [vector-icons/icon :icons/add {:width 20 :height 20 :color colors/white}]] + [react/view {:width 4}] + [pack-icon {:id :recent :selected? (or (= :recent selected-pack) (and (nil? selected-pack) (seq installed-packs)))} + [vector-icons/icon :icons/clock]] + ;; TODO make scrollable + (for [{:keys [id thumbnail]} installed-packs] + ^{:key id} + [pack-icon {:id id :selected? (= id selected-pack)} + [react/image {:style {:width icon-size :height icon-size :border-radius (/ icon-size 2)} + :source {:uri thumbnail}}]])]])) diff --git a/src/status_im/ui/screens/chat/views.cljs b/src/status_im/ui/screens/chat/views.cljs index 770aab8ed8..91b7bb3709 100644 --- a/src/status_im/ui/screens/chat/views.cljs +++ b/src/status_im/ui/screens/chat/views.cljs @@ -26,7 +26,8 @@ [status-im.ui.components.animation :as animation] [status-im.ui.components.icons.vector-icons :as vector-icons] [status-im.ui.components.colors :as colors] - [status-im.ui.components.toolbar.actions :as toolbar.actions])) + [status-im.ui.components.toolbar.actions :as toolbar.actions] + [status-im.ui.screens.chat.stickers.views :as stickers])) (defview add-contact-bar [contact-identity] (letsubs [{:keys [hide-contact?] :as contact} [:contacts/contact-by-identity]] @@ -180,6 +181,7 @@ my-public-key [:account/public-key] show-bottom-info? [:chats/current-chat-ui-prop :show-bottom-info?] show-message-options? [:chats/current-chat-ui-prop :show-message-options?] + show-stickers? [:chats/current-chat-ui-prop :show-stickers?] current-view [:get :view-id]] ;; this scroll-view is a hack that allows us to use on-blur and on-focus on Android ;; more details here: https://github.com/facebook/react-native/issues/11071 @@ -197,6 +199,8 @@ [react/view style/message-view-preview]) (when (show-input-container? my-public-key current-chat) [input/container]) + (when show-stickers? + [stickers/stickers-view]) (when show-bottom-info? [bottom-info/bottom-info-view]) (when show-message-options? diff --git a/src/status_im/ui/screens/db.cljs b/src/status_im/ui/screens/db.cljs index 06098675fa..1894c8f3bd 100644 --- a/src/status_im/ui/screens/db.cljs +++ b/src/status_im/ui/screens/db.cljs @@ -193,6 +193,11 @@ (spec/def ::hardwallet (spec/nilable map?)) +(spec/def :stickers/packs (spec/nilable map?)) +(spec/def :stickers/packs-installed (spec/nilable map?)) +(spec/def :stickers/selected-pack (spec/nilable any?)) +(spec/def :stickers/recent (spec/nilable vector?)) + (spec/def ::db (spec/keys :opt [:contacts/contacts :contacts/dapps :contacts/new-identity @@ -259,7 +264,11 @@ :ui/contact :ui/search :ui/chat - :chats/loading?] + :chats/loading? + :stickers/packs + :stickers/packs-installed + :stickers/selected-pack + :stickers/recent] :opt-un [::modal ::was-modal? ::rpc-url diff --git a/src/status_im/ui/screens/events.cljs b/src/status_im/ui/screens/events.cljs index 672908fb0d..e1460dc26d 100644 --- a/src/status_im/ui/screens/events.cljs +++ b/src/status_im/ui/screens/events.cljs @@ -32,11 +32,7 @@ [status-im.utils.handlers :as handlers] [status-im.utils.http :as http] [status-im.utils.utils :as utils] - [status-im.utils.fx :as fx] - [status-im.utils.platform :as platform] - [taoensso.timbre :as log] - [clojure.string :as str] - [status-im.utils.types :as types])) + [status-im.utils.fx :as fx])) (defn- http-get [{:keys [url response-validator success-event-creator failure-event-creator timeout-ms]}] (let [on-success #(re-frame/dispatch (success-event-creator %)) diff --git a/src/status_im/ui/screens/home/views/inner_item.cljs b/src/status_im/ui/screens/home/views/inner_item.cljs index c1e0d03c9d..534260acb4 100644 --- a/src/status_im/ui/screens/home/views/inner_item.cljs +++ b/src/status_im/ui/screens/home/views/inner_item.cljs @@ -39,6 +39,10 @@ (= constants/content-type-command content-type) [command-short-preview message] + (= constants/content-type-sticker content-type) + [react/image {:style {:margin 2 :width 30 :height 30} + :source {:uri (:uri content)}}] + (str/blank? (:text content)) [react/text {:style styles/last-message-text} ""] diff --git a/src/status_im/ui/screens/stickers/styles.cljs b/src/status_im/ui/screens/stickers/styles.cljs new file mode 100644 index 0000000000..b4eaf877c3 --- /dev/null +++ b/src/status_im/ui/screens/stickers/styles.cljs @@ -0,0 +1,28 @@ +(ns status-im.ui.screens.stickers.styles + (:require [status-im.ui.components.colors :as colors])) + +(def screen + {:flex 1 + :background-color colors/white}) + +(defn sticker-image [sticker-icon-size] + {:margin 16 + :width sticker-icon-size + :height sticker-icon-size + :border-radius (/ sticker-icon-size 2)}) + +(defn price-badge [not-enough-snt?] + {:background-color (if not-enough-snt? colors/gray colors/blue) + :border-radius 14 + :flex-direction :row + :padding-horizontal 8 + :height 28 + :align-items :center}) + +(def installed-icon + {:height 28 + :width 28 + :border-radius 14 + :background-color colors/green + :align-items :center + :justify-content :center}) \ No newline at end of file diff --git a/src/status_im/ui/screens/stickers/subs.cljs b/src/status_im/ui/screens/stickers/subs.cljs new file mode 100644 index 0000000000..66a2f81b45 --- /dev/null +++ b/src/status_im/ui/screens/stickers/subs.cljs @@ -0,0 +1,31 @@ +(ns status-im.ui.screens.stickers.subs + (:require [re-frame.core :as re-frame])) + +(re-frame/reg-sub + :stickers/packs + (fn [db] + (:stickers/packs db))) + +(re-frame/reg-sub + :stickers/installed-packs + (fn [db] + (:stickers/packs-installed db))) + +(re-frame/reg-sub + :stickers/installed-packs-vals + :<- [:stickers/installed-packs] + (fn [packs] + (vals packs))) + +(re-frame/reg-sub + :stickers/all-packs + :<- [:stickers/packs] + :<- [:stickers/installed-packs] + (fn [[packs installed]] + (map #(if (get installed (:id %)) (assoc % :installed true) %) (vals packs)))) + +(re-frame/reg-sub + :stickers/recent + :<- [:account/account] + (fn [{:keys [recent-stickers]}] + recent-stickers)) \ No newline at end of file diff --git a/src/status_im/ui/screens/stickers/views.cljs b/src/status_im/ui/screens/stickers/views.cljs new file mode 100644 index 0000000000..4f6a67035f --- /dev/null +++ b/src/status_im/ui/screens/stickers/views.cljs @@ -0,0 +1,89 @@ +(ns status-im.ui.screens.stickers.views + (:require-macros [status-im.utils.views :refer [defview letsubs]]) + (:require [re-frame.core :as re-frame] + [status-im.i18n :as i18n] + [status-im.ui.components.react :as react] + [status-im.ui.screens.stickers.styles :as styles] + [status-im.ui.components.status-bar.view :as status-bar] + [status-im.ui.components.styles :as components.styles] + [status-im.ui.components.toolbar.view :as toolbar] + [status-im.ui.components.colors :as colors] + [status-im.ui.components.icons.vector-icons :as icons] + [status-im.utils.money :as money])) + +(def thumbnail-icon-size 40) + +(defn- thumbnail-icon [uri] + [react/image {:style {:width thumbnail-icon-size :height thumbnail-icon-size :border-radius (/ thumbnail-icon-size 2)} + :source {:uri uri}}]) + +(defn- installed-icon [] + [react/view styles/installed-icon + [icons/icon :icons/ok {:color colors/white :height 20 :width 20}]]) + +(defview price-badge [price id] + (letsubs [balance [:balance]] + (let [snt (money/wei-> :eth (:SNT balance)) + not-enough-snt? (> price snt) + no-snt? (nil? snt)] + [react/touchable-highlight {:on-press #(when (zero? price) (re-frame/dispatch [:stickers/install-pack id]))} + [react/view (styles/price-badge not-enough-snt?) + (when (and (not (zero? price)) (not no-snt?)) + [icons/icon :icons/logo {:color colors/white :width 12 :height 12 :container-style {:margin-right 8}}]) + [react/text {:style {:font-size 15 :color colors/white}} + (cond (zero? price) + (i18n/label :t/install) + no-snt? + (i18n/label :t/buy-with-snt) + :else + (str price))]]]))) + +(defn pack-badge [{:keys [name author price thumbnail preview id installed] :as pack}] + [react/view {:margin-bottom 27} + [react/touchable-highlight {:on-press #(re-frame/dispatch [:navigate-to :stickers-pack pack])} + [react/image {:style {:height 200 :border-radius 20} :source {:uri preview}}]] + [react/view {:height 64 :align-items :center :flex-direction :row} + [thumbnail-icon thumbnail] + [react/view {:padding-horizontal 16 :flex 1} + [react/text {:style {:font-size 15}} name] + [react/text {:style {:font-size 15 :color colors/gray :margin-top 6}} author]] + (if installed + [installed-icon] + [price-badge price id])]]) + +(defview packs [] + (letsubs [packs [:stickers/all-packs]] + [react/view styles/screen + [status-bar/status-bar] + [react/keyboard-avoiding-view components.styles/flex + [toolbar/simple-toolbar (i18n/label :t/sticker-market)] + [react/view {:style {:padding-top 8 :flex 1}} + [react/scroll-view {:keyboard-should-persist-taps :handled :style {:flex 1 :padding 16}} + [react/view + (for [pack packs] + ^{:key pack} + [pack-badge pack])]]]]])) + +(def sticker-icon-size 60) + +(defview pack [] + (letsubs [{:keys [id name author price thumbnail stickers installed]} [:get-screen-params]] + [react/view styles/screen + [status-bar/status-bar] + [react/keyboard-avoiding-view components.styles/flex + [toolbar/simple-toolbar] + [react/view {:height 94 :align-items :center :flex-direction :row :padding-horizontal 16} + [thumbnail-icon thumbnail] + [react/view {:padding-horizontal 16 :flex 1} + [react/text {:style {:font-size 22 :font-weight :bold}} name] + [react/text {:style {:font-size 15 :color colors/gray :margin-top 6}} author]] + (if installed + [installed-icon] + [price-badge price id])] + [react/view {:style {:padding-top 8 :flex 1}} + [react/scroll-view {:keyboard-should-persist-taps :handled :style {:flex 1}} + [react/view {:flex-direction :row :flex-wrap :wrap} + (for [{:keys [uri]} stickers] + ^{:key uri} + [react/image {:style (styles/sticker-image sticker-icon-size) + :source {:uri uri}}])]]]]])) diff --git a/src/status_im/ui/screens/subs.cljs b/src/status_im/ui/screens/subs.cljs index 792a055c45..37b384beea 100644 --- a/src/status_im/ui/screens/subs.cljs +++ b/src/status_im/ui/screens/subs.cljs @@ -7,9 +7,11 @@ status-im.mailserver.subs status-im.ui.components.connectivity.subs status-im.ui.screens.accounts.subs + status-im.ui.screens.chat.stickers.subs status-im.ui.screens.extensions.subs status-im.ui.screens.home.subs status-im.ui.screens.group.subs + status-im.ui.screens.stickers.subs status-im.ui.screens.wallet.subs status-im.ui.screens.wallet.collectibles.subs status-im.ui.screens.wallet.request.subs diff --git a/src/status_im/ui/screens/views.cljs b/src/status_im/ui/screens/views.cljs index 1450589958..b9ddf10e98 100644 --- a/src/status_im/ui/screens/views.cljs +++ b/src/status_im/ui/screens/views.cljs @@ -64,6 +64,7 @@ [status-im.ui.screens.hardwallet.success.views :refer [hardwallet-success]] [status-im.ui.screens.profile.seed.views :refer [backup-seed]] [status-im.ui.screens.about-app.views :as about-app] + [status-im.ui.screens.stickers.views :as stickers] [status-im.utils.navigation :as navigation] [reagent.core :as reagent] [cljs-react-navigation.reagent :as nav-reagent] @@ -167,6 +168,8 @@ :open-dapp open-dapp :dapp-description dapp-description :browser browser + :stickers stickers/packs + :stickers-pack stickers/pack :login login} :config {:headerMode "none" diff --git a/src/status_im/utils/config.cljs b/src/status_im/utils/config.cljs index 8cdbe96ae5..1c982cb2cf 100644 --- a/src/status_im/utils/config.cljs +++ b/src/status_im/utils/config.cljs @@ -27,6 +27,7 @@ (def cached-webviews-enabled? (enabled? (get-config :CACHED_WEBVIEWS_ENABLED 0))) (def rn-bridge-threshold-warnings-enabled? (enabled? (get-config :RN_BRIDGE_THRESHOLD_WARNINGS 0))) (def extensions-enabled? (enabled? (get-config :EXTENSIONS 0))) +(def stickers-enabled? (enabled? (get-config :STICKERS_ENABLED 0))) (def hardwallet-enabled? (enabled? (get-config :HARDWALLET_ENABLED 0))) (def dev-build? (enabled? (get-config :DEV_BUILD 0))) (def erc20-contract-warnings-enabled? (enabled? (get-config :ERC20_CONTRACT_WARNINGS))) diff --git a/translations/en.json b/translations/en.json index b51b8645a0..2aa927e17e 100644 --- a/translations/en.json +++ b/translations/en.json @@ -885,5 +885,10 @@ "migrations-erase-accounts-data-button": "Erase account's db", "account-and-db-password-mismatch-title": "The problem occurred!", "account-and-db-password-mismatch-content": "Account's and realm db passwords do not match.", - "recover-account-warning": "Your wallet information will be exposed by importing this account." + "recover-account-warning": "Your wallet information will be exposed by importing this account.", + "buy-with-snt": "Buy with SNT", + "sticker-market": "Sticker market", + "you-dont-have-stickers": "You don’t have any stickers yet", + "get-stickers": "Get Stickers", + "recently-used-stickers": "Recently used stickers will appear here" }