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