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"
}