diff --git a/package.json b/package.json index c33906ec1f..f4d521ea36 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "awesome-phonenumber": "^1.0.13", "babel-plugin-transform-es2015-block-scoping": "6.15.0", "browserify-zlib": "^0.1.4", + "buffer": "^3.6.0", "chance": "1.0.4", "console-browserify": "^1.1.0", "constants-browserify": "0.0.1", diff --git a/src/status_im/accounts/handlers.cljs b/src/status_im/accounts/handlers.cljs index 3345325e54..51ab478c95 100644 --- a/src/status_im/accounts/handlers.cljs +++ b/src/status_im/accounts/handlers.cljs @@ -65,7 +65,7 @@ [{:keys [current-account-id current-public-key web3 accounts]} _] (let [{:keys [name photo-path status]} (get accounts current-account-id) {:keys [updates-public-key updates-private-key]} (accounts current-account-id)] - (protocol/broadcats-profile! + (protocol/broadcast-profile! {:web3 web3 :message {:from current-public-key :message-id (random/id) diff --git a/src/status_im/android/core.cljs b/src/status_im/android/core.cljs index 0bba7c9d72..a41dbcde48 100644 --- a/src/status_im/android/core.cljs +++ b/src/status_im/android/core.cljs @@ -15,7 +15,6 @@ [status-im.contacts.views.contact-list :refer [contact-list]] [status-im.contacts.views.new-contact :refer [new-contact]] [status-im.qr-scanner.screen :refer [qr-scanner]] - [status-im.discovery.tag :refer [discovery-tag]] [status-im.discovery.search-results :refer [discovery-search-results]] [status-im.chat.screen :refer [chat]] [status-im.accounts.login.screen :refer [login]] @@ -89,7 +88,6 @@ (let [current-view (validate-current-view @view-id @signed-up?)] (let [component (case current-view :discovery main-tabs - :discovery-tag discovery-tag :discovery-search-results discovery-search-results :add-participants new-participants :remove-participants remove-participants diff --git a/src/status_im/android/platform.cljs b/src/status_im/android/platform.cljs index a659f4439c..b145dd6885 100644 --- a/src/status_im/android/platform.cljs +++ b/src/status_im/android/platform.cljs @@ -18,6 +18,22 @@ :additional-height 0} :chat {:new-message {:border-top-color styles/color-transparent :border-top-width 0.5}} + :discovery {:subtitle {:color styles/color-gray2 + :font-size 14} + :popular {:border-radius 1 + :margin-top 2 + :margin-bottom 4 + :margin-right 2 + :elevation 3} + :tag {:flex-direction "column" + :background-color "#7099e619" + :border-radius 5 + :padding 4} + :item {:status-text {:color styles/color-black + :line-height 22 + :font-size 14}}} + :contacts {:subtitle {:color styles/color-gray2 + :font-size 14}} :bottom-gradient {:height 3} :input-label {:left 4} :input-error-text {:margin-left 4} @@ -58,4 +74,7 @@ :chats {:action-button? true :new-chat-in-toolbar? false} :contacts {:action-button? true - :new-contact-in-toolbar? false}}) + :new-contact-in-toolbar? false + :uppercase-subtitles? false + :group-block-shadows? true} + :discovery {:uppercase-subtitles? false}}) diff --git a/src/status_im/chat/handlers.cljs b/src/status_im/chat/handlers.cljs index 8ac958be28..98985cae32 100644 --- a/src/status_im/chat/handlers.cljs +++ b/src/status_im/chat/handlers.cljs @@ -393,10 +393,12 @@ (register-handler :start-chat (u/side-effect! - (fn [{:keys [chats]} [_ contact-id options navigation-type]] - (if (chats contact-id) - (dispatch [(or navigation-type :navigate-to) :chat contact-id]) - (dispatch [::start-chat! contact-id options navigation-type]))))) + (fn [{:keys [chats current-public-key]} + [_ contact-id options navigation-type]] + (when-not (= current-public-key contact-id) + (if (chats contact-id) + (dispatch [(or navigation-type :navigate-to) :chat contact-id]) + (dispatch [::start-chat! contact-id options navigation-type])))))) (register-handler :add-chat (u/side-effect! diff --git a/src/status_im/components/drawer/view.cljs b/src/status_im/components/drawer/view.cljs index a37d99dee8..ec244765fa 100644 --- a/src/status_im/components/drawer/view.cljs +++ b/src/status_im/components/drawer/view.cljs @@ -39,6 +39,12 @@ :font :default} name]]) +(defn clean-text [s] + (-> s + (str/replace #"\n" " ") + (str/replace #"\r" "") + (str/trim))) + (defn drawer-menu [] (let [account (subscribe [:get-current-account]) @@ -65,7 +71,7 @@ :value name :on-change-text #(dispatch [:set-in [:profile-edit :name] %]) :on-end-editing #(when (and new-name (not (str/blank? new-name))) - (dispatch [:account-update {:name new-name}]))}]] + (dispatch [:account-update {:name (clean-text new-name)}]))}]] [view st/status-container [text-input {:style st/status-input :editable true @@ -77,8 +83,8 @@ :on-change-text #(dispatch [:set-in [:profile-edit :status] %]) :on-blur (fn [] (when (and new-status (not (str/blank? new-status))) - (dispatch [:check-status-change new-status]) - (dispatch [:account-update {:status new-status}]))) + (dispatch [:check-status-change (clean-text new-status)]) + (dispatch [:account-update {:status (clean-text new-status)}]))) :default-value status}]] [view st/menu-items-container [menu-item {:name (label :t/profile) diff --git a/src/status_im/components/react.cljs b/src/status_im/components/react.cljs index 0ac642c557..7d0c87f11a 100644 --- a/src/status_im/components/react.cljs +++ b/src/status_im/components/react.cljs @@ -54,16 +54,18 @@ (defn text ([t] (r/as-element [text-class t])) - ([{:keys [style font] :as opts - :or {font :default}} t & other] + ([{:keys [style font uppercase?] :as opts + :or {font :default}} t & ts] (r/as-element - (let [font (get-in platform-specific [:fonts font])] + (let [font (get-in platform-specific [:fonts font]) + ts (cond->> (conj ts t) + uppercase? (map clojure.string/upper-case))] (vec (concat [text-class (-> opts (dissoc :font) (assoc :style (merge style font)))] - (conj other t))))))) + ts)))))) (defn text-input [props text] [text-input-class (merge diff --git a/src/status_im/components/selectable_field/view.cljs b/src/status_im/components/selectable_field/view.cljs index 5e7e05af87..22048af6c7 100644 --- a/src/status_im/components/selectable_field/view.cljs +++ b/src/status_im/components/selectable_field/view.cljs @@ -5,6 +5,7 @@ [reagent.core :as r] [status-im.components.selectable-field.styles :as st] [status-im.i18n :refer [label]] + [status-im.utils.platform :as p] [taoensso.timbre :as log])) (defn- on-press @@ -41,7 +42,7 @@ [text-input {:style (st/sized-text full-height) :multiline true :selectTextOnFocus true - :editable editable? + :editable (if p/android? true editable?) :auto-focus true :on-selection-change #(on-selection-change % component) :on-focus #(log/debug "Focused" %) diff --git a/src/status_im/components/styles.cljs b/src/status_im/components/styles.cljs index 66ac82dee9..3c62ea6771 100644 --- a/src/status_im/components/styles.cljs +++ b/src/status_im/components/styles.cljs @@ -8,6 +8,7 @@ (def color-gray "#838c93de") (def color-gray2 "#8f838c93") (def color-gray3 "#00000040") +(def color-steel "#838b91") (def color-white "white") (def color-light-blue-transparent "#bbc4cb32") (def color-light-gray "#EEF2F5") diff --git a/src/status_im/components/toolbar/styles.cljs b/src/status_im/components/toolbar/styles.cljs index eb1ce94b89..0ced83aad6 100644 --- a/src/status_im/components/toolbar/styles.cljs +++ b/src/status_im/components/toolbar/styles.cljs @@ -22,7 +22,7 @@ :height toolbar-height}) (defn toolbar-nav-actions-container [actions] - {:width (if (and actions (> (count actions) 0)) + {:width (when (and actions (> (count actions) 0)) (-> (+ toolbar-icon-width toolbar-icon-spacing) (* (count actions)) (+ toolbar-icon-spacing))) @@ -45,9 +45,12 @@ :color text1-color :font-size 16}) -(def toolbar-actions-container - {:flex-direction "row" - :margin-left 8}) +(defn toolbar-actions-container [actions-count custom] + (merge {:flex-direction "row" + :margin-left toolbar-icon-spacing} + (when (and (= actions-count 0) + (not custom)) + {:width (+ toolbar-icon-width toolbar-icon-spacing)}))) (def toolbar-action {:width toolbar-icon-width diff --git a/src/status_im/components/toolbar/view.cljs b/src/status_im/components/toolbar/view.cljs index 200726d866..5136dd4b00 100644 --- a/src/status_im/components/toolbar/view.cljs +++ b/src/status_im/components/toolbar/view.cljs @@ -11,14 +11,14 @@ [status-im.components.toolbar.styles :as st] [status-im.utils.platform :refer [platform-specific]])) -(defn toolbar [{title :title - nav-action :nav-action - hide-nav? :hide-nav? - actions :actions - custom-action :custom-action - background-color :background-color - custom-content :custom-content - style :style}] +(defn toolbar [{title :title + nav-action :nav-action + hide-nav? :hide-nav? + actions :actions + custom-action :custom-action + background-color :background-color + custom-content :custom-content + style :style}] (let [style (merge (st/toolbar-wrapper background-color) style)] [view {:style style} [view st/toolbar @@ -38,7 +38,7 @@ [text {:style st/toolbar-title-text :font :toolbar-title} title]]) - [view st/toolbar-actions-container + [view (st/toolbar-actions-container (count actions) (or custom-content custom-action)) (if actions (for [{action-image :image action-handler :handler} actions] diff --git a/src/status_im/contacts/handlers.cljs b/src/status_im/contacts/handlers.cljs index df3d38be70..657c095ad9 100644 --- a/src/status_im/contacts/handlers.cljs +++ b/src/status_im/contacts/handlers.cljs @@ -205,7 +205,8 @@ (dispatch [:update-chat! {:chat-id chat-id :contact-info nil :pending-contact? false}]) - (dispatch [:watch-contact contact]))))) + (dispatch [:watch-contact contact]) + (dispatch [:discoveries-send-portions chat-id]))))) (defn set-contact-identity-from-qr [db [_ _ contact-identity]] @@ -215,21 +216,21 @@ (register-handler :contact-update-received (u/side-effect! - (fn [{:keys [chats] :as db} [_ {:keys [from payload]}]] - (let [{:keys [content timestamp]} payload - {:keys [status name profile-image]} (:profile content) - prev-last-updated (get-in db [:contacts from :last-updated])] - (if (<= prev-last-updated timestamp) - (let [contact {:whisper-identity from - :name name - :photo-path profile-image - :status status - :last-updated timestamp}] - (dispatch [:check-status! contact payload]) - (dispatch [:update-contact! contact]) - (when (chats from) - (dispatch [:update-chat! {:chat-id from - :name name}])))))))) + (fn [{:keys [chats current-public-key] :as db} [_ {:keys [from payload]}]] + (when (not= current-public-key from) + (let [{:keys [content timestamp]} payload + {:keys [status name profile-image]} (:profile content) + prev-last-updated (get-in db [:contacts from :last-updated])] + (if (<= prev-last-updated timestamp) + (let [contact {:whisper-identity from + :name name + :photo-path profile-image + :status status + :last-updated timestamp}] + (dispatch [:update-contact! contact]) + (when (chats from) + (dispatch [:update-chat! {:chat-id from + :name name}]))))))))) (register-handler :contact-online-received (u/side-effect! diff --git a/src/status_im/contacts/screen.cljs b/src/status_im/contacts/screen.cljs index 9bef7882f7..f13b3d49e7 100644 --- a/src/status_im/contacts/screen.cljs +++ b/src/status_im/contacts/screen.cljs @@ -27,7 +27,9 @@ [status-im.i18n :refer [label]] [status-im.utils.platform :refer [platform-specific]])) -(defn contact-list-toolbar [] +(def contacts-limit 50) + +(defn toolbar-view [] (let [new-contact? (get-in platform-specific [:contacts :new-contact-in-toolbar?]) actions (cond->> [{:image {:source {:uri :icon_search} :style icon-search} @@ -44,35 +46,57 @@ :style {:elevation 0} :actions actions}])) -(def contacts-limit 10) +(defn subtitle-view [subtitle contacts-count] + [view st/contact-group-header-inner + [text {:style (merge st/contact-group-subtitle + (get-in platform-specific [:component-styles :contacts :subtitle])) + :uppercase? (get-in platform-specific [:contacts :uppercase-subtitles?]) + :font :medium} + subtitle] + [text {:style (merge st/contact-group-count + (get-in platform-specific [:component-styles :contacts :subtitle])) + :uppercase? (get-in platform-specific [:contacts :uppercase-subtitles?]) + :font :medium} + (str contacts-count)]]) -(defn contact-group [contacts contacts-count title group top? click-handler] - [view st/contact-group - [view st/contact-group-header - (when-not top? - [linear-gradient {:style st/contact-group-header-gradient-top - :colors st/contact-group-header-gradient-top-colors}]) - [view st/contact-group-header-inner - [text {:style st/contact-group-text} title] - [text {:style st/contact-group-size-text} (str contacts-count)]] - [linear-gradient {:style st/contact-group-header-gradient-bottom - :colors st/contact-group-header-gradient-bottom-colors}]] - ;; todo what if there is no contacts, should we show some information - ;; about this? - [view {:flexDirection :column} - (doall - ;; TODO not imlemented: contact more button handler - (map (fn [contact] - (let [whisper-identity (:whisper-identity contact) - click-handler (or click-handler on-press)] - ^{:key contact} - [contact-extended-view contact nil (click-handler whisper-identity) nil])) - contacts))] - (when (<= contacts-limit (count contacts)) - [view st/show-all - [touchable-highlight {:on-press #(dispatch [:navigate-to :group-contacts group])} - [view - [text {:style st/show-all-text} (label :t/show-all)]]]])]) +(defn group-top-view [] + [linear-gradient {:style st/contact-group-header-gradient-bottom + :colors st/contact-group-header-gradient-bottom-colors}]) + +(defn group-bottom-view [] + [linear-gradient {:style st/contact-group-header-gradient-top + :colors st/contact-group-header-gradient-top-colors}]) + +(defn line-view [] + [view {:style {:background-color "#D7D7D7" + :height 1}}]) + +(defn contact-group-view [contacts contacts-count subtitle group click-handler] + (let [shadows? (get-in platform-specific [:contacts :group-block-shadows?])] + [view st/contact-group + [view st/contact-group-header + [subtitle-view subtitle contacts-count]] + (if shadows? + [group-top-view] + [line-view]) + [view + (doall + (map (fn [contact] + (let [whisper-identity (:whisper-identity contact) + click-handler (or click-handler on-press)] + ^{:key contact} + [contact-extended-view contact nil (click-handler whisper-identity) nil])) + contacts))] + (when (<= contacts-limit (count contacts)) + [view st/show-all + [touchable-highlight {:on-press #(dispatch [:navigate-to :group-contacts group])} + [view + [text {:style st/show-all-text + :font :medium} + (label :t/show-all)]]]]) + (if shadows? + [group-bottom-view] + [line-view])])) (defn contacts-action-button [] [view st/buttons-container @@ -97,7 +121,7 @@ show-toolbar-shadow? (r/atom false)] (fn [] [view st/contacts-list-container - [contact-list-toolbar] + [toolbar-view] [view {:style st/toolbar-shadow} (when @show-toolbar-shadow? [linear-gradient {:style st/contact-group-header-gradient-bottom @@ -108,18 +132,18 @@ (let [offset (.. e -nativeEvent -contentOffset -y)] (reset! show-toolbar-shadow? (<= st/contact-group-header-height offset))))} (when (pos? @dapps-count) - [contact-group + [contact-group-view @dapps @dapps-count (label :t/contacts-group-dapps) - :dapps true + :dapps @click-handler]) (when (pos? @people-count) - [contact-group + [contact-group-view @peoples @people-count (label :t/contacts-group-people) - :people false + :people @click-handler])] [view st/empty-contact-groups [react/icon :group_big st/empty-contacts-icon] diff --git a/src/status_im/contacts/styles.cljs b/src/status_im/contacts/styles.cljs index ccb73898ac..bf1b08a893 100644 --- a/src/status_im/contacts/styles.cljs +++ b/src/status_im/contacts/styles.cljs @@ -18,13 +18,13 @@ :backgroundColor toolbar-background2}) (def contact-groups - {:flex 1 - :backgroundColor toolbar-background2}) + {:flex 1 + :background-color toolbar-background2}) (def empty-contact-groups (merge contact-groups - {:align-items :center - :padding-top 150})) + {:align-items :center + :justify-content :center})) (def empty-contacts-icon {:height 62 @@ -39,11 +39,11 @@ {:backgroundColor color-white}) (def contact-group - {:flexDirection :column}) + {:flex-direction :column}) (def contact-group-header - {:flexDirection :column - :backgroundColor toolbar-background2}) + {:flex-direction :column + :background-color toolbar-background2}) (def contact-group-header-inner {:flexDirection :row @@ -51,16 +51,12 @@ :height 48 :backgroundColor toolbar-background2}) -(def contact-group-text +(def contact-group-subtitle {:flex 1 - :marginLeft 16 - :fontSize 14 - :color text5-color}) + :margin-left 16}) -(def contact-group-size-text - {:marginRight 14 - :fontSize 12 - :color text2-color}) +(def contact-group-count + {:margin-right 14}) (def contact-group-header-gradient-top {:flexDirection :row diff --git a/src/status_im/contacts/subs.cljs b/src/status_im/contacts/subs.cljs index 46afd31d11..7e8358e4e3 100644 --- a/src/status_im/contacts/subs.cljs +++ b/src/status_im/contacts/subs.cljs @@ -1,7 +1,8 @@ (ns status-im.contacts.subs (:require-macros [reagent.ratom :refer [reaction]]) (:require [re-frame.core :refer [register-sub subscribe]] - [status-im.utils.identicon :refer [identicon]])) + [status-im.utils.identicon :refer [identicon]] + [taoensso.timbre :as log])) (register-sub :get-contacts (fn [db _] @@ -19,7 +20,7 @@ (register-sub :all-added-contacts (fn [db _] (let [contacts (reaction (:contacts @db))] - (->> (remove :pending @contacts) + (->> (remove #(true? (:pending (second %))) @contacts) (sort-contacts) (reaction))))) diff --git a/src/status_im/data_store/contacts.cljs b/src/status_im/data_store/contacts.cljs index 714e8d0fdb..0a8a62b056 100644 --- a/src/status_im/data_store/contacts.cljs +++ b/src/status_im/data_store/contacts.cljs @@ -16,7 +16,12 @@ (let [{pending-db :pending :as contact-db} (data-store/get-by-id whisper-identity) contact (assoc contact :pending (boolean (if contact-db - (and pending-db pending) + ;; TODO: + ;; this is temporary fix for pending users + ;; we need to change this (if ...) to (and pending-db pending) + (if (nil? pending) + pending-db + (and pending-db pending)) pending)))] (data-store/save contact (if contact-db true false)))) diff --git a/src/status_im/data_store/discovery.cljs b/src/status_im/data_store/discovery.cljs index 3a898d0ed2..6f5aafed93 100644 --- a/src/status_im/data_store/discovery.cljs +++ b/src/status_im/data_store/discovery.cljs @@ -2,14 +2,18 @@ (:require [status-im.data-store.realm.discovery :as data-store])) (defn get-all - [] - (->> (data-store/get-all-as-list) + [ordering] + (->> (data-store/get-all-as-list ordering) (mapv #(update % :tags vals)))) (defn save [discovery] (data-store/save discovery)) +(defn exists? + [message-id] + (data-store/exists? message-id)) + (defn save-all [discoveries] (data-store/save-all discoveries)) diff --git a/src/status_im/data_store/realm/discovery.cljs b/src/status_im/data_store/realm/discovery.cljs index 9f01e4c9a5..355367c887 100644 --- a/src/status_im/data_store/realm/discovery.cljs +++ b/src/status_im/data_store/realm/discovery.cljs @@ -1,15 +1,16 @@ (ns status-im.data-store.realm.discovery (:require [status-im.data-store.realm.core :as realm] - [taoensso.timbre :as log])) + [taoensso.timbre :as log]) + (:refer-clojure :exclude [exists?])) (defn get-all - [] + [ordering] (-> (realm/get-all @realm/account-realm :discovery) - (realm/sorted :priority :desc))) + (realm/sorted :created-at ordering))) (defn get-all-as-list - [] - (-> (get-all) + [ordering] + (-> (get-all ordering) realm/realm-collection->list)) (defn get-tag-by-name [tag] @@ -38,11 +39,15 @@ (defn- upsert-discovery [{:keys [message-id tags] :as discovery}] (log/debug "Creating/updating discovery with tags: " tags) (let [prev-tags (get-tags message-id)] - (if prev-tags + (when prev-tags (update-tags-counter dec prev-tags)) (realm/create @realm/account-realm :discovery discovery true) (update-tags-counter inc tags))) +(defn exists? + [message-id] + (realm/exists? @realm/account-realm :discovery {:message-id message-id})) + (defn save [discovery] (realm/write @realm/account-realm 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 85a9629e22..5e9cd3e00f 100644 --- a/src/status_im/data_store/realm/schemas/account/core.cljs +++ b/src/status_im/data_store/realm/schemas/account/core.cljs @@ -3,6 +3,7 @@ [status-im.data-store.realm.schemas.account.v2.core :as v2] [status-im.data-store.realm.schemas.account.v3.core :as v3] [status-im.data-store.realm.schemas.account.v4.core :as v4] + [status-im.data-store.realm.schemas.account.v5.core :as v5] )) ; put schemas ordered by version @@ -17,4 +18,7 @@ :migration v3/migration} {:schema v4/schema :schemaVersion 4 - :migration v4/migration}]) + :migration v4/migration} + {:schema v5/schema + :schemaVersion 5 + :migration v5/migration}]) diff --git a/src/status_im/data_store/realm/schemas/account/v2/core.cljs b/src/status_im/data_store/realm/schemas/account/v2/core.cljs index bd379a1b8a..d0499eeb85 100644 --- a/src/status_im/data_store/realm/schemas/account/v2/core.cljs +++ b/src/status_im/data_store/realm/schemas/account/v2/core.cljs @@ -25,5 +25,4 @@ user-status/schema]) (defn migration [old-realm new-realm] - (log/debug "migrating v2 account database: " old-realm new-realm) - (contact/migration old-realm new-realm)) \ No newline at end of file + (log/debug "migrating v2 account database: " old-realm new-realm)) \ No newline at end of file diff --git a/src/status_im/data_store/realm/schemas/account/v3/core.cljs b/src/status_im/data_store/realm/schemas/account/v3/core.cljs index 92ae33d7a5..bc54fe5e49 100644 --- a/src/status_im/data_store/realm/schemas/account/v3/core.cljs +++ b/src/status_im/data_store/realm/schemas/account/v3/core.cljs @@ -25,5 +25,4 @@ user-status/schema]) (defn migration [old-realm new-realm] - (log/debug "migrating v3 account database: " old-realm new-realm) - (contact/migration old-realm new-realm)) \ No newline at end of file + (log/debug "migrating v3 account database: " old-realm new-realm)) \ No newline at end of file diff --git a/src/status_im/data_store/realm/schemas/account/v5/core.cljs b/src/status_im/data_store/realm/schemas/account/v5/core.cljs new file mode 100644 index 0000000000..11533a252d --- /dev/null +++ b/src/status_im/data_store/realm/schemas/account/v5/core.cljs @@ -0,0 +1,28 @@ +(ns status-im.data-store.realm.schemas.account.v5.core + (:require [taoensso.timbre :as log] + [status-im.data-store.realm.schemas.account.v4.chat :as chat] + [status-im.data-store.realm.schemas.account.v3.message :as message] + [status-im.data-store.realm.schemas.account.v2.contact :as contact] + [status-im.data-store.realm.schemas.account.v1.chat-contact :as chat-contact] + [status-im.data-store.realm.schemas.account.v1.command :as command] + [status-im.data-store.realm.schemas.account.v5.discovery :as discovery] + [status-im.data-store.realm.schemas.account.v1.kv-store :as kv-store] + [status-im.data-store.realm.schemas.account.v1.pending-message :as pending-message] + [status-im.data-store.realm.schemas.account.v1.request :as request] + [status-im.data-store.realm.schemas.account.v1.tag :as tag] + [status-im.data-store.realm.schemas.account.v1.user-status :as user-status])) + +(def schema [chat/schema + chat-contact/schema + command/schema + contact/schema + discovery/schema + kv-store/schema + message/schema + pending-message/schema + request/schema + tag/schema + user-status/schema]) + +(defn migration [old-realm new-realm] + (log/debug "migrating v5 account database: " old-realm new-realm)) diff --git a/src/status_im/data_store/realm/schemas/account/v5/discovery.cljs b/src/status_im/data_store/realm/schemas/account/v5/discovery.cljs new file mode 100644 index 0000000000..3642ec29ca --- /dev/null +++ b/src/status_im/data_store/realm/schemas/account/v5/discovery.cljs @@ -0,0 +1,16 @@ +(ns status-im.data-store.realm.schemas.account.v5.discovery + (:require [taoensso.timbre :as log])) + +(def schema {:name :discovery + :primaryKey :message-id + :properties {:message-id "string" + :name {:type "string" :optional true} + :status "string" + :whisper-id "string" + :photo-path {:type "string" :optional true} + :tags {:type "list" + :objectType "tag"} + :created-at {:type "int" :default 0}}}) + +(defn migration [old-realm new-realm] + (log/debug "migrating discovery schema")) \ No newline at end of file diff --git a/src/status_im/db.cljs b/src/status_im/db.cljs index 429a4237b3..0fc4e534dd 100644 --- a/src/status_im/db.cljs +++ b/src/status_im/db.cljs @@ -19,7 +19,7 @@ :new-contact-identity "" :contacts {} - :discoveries [] + :discoveries {} :discovery-search-tags [] :tags {} diff --git a/src/status_im/discovery/handlers.cljs b/src/status_im/discovery/handlers.cljs index 729436969e..40b094eaf7 100644 --- a/src/status_im/discovery/handlers.cljs +++ b/src/status_im/discovery/handlers.cljs @@ -5,103 +5,117 @@ [status-im.protocol.core :as protocol] [status-im.navigation.handlers :as nav] [status-im.data-store.discovery :as discoveries] - [status-im.data-store.contacts :as contacts] [status-im.utils.handlers :as u] [status-im.utils.datetime :as time] - [status-im.utils.random :as random] - [status-im.data-store.contacts :as contacts])) + [status-im.utils.random :as random])) + +(def request-discoveries-interval-s 600) (register-handler :init-discoveries (fn [db _] (-> db - (assoc :discoveries [])))) + (assoc :tags []) + (assoc :discoveries {})))) -(defn calculate-priority [{:keys [chats contacts]} from payload] - (let [contact (get contacts from) - chat (get chats from) - seen-online-recently? (< (- (time/now-ms) (get contact :last-online)) - time/hour)] - (+ (time/now-ms) ;; message is newer => priority is higher - (if contact time/day 0) ;; user exists in contact list => increase priority - (if chat time/day 0) ;; chat with this user exists => increase priority - (if seen-online-recently? time/hour 0) ;; the user was online recently => increase priority - ))) +(defn identities [contacts] + (->> (map second contacts) + (remove (fn [{:keys [dapp? pending]}] + (or pending dapp?))) + (map :whisper-identity))) (defmethod nav/preload-data! :discovery - [{:keys [discoveries] :as db} _] + [db _] (-> db (assoc :tags (discoveries/get-all-tags)) - ;; todo add limit - ;; todo hash-map with whisper-id as key and sorted by last-update - ;; may be more efficient here - (assoc :discoveries (discoveries/get-all)))) - -(register-handler :discovery-response-received - (u/side-effect! - (fn [db [_ {:keys [from payload]}]] - (let [{:keys [discovery-id profile hashtags]} payload - {:keys [name profile-image status]} profile - discovery {:message-id discovery-id - :name name - :photo-path profile-image - :status status - :whisper-id from - :tags (map #(hash-map :name %) hashtags) - :last-updated (js/Date.) - :priority (calculate-priority db from payload)}] - (dispatch [:add-discovery discovery]))))) - -(register-handler :check-status! - (u/side-effect! - (fn [db [_ {:keys [whisper-identity status]} payload]] - (let [{old-status :status} (contacts/get-by-id whisper-identity)] - (when (not= old-status status) - (let [hashtags (get-hashtags status)] - (when-not (empty? hashtags) - (let [{:keys [message-id content]} payload - {:keys [name profile-image status]} (content :profile) - discovery {:message-id message-id - :name name - :photo-path profile-image - :status status - :whisper-id whisper-identity - :tags (map #(hash-map :name %) hashtags) - :last-updated (js/Date.) - :priority (calculate-priority db whisper-identity payload)}] - (dispatch [:add-discovery discovery]))))))))) + (assoc :discoveries (->> (discoveries/get-all :desc) + (map (fn [{:keys [message-id] :as discovery}] + [message-id discovery])) + (into {}))))) (register-handler :broadcast-status (u/side-effect! - (fn [{:keys [current-public-key web3 current-account-id accounts]} + (fn [{:keys [current-public-key web3 current-account-id accounts contacts]} [_ status hashtags]] - (let [{:keys [name photo-path]} (get accounts current-account-id)] - (protocol/broadcats-discoveries! - {:web3 web3 - :hashtags (set hashtags) - :message {:from current-public-key - :message-id (random/id) - :payload {:status status - :profile {:name name - :status status - :profile-image photo-path}}}}) - (protocol/watch-hashtags! {:web3 web3 - :hashtags (set hashtags) - :callback #(dispatch [:incoming-message %1 %2])}))))) + (let [{:keys [name photo-path]} (get accounts current-account-id) + message-id (random/id) + message {:message-id message-id + :from current-public-key + :payload {:message-id message-id + :status status + :hashtags (vec hashtags) + :profile {:name name + :profile-image photo-path}}}] + (doseq [id (identities contacts)] + (protocol/send-status! + {:web3 web3 + :message (assoc message :to id)})) + (dispatch [:status-received message]))))) -(register-handler :show-discovery-tag - (fn [db [_ tag]] - (dispatch [:navigate-to :discovery-tag]) - (assoc db :current-tag tag))) +(register-handler :status-received + (u/side-effect! + (fn [{:keys [discoveries] :as db} [_ {:keys [from payload]}]] + (when (and (not (discoveries/exists? (:message-id payload))) + (not (get discoveries (:message-id payload)))) + (let [{:keys [message-id status hashtags profile]} payload + {:keys [name profile-image]} profile + discovery {:message-id message-id + :name name + :photo-path profile-image + :status status + :whisper-id from + :tags (map #(hash-map :name %) hashtags) + :created-at (time/now-ms)}] + (dispatch [:add-discovery discovery])))))) + +(register-handler :start-requesting-discoveries + (fn [{:keys [request-discoveries-timer] :as db}] + (when request-discoveries-timer + (js/clearInterval request-discoveries-timer)) + (dispatch [:request-discoveries]) + (assoc db :request-discoveries-timer + (js/setInterval #(dispatch [:request-discoveries]) + (* request-discoveries-interval-s 1000))))) + +(register-handler :request-discoveries + (u/side-effect! + (fn [{:keys [current-public-key web3 contacts]}] + (doseq [id (identities contacts)] + (when-not (protocol/message-pending? web3 :discoveries-request id) + (protocol/send-discoveries-request! + {:web3 web3 + :message {:from current-public-key + :to id + :message-id (random/id)}})))))) + +(register-handler :discoveries-send-portions + (u/side-effect! + (fn [{:keys [current-public-key contacts web3]} [_ to]] + (when (get contacts to) + (protocol/send-discoveries-response! + {:web3 web3 + :discoveries (discoveries/get-all :asc) + :message {:from current-public-key + :to to}}))))) + +(register-handler :discoveries-request-received + (u/side-effect! + (fn [_ [_ {:keys [from]}]] + (dispatch [:discoveries-send-portions from])))) + +(register-handler :discoveries-response-received + (u/side-effect! + (fn [{:keys [discoveries contacts]} [_ {:keys [payload from]}]] + (when (get contacts from) + (when-let [data (:data payload)] + (doseq [{:keys [message-id] :as discovery} data] + (when (and (not (discoveries/exists? message-id)) + (not (get discoveries message-id))) + (let [discovery (assoc discovery :created-at (time/now-ms))] + (dispatch [:add-discovery discovery]))))))))) (defn add-discovery - [{db-discoveries :discoveries - :as db} [_ {:keys [message-id] :as discovery}]] - (let [updated-discoveries (if-let [i (first-index #(= (:message-id %) message-id) db-discoveries)] - (assoc db-discoveries i discovery) - (conj db-discoveries discovery))] - (-> db - (assoc :new-discovery discovery) - (assoc :discoveries updated-discoveries)))) + [db [_ discovery]] + (assoc db :new-discovery discovery)) (defn save-discovery! [{:keys [new-discovery]} _] @@ -109,7 +123,11 @@ (defn reload-tags! [db _] - (assoc db :tags (discoveries/get-all-tags))) + (assoc db :tags (discoveries/get-all-tags) + :discoveries (->> (discoveries/get-all :desc) + (map (fn [{:keys [message-id] :as discovery}] + [message-id discovery])) + (into {})))) (register-handler :add-discovery (-> add-discovery @@ -120,4 +138,4 @@ :remove-old-discoveries! (u/side-effect! (fn [_ _] - (discoveries/delete :priority :asc 1000 200)))) + (discoveries/delete :created-at :asc 1000 200)))) diff --git a/src/status_im/discovery/screen.cljs b/src/status_im/discovery/screen.cljs index 09a8f96f3f..35b3319300 100644 --- a/src/status_im/discovery/screen.cljs +++ b/src/status_im/discovery/screen.cljs @@ -16,7 +16,8 @@ [status-im.components.carousel.carousel :refer [carousel]] [status-im.discovery.views.popular-list :refer [discovery-popular-list]] [status-im.discovery.views.discovery-list-item :refer [discovery-list-item]] - [status-im.contacts.styles :as contacts-styles])) + [status-im.contacts.styles :as contacts-styles] + [status-im.utils.platform :refer [platform-specific]])) (defn get-hashtags [status] (let [hashtags (map #(str/lower-case (str/replace % #"#" "")) (re-seq #"[^ !?,;:.]+" status))] @@ -35,7 +36,7 @@ (dispatch [:navigate-to :discovery-search-results])))}] [view [text {:style st/discovery-title - :font :default} + :font :toolbar-title} (label :t/discovery)]])]) (defn toogle-search [current-value] @@ -52,46 +53,51 @@ :style st/search-icon} :handler #(toogle-search show-search?)}]}]) -(defn title [label-kw] +(defn title [label-kw spacing?] [view st/section-spacing - [text {:style st/discovery-subtitle - :font :medium} + [text {:style (merge (get-in platform-specific [:component-styles :discovery :subtitle]) + (when spacing? {:margin-top 16})) + :uppercase? (get-in platform-specific [:discovery :uppercase-subtitles?]) + :font :medium} (label label-kw)]]) (defview discovery-popular [{:keys [contacts]}] [popular-tags [:get-popular-tags 10]] - (if (seq popular-tags) - [view - [title :t/popular-tags] - (if (pos? (count popular-tags)) - [carousel {:pageStyle st/carousel-page-style} - (for [{:keys [name count]} popular-tags] - [discovery-popular-list {:tag name - :count count - :contacts contacts}])] - [text (label :t/none)])] - [view contacts-styles/empty-contact-groups - ;; todo change icon - [icon :group_big contacts-styles/empty-contacts-icon] - [text {:style contacts-styles/empty-contacts-text} - (label :t/no-statuses-discovered)]])) + [view st/popular-container + [title :t/popular-tags false] + (if (pos? (count popular-tags)) + [carousel {:pageStyle st/carousel-page-style + :gap 0 + :sneak (if (> (count popular-tags) 1) 16 8)} + (for [{:keys [name]} popular-tags] + [discovery-popular-list {:tag name + :contacts contacts}])] + [text (label :t/none)])]) (defview discovery-recent [{:keys [contacts]}] - [discoveries [:get :discoveries]] + [discoveries [:get-recent-discoveries]] (when (seq discoveries) - [view - [title :t/recent] + [view st/recent-container + [title :t/recent true] [view st/recent-list - (for [{:keys [message-id] :as discovery} discoveries] - ^{:key (str "message-" message-id)} - [discovery-list-item discovery])]])) + (let [discoveries (map-indexed vector discoveries)] + (for [[i {:keys [message-id] :as discovery}] discoveries] + ^{:key (str "message-" message-id)} + [discovery-list-item discovery (not= (inc i) (count discoveries))]))]])) (defview discovery [] [show-search? [:get ::show-search?] - contacts [:get :contacts]] + contacts [:get :contacts] + discoveries [:get-recent-discoveries]] [view st/discovery-container [discovery-toolbar show-search?] - [scroll-view st/scroll-view-container - [discovery-popular {:contacts contacts}] - [discovery-recent {:contacts contacts}]] + (if discoveries + [scroll-view st/scroll-view-container + [discovery-popular {:contacts contacts}] + [discovery-recent {:contacts contacts}]] + [view contacts-styles/empty-contact-groups + ;; todo change icon + [icon :group_big contacts-styles/empty-contacts-icon] + [text {:style contacts-styles/empty-contacts-text} + (label :t/no-statuses-discovered)]]) [bottom-gradient]]) diff --git a/src/status_im/discovery/search_results.cljs b/src/status_im/discovery/search_results.cljs index dd05f30994..90b10e562a 100644 --- a/src/status_im/discovery/search_results.cljs +++ b/src/status_im/discovery/search_results.cljs @@ -2,39 +2,59 @@ (:require-macros [status-im.utils.views :refer [defview]]) (:require [re-frame.core :refer [subscribe dispatch]] [status-im.utils.listview :refer [to-datasource]] - [status-im.components.react :refer [view text list-view list-item]] + [status-im.components.status-bar :refer [status-bar]] + [status-im.components.react :refer [view + text + icon + list-view + list-item + scroll-view]] + [status-im.i18n :refer [label]] [status-im.components.toolbar.view :refer [toolbar]] [status-im.discovery.views.discovery-list-item :refer [discovery-list-item]] - [status-im.discovery.styles :as st])) + [status-im.discovery.styles :as st] + [status-im.utils.platform :refer [platform-specific]] + [status-im.contacts.styles :as contacts-styles] + [taoensso.timbre :as log])) (defn render-separator [_ row-id _] (list-item [view {:style st/row-separator :key row-id}])) (defn title-content [tags] - [view st/tag-title-container - (for [tag (take 3 tags)] - ^{:key (str "tag-" tag)} - [view {:style st/tag-container} - [text {:style st/tag-title - :font :default} - (str " #" tag)]])]) + [scroll-view {:horizontal true + :bounces false + :flex 1 + :contentContainerStyle st/tag-title-scroll} + [view st/tag-title-container + (for [tag (take 3 tags)] + ^{:key (str "tag-" tag)} + [view (merge (get-in platform-specific [:component-styles :discovery :tag]) + {:margin-left 2 :margin-right 2}) + [text {:style st/tag-title + :font :default} + (str " #" tag)]])]]) (defview discovery-search-results [] - [discoveries [:get-discovery-search-results] + [discoveries [:get-popular-discoveries 250] tags [:get :discovery-search-tags]] - (let [datasource (to-datasource discoveries)] + (let [discoveries (:discoveries discoveries) + datasource (to-datasource discoveries)] [view st/discovery-tag-container + [status-bar] [toolbar {:nav-action {:image {:source {:uri :icon_back} :style st/icon-back} :handler #(dispatch [:navigate-back])} :custom-content (title-content tags) - :actions [{:image {:source {:uri :icon_search} - :style st/icon-search} - :handler (fn [])}]}] - - [list-view {:dataSource datasource - :renderRow (fn [row _ _] - (list-item [discovery-list-item row])) - :renderSeparator render-separator - :style st/recent-list}]])) + :style st/discovery-tag-toolbar}] + (if (empty? discoveries) + [view st/empty-view + ;; todo change icon + [icon :group_big contacts-styles/empty-contacts-icon] + [text {:style contacts-styles/empty-contacts-text} + (label :t/no-statuses-found)]] + [list-view {:dataSource datasource + :renderRow (fn [row _ _] + (list-item [discovery-list-item row])) + :renderSeparator render-separator + :style st/recent-list}])])) diff --git a/src/status_im/discovery/styles.cljs b/src/status_im/discovery/styles.cljs index 8876e16452..3fce84917e 100644 --- a/src/status_im/discovery/styles.cljs +++ b/src/status_im/discovery/styles.cljs @@ -1,5 +1,7 @@ (ns status-im.discovery.styles - (:require [status-im.components.styles :refer [color-gray2]])) + (:require [status-im.components.styles :refer [color-gray2 + color-white]] + [status-im.components.toolbar.styles :refer [toolbar-background2]])) ;; common @@ -8,11 +10,18 @@ :border-bottom-color "#eff2f3"}) (def row - {:flex-direction :row}) + {:flex-direction :row + :margin-bottom 10}) (def column {:flex-direction :column}) +(def empty-view + {:flex 1 + :background-color color-white + :align-items :center + :justify-content :center}) + ;; Toolbar (def discovery-toolbar-content @@ -21,7 +30,7 @@ :justify-content :center}) (def discovery-toolbar - {:background-color "#eef2f5" + {:background-color toolbar-background2 :elevation 0}) (def discovery-search-input @@ -30,7 +39,7 @@ :margin-left 18 :line-height 42 :font-size 14 - :color "#9CBFC0"}) + :color "#7099e6"}) (def discovery-title {:color "#000000de" @@ -38,25 +47,19 @@ :text-align :center :font-size 16}) -(def discovery-subtitle - {:color color-gray2 - :font-size 14}) - (def section-spacing {:padding 16}) (def scroll-view-container - {}) + {:bounces false}) ;; Popular +(def popular-container + {:background-color toolbar-background2}) + (def carousel-page-style - {:borderRadius 1 - :shadowColor "black" - :shadowRadius 1 - :shadowOpacity 0.8 - :elevation 2 - :marginBottom 10}) + {}) (def tag-name {:color "#7099e6" @@ -66,16 +69,10 @@ :align-items :center :justify-content :center}) -(def tag-name-container - {:flex-direction "column" - :background-color "#eef2f5" - :border-radius 5 - :padding 4}) - (def tag-count {:color "#838c93" :font-size 12 - :padding-right 5 + :padding-right 6 :padding-bottom 2 :align-items :center :justify-content :center}) @@ -84,63 +81,67 @@ {:flex 0.2 :flex-direction "column" :align-items "flex-end" - :padding-top 10 + :padding-top 6 :padding-right 9}) +(def separator + {:background-color "rgb(200, 199, 204)" + :height 0.5}) + +;; Popular list item + (def popular-list-container {:flex 1 :background-color :white - :padding-left 10 - :padding-top 16}) - -(def popular-list - {:background-color :white - :padding-top 13}) - -;; Popular list item + :margin-left 16 + :padding-left 16 + :padding-top 18}) (def popular-list-item {:flex-direction :row - :padding-top 10 - :padding-bottom 10}) - -(def popular-list-item-status - {:color "black" - :line-height 22 - :font-size 14}) + :padding-bottom 16 + :top 1}) (def popular-list-item-name - {:color "black" - :font-size 14 - :line-height 24}) + {:color "black" + :font-size 15 + :padding-bottom 4}) (def popular-list-item-name-container {:flex 0.8 - :flex-direction "column"}) + :flex-direction "column" + :padding-top 16}) (def popular-list-item-avatar-container {:flex 0.2 :flex-direction "column" :align-items :center - :padding-top 5}) - -(def popular-list-item-avatar - {:border-radius 18 - :width 36 - :height 36}) + :padding-top 16}) ;; discovery_recent +(def recent-container + {:background-color toolbar-background2}) + (def recent-list {:background-color :white :padding-left 16}) ;; Discovery tag +(def discovery-tag-toolbar + {:border-bottom-color "#D7D7D7" + :border-bottom-width 1}) + (def discovery-tag-container {:flex 1 :backgroundColor "#eef2f5"}) +(def tag-title-scroll + {:flex 1 + :alignItems "center" + :justifyContent "center"}) + (def tag-title-container {:flex 1 :alignItems "center" @@ -153,14 +154,6 @@ :padding-right 5 :padding-bottom 2}) -(def tag-container - {:backgroundColor "#eef2f5" - :flexWrap :wrap - :borderRadius 5 - :padding 4 - :margin-left 2 - :margin-right 2}) - (def icon-back {:width 8 :height 14}) @@ -171,7 +164,7 @@ (def discovery-container {:flex 1 - :backgroundColor :#eef2f5}) + :backgroundColor color-white}) (def hamburger-icon {:width 16 diff --git a/src/status_im/discovery/subs.cljs b/src/status_im/discovery/subs.cljs index daede5ab7f..a5ce440d8c 100644 --- a/src/status_im/discovery/subs.cljs +++ b/src/status_im/discovery/subs.cljs @@ -1,21 +1,44 @@ (ns status-im.discovery.subs (:require-macros [reagent.ratom :refer [reaction]]) - (:require [re-frame.core :refer [register-sub]])) + (:require [re-frame.core :refer [register-sub]] + [status-im.utils.datetime :as time])) -(defn- get-discoveries-by-tags [{:keys [discoveries current-tag]} tags limit] - (let [tags' (or tags [current-tag]) - filter-tag (filter #(every? (->> (map :name (:tags %)) - (into (hash-set))) - tags')) - xform (if limit - (comp filter-tag (take limit)) - filter-tag)] - (into [] xform discoveries))) +(defn- calculate-priority [{:keys [chats contacts current-public-key]} + {:keys [whisper-id created-at]}] + (let [contact (get contacts whisper-id) + chat (get chats whisper-id) + seen-online-recently? (< (- (time/now-ms) (get contact :last-online)) + time/hour) + me? (= current-public-key whisper-id)] + (+ created-at ;; message is newer => priority is higher + (if (or me? contact) time/day 0) ;; user exists in contact list => increase priority + (if (or me? chat) time/day 0) ;; chat with this user exists => increase priority + (if (or me? seen-online-recently?) time/hour 0) ;; the user was online recently => increase priority + ))) -(register-sub :get-discoveries-by-tags - (fn [db [_ tags limit]] - (-> (get-discoveries-by-tags @db tags limit) - (reaction)))) +(defn- get-discoveries-by-tags [discoveries current-tag tags] + (let [tags' (or tags [current-tag])] + (filter #(every? (->> (map :name (:tags %)) + (into (hash-set))) + tags') + (vals discoveries)))) + +(register-sub :get-popular-discoveries + (fn [db [_ limit tags]] + (let [discoveries (reaction (:discoveries @db)) + current-tag (reaction (:current-tag @db)) + search-tags (reaction (:discovery-search-tags @db)) + discoveries (->> (get-discoveries-by-tags @discoveries @current-tag (or tags @search-tags)) + (map #(assoc % :priority (calculate-priority db %))) + (sort-by :priority >))] + (reaction {:discoveries (take limit discoveries) + :total (count discoveries)})))) + +(register-sub :get-recent-discoveries + (fn [db] + (->> (:discoveries @db) + (vals) + (reaction)))) (register-sub :get-popular-tags (fn [db [_ limit]] @@ -24,6 +47,8 @@ (register-sub :get-discovery-search-results (fn [db _] - (let [tags (get-in @db [:discovery-search-tags])] - (-> (get-discoveries-by-tags @db tags nil) + (let [discoveries (reaction (:discoveries @db)) + current-tag (reaction (:current-tag @db)) + tags (reaction (:discovery-search-tags @db))] + (-> (get-discoveries-by-tags @discoveries @current-tag @tags) (reaction))))) diff --git a/src/status_im/discovery/tag.cljs b/src/status_im/discovery/tag.cljs deleted file mode 100644 index 5d9c5be2f3..0000000000 --- a/src/status_im/discovery/tag.cljs +++ /dev/null @@ -1,36 +0,0 @@ -(ns status-im.discovery.tag - (:require-macros [status-im.utils.views :refer [defview]]) - (:require [re-frame.core :refer [subscribe dispatch]] - [status-im.utils.listview :refer [to-datasource]] - [status-im.components.react :refer [view text list-view list-item]] - [status-im.components.toolbar.view :refer [toolbar]] - [status-im.discovery.views.discovery-list-item :refer [discovery-list-item]] - [status-im.discovery.styles :as st])) - -(defn render-separator [_ row-id _] - (list-item [view {:style st/row-separator - :key row-id}])) - -(defn title-content [tag] - [view st/tag-title-container - [view {:style st/tag-container} - [text {:style st/tag-title} (str " #" tag)]]]) - -(defview discovery-tag [] - [tag [:get :current-tag] - discoveries [:get-discoveries-by-tags]] - (let [datasource (to-datasource discoveries)] - [view st/discovery-tag-container - [toolbar {:nav-action {:image {:source {:uri :icon_back} - :style st/icon-back} - :handler #(dispatch [:navigate-back])} - :custom-content (title-content tag) - :actions [{:image {:source {:uri :icon_search} - :style st/icon-search} - :handler (fn [])}]}] - - [list-view {:dataSource datasource - :renderRow (fn [row _ _] - (list-item [discovery-list-item row])) - :renderSeparator render-separator - :style st/recent-list}]])) diff --git a/src/status_im/discovery/views/discovery_list_item.cljs b/src/status_im/discovery/views/discovery_list_item.cljs index 4e75aada58..71e8c9baab 100644 --- a/src/status_im/discovery/views/discovery_list_item.cljs +++ b/src/status_im/discovery/views/discovery_list_item.cljs @@ -2,32 +2,54 @@ (:require-macros [status-im.utils.views :refer [defview]]) (:require [re-frame.core :refer [subscribe dispatch]] [clojure.string :as str] - [status-im.components.react :refer [view text image]] + [status-im.components.react :refer [view text image touchable-highlight]] [status-im.discovery.styles :as st] [status-im.utils.gfycat.core :refer [generate-gfy]] [status-im.utils.identicon :refer [identicon]] [status-im.i18n :refer [label]] - [status-im.components.chat-icon.screen :as ci])) + [status-im.components.chat-icon.screen :as ci] + [status-im.utils.platform :refer [platform-specific]] + [taoensso.timbre :as log])) -(defview discovery-list-item [{:keys [name photo-path status whisper-id]}] +(defn tag-view [tag] + [text {:style {:color "#7099e6"} + :font :medium} + (str tag " ")]) + +(defn status-view [item-style {:keys [message-id status]}] + [text {:style (:status-text item-style) + :font :default} + (for [[i status] (map-indexed vector (str/split status #" "))] + (if (.startsWith status "#") + ^{:key (str "item-" message-id "-" i)} + [tag-view status] + ^{:key (str "item-" message-id "-" i)} + (str status " ")))]) + +(defview discovery-list-item [{:keys [name photo-path whisper-id] :as message} + show-separator?] [{contact-name :name contact-photo-path :photo-path} [:get-in [:contacts whisper-id]]] - [view st/popular-list-item - [view st/popular-list-item-name-container - [text {:style st/popular-list-item-name - :font :medium - :number-of-lines 1} - (cond - (not (str/blank? contact-name)) contact-name - (not (str/blank? name)) name - :else (generate-gfy))] - [text {:style st/popular-list-item-status - :font :default - :number-of-lines 2} - status]] - [view st/popular-list-item-avatar-container - [ci/chat-icon (cond - (not (str/blank? contact-photo-path)) contact-photo-path - (not (str/blank? photo-path)) photo-path - :else (identicon whisper-id)) - {:size 36}]]]) + (let [item-style (get-in platform-specific [:component-styles :discovery :item])] + [view + [view st/popular-list-item + [view st/popular-list-item-name-container + [text {:style st/popular-list-item-name + :font :medium + :number-of-lines 1} + (cond + (not (str/blank? contact-name)) contact-name + (not (str/blank? name)) name + :else (generate-gfy))] + [status-view item-style message]] + [view (merge st/popular-list-item-avatar-container + (:icon item-style)) + [touchable-highlight {:on-press #(dispatch [:start-chat whisper-id])} + [view + [ci/chat-icon (cond + (not (str/blank? contact-photo-path)) contact-photo-path + (not (str/blank? photo-path)) photo-path + :else (identicon whisper-id)) + {:size 36}]]]]] + (when show-separator? + [view st/separator])])) diff --git a/src/status_im/discovery/views/popular_list.cljs b/src/status_im/discovery/views/popular_list.cljs index f8a7dd2bb4..8e2c6e1691 100644 --- a/src/status_im/discovery/views/popular_list.cljs +++ b/src/status_im/discovery/views/popular_list.cljs @@ -3,20 +3,23 @@ (:require [re-frame.core :refer [subscribe dispatch]] [status-im.components.react :refer [view - list-view - list-item - touchable-highlight - text]] + list-view + list-item + touchable-highlight + text]] [status-im.discovery.styles :as st] [status-im.utils.listview :refer [to-datasource]] - [status-im.discovery.views.discovery-list-item :refer [discovery-list-item]])) + [status-im.discovery.views.discovery-list-item :refer [discovery-list-item]] + [status-im.utils.platform :refer [platform-specific]])) -(defview discovery-popular-list [{:keys [tag count contacts]}] - [discoveries [:get-discoveries-by-tags [tag] 3]] - [view st/popular-list-container +(defview discovery-popular-list [{:keys [tag contacts]}] + [discoveries [:get-popular-discoveries 3 [tag]]] + [view (merge st/popular-list-container + (get-in platform-specific [:component-styles :discovery :popular])) [view st/row - [view st/tag-name-container - [touchable-highlight {:onPress #(dispatch [:show-discovery-tag tag])} + [view (get-in platform-specific [:component-styles :discovery :tag]) + [touchable-highlight {:on-press #(do (dispatch [:set :discovery-search-tags [tag]]) + (dispatch [:navigate-to :discovery-search-results]))} [view [text {:style st/tag-name :font :medium} @@ -24,7 +27,8 @@ [view st/tag-count-container [text {:style st/tag-count :font :default} - count]]] - (for [{:keys [message-id] :as discovery} discoveries] - ^{:key (str "message-" message-id)} - [discovery-list-item discovery])]) + (:total discoveries)]]] + (let [discoveries (map-indexed vector (:discoveries discoveries))] + (for [[i {:keys [message-id] :as discovery}] discoveries] + ^{:key (str "message-" message-id)} + [discovery-list-item discovery (not= (inc i) (count discoveries))]))]) diff --git a/src/status_im/handlers.cljs b/src/status_im/handlers.cljs index 152e8f1de0..dd91ed2741 100644 --- a/src/status_im/handlers.cljs +++ b/src/status_im/handlers.cljs @@ -64,6 +64,7 @@ (dispatch [:init-chat]) (dispatch [:init-discoveries]) (dispatch [:send-account-update-if-needed]) + (dispatch [:start-requesting-discoveries]) (dispatch [:remove-old-discoveries!])))) (register-handler :reset-app diff --git a/src/status_im/ios/core.cljs b/src/status_im/ios/core.cljs index c369f96860..e21d683103 100644 --- a/src/status_im/ios/core.cljs +++ b/src/status_im/ios/core.cljs @@ -13,7 +13,6 @@ [status-im.contacts.views.contact-list :refer [contact-list]] [status-im.contacts.views.new-contact :refer [new-contact]] [status-im.qr-scanner.screen :refer [qr-scanner]] - [status-im.discovery.tag :refer [discovery-tag]] [status-im.discovery.search-results :refer [discovery-search-results]] [status-im.chat.screen :refer [chat]] [status-im.accounts.login.screen :refer [login]] @@ -74,7 +73,6 @@ (let [current-view (validate-current-view @view-id @signed-up?)] (let [component (case current-view :discovery main-tabs - :discovery-tag discovery-tag :discovery-search-results discovery-search-results :add-participants new-participants :remove-participants remove-participants diff --git a/src/status_im/ios/platform.cljs b/src/status_im/ios/platform.cljs index 6fa847b30e..91abef0a0d 100644 --- a/src/status_im/ios/platform.cljs +++ b/src/status_im/ios/platform.cljs @@ -21,6 +21,27 @@ :border-bottom-width 0.5} :chat {:new-message {:border-top-color styles/color-gray3 :border-top-width 0.5}} + :discovery {:subtitle {:color styles/color-steel + :font-size 13 + :letter-spacing 1} + :popular {:border-radius 3 + :border-width 1 + :border-color "#D7D7D7"} + :tag {:flex-direction "column" + :background-color "rgb(227, 235, 250)" + :border-radius 4 + :border-width 1 + :border-color "rgba(112, 153, 230, 0.31)" + :padding 6} + :item {:status-text {:color styles/color-steel + :font-size 14 + :letter-spacing -0.1} + :icon {:padding-top 0 + :bottom -4 + :justify-content :flex-end}}} + :contacts {:subtitle {:color styles/color-steel + :font-size 13 + :letter-spacing 1}} :bottom-gradient {:height 1} :input-label {:left 0} :input-error-text {:margin-left 0} @@ -60,5 +81,8 @@ :chats {:action-button? false :new-chat-in-toolbar? true} :contacts {:action-button? false - :new-contact-in-toolbar? true}}) + :new-contact-in-toolbar? true + :uppercase-subtitles? true + :group-block-shadows? false} + :discovery {:uppercase-subtitles? true}}) diff --git a/src/status_im/protocol/core.cljs b/src/status_im/protocol/core.cljs index f1207e75cc..ee81cb1f5f 100644 --- a/src/status_im/protocol/core.cljs +++ b/src/status_im/protocol/core.cljs @@ -36,9 +36,12 @@ ;; discoveries (def watch-user! discoveries/watch-user!) (def contact-request! discoveries/contact-request!) -(def watch-hashtags! discoveries/watch-hashtags!) -(def broadcats-profile! discoveries/broadcats-profile!) -(def broadcats-discoveries! discoveries/broadcats-discoveries!) +(def broadcast-profile! discoveries/broadcast-profile!) +(def send-status! discoveries/send-status!) +(def send-discoveries-request! discoveries/send-discoveries-request!) +(def send-discoveries-response! discoveries/send-discoveries-response!) + +(def message-pending? d/message-pending?) ;; initialization (s/def ::rpc-url string?) @@ -82,10 +85,6 @@ {:topics [chat-id]} (l/message-listener (assoc listener-options :callback callback :keypair keypair)))) - ;; start listening to discoveries - (watch-hashtags! {:web3 web3 - :hashtags hashtags - :callback callback}) ;; start listening to profiles (doseq [{:keys [identity keypair]} contacts] (watch-user! {:web3 web3 diff --git a/src/status_im/protocol/discoveries.cljs b/src/status_im/protocol/discoveries.cljs index 22667337b0..549a8dbb2b 100644 --- a/src/status_im/protocol/discoveries.cljs +++ b/src/status_im/protocol/discoveries.cljs @@ -9,13 +9,9 @@ [status-im.protocol.validation :refer-macros [valid?]] [status-im.utils.random :as random])) -(def discovery-topic "status-discovery") (def discovery-topic-prefix "status-discovery-") (def discovery-hashtag-prefix "status-hashtag-") -(defn- add-hashtag-prefix [hashtag] - (str discovery-hashtag-prefix hashtag)) - (defn- make-discovery-topic [identity] (str discovery-topic-prefix identity)) @@ -72,35 +68,12 @@ (defonce watched-hashtag-topics (atom nil)) -(defn- hashtags->topics - "Create topics from hashtags." - [hashtags] - (->> hashtags - (map (fn [tag] - [tag [(add-hashtag-prefix tag) discovery-topic]])) - (into {}))) - (s/def :discoveries/hashtags (s/every string? :kind-of set?)) -(defn stop-watching-hashtags! - [web3] - (doseq [topics @watched-hashtag-topics] - (f/remove-filter! web3 topics))) - (s/def ::callback fn?) (s/def :watch-hashtags/options (s/keys :req-un [:options/web3 :discoveries/hashtags ::callback])) -(defn watch-hashtags! - [{:keys [web3 hashtags] :as options}] - {:pre [(valid? :watch-hashtags/options options)]} - (debug :watch-hashtags hashtags) - (stop-watching-hashtags! web3) - (let [hashtag-topics (vals (hashtags->topics hashtags))] - (reset! watched-hashtag-topics hashtag-topics) - (doseq [topics hashtag-topics] - (f/add-filter! web3 {:topics topics} (l/message-listener options))))) - (s/def ::status (s/nilable string?)) (s/def ::profile (s/keys :req-un [::status])) (s/def :profile/payload @@ -111,7 +84,7 @@ (s/def :broadcast-profile/options (s/keys :req-un [:profile/message :options/web3])) -(defn broadcats-profile! +(defn broadcast-profile! [{:keys [web3 message] :as options}] {:pre [(valid? :broadcast-profile/options options)]} (debug :broadcasting-status) @@ -132,18 +105,32 @@ (s/def :broadcast-hasthags/options (s/keys :req-un [:discoveries/hashtags :status/message :options/web3])) -(defn broadcats-discoveries! - [{:keys [web3 hashtags message] :as options}] - {:pre [(valid? :broadcast-hasthags/options options)]} +(defn send-status! + [{:keys [web3 message]}] (debug :broadcasting-status) - (let [discovery-id (random/id)] - (doseq [[tag hashtag-topics] (hashtags->topics hashtags)] - (d/add-pending-message! - web3 - (-> message - (assoc :type :discovery - :topics hashtag-topics) - (assoc-in [:payload :tag] tag) - (assoc-in [:payload :hashtags] (vec hashtags)) - (assoc-in [:payload :discovery-id] discovery-id) - (update :message-id str tag)))))) + (let [message (-> message + (assoc :type :discovery + :topics [(make-discovery-topic (:from message))]))] + (d/add-pending-message! web3 message))) + +(defn send-discoveries-request! + [{:keys [web3 message]}] + (debug :sending-discoveries-request) + (d/add-pending-message! + web3 + (-> message + (assoc :type :discoveries-request + :topics [(make-discovery-topic (:from message))])))) + +(defn send-discoveries-response! + [{:keys [web3 discoveries message]}] + (debug :sending-discoveries-response) + (doseq [portion (->> (take 100 discoveries) + (partition 10 10 nil))] + (d/add-pending-message! + web3 + (-> message + (assoc :type :discoveries-response + :topics [(make-discovery-topic (:from message))] + :message-id (random/id) + :payload {:data (into [] portion)}))))) diff --git a/src/status_im/protocol/handlers.cljs b/src/status_im/protocol/handlers.cljs index 6e48be10af..b950cd7039 100644 --- a/src/status_im/protocol/handlers.cljs +++ b/src/status_im/protocol/handlers.cljs @@ -85,7 +85,9 @@ :add-group-identity (dispatch [:participant-invited-to-group message]) :leave-group (dispatch [:participant-left-group message]) :contact-request (dispatch [:contact-request-received message]) - :discovery (dispatch [:discovery-response-received message]) + :discovery (dispatch [:status-received message]) + :discoveries-request (dispatch [:discoveries-request-received message]) + :discoveries-response (dispatch [:discoveries-response-received message]) :profile (dispatch [:contact-update-received message]) :online (dispatch [:contact-online-received message]) :pending (dispatch [:pending-message-upsert message]) diff --git a/src/status_im/protocol/web3/delivery.cljs b/src/status_im/protocol/web3/delivery.cljs index d4227a4da0..0eaea6ebf6 100644 --- a/src/status_im/protocol/web3/delivery.cljs +++ b/src/status_im/protocol/web3/delivery.cljs @@ -129,7 +129,7 @@ (defn delivery-callback [web3 {:keys [id requires-ack? to]}] (fn [error _] - (when error (log/error :shh-post-error error)) + (when error (log/warn :shh-post-error error)) (when-not error (debug :delivery-callback) (message-was-sent! web3 id to) @@ -159,7 +159,7 @@ ;; todo add some notification about network issues (<= attempts (* 5 max-attempts-number)) (and - ;; if message was not send lees then max-attempts-number times + ;; if message was not send less then max-attempts-number times ;; continue attempts (<= attempts max-attempts-number) ;; check retransmition interval @@ -169,6 +169,15 @@ [message message-type ttl-config default-ttl] (update message :ttl #(or % ((keyword message-type) ttl-config) default-ttl))) +(defn message-pending? + [web3 required-type required-to] + (some (fn [[_ messages]] + (some (fn [[_ {:keys [type to]}]] + (and (= type required-type) + (= to required-to))) + messages)) + (@messages web3))) + (defn run-delivery-loop! [web3 {:keys [delivery-loop-ms-interval default-ttl ttl-config send-online-s-interval online-message] diff --git a/src/status_im/translations/en.cljs b/src/status_im/translations/en.cljs index 955cfb9deb..3b3b828f00 100644 --- a/src/status_im/translations/en.cljs +++ b/src/status_im/translations/en.cljs @@ -107,6 +107,7 @@ :popular-tags "Popular tags" :recent "Recent" :no-statuses-discovered "No statuses discovered" + :no-statuses-found "No statuses found" ;settings :settings "Settings"