diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 17990f7d82..335316253b 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -4,6 +4,7 @@
+
clj %))
:onEndReached #(dispatch [:load-more-messages])
+ :enableEmptySections true
:dataSource (to-datasource2 @messages)}]))))
(defn chat []
diff --git a/src/syng_im/components/carousel/carousel.cljs b/src/syng_im/components/carousel/carousel.cljs
new file mode 100644
index 0000000000..b489482f5d
--- /dev/null
+++ b/src/syng_im/components/carousel/carousel.cljs
@@ -0,0 +1,178 @@
+(ns syng-im.components.carousel
+ (:require [syng-im.components.react :refer [android?
+ view
+ scroll-view
+ touchable-without-feedback
+ text]]
+ [syng-im.components.carousel.styles :as st]
+ [syng-im.utils.logging :as log]))
+
+
+(defn window-page-width []
+ (.-width (.get (.. js/React -Dimensions) "window")))
+
+(def defaults {:gap 10
+ :sneak 10
+ :pageStyle {}
+ :scrollThreshold 20})
+
+(defn get-active-page [data]
+ (get data :activePage 0))
+
+(defn get-sneak [data]
+ (get data :sneak (:sneak defaults)))
+
+(defn get-gap [data]
+ (get data :gap (:gap defaults)))
+
+(defn compute-page-width
+ ([gap sneak]
+ (compute-page-width (window-page-width) gap sneak))
+ ([window-page-width gap sneak]
+ (- window-page-width (+ (* 2 gap) (* 2 sneak)))))
+
+(defn get-page-width [data]
+ (get data :pageWidth (compute-page-width (get-gap data) (get-sneak data))))
+
+(defn get-page-style [data]
+ (let [data-style (get data :pageStyle {})]
+ (merge (:pageStyle defaults) data-style)))
+
+(defn get-scroll-threshold [data]
+ (get data :scrollThreshold (:scrollThreshold defaults)))
+
+(defn apply-props [component props]
+ (let [sneak (get-sneak props)
+ page-width (get-page-width props)
+ style (get-page-style props)
+ gap (quot (- (- (window-page-width) (* 2 sneak)) page-width) 2)]
+ (reagent.core/set-state component {:sneak sneak
+ :pageWidth page-width
+ :pageStyle style
+ :gap gap})))
+
+(defn scroll-to [component x y]
+ (.scrollTo (.-scrollView component) (clj->js {:y y
+ :x x})))
+
+(defn get-current-position [event]
+ (.-x (.-contentOffset (.-nativeEvent event))))
+
+(defn go-to-page [component page]
+ (let [props (reagent.core/props component)
+ state (reagent.core/state component)
+ page-width (get-page-width state)
+ gap (get-gap state)
+ page-position (* page (+ page-width gap))]
+ (log/debug "go-to-page: props-page-width=" page-width "; gap=" gap
+ "; page-position=" page-position)
+ (scroll-to component page-position 0)
+ (reagent.core/set-state component {:activePage page})
+ (when (:onPageChange props)
+ ((:onPageChange props) page))))
+
+(defn on-scroll-end [event component starting-position]
+ (let [props (reagent.core/props component)
+ state (reagent.core/state component)
+ scroll-threshold (get-scroll-threshold props)
+ current-page (get-active-page state)
+ current-position (get-current-position event)
+ direction (cond
+ (> current-position (+ starting-position scroll-threshold)) 1
+ (< current-position (- starting-position scroll-threshold)) -1
+ :else 0)
+ new-page (+ current-page direction)
+ ]
+ (log/debug state "on-scroll-end: starting position=" starting-position
+ "; current-position=" current-position "; direction=" direction
+ "; current-page=" current-page "; new-page=" new-page)
+ (if (not= current-page new-page)
+ (go-to-page component new-page)
+ (scroll-to component starting-position 0))))
+
+(defn component-will-mount [component new-args]
+ (let [props (reagent.core/props component)]
+ (log/debug "component-will-mount: new-args="new-args)
+ (apply-props component props)))
+
+(defn component-did-mount [component]
+ (let [props (reagent.core/props component)
+ initial-page (.-initialPage props)]
+ (log/debug "component-did-mount: initial-page="initial-page)
+ (when (pos? initial-page)
+ (go-to-page component initial-page))))
+
+(defn component-will-update [component new-argv]
+ (log/debug "component-will-update: "))
+
+(defn component-did-update [component old-argv]
+ (log/debug "component-did-update"))
+
+(defn component-will-receive-props [component new-argv]
+ (log/debug "component-will-receive-props: new-argv=" new-argv)
+ (apply-props component new-argv))
+
+(defn get-event-width [event]
+ (.-width (.-layout (.-nativeEvent event))))
+
+(defn on-layout-change [event component]
+ (let [state (reagent.core/state component)
+ page-width (compute-page-width (get-event-width event) (get-gap state) (get-sneak state))
+ state-page-width (get-page-width state)
+ active-page (get-active-page state)
+ gap (get-gap state)
+ page-position (* active-page (+ page-width gap))]
+ (log/debug "Layout changed: " " page-width=" page-width "; state-page-width=" state-page-width)
+ (if (not= page-width state-page-width)
+ (do
+ (reagent.core/set-state component {:pageWidth page-width})
+ (.setState component {:layout (.-layout (.-nativeEvent event))})
+ )
+ (scroll-to component page-position 0))))
+
+(defn get-pages [component data children]
+ (let [page-width (get-page-width data)
+ page-style (get-page-style data)
+ gap (get-gap data)
+ margin (quot gap 2)]
+ (doall (map-indexed (fn [index child]
+ (let [page-index index
+ touchable-data {:key index
+ :onPress #(go-to-page component page-index)}]
+ [touchable-without-feedback touchable-data
+ [view {:style [(st/page page-width margin)
+ page-style]
+ :onLayout #(log/debug "view onLayout" %)}
+
+ child]])) children))))
+
+(defn reagent-render [data children]
+ (let [starting-position (atom 0)
+ component (reagent.core/current-component)
+ state (reagent.core/state component)
+ sneak (get-sneak state)
+ gap (get-gap state)]
+ (log/debug "reagent-render: " data state)
+ [view {:style st/scroll-view-container}
+ [scroll-view {:contentContainerStyle (st/content-container sneak gap)
+ :automaticallyAdjustContentInsets false
+ :bounces false
+ :decelerationRate 0.9
+ :horizontal true
+ :onLayout #(on-layout-change % component)
+ :onScrollBeginDrag #(reset! starting-position (get-current-position %))
+ :onScrollEndDrag #(on-scroll-end % component @starting-position)
+ :showsHorizontalScrollIndicator false
+ :ref #(set! (.-scrollView component) %)}
+ (get-pages component state children)]]))
+
+(defn carousel [data children]
+ (let [component-data {:component-did-mount component-did-mount
+ :component-will-mount component-will-mount
+ :component-will-receive-props component-will-receive-props
+ :component-will-update component-will-update
+ :component-did-update component-did-update
+ :display-name "carousel"
+ :reagent-render reagent-render}]
+ (log/debug "Creating carousel component: " data)
+ (reagent.core/create-class component-data)))
diff --git a/src/syng_im/components/carousel/styles.cljs b/src/syng_im/components/carousel/styles.cljs
new file mode 100644
index 0000000000..def9de8c10
--- /dev/null
+++ b/src/syng_im/components/carousel/styles.cljs
@@ -0,0 +1,24 @@
+(ns syng-im.components.carousel.styles
+ (:require [syng-im.components.styles :refer [font
+ title-font
+ color-white
+ chat-background
+ online-color
+ selected-message-color
+ separator-color
+ text1-color
+ text2-color
+ toolbar-background1]]))
+
+(def scroll-view-container
+ {:flex 1})
+
+(defn content-container [sneak gap]
+ {:paddingLeft (+ sneak (quot gap 2))
+ :paddingRight (+ sneak (quot gap 2))})
+
+(defn page [page-width margin]
+ {:width page-width
+ :justifyContent "center"
+ :marginLeft margin
+ :marginRight margin})
\ No newline at end of file
diff --git a/src/syng_im/components/discovery/discovery.cljs b/src/syng_im/components/discovery/discovery.cljs
new file mode 100644
index 0000000000..8536715102
--- /dev/null
+++ b/src/syng_im/components/discovery/discovery.cljs
@@ -0,0 +1,83 @@
+(ns syng-im.components.discovery.discovery
+
+ (:require
+ [syng-im.utils.logging :as log]
+ [re-frame.core :refer [dispatch]]
+ [syng-im.models.discoveries :refer [save-discoveries]]
+ [syng-im.components.react :refer [android?
+ view
+ scroll-view
+ text
+ text-input]]
+ [reagent.core :as r]
+ [syng-im.components.toolbar :refer [toolbar]]
+ [syng-im.components.discovery.discovery-popular :refer [discovery-popular]]
+ [syng-im.components.discovery.discovery-recent :refer [discovery-recent]]
+ [syng-im.resources :as res]
+ [syng-im.components.discovery.styles :as st]
+ [syng-im.persistence.realm :as realm]))
+
+(def search-input (atom {:search "x"}))
+
+(defn get-hashtags [status]
+ (let [hashtags (map #(subs % 1) (re-seq #"#[^ !?,;:.]+" status))]
+ (if hashtags
+ hashtags
+ [])))
+
+(defn title-content [showSearch]
+ (if showSearch
+ [text-input {:underlineColorAndroid "transparent"
+ :style st/discovery-search-input
+ :autoFocus true
+ :placeholder "Type your search tags here"
+ :onSubmitEditing (fn [e]
+ (let [search (aget e "nativeEvent" "text")
+ hashtags (get-hashtags search)]
+ (dispatch [:broadcast-status search hashtags])))}]
+ [view
+ [text {:style st/discovery-title} "Discover"]]))
+
+(defn create-fake-discovery []
+ (let [number (rand-int 999)]
+ (do
+ (save-discoveries [{:name (str "Name " number)
+ :status (str "Status This is some longer status to get the second line " number)
+ :whisper-id (str number)
+ :photo ""
+ :location ""
+ :tags ["tag1" "tag2" "tag3"]
+ :last-updated (new js/Date)}])
+ (dispatch [:updated-discoveries]))))
+
+(defn discovery [{:keys [navigator]}]
+ (let [showSearch (r/atom false)]
+ (fn []
+ [view {:style {:flex 1
+ :backgroundColor "#eef2f5"}}
+ [toolbar {:style st/discovery-toolbar
+ :navigator navigator
+ :nav-action {:image {:source {:uri "icon_hamburger"}
+ :style {:width 16
+ :height 12}}
+ :handler create-fake-discovery}
+ :title "Add Participants"
+ :content (title-content @showSearch)
+ :action {:image {:source {:uri "icon_search"}
+ :style {:width 17
+ :height 17}}
+ :handler (fn []
+ (if @showSearch
+ (reset! showSearch false)
+ (reset! showSearch true)))}}]
+ [scroll-view {:style {}}
+ [view {:style st/section-spacing}
+ [text {:style st/discovery-subtitle} "Popular tags"]]
+ [discovery-popular navigator]
+ [view {:style st/section-spacing}
+ [text {:style st/discovery-subtitle} "Recent"]]
+ [discovery-recent]]])))
+ (comment
+ (def page-width (aget (natal-shell.dimensions/get "window") "width"))
+ (def page-height (aget (natal-shell.dimensions/get "window") "height"))
+ )
diff --git a/src/syng_im/components/discovery/discovery_popular.cljs b/src/syng_im/components/discovery/discovery_popular.cljs
new file mode 100644
index 0000000000..df33467899
--- /dev/null
+++ b/src/syng_im/components/discovery/discovery_popular.cljs
@@ -0,0 +1,23 @@
+(ns syng-im.components.discovery.discovery-popular
+ (:require
+ [re-frame.core :refer [subscribe]]
+ [syng-im.utils.logging :as log]
+ [syng-im.components.react :refer [android?
+ text]]
+ [syng-im.components.carousel :refer [carousel]]
+ [syng-im.components.discovery.styles :as st]
+ [syng-im.components.discovery.discovery-popular-list :refer [discovery-popular-list]]
+ ))
+
+(defn page-width []
+ (.-width (.get (.. js/React -Dimensions) "window")))
+
+(defn discovery-popular [navigator]
+ (let [popular-tags (subscribe [:get-popular-tags 3])]
+ (log/debug "Got popular tags: " @popular-tags)
+ (if (> (count @popular-tags) 0)
+ [carousel {:pageStyle st/carousel-page-style
+ :sneak 20}
+ (for [tag @popular-tags]
+ (discovery-popular-list (.-name tag) (.-count tag) navigator))]
+ [text "None"])))
\ No newline at end of file
diff --git a/src/syng_im/components/discovery/discovery_popular_list.cljs b/src/syng_im/components/discovery/discovery_popular_list.cljs
new file mode 100644
index 0000000000..0291a1bb82
--- /dev/null
+++ b/src/syng_im/components/discovery/discovery_popular_list.cljs
@@ -0,0 +1,44 @@
+(ns syng-im.components.discovery.discovery-popular-list
+ (:require
+ [re-frame.core :refer [subscribe dispatch dispatch-sync]]
+ [syng-im.utils.logging :as log]
+ [syng-im.components.react :refer [android?
+ view
+ list-view
+ touchable-highlight
+ text
+ image]]
+ [reagent.core :as r]
+ [syng-im.components.realm :refer [list-view]]
+ [syng-im.components.discovery.styles :as st]
+ [syng-im.utils.listview :refer [to-realm-datasource]]
+ [syng-im.components.discovery.discovery-popular-list-item :refer [discovery-popular-list-item] ])
+ )
+
+
+(defn render-row [row section-id row-id]
+ (let [elem (discovery-popular-list-item row)]
+ elem)
+)
+
+(defn render-separator [sectionID, rowID, adjacentRowHighlighted]
+ (let [elem (r/as-element [view {:style st/row-separator
+ :key rowID}])]
+ elem))
+
+(defn discovery-popular-list [tag count navigator]
+ (let [discoveries (subscribe [:get-discoveries-by-tag tag 3])]
+ [view {:style st/popular-list-container}
+ [view st/row
+ [view st/tag-name-container
+ [touchable-highlight {:onPress #(dispatch [:show-discovery-tag tag navigator :push])}
+ [text {:style st/tag-name}
+ (str " #" (name tag))]]]
+ [view {:style st/tag-count-container}
+ [text {:style st/tag-count}
+ count]]]
+ [list-view {:dataSource (to-realm-datasource @discoveries)
+ :enableEmptySections true
+ :renderRow render-row
+ :renderSeparator render-separator
+ :style st/popular-list}]]))
diff --git a/src/syng_im/components/discovery/discovery_popular_list_item.cljs b/src/syng_im/components/discovery/discovery_popular_list_item.cljs
new file mode 100644
index 0000000000..49e08f2672
--- /dev/null
+++ b/src/syng_im/components/discovery/discovery_popular_list_item.cljs
@@ -0,0 +1,24 @@
+(ns syng-im.components.discovery.discovery-popular-list-item
+ (:require
+ [syng-im.utils.logging :as log]
+ [syng-im.components.react :refer [android?
+ view
+ text
+ image]]
+ [syng-im.resources :as res]
+ [syng-im.components.discovery.styles :as st]
+ [reagent.core :as r])
+ )
+
+(defn discovery-popular-list-item [discovery]
+ (r/as-element [view {:style st/popular-list-item}
+ [view {:style st/popular-list-item-name-container}
+ [text {:style st/popular-list-item-name} (aget discovery "name")]
+ [text {:style st/popular-list-item-status
+ :numberOfLines 2} (aget discovery "status")]
+ ]
+ [view {:style st/popular-list-item-avatar-container}
+ [image {:style st/popular-list-item-avatar
+ :source res/user-no-photo}]
+ ]
+ ]))
\ No newline at end of file
diff --git a/src/syng_im/components/discovery/discovery_recent.cljs b/src/syng_im/components/discovery/discovery_recent.cljs
new file mode 100644
index 0000000000..4b1a650322
--- /dev/null
+++ b/src/syng_im/components/discovery/discovery_recent.cljs
@@ -0,0 +1,35 @@
+(ns syng-im.components.discovery.discovery-recent
+ (:require-macros
+ [natal-shell.data-source :refer [data-source clone-with-rows]]
+
+ )
+ (:require
+ [re-frame.core :refer [subscribe]]
+ [syng-im.components.react :refer [android?
+ view]]
+ [syng-im.components.realm :refer [list-view]]
+ [syng-im.utils.listview :refer [to-realm-datasource]]
+ [syng-im.components.discovery.styles :as st]
+ [syng-im.components.discovery.discovery-popular-list-item :refer [discovery-popular-list-item]]
+ [reagent.core :as r]))
+
+
+(defn render-row [row section-id row-id]
+ (let [elem (discovery-popular-list-item row)]
+ elem)
+ )
+
+(defn render-separator [sectionID, rowID, adjacentRowHighlighted]
+ (let [elem (r/as-element [view {:style st/row-separator
+ :key rowID}])]
+ elem))
+
+(defn discovery-recent []
+ (let [discoveries (subscribe [:get-discoveries])
+ datasource (to-realm-datasource @discoveries)]
+ [list-view {:dataSource datasource
+ :enableEmptySections true
+ :renderRow render-row
+ :renderSeparator render-separator
+ :style st/recent-list}]
+ ))
\ No newline at end of file
diff --git a/src/syng_im/components/discovery/discovery_tag.cljs b/src/syng_im/components/discovery/discovery_tag.cljs
new file mode 100644
index 0000000000..ba7b247fc4
--- /dev/null
+++ b/src/syng_im/components/discovery/discovery_tag.cljs
@@ -0,0 +1,60 @@
+(ns syng-im.components.discovery.discovery-tag
+ (:require
+ [re-frame.core :refer [subscribe]]
+ [syng-im.utils.logging :as log]
+ [syng-im.utils.listview :refer [to-realm-datasource
+ to-datasource]]
+ [syng-im.navigation :refer [nav-pop]]
+ [syng-im.components.react :refer [android?
+ view
+ text]]
+ [syng-im.components.realm :refer [list-view]]
+ [syng-im.components.toolbar :refer [toolbar]]
+ [reagent.core :as r]
+ [syng-im.components.discovery.discovery-popular-list-item :refer [discovery-popular-list-item]]
+ [syng-im.resources :as res]
+ [syng-im.components.discovery.styles :as st]))
+
+(defn render-row [row section-id row-id]
+ (log/debug "discovery-tag-row: " row section-id row-id)
+ (if row
+ (let [elem (discovery-popular-list-item row)]
+ elem)
+ (r/as-element [text "null"])
+ ))
+
+(defn render-separator [sectionID, rowID, adjacentRowHighlighted]
+ (let [elem (r/as-element [view {:style st/row-separator
+ :key rowID}])]
+ elem))
+
+(defn title-content [tag]
+ [view {:style st/tag-title-container}
+ [text {:style st/tag-title}
+ (str " #" tag)]])
+
+(defn discovery-tag [{:keys [tag navigator]}]
+ (let [tag (subscribe [:get-current-tag])
+ discoveries (subscribe [:get-discoveries-by-tag @tag 0])]
+ (log/debug "Got discoveries: " @discoveries)
+ (fn []
+ (let [items @discoveries
+ datasource (to-realm-datasource items)]
+ [view {:style st/discovery-tag-container}
+ [toolbar {:navigator navigator
+ :nav-action {:image {:source {:uri "icon_back"}
+ :style st/icon-back}
+ :handler (fn [] (nav-pop navigator))}
+ :title "Add Participants"
+ :content (title-content @tag)
+ :action {:image {:source {:uri "icon_search"}
+ :style st/icon-search}
+ :handler (fn []
+ ())}}]
+
+ [list-view {:dataSource datasource
+ :enableEmptySections true
+ :renderRow render-row
+ :renderSeparator render-separator
+ :style st/recent-list}]
+ ]))))
\ No newline at end of file
diff --git a/src/syng_im/components/discovery/handlers.cljs b/src/syng_im/components/discovery/handlers.cljs
new file mode 100644
index 0000000000..44d24c99d9
--- /dev/null
+++ b/src/syng_im/components/discovery/handlers.cljs
@@ -0,0 +1,44 @@
+(ns syng-im.components.discovery.handlers
+ (:require [re-frame.core :refer [register-handler after dispatch]]
+ [syng-im.utils.logging :as log]
+ [syng-im.protocol.api :as api]
+ [syng-im.navigation :refer [nav-push
+ nav-replace
+ nav-pop]]
+ [syng-im.models.discoveries :refer [save-discoveries
+ set-current-tag
+ signal-discoveries-updated]]))
+
+
+;; -- Discovery --------------------------------------------------------------
+
+(register-handler :discovery-response-received
+ (fn [db [_ from payload]]
+ (let [{:keys [name status hashtags location]} payload
+ location (if location location "")]
+ (save-discoveries [{:name name
+ :status status
+ :whisper-id from
+ :photo ""
+ :location location
+ :tags hashtags
+ :last-updated (js/Date.)}])
+ (signal-discoveries-updated db))))
+
+(register-handler :updated-discoveries
+ (fn [db _]
+ (signal-discoveries-updated db)))
+
+(register-handler :broadcast-status
+ (fn [db [action status hashtags]]
+ (let [name (:name db)]
+ (log/debug "Status: " status ", Hashtags: " hashtags)
+ (api/broadcast-discover-status name status hashtags)
+ db)))
+
+(register-handler :show-discovery-tag
+ (fn [db [action tag navigator nav-type]]
+ (log/debug action "setting current tag: " tag)
+ (let [db (set-current-tag db tag)]
+ (dispatch [:navigate-to navigator {:view-id :discovery-tag} nav-type])
+ db)))
\ No newline at end of file
diff --git a/src/syng_im/components/discovery/styles.cljs b/src/syng_im/components/discovery/styles.cljs
new file mode 100644
index 0000000000..9c988558f9
--- /dev/null
+++ b/src/syng_im/components/discovery/styles.cljs
@@ -0,0 +1,175 @@
+(ns syng-im.components.discovery.styles
+ (:require [syng-im.components.styles :refer [font
+ title-font
+ color-white
+ chat-background
+ online-color
+ selected-message-color
+ separator-color
+ text1-color
+ text2-color
+ toolbar-background1]]))
+
+;; common
+
+(def row-separator
+ {:borderBottomWidth 1
+ :borderBottomColor "#eff2f3"})
+
+(def row
+ {:flexDirection "row"})
+
+(def column
+ {:flexDirection "column"})
+
+;; discovery.cljs
+
+(def discovery-search-input
+ {:flex 1
+ :marginLeft 18
+ :lineHeight 42
+ :fontSize 14
+ :fontFamily "Avenir-Roman"
+ :color "#9CBFC0"})
+
+(def discovery-title
+ {:color "#000000de"
+ :alignSelf "center"
+ :textAlign "center"
+ :fontFamily "sans-serif"
+ :fontSize 16})
+
+(def discovery-toolbar
+ {:backgroundColor "#eef2f5"
+ :elevation 0})
+
+(def discovery-subtitle
+ {:color "#8f838c93"
+ :fontFamily "sans-serif-medium"
+ :fontSize 14})
+
+(def section-spacing
+ {:paddingLeft 30
+ :paddingTop 15
+ :paddingBottom 15})
+
+;; discovery_popular.cljs
+
+(def carousel-page-style
+ {:borderRadius 1
+ :shadowColor "black"
+ :shadowRadius 1
+ :shadowOpacity 0.8
+ :elevation 2
+ :marginBottom 10})
+
+;; discovery_populat_list.cljs
+
+(def tag-name
+ {:color "#7099e6"
+ :fontFamily "sans-serif-medium"
+ :fontSize 14
+ :paddingRight 5
+ :paddingBottom 2
+ :alignItems "center"
+ :justifyContent "center"})
+
+(def tag-name-container
+ {:flexDirection "column"
+ :backgroundColor "#eef2f5"
+ :borderRadius 5
+ :padding 4})
+
+(def tag-count
+ {:color "#838c93"
+ :fontFamily "sans-serif"
+ :fontSize 12
+ :paddingRight 5
+ :paddingBottom 2
+ :alignItems "center"
+ :justifyContent "center"})
+
+(def tag-count-container
+ {:flex 0.2
+ :flexDirection "column"
+ :alignItems "flex-end"
+ :paddingTop 10
+ :paddingRight 9})
+
+(def popular-list-container
+ {:flex 1
+ :backgroundColor "white"
+ :paddingLeft 10
+ :paddingTop 16})
+
+(def popular-list
+ {:backgroundColor "white"
+ :paddingTop 13})
+
+;; discover_popular_list_item.cjls
+
+(def popular-list-item
+ {:flexDirection "row"
+ :paddingTop 10
+ :paddingBottom 10})
+
+(def popular-list-item-status
+ {:color "black"
+ :fontFamily "sans-serif"
+ :lineHeight 22
+ :fontSize 14})
+
+(def popular-list-item-name
+ {:color "black"
+ :fontFamily "sans-serif-medium"
+ :fontSize 14
+ :lineHeight 24})
+
+(def popular-list-item-name-container
+ {:flex 0.8
+ :flexDirection "column"})
+
+(def popular-list-item-avatar-container
+ {:flex 0.2
+ :flexDirection "column"
+ :alignItems "center"
+ :paddingTop 5})
+
+(def popular-list-item-avatar
+ {:resizeMode "contain"
+ :borderRadius 150
+ :width 40
+ :height 40})
+
+;; discovery_recent
+
+(def recent-list
+ {:backgroundColor "white"
+ :paddingLeft 15})
+
+;; discovery_tag
+
+(def discovery-tag-container
+ {:flex 1
+ :backgroundColor "#eef2f5"})
+
+(def tag-title
+ {:color "#7099e6"
+ :fontFamily "sans-serif-medium"
+ :fontSize 14
+ :paddingRight 5
+ :paddingBottom 2})
+
+(def tag-title-container
+ {:backgroundColor "#eef2f5"
+ :flexWrap :wrap
+ :borderRadius 5
+ :padding 4})
+
+(def icon-back
+ {:width 8
+ :height 14})
+
+(def icon-search
+ {:width 17
+ :height 17})
\ No newline at end of file
diff --git a/src/syng_im/components/discovery/subs.cljs b/src/syng_im/components/discovery/subs.cljs
new file mode 100644
index 0000000000..d1a8eb5399
--- /dev/null
+++ b/src/syng_im/components/discovery/subs.cljs
@@ -0,0 +1,48 @@
+(ns syng-im.components.discovery.subs
+ (:require-macros [reagent.ratom :refer [reaction]])
+ (:require [re-frame.core :refer [register-sub]]
+ [syng-im.db :as db]
+ [syng-im.utils.logging :as log]
+ [syng-im.models.discoveries :refer [discovery-list
+ current-tag
+ get-tag-popular
+ discoveries-by-tag
+ current-tag-updated?
+ discoveries-updated?]]))
+
+
+
+(register-sub :get-discoveries
+ (fn [db _]
+ (let [discoveries-updated (-> (discoveries-updated? @db)
+ (reaction))]
+ (reaction
+ (let [_ @discoveries-updated]
+ (discovery-list))))))
+
+(register-sub :get-discoveries-by-tag
+ (fn [db [_ tag limit]]
+ (let [discoveries-updated (-> (discoveries-updated? @db)
+ (reaction))]
+ (log/debug "Getting discoveries for: " tag)
+ (reaction
+ (let [_ @discoveries-updated]
+ (discoveries-by-tag tag limit))))))
+
+(register-sub :get-popular-tags
+ (fn [db [_ limit]]
+ (let [discoveries-updated (-> (discoveries-updated? @db)
+ (reaction))]
+ (log/debug "Getting tags limited: " limit)
+ (reaction
+ (let [_ @discoveries-updated]
+ (get-tag-popular limit))))))
+
+(register-sub :get-current-tag
+ (fn [db _]
+ (let [current-tag-updated (-> (current-tag-updated? @db)
+ (reaction))]
+ (reaction
+ (let [_ @current-tag-updated]
+ (current-tag @db))))))
+
diff --git a/src/syng_im/components/react.cljs b/src/syng_im/components/react.cljs
index 81e65cc804..64b6dcd001 100644
--- a/src/syng_im/components/react.cljs
+++ b/src/syng_im/components/react.cljs
@@ -16,6 +16,8 @@
content])
(def toolbar-android (r/adapt-react-class (.-ToolbarAndroid js/React)))
(def list-view (r/adapt-react-class (.-ListView js/React)))
+(def scroll-view (r/adapt-react-class (.-ScrollView js/React)))
+(def touchable-without-feedback (r/adapt-react-class (.-TouchableWithoutFeedback js/React)))
(def text-input-class (r/adapt-react-class (.-TextInput js/React)))
(defn text-input [props text]
[text-input-class (merge
diff --git a/src/syng_im/components/toolbar.cljs b/src/syng_im/components/toolbar.cljs
index ad34b23efe..4e9a38fd0c 100644
--- a/src/syng_im/components/toolbar.cljs
+++ b/src/syng_im/components/toolbar.cljs
@@ -18,37 +18,44 @@
[reagent.core :as r]
[syng-im.navigation :refer [nav-pop]]))
-(defn toolbar [{:keys [navigator title nav-action action background-color]}]
- [view {:style {:flexDirection "row"
- :backgroundColor (or background-color toolbar-background1)
- :height 56
- :elevation 2}}
- (if nav-action
- [touchable-highlight {:on-press (:handler nav-action)}
+(defn toolbar [{:keys [navigator title nav-action action background-color content style]}]
+ (let [style (merge {:flexDirection "row"
+ :backgroundColor (or background-color toolbar-background1)
+ :height 56
+ :elevation 2} style)]
+ [view {:style style}
+ (if nav-action
+ [touchable-highlight {:on-press (:handler nav-action)}
+ [view {:width 56
+ :height 56
+ :alignItems "center"
+ :justifyContent "center"}
+ [image (:image nav-action)]]]
+ [touchable-highlight {:on-press #(nav-pop navigator)}
+ [view {:width 56
+ :height 56}
+ [image {:source {:uri "icon_back"}
+ :style {:marginTop 21
+ :marginLeft 23
+ :width 8
+ :height 14}}]]])
+ (if content
+ [view {:style {:flex 1
+ :alignItems "center"
+ :justifyContent "center"}}
+ content]
+ [view {:style {:flex 1
+ :alignItems "center"
+ :justifyContent "center"}}
+ [text {:style {:marginTop -2.5
+ :color text1-color
+ :fontSize 16
+ :fontFamily font}}
+ title]])
+ [touchable-highlight {:on-press (:handler action)}
[view {:width 56
:height 56
:alignItems "center"
:justifyContent "center"}
- [image (:image nav-action)]]]
- [touchable-highlight {:on-press #(nav-pop navigator)}
- [view {:width 56
- :height 56}
- [image {:source {:uri "icon_back"}
- :style {:marginTop 21
- :marginLeft 23
- :width 8
- :height 14}}]]])
- [view {:style {:flex 1
- :alignItems "center"
- :justifyContent "center"}}
- [text {:style {:marginTop -2.5
- :color text1-color
- :fontSize 16
- :fontFamily font}}
- title]]
- [touchable-highlight {:on-press (:handler action)}
- [view {:width 56
- :height 56
- :alignItems "center"
- :justifyContent "center"}
- [image (:image action)]]]])
+ [image (:image action)]]]]))
+
diff --git a/src/syng_im/db.cljs b/src/syng_im/db.cljs
index 2e5e3baf63..09f45f2725 100644
--- a/src/syng_im/db.cljs
+++ b/src/syng_im/db.cljs
@@ -4,7 +4,7 @@
;; schema of app-db
(def schema {:greeting s/Str})
-(def default-view :chat-list)
+(def default-view :discovery)
;; initial state of app-db
(def app-db {:greeting "Hello Clojure in iOS and Android!"
@@ -21,8 +21,9 @@
:new-participants #{}
:signed-up false
:view-id default-view
- :navigation-stack (list default-view)})
-
+ :navigation-stack (list default-view)
+ :name "My Name"
+ :current-tag nil})
(def protocol-initialized-path [:protocol-initialized])
(def identity-password-path [:identity-password])
@@ -47,3 +48,8 @@
(def show-actions-path [:show-actions])
(def new-group-path [:new-group])
(def new-participants-path [:new-participants])
+(def updated-discoveries-signal-path [:discovery-updated-signal])
+(defn updated-discovery-signal-path [whisper-id]
+ [:discoveries whisper-id :discovery-updated-signal])
+(def current-tag-path [:current-tag])
+(def updated-current-tag-signal-path [:current-tag-updated-signal])
diff --git a/src/syng_im/models/discoveries.cljs b/src/syng_im/models/discoveries.cljs
new file mode 100644
index 0000000000..1efa9c5012
--- /dev/null
+++ b/src/syng_im/models/discoveries.cljs
@@ -0,0 +1,125 @@
+(ns syng-im.models.discoveries
+ (:require [cljs.core.async :as async :refer [chan put! !]]
+ [re-frame.core :refer [subscribe dispatch dispatch-sync]]
+ [syng-im.utils.logging :as log]
+ [syng-im.persistence.realm :as realm]
+ [syng-im.persistence.realm :as r]
+ [syng-im.resources :as res]
+ [syng-im.db :as db]))
+
+(defn signal-discoveries-updated [db]
+ (update-in db db/updated-discoveries-signal-path (fn [current]
+ (if current
+ (inc current)
+ 0))))
+
+(defn discoveries-updated? [db]
+ (get-in db db/updated-discoveries-signal-path))
+
+(defn current-tag-updated? [db]
+ (get-in db db/updated-current-tag-signal-path))
+
+
+
+(defn current-tag [db]
+ (get-in db db/current-tag-path))
+
+(defn set-current-tag [db tag]
+ (assoc-in db db/current-tag-path tag))
+
+(defn get-tag [tag]
+ (log/debug "Getting tag: " tag)
+ (-> (r/get-by-field :tag :name tag)
+ (r/single-cljs)))
+
+(defn decrease-tag-counter [tag]
+ (let [tag (:name tag)
+ tag-object (get-tag tag)]
+ (if tag-object
+ (let [counter (dec (:count tag-object))]
+ (if (= counter 0)
+ (realm/delete tag-object)
+ (realm/create :tag {:name tag
+ :count counter}
+ true))))))
+
+(defn increase-tag-counter [tag]
+ (let [tag (:name tag)
+ tag-object (get-tag tag)]
+ (if tag-object
+ (realm/create :tag {:name tag
+ :count (inc (:count tag-object))}
+ true))))
+
+(defn decrease-tags-counter [tags]
+ (doseq [tag tags]
+ (decrease-tag-counter tag)))
+
+(defn increase-tags-counter [tags]
+ (doseq [tag tags]
+ (increase-tag-counter tag)))
+
+(defn get-tags [whisper-id]
+ (:tags (-> (r/get-by-field :discoveries :whisper-id whisper-id)
+ (r/single-cljs))))
+
+(defn- create-discovery [{:keys [name status whisper-id photo location tags last-updated]}]
+ (let [tags (mapv (fn [tag] {:name tag}) tags)
+ discovery {:name name
+ :status status
+ :whisper-id whisper-id
+ :photo photo
+ :location location
+ :tags tags
+ :last-updated last-updated}]
+ (log/debug "Creating discovery: " discovery tags)
+ (realm/create :discoveries discovery true)
+ (increase-tags-counter tags)))
+
+(defn- update-discovery [{:keys [name status whisper-id photo location tags last-updated]}]
+ (let [old-tags (get-tags whisper-id)
+ tags (mapv (fn [tag] {:name tag}) tags)
+ discovery {:name name
+ :status status
+ :whisper-id whisper-id
+ :photo photo
+ :location location
+ :tags tags
+ :last-updated last-updated}]
+ (decrease-tags-counter old-tags)
+ (realm/create :discoveries discovery true)
+ (increase-tags-counter tags)))
+
+(defn- discovery-exist? [discoveries discovery]
+ (some #(= (:whisper-id discovery) (:whisper-id %)) discoveries))
+
+(defn discovery-list []
+ (-> (r/get-all :discoveries)
+ (r/sorted :last-updated :desc)))
+
+(defn- add-discoveries [discoveries]
+ (realm/write (fn []
+ (let [db-discoveries (.slice (discovery-list) 0)]
+ (dorun (map (fn [discovery]
+ (if (not (discovery-exist? db-discoveries discovery))
+ (create-discovery discovery)
+ (update-discovery discovery)
+ ))
+ discoveries))))))
+
+(defn save-discoveries [discoveries]
+ (add-discoveries discoveries))
+
+(defn discoveries-by-tag [tag limit]
+ (let [discoveries (-> (r/get-by-filter :discoveries (str "tags.name = '" tag "'"))
+ (r/sorted :last-updated :desc))]
+ (log/debug "Discoveries by tag: " tag)
+ (if (pos? limit)
+ (r/page discoveries 0 limit)
+ discoveries)))
+
+(defn get-tag-popular [limit]
+ (-> (r/get-all :tag)
+ (r/sorted :count :desc)
+ (r/page 0 limit)))
+
diff --git a/src/syng_im/persistence/realm.cljs b/src/syng_im/persistence/realm.cljs
index 7fb0dd45d3..83faef94a4 100644
--- a/src/syng_im/persistence/realm.cljs
+++ b/src/syng_im/persistence/realm.cljs
@@ -45,12 +45,27 @@
:name "string"
:group-chat {:type "bool"
:indexed true}
- :is-active "bool"
- :timestamp "int"
- :contacts {:type "list"
- :objectType "chat-contact"}
- :last-msg-id "string"}}]})
-
+ :is-active "bool"
+ :timestamp "int"
+ :contacts {:type "list"
+ :objectType "chat-contact"}
+ :last-msg-id "string"}}
+ {:name :tag
+ :primaryKey :name
+ :properties {:name "string"
+ :count {:type "int"
+ :optional true
+ :default 0}}}
+ {:name :discoveries
+ :primaryKey :whisper-id
+ :properties {:name "string"
+ :status "string"
+ :whisper-id "string"
+ :photo "string"
+ :location "string"
+ :tags {:type "list"
+ :objectType "tag"}
+ :last-updated "date"}}]})
(def realm (js/Realm. (clj->js opts)))
@@ -84,6 +99,10 @@
value))]
query))
+(defn get-by-filter [schema-name filter]
+ (-> (.objects realm (name schema-name))
+ (.filtered filter)))
+
(defn get-by-field [schema-name field value]
(let [q (to-query schema-name :eq field value)]
(.filtered (.objects realm (name schema-name)) q)))
diff --git a/src/syng_im/protocol/protocol_handler.cljs b/src/syng_im/protocol/protocol_handler.cljs
index 8bb19c8f09..dadeee0b5f 100644
--- a/src/syng_im/protocol/protocol_handler.cljs
+++ b/src/syng_im/protocol/protocol_handler.cljs
@@ -40,4 +40,6 @@
(dispatch [:you-removed-from-group from group-id msg-id]))
:participant-left-group (let [{:keys [group-id from msg-id]} event]
(dispatch [:participant-left-group from group-id msg-id]))
+ :discover-response (let [{:keys [from payload]} event]
+ (dispatch [:discovery-response-received from payload]))
(log/info "Don't know how to handle" event-type)))})
diff --git a/src/syng_im/resources.cljs b/src/syng_im/resources.cljs
index ce2a17a11e..f3fc2639e0 100644
--- a/src/syng_im/resources.cljs
+++ b/src/syng_im/resources.cljs
@@ -16,5 +16,6 @@
(def add-icon (js/require "./images/add.png"))
(def trash-icon (js/require "./images/trash.png"))
(def leave-icon (js/require "./images/leave.png"))
-
(def icon-close-gray (js/require "./images/icon_close_gray.png"))
+(def menu (js/require "./images/ic_menu_black_24dp_1x.png"))
+(def search (js.require "./images/ic_search_black_24dp_1x.png"))