Merge pull request #68 from status-im/structure

Structure
This commit is contained in:
Jarrad 2016-05-16 15:05:34 +02:00
commit 44074bd054
104 changed files with 2204 additions and 2470 deletions

View File

@ -2,28 +2,22 @@
(:require-macros
[natal-shell.back-android :refer [add-event-listener remove-event-listener]])
(:require [reagent.core :as r :refer [atom]]
[cljs.core :as cljs]
[re-frame.core :refer [subscribe dispatch dispatch-sync]]
[syng-im.handlers]
[syng-im.subs]
[syng-im.components.react :refer [navigator app-registry]]
[syng-im.components.contact-list.contact-list :refer [contact-list]]
[syng-im.components.discovery.discovery :refer [discovery]]
[syng-im.components.discovery.discovery-tag :refer [discovery-tag]]
[syng-im.components.chat :refer [chat]]
[syng-im.components.chats.chats-list :refer [chats-list]]
[syng-im.components.chats.new-group :refer [new-group]]
[syng-im.components.chat.new-participants :refer [new-participants]]
[syng-im.components.chat.remove-participants :refer [remove-participants]]
[syng-im.components.profile :refer [profile my-profile]]
[syng-im.utils.logging :as log]
[syng-im.contacts.screen :refer [contact-list]]
[syng-im.discovery.screen :refer [discovery]]
[syng-im.discovery.tag :refer [discovery-tag]]
[syng-im.chat.screen :refer [chat]]
[syng-im.chats-list.screen :refer [chats-list]]
[syng-im.new-group.screen :refer [new-group]]
[syng-im.participants.views.create :refer [new-participants]]
[syng-im.participants.views.remove :refer [remove-participants]]
[syng-im.profile.screen :refer [profile my-profile]]
[syng-im.utils.utils :refer [toast]]
[syng-im.navigation :as nav]
[syng-im.utils.encryption]))
(def back-button-handler (cljs/atom {:nav nil
:handler nil}))
(defn init-back-button-handler! []
(let [new-listener (fn []
;; todo: it might be better always return false from
@ -37,7 +31,7 @@
(defn app-root []
(let [signed-up (subscribe [:signed-up])
view-id (subscribe [:view-id])]
view-id (subscribe [:view-id])]
(fn []
(case (if @signed-up @view-id :chat)
:discovery [discovery]
@ -57,7 +51,7 @@
(dispatch [:initialize-chats])
(dispatch [:initialize-protocol])
(dispatch [:load-user-phone-number])
(dispatch [:load-syng-contacts])
(dispatch [:load-contacts])
;; load commands from remote server (todo: uncomment)
;; (dispatch [:load-commands])
(dispatch [:init-console-chat])

View File

@ -0,0 +1,346 @@
(ns syng-im.chat.handlers
(:require [re-frame.core :refer [register-handler enrich after debug dispatch]]
[syng-im.models.commands :as commands]
[clojure.string :as str]
[syng-im.chat.suggestions :as suggestions]
[syng-im.protocol.api :as api]
[syng-im.models.messages :as messages]
[syng-im.constants :refer [text-content-type
content-type-command]]
[syng-im.utils.random :as random]
[syng-im.components.react :as r]
[syng-im.handlers.sign-up :as sign-up-service]
[syng-im.models.chats :as chats]
[syng-im.navigation.handlers :as nav]
[syng-im.models.chats :as c]))
(register-handler :set-show-actions
(fn [db [_ show-actions]]
(assoc db :show-actions show-actions)))
(register-handler :load-more-messages
(fn [db _]
db
;; TODO implement
#_(let [chat-id (get-in db [:chat :current-chat-id])
messages [:chats chat-id :messages]
new-messages (gen-messages 10)]
(update-in db messages concat new-messages))))
(defn safe-trim [s]
(when (string? s)
(str/trim s)))
(register-handler :cancel-command
(fn [{:keys [current-chat-id] :as db} _]
(-> db
(assoc-in [:chats current-chat-id :command-input] {})
(update-in [:chats current-chat-id :input-text] safe-trim))))
(register-handler :set-chat-command-content
(fn [db [_ content]]
(commands/set-chat-command-content db content)))
(defn update-input-text
[{:keys [current-chat-id] :as db} text]
(assoc-in db [:chats current-chat-id :input-text] text))
(register-handler :stage-command
(fn [{:keys [current-chat-id] :as db} _]
(let [db (update-input-text db nil)
{:keys [command content]}
(get-in db [:chats current-chat-id :command-input])
command-info {:command command
:content content
:handler (:handler command)}]
(commands/stage-command db command-info))))
(register-handler :set-response-chat-command
(fn [db [_ to-msg-id command-key]]
(commands/set-response-chat-command db to-msg-id command-key)))
(defn update-text
[db [_ text]]
(update-input-text db text))
(defn update-command [db [_ text]]
(let [{:keys [command]} (suggestions/check-suggestion db text)]
(commands/set-chat-command db command)))
(register-handler :set-chat-input-text
((enrich update-command) update-text))
(register-handler :send-group-chat-msg
(fn [db [_ chat-id text]]
(let [{msg-id :msg-id
{from :from} :msg} (api/send-group-user-msg {:group-id chat-id
:content text})
msg {:msg-id msg-id
:from from
:to nil
:content text
:content-type text-content-type
:outgoing true}]
(messages/save-message chat-id msg))))
(defn console? [s]
(= "console" s))
(def not-console?
(complement console?))
(defn check-author-direction
[db chat-id {:keys [from outgoing] :as message}]
(let [previous-message (first (get-in db [:chats chat-id :messages]))]
(merge message
{:same-author (if previous-message
(= (:from previous-message) from)
true)
:same-direction (if previous-message
(= (:outgoing previous-message) outgoing)
true)})))
(defn add-message-to-db
[db chat-id message]
(let [messages [:chats chat-id :messages]]
(update-in db messages conj message)))
(defn prepare-message
[{:keys [identity current-chat-id] :as db} _]
(let [text (get-in db [:chats current-chat-id :input-text])
{:keys [command]} (suggestions/check-suggestion db (str text " "))
message (check-author-direction
db current-chat-id
{:msg-id (random/id)
:chat-id current-chat-id
:content text
:to current-chat-id
:from identity
:content-type text-content-type
:outgoing true})]
(if command
(commands/set-chat-command db command)
(assoc db :new-message (when-not (str/blank? text) message)))))
(defn prepare-command [identity chat-id staged-command]
(let [command-key (get-in staged-command [:command :command])
content {:command (name command-key)
:content (:content staged-command)}]
{:msg-id (random/id)
:from identity
:to chat-id
:content content
:content-type content-type-command
:outgoing true
:handler (:handler staged-command)}))
(defn prepare-staged-commans
[{:keys [current-chat-id identity] :as db} _]
(let [staged-commands (get-in db [:chats current-chat-id :staged-commands])]
(->> staged-commands
(map #(prepare-command identity current-chat-id %))
;todo this is wrong :(
(map #(check-author-direction db current-chat-id %))
(assoc db :new-commands))))
(defn add-message
[{:keys [new-message current-chat-id] :as db}]
(if new-message
(add-message-to-db db current-chat-id new-message)
db))
(defn add-commands
[{:keys [new-commands current-chat-id] :as db}]
(reduce
#(add-message-to-db %1 current-chat-id %2)
db
new-commands))
(defn clear-input
[{:keys [current-chat-id new-message] :as db} _]
(if new-message
(assoc-in db [:chats current-chat-id :input-text] nil)
db))
(defn clear-staged-commands
[{:keys [current-chat-id] :as db} _]
(assoc-in db [:chats current-chat-id :staged-commands] []))
(defn send-message!
[{:keys [new-message current-chat-id]} _]
(when (and new-message (not-console? current-chat-id))
(api/send-user-msg {:to current-chat-id
:content (:content new-message)})))
(defn save-message-to-realm!
[{:keys [new-message current-chat-id]} _]
(when new-message
(messages/save-message current-chat-id new-message)))
(defn save-commands-to-realm!
[{:keys [new-commands current-chat-id]} _]
(doseq [new-command new-commands]
(messages/save-message current-chat-id (dissoc new-command :handler))))
(defn handle-commands
[{:keys [new-commands]}]
(doseq [{{content :content} :content
handler :handler} new-commands]
(when handler
(handler content))))
(register-handler :send-chat-msg
(-> prepare-message
((enrich prepare-staged-commans))
((enrich add-message))
((enrich add-commands))
((enrich clear-input))
((enrich clear-staged-commands))
((after (fn [_ _] (r/dismiss-keyboard!))))
((after send-message!))
((after save-message-to-realm!))
((after save-commands-to-realm!))
((after handle-commands))))
(register-handler :unstage-command
(fn [db [_ staged-command]]
(let []
(commands/unstage-command db staged-command))))
(register-handler :set-chat-command
(fn [db [_ command-key]]
;; todo what is going on there?!
(commands/set-chat-command db command-key)))
(register-handler :init-console-chat
(fn [db [_]]
(sign-up-service/init db)))
(register-handler :save-password
(fn [db [_ password]]
(sign-up-service/save-password password)
(assoc db :password-saved true)))
(register-handler :sign-up
(-> (fn [db [_ phone-number]]
;; todo save phone number to db
(assoc db :user-phone-number phone-number))
((after (fn [& _] (sign-up-service/on-sign-up-response))))))
(register-handler :sign-up-confirm
(fn [db [_ confirmation-code]]
(sign-up-service/on-send-code-response confirmation-code)
(sign-up-service/set-signed-up db true)))
(register-handler :set-signed-up
(fn [db [_ signed-up]]
(sign-up-service/set-signed-up db signed-up)))
(defn load-messages!
([db] (load-messages! db nil))
([db _]
(->> (:current-chat-id db)
messages/get-messages
(assoc db :messages))))
(defn init-chat
([db] (init-chat db nil))
([{:keys [messages current-chat-id] :as db} _]
(assoc-in db [:chats current-chat-id :messages] messages)))
(register-handler :init-chat
(-> load-messages!
((enrich init-chat))
debug))
(defn initialize-chats
[{:keys [loaded-chats] :as db} _]
(let [chats (->> loaded-chats
(map (fn [{:keys [chat-id] :as chat}]
[chat-id chat]))
(into {}))
ids (set (keys chats))]
(-> db
(assoc :chats chats)
(assoc :chats-ids ids)
(dissoc :loaded-chats))))
(defn load-chats!
[db _]
(assoc db :loaded-chats (chats/chats-list)))
(register-handler :initialize-chats
((enrich initialize-chats) load-chats!))
(defn store-message!
[{:keys [new-message]} [_ {chat-id :from}]]
(messages/save-message chat-id new-message))
(defn receive-message
[db [_ {chat-id :from :as message}]]
(let [message' (check-author-direction db chat-id message)]
(-> db
(add-message-to-db chat-id message')
(assoc :new-message message'))))
(register-handler :received-msg
(-> receive-message
((after store-message!))))
(register-handler :group-received-msg
(fn [db [_ {chat-id :group-id :as msg}]]
(messages/save-message chat-id msg)
db))
(defn load-chat!
[{:keys [chats current-chat-id] :as db}]
(when-not (chats current-chat-id)
(c/create-chat {}))
db)
(defmethod nav/preload-data! :chat
[{:keys [current-chat-id] :as db} [_ _ id]]
(-> db
(assoc :current-chat-id (or id current-chat-id))
load-messages!
init-chat))
(defn prepare-chat
[{:keys [contacts] :as db} [_ contcat-id]]
(let [name (get-in contacts [contcat-id :name])
chat {:chat-id contcat-id
:name name
:group-chat false
:is-active true
:timestamp (.getTime (js/Date.))
;; todo how to choose color?
;; todo do we need to have some color for not group chat?
:contacts [{:identity contcat-id
:text-color :#FFFFFF
:background-color :#AB7967}]}]
(assoc db :new-chat chat)))
(defn add-chat [{:keys [new-chat] :as db} [_ chat-id]]
(-> db
(update :chats assoc chat-id new-chat)
(update :chats-ids conj chat-id)))
(defn save-chat!
[{:keys [new-chat]} _]
(chats/create-chat new-chat))
(defn open-chat!
[_ [_ chat-id]]
(dispatch [:navigate-to :chat chat-id]))
(register-handler :start-chat
(-> prepare-chat
((enrich add-chat))
((after save-chat!))
((after open-chat!))
debug))
(register-handler :switch-command-suggestions
(fn [db [_]]
(suggestions/switch-command-suggestions db)))

View File

@ -1,25 +1,22 @@
(ns syng-im.components.chat
(ns syng-im.chat.screen
(:require-macros [syng-im.utils.views :refer [defview]])
(:require [clojure.string :as s]
[re-frame.core :refer [subscribe dispatch dispatch-sync]]
[re-frame.core :refer [subscribe dispatch]]
[syng-im.components.react :refer [view
text
image
icon
navigator
touchable-highlight
list-view
list-item
android?]]
[syng-im.components.chat-styles :as st]
[syng-im.utils.logging :as log]
list-item]]
[syng-im.chat.styles.screen :as st]
[syng-im.resources :as res]
[syng-im.constants :refer [content-type-status]]
[syng-im.utils.listview :refer [to-datasource
to-datasource2]]
[syng-im.utils.listview :refer [to-datasource]]
[syng-im.components.invertible-scroll-view :refer [invertible-scroll-view]]
[syng-im.components.toolbar :refer [toolbar]]
[syng-im.components.chat.chat-message :refer [chat-message]]
[syng-im.components.chat.chat-message-new :refer [chat-message-new]]))
[syng-im.chat.views.message :refer [chat-message]]
[syng-im.chat.views.new-message :refer [chat-message-new]]))
(defn contacts-by-identity [contacts]
(->> contacts
@ -29,8 +26,8 @@
(defn add-msg-color [{:keys [from] :as msg} contact-by-identity]
(if (= "system" from)
(assoc msg :text-color "#4A5258"
:background-color "#D3EEEF")
(assoc msg :text-color :#4A5258
:background-color :#D3EEEF)
(let [{:keys [text-color background-color]} (get contact-by-identity from)]
(assoc msg :text-color text-color
:background-color background-color))))
@ -70,9 +67,9 @@
(defn on-action-selected [position]
(case position
0 (dispatch [:show-add-participants #_navigator])
1 (dispatch [:show-remove-participants #_navigator])
2 (dispatch [:leave-group-chat #_navigator])))
0 (dispatch [:show-add-participants])
1 (dispatch [:show-remove-participants])
2 (dispatch [:leave-group-chat])))
(defn overlay [{:keys [on-click-outside]} items]
[view st/actions-overlay
@ -122,21 +119,18 @@
:icon :menu_group
:icon-style {:width 25
:height 19}
:handler nil #_#(dispatch [:show-add-participants
navigator])}
:handler #(dispatch [:show-add-participants])}
{:title "Remove Contact from chat"
:subtitle "Alex, John"
:icon :search_gray_copy
:icon-style {:width 17
:height 17}
:handler nil #_#(dispatch
[:show-remove-participants navigator])}
:handler #(dispatch [:show-remove-participants])}
{:title "Leave Chat"
:icon :muted
:icon-style {:width 18
:height 21}
:handler nil #_#(dispatch [:leave-group-chat
navigator])}
:handler #(dispatch [:leave-group-chat])}
{:title "Settings"
:subtitle "Not implemented"
:icon :settings
@ -222,25 +216,23 @@
:custom-content [toolbar-content]
:custom-action [toolbar-action]}])))
(defn messages-view [group-chat]
(let [messages (subscribe [:chat :messages])
contacts (subscribe [:chat :contacts])]
(fn [group-chat]
(let [contacts' (contacts-by-identity @contacts)]
[list-view {:renderRow (message-row contacts' group-chat)
:renderScrollComponent #(invertible-scroll-view (js->clj %))
:onEndReached #(dispatch [:load-more-messages])
:enableEmptySections true
:dataSource (to-datasource2 @messages)}]))))
(defview messages-view [group-chat]
[messages [:chat :messages]
contacts [:chat :contacts]]
(let [contacts' (contacts-by-identity contacts)]
[list-view {:renderRow (message-row contacts' group-chat)
:renderScrollComponent #(invertible-scroll-view (js->clj %))
:onEndReached #(dispatch [:load-more-messages])
:enableEmptySections true
:dataSource (to-datasource messages)}]))
(defn chat []
(let [is-active (subscribe [:chat :is-active])
group-chat (subscribe [:chat :group-chat])
show-actions-atom (subscribe [:show-actions])]
(fn []
[view st/chat-view
[chat-toolbar]
[messages-view @group-chat]
(when @group-chat [typing-all])
(when is-active [chat-message-new])
(when @show-actions-atom [actions-view])])))
(defview chat []
[is-active [:chat :is-active]
group-chat [:chat :group-chat]
show-actions-atom [:show-actions]]
[view st/chat-view
[chat-toolbar]
[messages-view group-chat]
(when group-chat [typing-all])
(when is-active [chat-message-new])
(when show-actions-atom [actions-view])])

View File

@ -1,4 +1,4 @@
(ns syng-im.components.chat.content-suggestions-styles
(ns syng-im.chat.styles.content-suggestions
(:require [syng-im.components.styles :refer [font
color-light-blue-transparent
color-white

View File

@ -1,4 +1,4 @@
(ns syng-im.components.chat.input.input-styles
(ns syng-im.chat.styles.input
(:require [syng-im.components.styles :refer [font
color-white
color-blue

View File

@ -1,4 +1,4 @@
(ns syng-im.components.chat.chat-message-styles
(ns syng-im.chat.styles.message
(:require [syng-im.components.styles :refer [font
color-light-blue-transparent
color-white
@ -312,18 +312,3 @@
(def new-message-container
{:backgroundColor color-white
:elevation 4})
(def participants-container
{:flex 1
:backgroundColor :white})
(def participants-list
{:backgroundColor :white})
(def new-participant-image
{:width 20
:height 18})
(def remove-participants-image
{:width 22
:height 30})

View File

@ -1,4 +1,4 @@
(ns syng-im.components.chat.plain-message-input-styles
(ns syng-im.chat.styles.plain-input
(:require [syng-im.components.styles :refer [font
text2-color
color-white

View File

@ -1,4 +1,4 @@
(ns syng-im.components.chat-styles
(ns syng-im.chat.styles.screen
(:require [syng-im.components.styles :refer [font
title-font
color-white

View File

@ -1,4 +1,4 @@
(ns syng-im.components.chat.suggestions-styles
(ns syng-im.chat.styles.suggestions
(:require [syng-im.components.styles :refer [font
color-light-blue-transparent
color-white

View File

@ -0,0 +1,94 @@
(ns syng-im.chat.subs
(:require-macros [reagent.ratom :refer [reaction]])
(:require [re-frame.core :refer [register-sub]]
[syng-im.db :as db]
;todo handlers in subs?...
[syng-im.chat.suggestions :refer
[get-suggestions typing-command? get-content-suggestions]]
[syng-im.models.commands :as commands]
[syng-im.handlers.content-suggestions :refer [get-content-suggestions]]))
(register-sub :chat-properties
(fn [db [_ properties]]
(->> properties
(map (fn [k]
[k (-> @db
(get-in [:chats (:current-chat-id @db) k])
(reaction))]))
(into {}))))
(register-sub
:show-actions
(fn [db _]
(reaction (:show-actions @db))))
(register-sub :chat
(fn [db [_ k]]
(-> @db
(get-in [:chats (:current-chat-id @db) k])
(reaction))))
(register-sub :get-chat-messages
(fn [db _]
(let [chat-id (:current-chat-id @db)]
(reaction (get-in @db [:chats chat-id :messages])))))
(register-sub :get-current-chat-id
(fn [db _]
(reaction (:current-chat-id @db))))
(register-sub :get-suggestions
(fn [db _]
(let [input-text (->> (:current-chat-id @db)
db/chat-input-text-path
(get-in @db)
(reaction))]
(reaction (get-suggestions @db @input-text)))))
(register-sub :get-commands
(fn [db _]
(reaction (commands/get-commands @db))))
(register-sub :get-chat-input-text
(fn [db _]
(->> [:chats (:current-chat-id @db) :input-text]
(get-in @db)
(reaction))))
(register-sub :get-chat-staged-commands
(fn [db _]
(->> [:chats (:current-chat-id @db) :staged-commands]
(get-in @db)
(reaction))))
(register-sub :get-chat-command
(fn [db _]
(reaction (commands/get-chat-command @db))))
(register-sub :get-chat-command-content
(fn [db _]
(reaction (commands/get-chat-command-content @db))))
(register-sub :chat-command-request
(fn [db _]
(reaction (commands/get-chat-command-request @db))))
(register-sub :get-current-chat
(fn [db _]
(let [current-chat-id (:current-chat-id @db)]
(reaction (get-in @db [:chats current-chat-id])))))
(register-sub :get-chat
(fn [db [_ chat-id]]
(reaction (get-in @db [:chats chat-id]))))
(register-sub :typing-command?
(fn [db _]
(reaction (typing-command? @db))))
(register-sub :get-content-suggestions
(fn [db _]
(let [command (reaction (commands/get-chat-command @db))
text (reaction (commands/get-chat-command-content @db))]
(reaction (get-content-suggestions @db @command @text)))))

View File

@ -1,9 +1,6 @@
(ns syng-im.handlers.suggestions
(ns syng-im.chat.suggestions
(:require [re-frame.core :refer [subscribe dispatch dispatch-sync]]
[syng-im.db :as db]
[syng-im.models.chat :refer [current-chat-id
set-chat-input-text
get-chat-input-text]]
[syng-im.models.commands :refer [commands
suggestions
get-commands
@ -11,7 +8,6 @@
get-chat-command-to-msg-id
clear-staged-commands]]
[syng-im.utils.utils :refer [log on-error http-get]]
[syng-im.utils.logging :as log]
[clojure.string :as s]))
(defn suggestion? [text]
@ -41,7 +37,8 @@
(command-handler to-msg-id command-key content)))))
(defn apply-staged-commands [db]
(let [staged-commands (get-in db (db/chat-staged-commands-path (current-chat-id db)))]
(let [staged-commands (get-in db (db/chat-staged-commands-path
(:current-chat-id db)))]
(dorun (map (fn [staged-command]
(when-let [handler (:handler staged-command)]
(handler)))
@ -66,8 +63,10 @@
suggestion)))
(defn typing-command? [db]
(let [text (get-chat-input-text db)]
(suggestion? text)))
(-> db
(get-in [:chats (:current-chat-id db) :input-text])
suggestion?))
(defn switch-command-suggestions [db]
(set-chat-input-text db (if (typing-command? db) nil "!")))
(let [text (if (typing-command? db) nil "!")]
(assoc-in db [:chats (:current-chat-id db) :input-text] text)))

View File

@ -1,13 +1,13 @@
(ns syng-im.components.chat.input.simple-command
(:require [re-frame.core :refer [subscribe dispatch dispatch-sync]]
(ns syng-im.chat.views.command
(:require [re-frame.core :refer [subscribe dispatch]]
[syng-im.components.react :refer [view
icon
text
text-input
touchable-highlight]]
[syng-im.resources :as res]
[syng-im.components.chat.content-suggestions :refer [content-suggestions-view]]
[syng-im.components.chat.input.input-styles :as st]))
[syng-im.chat.views.content-suggestions :refer
[content-suggestions-view]]
[syng-im.chat.styles.input :as st]))
(defn cancel-command-input []
(dispatch [:cancel-command]))

View File

@ -0,0 +1,6 @@
(ns syng-im.chat.views.confirmation-code
(:require
[syng-im.chat.views.command :refer [simple-command-input-view]]))
(defn confirmation-code-input-view [command]
[simple-command-input-view command {:keyboardType :numeric}])

View File

@ -0,0 +1,36 @@
(ns syng-im.chat.views.content-suggestions
(:require-macros [syng-im.utils.views :refer [defview]])
(:require [re-frame.core :refer [subscribe dispatch]]
[syng-im.components.react :refer [view
icon
text
touchable-highlight
list-view
list-item]]
[syng-im.chat.styles.content-suggestions :as st]
[syng-im.utils.listview :refer [to-datasource]]))
(defn set-command-content [content]
(dispatch [:set-chat-command-content content]))
(defn suggestion-list-item [{:keys [value description]}]
[touchable-highlight {:onPress #(set-command-content value)}
[view st/suggestion-container
[view st/suggestion-sub-container
[text {:style st/value-text} value]
[text {:style st/description-text} description]]]])
(defn render-row [row _ _]
(list-item [suggestion-list-item row]))
(defview content-suggestions-view []
[suggestions [:get-content-suggestions]]
(when (seq suggestions)
[view
[touchable-highlight {:style st/drag-down-touchable
;; TODO hide suggestions?
:onPress (fn [])}
[view [icon :drag_down st/drag-down-icon]]]
[view (st/suggestions-container (count suggestions))
[list-view {:dataSource (to-datasource suggestions)
:renderRow render-row}]]]))

View File

@ -1,11 +1,11 @@
(ns syng-im.components.chat.chat-message
(ns syng-im.chat.views.message
(:require [clojure.string :as s]
[re-frame.core :refer [subscribe dispatch]]
[syng-im.components.react :refer [view
text
image
touchable-highlight]]
[syng-im.components.chat.chat-message-styles :as st]
[syng-im.chat.styles.message :as st]
[syng-im.models.commands :refer [parse-command-msg-content
parse-command-request]]
[syng-im.resources :as res]
@ -112,8 +112,7 @@
(defn text-message
[{:keys [content] :as message}]
[message-view message
[text {:style (st/text-message message)}
content]])
[text {:style (st/text-message message)} content]])
(defmethod message-content text-content-type
[wrapper message]

View File

@ -0,0 +1,7 @@
(ns syng-im.chat.views.money
(:require
[syng-im.chat.views.command :refer [simple-command-input-view]]))
(defn money-input-view [command]
[simple-command-input-view command
{:keyboardType :numeric}])

View File

@ -1,15 +1,15 @@
(ns syng-im.components.chat.chat-message-new
(ns syng-im.chat.views.new-message
(:require
[re-frame.core :refer [subscribe]]
[syng-im.components.react :refer [view]]
[syng-im.components.chat.plain-message-input :refer [plain-message-input-view]]
[syng-im.components.chat.input.simple-command :refer [simple-command-input-view]]
[syng-im.components.chat.input.phone :refer [phone-input-view]]
[syng-im.components.chat.input.password :refer [password-input-view]]
[syng-im.components.chat.input.confirmation-code :refer [confirmation-code-input-view]]
[syng-im.components.chat.input.money :refer [money-input-view]]
[syng-im.components.chat.input.simple-command-staged :refer [simple-command-staged-view]]
[syng-im.components.chat.chat-message-styles :as st]))
[syng-im.chat.views.plain-input :refer [plain-message-input-view]]
[syng-im.chat.views.command :refer [simple-command-input-view]]
[syng-im.chat.views.phone :refer [phone-input-view]]
[syng-im.chat.views.password :refer [password-input-view]]
[syng-im.chat.views.confirmation-code :refer [confirmation-code-input-view]]
[syng-im.chat.views.money :refer [money-input-view]]
[syng-im.chat.views.staged-command :refer [simple-command-staged-view]]
[syng-im.chat.styles.message :as st]))
(defn staged-command-view [stage-command]
[simple-command-staged-view stage-command])

View File

@ -1,6 +1,6 @@
(ns syng-im.components.chat.input.password
(ns syng-im.chat.views.password
(:require
[syng-im.components.chat.input.simple-command
[syng-im.chat.views.command
:refer [simple-command-input-view]]))
(defn password-input-view [command]

View File

@ -1,6 +1,6 @@
(ns syng-im.components.chat.input.phone
(ns syng-im.chat.views.phone
(:require
[syng-im.components.chat.input.simple-command
[syng-im.chat.views.command
:refer [simple-command-input-view]]
[syng-im.utils.phone-number :refer [valid-mobile-number?]]))

View File

@ -1,11 +1,11 @@
(ns syng-im.components.chat.plain-message-input
(:require [re-frame.core :refer [subscribe dispatch dispatch-sync]]
(ns syng-im.chat.views.plain-input
(:require [re-frame.core :refer [subscribe dispatch]]
[syng-im.components.react :refer [view
icon
touchable-highlight
text-input]]
[syng-im.components.chat.suggestions :refer [suggestions-view]]
[syng-im.components.chat.plain-message-input-styles :as st]))
[syng-im.chat.views.suggestions :refer [suggestions-view]]
[syng-im.chat.styles.plain-input :as st]))
(defn set-input-message [message]
(dispatch [:set-chat-input-text message]))

View File

@ -1,11 +1,11 @@
(ns syng-im.components.chat.input.simple-command-staged
(ns syng-im.chat.views.staged-command
(:require [re-frame.core :refer [subscribe dispatch]]
[syng-im.components.react :refer [view
image
text
touchable-highlight]]
[syng-im.resources :as res]
[syng-im.components.chat.input.input-styles :as st]))
[syng-im.chat.styles.input :as st]))
(defn cancel-command-input [staged-command]
(dispatch [:unstage-command staged-command]))

View File

@ -1,6 +1,4 @@
(ns syng-im.components.chat.suggestions
(:require-macros
[natal-shell.core :refer [with-error-view]])
(ns syng-im.chat.views.suggestions
(:require [re-frame.core :refer [subscribe dispatch]]
[syng-im.components.react :refer [view
text
@ -9,38 +7,38 @@
list-view
list-item]]
[syng-im.utils.listview :refer [to-datasource]]
[syng-im.components.chat.suggestions-styles :as st]))
[syng-im.chat.styles.suggestions :as st]))
(defn set-command-input [command]
(dispatch [:set-chat-command command]))
(defn suggestion-list-item [suggestion]
(defn suggestion-list-item
[{:keys [description command]
label :text
:as suggestion}]
[touchable-highlight
{:onPress #(set-command-input (keyword (:command suggestion)))}
{:onPress #(set-command-input (keyword command))}
[view st/suggestion-container
[view st/suggestion-sub-container
[view (st/suggestion-background suggestion)
[text {:style st/suggestion-text}
(:text suggestion)]]
[text {:style st/value-text}
(:text suggestion)]
[text {:style st/description-text}
(:description suggestion)]]]])
[text {:style st/suggestion-text} label]]
[text {:style st/value-text} label]
[text {:style st/description-text} description]]]])
(defn render-row [row _ _]
(list-item [suggestion-list-item (js->clj row :keywordize-keys true)]))
(list-item [suggestion-list-item row]))
(defn suggestions-view []
(let [suggestions-atom (subscribe [:get-suggestions])]
(fn []
(let [suggestions @suggestions-atom]
(when (seq suggestions)
[view nil
[view
[touchable-highlight {:style st/drag-down-touchable
:onPress (fn []
;; TODO hide suggestions?
)}
[view nil
[view
[icon :drag_down st/drag-down-icon]]]
[view (st/suggestions-container (count suggestions))
[list-view {:dataSource (to-datasource suggestions)

View File

@ -0,0 +1,52 @@
(ns syng-im.chats-list.screen
(:require [re-frame.core :refer [subscribe dispatch]]
[syng-im.components.react :refer [list-view
list-item
view
text
image
touchable-highlight]]
[syng-im.utils.listview :refer [to-datasource]]
[reagent.core :as r]
[syng-im.chats-list.views.chat-list-item :refer [chat-list-item]]
[syng-im.components.action-button :refer [action-button
action-button-item]]
[syng-im.components.drawer.view :refer [drawer-view open-drawer]]
[syng-im.components.styles :refer [color-blue]]
[syng-im.components.toolbar :refer [toolbar]]
[syng-im.components.icons.ionicons :refer [icon]]
[syng-im.chats-list.styles :as st]))
(defn chats-list-toolbar []
[toolbar {:nav-action {:image {:source {:uri :icon_hamburger}
:style st/hamburger-icon}
:handler open-drawer}
:title "Chats"
:action {:image {:source {:uri :icon_search}
:style st/search-icon}
:handler (fn [])}}])
(defn chats-list []
(let [chats (subscribe [:get :chats])]
(fn []
[drawer-view
[view st/chats-container
[chats-list-toolbar]
[list-view {:dataSource (to-datasource (vals @chats))
:renderRow (fn [row _ _]
(list-item [chat-list-item row]))
:style st/list-container}]
[action-button {:buttonColor color-blue}
[action-button-item
{:title "New Chat"
:buttonColor :#9b59b6
:onPress #(dispatch [:navigate-to :contact-list])}
[icon {:name :android-create
:style st/create-icon}]]
[action-button-item
{:title "New Group Chat"
:buttonColor :#1abc9c
:onPress #(dispatch [:show-group-new])}
[icon {:name :person-stalker
:style st/person-stalker-icon}]]]]])))

View File

@ -0,0 +1,147 @@
(ns syng-im.chats-list.styles
(:require [syng-im.components.styles :refer [font
title-font
color-white
color-blue
online-color
text1-color
text2-color
new-messages-count-color]]))
(def contact-photo-container
{:borderRadius 50})
(def contact-photo-image
{:borderRadius 50
:width 40
:height 40})
(def online-container
{:position :absolute
:top 24
:left 24
:width 20
:height 20
:borderRadius 50
:backgroundColor online-color
:borderWidth 2
:borderColor color-white})
(def online-dot
{:position :absolute
:top 6
:width 4
:height 4
:borderRadius 50
:backgroundColor color-white})
(def online-dot-left
(assoc online-dot :left 3))
(def online-dot-right
(assoc online-dot :left 9))
(def chat-container
{:flexDirection :row
:paddingVertical 15
:paddingHorizontal 16
:height 90})
(def photo-container
{:marginTop 2
:width 44
:height 44})
(def item-container
{:flexDirection :column
:marginLeft 12
:flex 1})
(def name-view
{:flexDirection :row})
(def name-text
{:marginTop -2.5
:color text1-color
:fontSize 14
:fontFamily title-font})
(def group-icon
{:marginTop 4
:marginLeft 8
:width 14
:height 9})
(def memebers-text
{:marginTop -0.5
:marginLeft 4
:fontFamily font
:fontSize 12
:color text2-color})
(def last-message-text
{:marginTop 7
:marginRight 40
:color text1-color
:fontFamily font
:fontSize 14
:lineHeight 20})
(def status-container
{:flexDirection :row
:position :absolute
:top 0
:right 0})
(def status-image
{:marginTop 6
:width 9
:height 7})
(def datetime-text
{:fontFamily font
:fontSize 12
:color text2-color
:marginLeft 5})
(def new-messages-container
{:position :absolute
:top 36
:right 0
:width 24
:height 24
:backgroundColor new-messages-count-color
:borderRadius 50})
(def new-messages-text
{:top 4
:left 0
:fontFamily title-font
:fontSize 10
:color color-blue
:textAlign :center})
(def hamburger-icon
{:width 16
:height 12})
(def search-icon
{:width 17
:height 17})
(def chats-container
{:flex 1
:backgroundColor :white})
(def list-container
{:backgroundColor :white})
(def create-icon
{:fontSize 20
:height 22
:color :white})
(def person-stalker-icon
{:fontSize 20
:height 22
:color :white})

View File

@ -0,0 +1,20 @@
(ns syng-im.chats-list.views.chat-list-item
(:require [re-frame.core :refer [subscribe dispatch dispatch-sync]]
[syng-im.components.react :refer [view
text
image
touchable-highlight]]
[syng-im.components.styles :refer [font]]
[syng-im.chats-list.views.inner-item :refer
[chat-list-item-inner-view]]))
(defn chat-list-item [{:keys [chat-id] :as chat}]
[touchable-highlight
{:on-press #(dispatch [:show-chat chat-id :push])}
;; TODO add [photo-path delivery-status new-messages-count online] values to chat-obj
[view [chat-list-item-inner-view (merge chat
{:photo-path nil
:delivery-status :seen
:new-messages-count 3
:timestamp "13:54"
:online true})]]])

View File

@ -0,0 +1,52 @@
(ns syng-im.chats-list.views.inner-item
(:require [clojure.string :as s]
[syng-im.components.react :refer [view image icon text]]
[syng-im.chats-list.styles :as st]
[syng-im.resources :as res]))
(defn contact-photo [photo-path]
[view st/contact-photo-container
[image {:source (if (s/blank? photo-path)
res/user-no-photo
{:uri photo-path})
:style st/contact-photo-image}]])
(defn contact-online [online]
(when online
[view st/online-container
[view st/online-dot-left]
[view st/online-dot-right]]))
(defn chat-list-item-inner-view
[{:keys [name photo-path delivery-status timestamp new-messages-count online
group-chat contacts]}]
[view st/chat-container
[view st/photo-container
[contact-photo photo-path]
[contact-online online]]
[view st/item-container
[view st/name-view
[text {:style st/name-text} name]
(when group-chat
[icon :group st/group-icon])
(when group-chat
[text {:style st/memebers-text}
(if (< 1 (count contacts))
(str (count contacts) " members")
"1 member")])]
[text {:style st/last-message-text
:numberOfLines 2}
(repeatedly 5 #(str "Hi, I'm " name "! "))]]
[view
[view st/status-container
(when delivery-status
[image {:source (if (= (keyword delivery-status) :seen)
{:uri :icon_ok_small}
;; todo change icon
{:uri :icon_ok_small})
:style st/status-image}])
[text {:style st/datetime-text} timestamp]]
(when (pos? new-messages-count)
[view st/new-messages-container
[text {:style st/new-messages-text} new-messages-count]])]])

View File

@ -1,4 +1,4 @@
(ns syng-im.components.carousel
(ns syng-im.components.carousel.carousel
(:require [syng-im.components.react :refer [android?
view
scroll-view

View File

@ -19,6 +19,6 @@
(defn page [page-width margin]
{:width page-width
:justifyContent "center"
:justifyContent :center
:marginLeft margin
:marginRight margin})
:marginRight margin})

View File

@ -1,50 +0,0 @@
(ns syng-im.components.chat.content-suggestions
(:require-macros
[natal-shell.core :refer [with-error-view]])
(:require [clojure.string :as cstr]
[reagent.core :as r]
[re-frame.core :refer [subscribe dispatch dispatch-sync]]
[syng-im.components.react :refer [view
icon
text
touchable-highlight
list-view
list-item]]
[syng-im.components.chat.content-suggestions-styles :as st]
[syng-im.utils.listview :refer [to-datasource]]
[syng-im.utils.utils :refer [log toast http-post]]
[syng-im.utils.logging :as log]))
(defn set-command-content [content]
(dispatch [:set-chat-command-content content]))
(defn suggestion-list-item [suggestion]
[touchable-highlight {:onPress (fn []
(set-command-content (:value suggestion)))
:underlay-color :transparent}
[view st/suggestion-container
[view st/suggestion-sub-container
[text {:style st/value-text}
(:value suggestion)]
[text {:style st/description-text}
(:description suggestion)]]]])
(defn render-row [row section-id row-id]
(list-item [suggestion-list-item (js->clj row :keywordize-keys true)]))
(defn content-suggestions-view []
(let [suggestions-atom (subscribe [:get-content-suggestions])]
(fn []
(let [suggestions @suggestions-atom]
(when (seq suggestions)
[view nil
[touchable-highlight {:style st/drag-down-touchable
:onPress (fn []
;; TODO hide suggestions?
)
:underlay-color :transparent}
[view nil
[icon :drag_down st/drag-down-icon]]]
[view (st/suggestions-container (count suggestions))
[list-view {:dataSource (to-datasource suggestions)
:renderRow render-row}]]])))))

View File

@ -1,7 +0,0 @@
(ns syng-im.components.chat.input.confirmation-code
(:require
[syng-im.components.chat.input.simple-command
:refer [simple-command-input-view]]))
(defn confirmation-code-input-view [command]
[simple-command-input-view command {:keyboardType :numeric}])

View File

@ -1,9 +0,0 @@
(ns syng-im.components.chat.input.money
(:require
[syng-im.components.chat.input.simple-command
:refer [simple-command-input-view]]
[syng-im.components.chat.input.input-styles :as st]))
(defn money-input-view [command]
[simple-command-input-view command
{:keyboardType :numeric}])

View File

@ -1,35 +0,0 @@
(ns syng-im.components.chat.new-participants
(:require [re-frame.core :refer [subscribe dispatch]]
[syng-im.resources :as res]
[syng-im.components.react :refer [view]]
[syng-im.components.realm :refer [list-view]]
[syng-im.components.toolbar :refer [toolbar]]
[syng-im.utils.listview :refer [to-realm-datasource]]
[syng-im.components.chats.new-participant-contact
:refer [new-participant-contact]]
[reagent.core :as r]
[syng-im.components.chat.chat-message-styles :as st]))
(defn new-participants-toolbar [navigator]
[toolbar
{:navigator navigator
:title "Add Participants"
:action {:image {:source res/v ;; {:uri "icon_search"}
:style st/new-participant-image}
:handler #(dispatch [:add-new-participants navigator])}}])
(defn new-participants-row [navigator]
(fn [row _ _]
(r/as-element
[new-participant-contact (js->clj row :keywordize-keys true) navigator])))
(defn new-participants [{:keys [navigator]}]
(let [contacts (subscribe [:all-new-contacts])]
(fn []
(let [contacts-ds (to-realm-datasource @contacts)]
[view st/participants-container
[new-participants-toolbar navigator]
[list-view {:dataSource contacts-ds
:enableEmptySections true
:renderRow (new-participants-row navigator)
:style st/participants-list}]]))))

View File

@ -1,36 +0,0 @@
(ns syng-im.components.chat.remove-participants
(:require [re-frame.core :refer [subscribe dispatch]]
[syng-im.resources :as res]
[syng-im.components.react :refer [view text-input text image
touchable-highlight]]
[syng-im.components.realm :refer [list-view]]
[syng-im.components.toolbar :refer [toolbar]]
[syng-im.utils.listview :refer [to-realm-datasource]]
[syng-im.components.chats.new-participant-contact
:refer [new-participant-contact]]
[reagent.core :as r]
[syng-im.components.chat.chat-message-styles :as st]))
(defn remove-participants-toolbar [navigator]
[toolbar
{:navigator navigator
:title "Remove Participants"
:action {:handler #(dispatch [:remove-selected-participants navigator])
:image {:source res/trash-icon ;; {:uri "icon_search"}
:style st/remove-participants-image}}}])
(defn remove-participants-row [navigator]
(fn [row _ _]
(r/as-element
[new-participant-contact (js->clj row :keywordize-keys true) navigator])))
(defn remove-participants [{:keys [navigator]}]
(let [contacts (subscribe [:current-chat-contacts])]
(fn []
(let [contacts-ds (to-realm-datasource @contacts)]
[view st/participants-container
[remove-participants-toolbar navigator]
[list-view {:dataSource contacts-ds
:enableEmptySections true
:renderRow (remove-participants-row navigator)
:style st/participants-list}]]))))

View File

@ -1,37 +0,0 @@
(ns syng-im.components.chats.chat-list-item
(:require [re-frame.core :refer [subscribe dispatch dispatch-sync]]
[syng-im.components.react :refer [view
text
image
touchable-highlight]]
[syng-im.components.styles :refer [font]]
[syng-im.components.chats.chat-list-item-inner :refer [chat-list-item-inner-view]]
[syng-im.utils.logging :as log]
[syng-im.resources :as res]))
(defn chat-list-item [chat-obj navigator]
[touchable-highlight
{:on-press #(dispatch [:show-chat (aget chat-obj "chat-id") navigator :push])}
;; TODO add [photo-path delivery-status new-messages-count online] values to chat-obj
;; TODO should chat-obj be clj-map?
[view {} [chat-list-item-inner-view (merge (js->clj chat-obj :keywordize-keys true)
{:photo-path nil
:delivery-status :seen
:new-messages-count 3
:timestamp "13:54"
:online true})]]])
(comment [view {:style {:flexDirection "row"
:width 260
:marginVertical 5}}
[image {:source res/chat-icon
:style {:borderWidth 2
:borderColor "#FFFFFF"
:width 32
:height 30
:marginRight 5
:marginLeft 5}}]
[text {:style {:fontSize 14
:fontFamily font
:color "#4A5258"}}
(subs (aget chat-obj "name") 0 30)]])

View File

@ -1,131 +0,0 @@
(ns syng-im.components.chats.chat-list-item-inner
(:require [clojure.string :as s]
[syng-im.components.react :refer [view image text]]
[syng-im.components.styles :refer [font
title-font
color-white
color-blue
online-color
text1-color
text2-color
new-messages-count-color]]
[syng-im.resources :as res]))
(defn contact-photo [{:keys [photo-path]}]
[view {:borderRadius 50}
[image {:source (if (s/blank? photo-path)
res/user-no-photo
{:uri photo-path})
:style {:borderRadius 50
:width 40
:height 40}}]])
(defn contact-online [{:keys [online]}]
(when online
[view {:position "absolute"
:top 24
:left 24
:width 20
:height 20
:borderRadius 50
:backgroundColor online-color
:borderWidth 2
:borderColor color-white}
[view {:position "absolute"
:top 6
:left 3
:width 4
:height 4
:borderRadius 50
:backgroundColor color-white}]
[view {:position "absolute"
:top 6
:left 9
:width 4
:height 4
:borderRadius 50
:backgroundColor color-white}]]))
(defn chat-list-item-inner-view
[{:keys [name photo-path delivery-status timestamp new-messages-count online
group-chat contacts]}]
[view {:style {:flexDirection "row"
:paddingVertical 15
:paddingHorizontal 16
:height 90}}
[view {:marginTop 2
:width 44
:height 44}
;;; photo
[contact-photo {:photo-path photo-path}]
;;; online
[contact-online {:online online}]]
[view {:style {:flexDirection "column"
:marginLeft 12
:flex 1}}
;;; name
[view {:style {:flexDirection "row"}}
[text {:style {:marginTop -2.5
:color text1-color
:fontSize 14
:fontFamily title-font}} name]
;;; group size
(when group-chat
[image {:source {:uri "icon_group"}
:style {:marginTop 4
:marginLeft 8
:width 14
:height 9}}])
(when group-chat
[text {:style {:marginTop -0.5
:marginLeft 4
:fontFamily font
:fontSize 12
:color text2-color}}
(if (< 1 (count contacts))
(str (count contacts) " members")
"1 member")])]
;;; last message
[text {:style {:marginTop 7
:marginRight 40
:color text1-color
:fontFamily font
:fontSize 14
:lineHeight 20}
:numberOfLines 2}
(repeatedly 5 #(str "Hi, I'm " name "! "))]]
[view {}
;;; delivery status
[view {:style {:flexDirection "row"
:position "absolute"
:top 0
:right 0}}
(when delivery-status
[image {:source (if (= (keyword delivery-status) :seen)
{:uri "icon_ok_small"}
{:uri "icon_ok_small"})
:style {:marginTop 6
:width 9
:height 7}}])
;;; datetime
[text {:style {:fontFamily font
:fontSize 12
:color text2-color
:marginLeft 5}}
timestamp]]
;;; new messages count
(when (pos? new-messages-count)
[view {:style {:position "absolute"
:top 36
:right 0
:width 24
:height 24
:backgroundColor new-messages-count-color
:borderRadius 50}}
[text {:style {:top 4
:left 0
:fontFamily title-font
:fontSize 10
:color color-blue
:textAlign "center"}}
new-messages-count]])]])

View File

@ -1,71 +0,0 @@
(ns syng-im.components.chats.chats-list
(:require [re-frame.core :refer [subscribe dispatch dispatch-sync]]
[syng-im.components.react :refer [android?
view
text
image
touchable-highlight
navigator]]
[syng-im.components.realm :refer [list-view]]
[syng-im.utils.logging :as log]
[syng-im.navigation :refer [nav-pop]]
[syng-im.resources :as res]
[syng-im.utils.listview :refer [to-realm-datasource]]
[reagent.core :as r]
[syng-im.components.chats.chat-list-item :refer [chat-list-item]]
[syng-im.components.drawer :refer [drawer-view open-drawer]]
[syng-im.components.action-button :refer [action-button
action-button-item]]
[syng-im.components.styles :refer [font
title-font
color-white
color-black
color-blue
text1-color
text2-color]]
[syng-im.components.toolbar :refer [toolbar]]
[syng-im.components.icons.ionicons :refer [icon]]))
(defn chats-list-toolbar []
[toolbar {:nav-action {:image {:source {:uri "icon_hamburger"}
:style {:width 16
:height 12}}
:handler open-drawer}
:title "Chats"
:action {:image {:source {:uri "icon_search"}
:style {:width 17
:height 17}}
:handler (fn [])}}])
(defn chats-list [{:keys [navigator]}]
(let [chats (subscribe [:get-chats])]
(fn []
(let [chats @chats
_ (log/debug "chats=" chats)
datasource (to-realm-datasource chats)]
[drawer-view {:navigator navigator}
[view {:style {:flex 1
:backgroundColor "white"}}
[chats-list-toolbar]
[list-view {:dataSource datasource
:enableEmptySections true
:renderRow (fn [row section-id row-id]
(r/as-element [chat-list-item row navigator]))
:style {:backgroundColor "white"}}]
[action-button {:buttonColor color-blue}
[action-button-item {:title "New Chat"
:buttonColor "#9b59b6"
:onPress #(dispatch [:navigate-to
:contact-list])}
[icon {:name "android-create"
:style {:fontSize 20
:height 22
:color "white"}}]]
[action-button-item {:title "New Group Chat"
:buttonColor "#1abc9c"
:onPress (fn []
(dispatch [:show-group-new]))}
[icon {:name "person-stalker"
:style {:fontSize 20
:height 22
:color "white"}}]]]]]))))

View File

@ -1,90 +0,0 @@
(ns syng-im.components.chats.new-group
(:require [re-frame.core :refer [subscribe dispatch dispatch-sync]]
[syng-im.resources :as res]
[syng-im.components.react :refer [view
text-input
text
image
touchable-highlight]]
[syng-im.components.styles :refer [font
title-font
color-white
color-purple
text1-color
text2-color
toolbar-background1]]
[syng-im.components.toolbar :refer [toolbar]]
[syng-im.components.realm :refer [list-view]]
[syng-im.utils.listview :refer [to-realm-datasource]]
[syng-im.components.chats.new-group-contact :refer [new-group-contact]]
[reagent.core :as r]
[syng-im.navigation :refer [nav-pop]]))
(defn new-group-toolbar [navigator group-name]
[toolbar {:navigator navigator
:title "New group chat"
:action {:image {:source res/v ;; {:uri "icon_search"}
:style {:width 20
:height 18}}
:handler (fn []
(dispatch [:create-new-group group-name navigator]))}}])
(defn new-group [{:keys [navigator]}]
(let [contacts (subscribe [:all-contacts])
group-name (r/atom nil)]
(fn [{:keys [navigator]}]
(let [contacts-ds (to-realm-datasource @contacts)]
[view {:style {:flex 1
:flexDirection "column"
:backgroundColor color-white}}
[new-group-toolbar navigator @group-name]
[view {:style {:marginHorizontal 16}}
[text {:style {:marginTop 24
:marginBottom 16
:color text2-color
:fontFamily font
:fontSize 14
:lineHeight 20}}
"Chat name"]
[text-input {:underlineColorAndroid color-purple
:style {:marginLeft -4
:fontSize 14
:fontFamily font
:color text1-color}
:autoFocus true
:placeholder "Group Name"
:placeholderTextColor text2-color
:onChangeText (fn [new-text]
(reset! group-name new-text)
(r/flush))
:onSubmitEditing (fn [e]
;(dispatch [:send-chat-msg @chat-id @text])
(reset! group-name nil))}
@group-name]
[text {:style {:marginTop 24
:marginBottom 16
:color text2-color
:fontFamily font
:fontSize 14
:lineHeight 20}}
"Members"]
[touchable-highlight {:on-press (fn [])}
[view {:style {:flexDirection "row"
:marginBottom 16}}
[image {:source {:uri "icon_add_gray"}
:style {:marginVertical 19
:marginHorizontal 3
:width 17
:height 17}}]
[text {:style {:marginTop 18
:marginLeft 32
:color text2-color
:fontFamily font
:fontSize 14
:lineHeight 20}}
"Add members"]]]
[list-view {:dataSource contacts-ds
:enableEmptySections true
:renderRow (fn [row section-id row-id]
(r/as-element [new-group-contact (js->clj row :keywordize-keys true) navigator]))
:style {:backgroundColor "white"}}]]]))))

View File

@ -1,20 +0,0 @@
(ns syng-im.components.chats.new-group-contact
(:require [syng-im.resources :as res]
[re-frame.core :refer [subscribe dispatch dispatch-sync]]
[syng-im.components.react :refer [view]]
[syng-im.components.contact-list.contact-inner :refer [contact-inner-view]]
[syng-im.components.item-checkbox :refer [item-checkbox]]
[syng-im.utils.logging :as log]
[reagent.core :as r]))
(defn new-group-contact [{:keys [whisper-identity] :as contact} navigator]
(let [checked (r/atom false)]
(fn []
[view {:style {:flexDirection "row"
:height 56}}
[item-checkbox {:onToggle (fn [checked?]
(reset! checked checked?)
(dispatch [:select-for-new-group whisper-identity checked?]))
:checked @checked
:size 30}]
[contact-inner-view contact]])))

View File

@ -1,24 +0,0 @@
(ns syng-im.components.chats.new-participant-contact
(:require [syng-im.resources :as res]
[re-frame.core :refer [subscribe dispatch dispatch-sync]]
[syng-im.components.react :refer [view]]
[syng-im.components.contact-list.contact-inner :refer [contact-inner-view]]
[syng-im.components.item-checkbox :refer [item-checkbox]]
[syng-im.utils.logging :as log]
[reagent.core :as r]))
(defn new-participant-contact [{:keys [whisper-identity] :as contact} navigator]
(let [checked (r/atom false)]
(fn []
[view {:style {:flexDirection "row"
:marginTop 5
:marginBottom 5
:paddingLeft 15
:paddingRight 15
:height 75}}
[item-checkbox {:onToggle (fn [checked?]
(reset! checked checked?)
(dispatch [:select-new-participant whisper-identity checked?]))
:checked @checked
:size 30}]
[contact-inner-view contact]])))

View File

@ -1,13 +0,0 @@
(ns syng-im.components.contact-list.contact
(:require [re-frame.core :refer [subscribe dispatch dispatch-sync]]
[syng-im.components.react :refer [view text image touchable-highlight]]
[syng-im.resources :as res]
[syng-im.components.contact-list.contact-inner :refer [contact-inner-view]]))
(defn show-chat [navigator whisper-identity]
(dispatch [:show-chat whisper-identity navigator :push]))
(defn contact-view [{:keys [navigator contact]}]
(let [{:keys [whisper-identity]} contact]
[touchable-highlight {:onPress #(show-chat navigator whisper-identity)}
[view {} [contact-inner-view contact]]]))

View File

@ -1,66 +0,0 @@
(ns syng-im.components.contact-list.contact-inner
(:require [clojure.string :as s]
[syng-im.components.react :refer [view image text]]
[syng-im.components.styles :refer [font
title-font
text1-color
color-white
online-color]]
[syng-im.resources :as res]))
(defn contact-photo [{:keys [photo-path]}]
[view {:borderRadius 50}
[image {:source (if (s/blank? photo-path)
res/user-no-photo
{:uri photo-path})
:style {:borderRadius 50
:width 40
:height 40}}]])
(defn contact-online [{:keys [online]}]
(when online
[view {:position "absolute"
:top 24
:left 24
:width 20
:height 20
:borderRadius 50
:backgroundColor online-color
:borderWidth 2
:borderColor color-white}
[view {:position "absolute"
:top 6
:left 3
:width 4
:height 4
:borderRadius 50
:backgroundColor color-white}]
[view {:position "absolute"
:top 6
:left 9
:width 4
:height 4
:borderRadius 50
:backgroundColor color-white}]]))
(defn contact-inner-view [{:keys [name photo-path delivery-status datetime new-messages-count
online whisper-identity]}]
[view {:style {:flexDirection "row"
:height 56}}
[view {:style {:marginTop 8
:marginLeft 16
:width 44
:height 44}}
;;; photo
[contact-photo {:photo-path photo-path}]
;;; online
[contact-online {:online online}]]
;;; name
[view {:style {:justifyContent "center"}}
[text {:style {:marginLeft 16
:fontSize 16
:fontFamily font
:color text1-color}}
(if (pos? (count name))
name
"Noname")]]])

View File

@ -1,52 +0,0 @@
(ns syng-im.components.contact-list.contact-list
(:require-macros
[natal-shell.data-source :refer [data-source clone-with-rows]]
[natal-shell.core :refer [with-error-view]])
(:require [re-frame.core :refer [subscribe dispatch dispatch-sync]]
[syng-im.components.react :refer [view text image touchable-highlight
navigator list-view
list-item]]
[syng-im.components.contact-list.contact :refer [contact-view]]
[syng-im.components.styles :refer [font
title-font
color-white
color-black
color-blue
text1-color
text2-color
toolbar-background2]]
[syng-im.components.toolbar :refer [toolbar]]
[syng-im.navigation :refer [nav-pop]]
[syng-im.resources :as res]
[syng-im.utils.logging :as log]))
(defn render-row [navigator row section-id row-id]
(list-item [contact-view {:navigator navigator
:contact (js->clj row :keywordize-keys true)}]))
(defn get-data-source [contacts]
(clone-with-rows (data-source {:rowHasChanged (fn [row1 row2]
(not= row1 row2))})
contacts))
(defn contact-list-toolbar [navigator]
[toolbar {:navigator navigator
:title "Contacts"
:background-color toolbar-background2
:action {:image {:source {:uri "icon_search"}
:style {:width 17
:height 17}}
:handler (fn [])}}])
(defn contact-list [{:keys [navigator]}]
(let [contacts (subscribe [:get-contacts])]
(fn []
(let [contacts-ds (get-data-source @contacts)]
[view {:style {:flex 1
:backgroundColor color-white}}
[contact-list-toolbar navigator]
(when contacts-ds
[list-view {:dataSource contacts-ds
:enableEmptySections true
:renderRow (partial render-row navigator)
:style {:backgroundColor "white"}}])]))))

View File

@ -1,83 +0,0 @@
(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.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]
[view st/discovery-toolbar-content
(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"
:custom-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"))
)

View File

@ -1,23 +0,0 @@
(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"])))

View File

@ -1,44 +0,0 @@
(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}]]))

View File

@ -1,23 +0,0 @@
(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.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 {:uri "icon_avatar"}}]
]
]))

View File

@ -1,35 +0,0 @@
(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}]
))

View File

@ -1,59 +0,0 @@
(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.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}]
]))))

View File

@ -1,44 +0,0 @@
(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)))

View File

@ -1,48 +0,0 @@
(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))))))

View File

@ -1,4 +1,4 @@
(ns syng-im.components.drawer-styles
(ns syng-im.components.drawer.styles
(:require [syng-im.components.styles :refer [font
color-light-blue-transparent
color-white

View File

@ -1,4 +1,4 @@
(ns syng-im.components.drawer
(ns syng-im.components.drawer.view
(:require [clojure.string :as s]
[re-frame.core :refer [subscribe dispatch dispatch-sync]]
[reagent.core :as r]
@ -11,7 +11,7 @@
drawer-layout-android
touchable-opacity]]
[syng-im.resources :as res]
[syng-im.components.drawer-styles :as st]))
[syng-im.components.drawer.styles :as st]))
(defonce drawer-atom (atom))
@ -35,9 +35,9 @@
[text {:style st/menu-item-text}
name]])
(defn drawer-menu [navigator]
(let [username (subscribe [:username])]
(fn [navigator]
(defn drawer-menu []
(let [username (subscribe [:get :username])]
(fn []
[view st/drawer-menu
[view st/user-photo-container
[user-photo {}]]
@ -72,10 +72,10 @@
[text {:style st/switch-users-text}
"Switch users"]]]])))
(defn drawer-view [{:keys [navigator]} items]
(defn drawer-view [items]
[drawer-layout-android {:drawerWidth 260
:drawerPosition js/React.DrawerLayoutAndroid.positions.Left
:render-navigation-view #(r/as-element [drawer-menu navigator])
:render-navigation-view #(r/as-element [drawer-menu])
:ref (fn [drawer]
(reset! drawer-atom drawer))}
items])

View File

@ -1,109 +0,0 @@
(ns syng-im.components.profile
(:require [clojure.string :as s]
[re-frame.core :refer [subscribe dispatch dispatch-sync]]
[syng-im.components.react :refer [android?
view
text
text-input
image
icon
scroll-view
touchable-highlight
touchable-opacity]]
[syng-im.resources :as res]
[syng-im.components.profile-styles :as st]))
(defn user-photo [{:keys [photo-path]}]
[image {:source (if (s/blank? photo-path)
res/user-no-photo
{:uri photo-path})
:style st/user-photo}])
(defn user-online [{:keys [online]}]
(when online
[view st/user-online-container
[view st/user-online-dot-left]
[view st/user-online-dot-right]]))
(defn profile-property-view [{:keys [name value]}]
[view st/profile-property-view-container
[view st/profile-property-view-sub-container
[text {:style st/profile-property-view-label}
name]
[text {:style st/profile-property-view-value}
value]]])
(defn message-user [identity]
(when identity
(dispatch [:show-chat identity nil :push])))
(defn profile []
(let [contact (subscribe [:contact])]
(fn []
[scroll-view {:style st/profile}
[touchable-highlight {:style st/back-btn-touchable
:on-press #(dispatch [:navigate-back])}
[view st/back-btn-container
[icon :back st/back-btn-icon]]]
[view st/status-block
[view st/user-photo-container
[user-photo {}]
[user-online {:online true}]]
[text {:style st/user-name}
(:name @contact)]
[text {:style st/status}
"!not implemented"]
[view st/btns-container
[touchable-highlight {:onPress #(message-user (:whisper-identity @contact))}
[view st/message-btn
[text {:style st/message-btn-text}
"Message"]]]
[touchable-highlight {:onPress (fn []
;; TODO not implemented
)}
[view st/more-btn
[icon :more_vertical_blue st/more-btn-image]]]]]
[view st/profile-properties-container
[profile-property-view {:name "Username"
:value (:name @contact)}]
[profile-property-view {:name "Phone number"
:value (:phone-number @contact)}]
[profile-property-view {:name "Email"
:value "!not implemented"}]
[view st/report-user-container
[touchable-opacity {}
[text {:style st/report-user-text}
"REPORT USER"]]]]])))
(defn my-profile []
(let [username (subscribe [:username])
phone-number (subscribe [:phone-number])
email (subscribe [:email])
status (subscribe [:status])]
(fn []
[scroll-view {:style st/profile}
[touchable-highlight {:style st/back-btn-touchable
:on-press #(dispatch [:navigate-back])}
[view st/back-btn-container
[icon :back st/back-btn-icon]]]
[touchable-highlight {:style st/actions-btn-touchable
:on-press (fn []
;; TODO not implemented
)}
[view st/actions-btn-container
[icon :dots st/actions-btn-icon]]]
[view st/status-block
[view st/user-photo-container
[user-photo {}]
[user-online {:online true}]]
[text {:style st/user-name}
@username]
[text {:style st/status}
@status]]
[view st/profile-properties-container
[profile-property-view {:name "Username"
:value @username}]
[profile-property-view {:name "Phone number"
:value @phone-number}]
[profile-property-view {:name "Email"
:value @email}]]])))

View File

@ -15,7 +15,9 @@
(merge {:underlay-color :transparent} props)
content])
(def toolbar-android (r/adapt-react-class (.-ToolbarAndroid js/React)))
(def list-view (r/adapt-react-class (.-ListView js/React)))
(def list-view-class (r/adapt-react-class (.-ListView js/React)))
(defn list-view [props]
[list-view-class (merge {:enableEmptySections true} props)])
(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)))
@ -42,42 +44,3 @@
(r/as-element component))
(def dismiss-keyboard! (js/require "dismissKeyboard"))
(comment
(.-width (.get (.. js/React -Dimensions) "window"))
)
;; (do
;; (def activity-indicator-ios (r/adapt-react-class (.-ActivityIndicatorIOS js/React)))
;; (def animated-image (r/adapt-react-class (.-Animated.Image js/React)))
;; (def animated-text (r/adapt-react-class (.-Animated.Text js/React)))
;; (def animated-view (r/adapt-react-class (.-Animated.View js/React)))
;; (def date-picker-ios (r/adapt-react-class (.-DatePickerIOS js/React)))
;; (def drawer-layout-android (r/adapt-react-class (.-DrawerLayoutAndroid js/React)))
;; (def image (r/adapt-react-class (.-Image js/React)))
;; (def list-view (r/adapt-react-class (.-ListView js/React)))
;; (def map-view (r/adapt-react-class (.-MapView js/React)))
;; (def modal (r/adapt-react-class (.-Modal js/React)))
;; (def navigator (r/adapt-react-class (.-Navigator js/React)))
;; (def navigator-ios (r/adapt-react-class (.-NavigatorIOS js/React)))
;; (def picker-ios (r/adapt-react-class (.-PickerIOS js/React)))
;; (def progress-bar-android (r/adapt-react-class (.-ProgressBarAndroid js/React)))
;; (def progress-view-ios (r/adapt-react-class (.-ProgressViewIOS js/React)))
;; (def pull-to-refresh-view-android (r/adapt-react-class (.-PullToRefreshViewAndroid js/React)))
;; (def scroll-view (r/adapt-react-class (.-ScrollView js/React)))
;; (def segmented-control-ios (r/adapt-react-class (.-SegmentedControlIOS js/React)))
;; (def slider-ios (r/adapt-react-class (.-SliderIOS js/React)))
;; (def switch (r/adapt-react-class (.-Switch js/React)))
;; (def tab-bar-ios (r/adapt-react-class (.-TabBarIOS js/React)))
;; (def tab-bar-ios-item (r/adapt-react-class (.-TabBarIOS.Item js/React)))
;; (def text (r/adapt-react-class (.-Text js/React)))
;; (def text-input (r/adapt-react-class (.-TextInput js/React)))
;; (def toolbar-android (r/adapt-react-class (.-ToolbarAndroid js/React)))
;; (def touchable-highlight (r/adapt-react-class (.-TouchableHighlight js/React)))
;; (def touchable-native-feedback (r/adapt-react-class (.-TouchableNativeFeedback js/React)))
;; (def touchable-opacity (r/adapt-react-class (.-TouchableOpacity js/React)))
;; (def touchable-without-feedback (r/adapt-react-class (.-TouchableWithoutFeedback js/React)))
;; (def view (r/adapt-react-class (.-View js/React)))
;; (def view-pager-android (r/adapt-react-class (.-ViewPagerAndroid js/React)))
;; (def web-view (r/adapt-react-class (.-WebView js/React))))

View File

@ -3,16 +3,7 @@
(set! js/window.RealmReactNative (js/require "realm/react-native"))
(def list-view (r/adapt-react-class (.-ListView js/RealmReactNative)))
(def list-view-class (r/adapt-react-class (.-ListView js/RealmReactNative)))
(comment
;(set! js/wat (js/require "realm.react-native.ListView"))
;(.-Results js/Realm)
;
;(r/realm)
;
;(require '[syng-im.persistence.realm :as r])
)
(defn list-view [props]
[list-view-class (merge {:enableEmptySections true} props)])

View File

@ -9,7 +9,7 @@
(def color-black "#000000de")
(def color-purple "#a187d5")
(def color-gray "#838c93de")
(def color-white "white")
(def color-white :white)
(def color-light-blue "#bbc4cb")
(def color-light-blue-transparent "#bbc4cb32")
(def color-dark-mint "#5fc48d")

View File

@ -1,8 +1,8 @@
(ns syng-im.components.toolbar
(:require [re-frame.core :refer [subscribe dispatch dispatch-sync]]
[syng-im.resources :as res]
(:require [re-frame.core :refer [subscribe dispatch]]
[syng-im.components.react :refer [view
text-input
icon
text
image
touchable-highlight]]
@ -14,10 +14,9 @@
text2-color
toolbar-background1]]
[syng-im.components.realm :refer [list-view]]
[syng-im.utils.listview :refer [to-realm-datasource]]
[reagent.core :as r]))
(defn toolbar [{:keys [navigator title nav-action hide-nav? action custom-action
(defn toolbar [{:keys [title nav-action hide-nav? action custom-action
background-color custom-content style]}]
(let [style (merge {:flexDirection "row"
:backgroundColor (or background-color toolbar-background1)
@ -54,6 +53,7 @@
[touchable-highlight {:on-press (:handler action)}
[view {:width 56
:height 56
:alignItems "center"
:justifyContent "center"}
[image (:image action)]]])]))

View File

@ -0,0 +1,101 @@
(ns syng-im.contacts.handlers
(:require [re-frame.core :refer [register-handler after dispatch]]
[syng-im.models.contacts :as contacts]
[syng-im.utils.crypt :refer [encrypt]]
[clojure.string :as s]
[syng-im.utils.utils :refer [http-post]]
[syng-im.utils.phone-number :refer [format-phone-number]]
[syng-im.utils.handlers :as u]))
(defn save-contact
[_ [_ contact]]
(contacts/save-contacts [contact]))
(register-handler :add-contact
(-> (fn [db [_ {:keys [whisper-identity] :as contact}]]
(update db :contacts assoc whisper-identity contact))
((after save-contact))))
(defn load-contacts! [db _]
(let [contacts (->> (contacts/get-contacts)
(map (fn [{:keys [whisper-identity] :as contact}]
[whisper-identity contact]))
(into {}))]
(assoc db :contacts contacts)))
(register-handler :load-contacts load-contacts!)
;; TODO see https://github.com/rt2zz/react-native-contacts/issues/45
(def react-native-contacts (js/require "react-native-contacts"))
(defn contact-name [contact]
(->> contact
((juxt :givenName :middleName :familyName))
(remove s/blank?)
(s/join " ")))
(defn normalize-phone-contacts [contacts]
(let [contacts' (js->clj contacts :keywordize-keys true)]
(map (fn [{:keys [thumbnailPath phoneNumbers] :as contact}]
{:name (contact-name contact)
:photo-path thumbnailPath
:phone-numbers phoneNumbers}) contacts')))
(defn fetch-contacts-from-phone!
[_ _]
(.getAll react-native-contacts
(fn [error contacts]
(if error
(dispatch [:error-on-fetching-loading error])
(let [contacts' (normalize-phone-contacts contacts)]
(dispatch [:get-contacts-identities contacts']))))))
(register-handler :sync-contacts
(u/side-effect! fetch-contacts-from-phone!))
(defn get-contacts-by-hash [contacts]
(->> contacts
(mapcat (fn [{:keys [phone-numbers] :as contact}]
(map (fn [{:keys [number]}]
(let [number' (format-phone-number number)]
[(encrypt number')
(-> contact
(assoc :phone-number number')
(dissoc :phone-numbers))]))
phone-numbers)))
(into {})))
(defn add-identity [contacts-by-hash contacts]
(map (fn [{:keys [phone-number-hash whisper-identity]}]
(let [contact (contacts-by-hash phone-number-hash)]
(assoc contact :whisper-identity whisper-identity)))
(js->clj contacts)))
(defn request-stored-contacts [contacts]
(let [contacts-by-hash (get-contacts-by-hash contacts)
data (keys contacts-by-hash)]
(http-post "get-contacts" {:phone-number-hashes data}
(fn [{:keys [contacts]}]
(let [contacts' (add-identity contacts-by-hash contacts)]
(dispatch [:add-contacts contacts']))))))
(defn get-identities-by-contacts! [_ [_ contacts]]
(request-stored-contacts contacts))
(register-handler :get-contacts-identities
(u/side-effect! get-identities-by-contacts!))
(defn save-contacts! [{:keys [new-contacts]} _]
(contacts/save-contacts new-contacts))
(defn add-new-contacts
[{:keys [contacts] :as db} [_ new-contacts]]
(let [identities (set (map :whisper-identity contacts))
new-contacts' (remove #(identities (:whisper-identity %)) new-contacts)]
(-> db
(update :contacts concat new-contacts')
(assoc :new-contacts new-contacts'))))
(register-handler :add-contacts
(after save-contacts!)
add-new-contacts)

View File

@ -0,0 +1,35 @@
(ns syng-im.contacts.screen
(:require-macros [syng-im.utils.views :refer [defview]])
(:require [re-frame.core :refer [subscribe dispatch dispatch-sync]]
[syng-im.components.react :refer [view text
image
touchable-highlight
list-view
list-item]]
[syng-im.contacts.views.contact :refer [contact-view]]
[syng-im.components.styles :refer [toolbar-background2]]
[syng-im.components.toolbar :refer [toolbar]]
[syng-im.contacts.styles :as st]
[syng-im.utils.listview :as lw]))
(defn render-row [row _ _]
(list-item [contact-view row]))
(defn contact-list-toolbar []
[toolbar {:title "Contacts"
:background-color toolbar-background2
:action {:image {:source {:uri :icon_search}
:style st/search-icon}
:handler (fn [])}}])
(defview contact-list []
[contacts [:get-contacts]]
[view st/contacts-list-container
[contact-list-toolbar]
;; todo what if there is no contacts, should we show some information
;; about this?
(when contacts
[list-view {:dataSource (lw/to-datasource contacts)
:enableEmptySections true
:renderRow render-row
:style st/contacts-list}])])

View File

@ -0,0 +1,69 @@
(ns syng-im.contacts.styles
(:require [syng-im.components.styles :refer [font
title-font
text1-color
color-white
online-color]]))
(def search-icon
{:width 17
:height 17})
(def contacts-list-container
{:flex 1
:backgroundColor :white})
(def contacts-list
{:backgroundColor :white})
(def contact-photo-container
{:borderRadius 50})
(def photo-image
{:borderRadius 50
:width 40
:height 40})
(def online-container
{:position :absolute
:top 24
:left 24
:width 20
:height 20
:borderRadius 50
:backgroundColor online-color
:borderWidth 2
:borderColor color-white})
(def online-dot
{:position :absolute
:top 6
:width 4
:height 4
:borderRadius 50
:backgroundColor color-white})
(def online-dot-left
(assoc online-dot :left 3))
(def online-dot-right
(assoc online-dot :left 9))
(def contact-container
{:flexDirection :row
:height 56})
(def photo-container
{:marginTop 8
:marginLeft 16
:width 44
:height 44})
(def name-container
{:justifyContent :center})
(def name-text
{:marginLeft 16
:fontSize 16
:fontFamily font
:color text1-color})

View File

@ -0,0 +1,39 @@
(ns syng-im.contacts.subs
(:require-macros [reagent.ratom :refer [reaction]])
(:require [re-frame.core :refer [register-sub]]))
(register-sub :get-contacts
(fn [db _]
(let [contacts (reaction (:contacts @db))]
(reaction (vals @contacts)))))
(register-sub :all-contacts
(fn [db _]
(let [contacts (reaction (:contacts @db))]
(reaction (sort-by :name (vals @contacts))))))
(defn contacts-by-current-chat [fn db]
(let [current-chat-id (:current-chat-id @db)
chat (reaction (get-in @db [:chats current-chat-id]))
contacts (reaction (:contacts @db))]
(reaction
(when @chat
(let [current-participants (->> @chat
:contacts
(map :identity)
set)]
(fn #(current-participants (:whisper-identity %))
(vals @contacts)))))))
(register-sub :contact
(fn [db _]
(let [identity (:contact-identity @db)]
(reaction (get-in @db [:contacts identity])))))
(register-sub :all-new-contacts
(fn [db _]
(contacts-by-current-chat remove db)))
(register-sub :current-chat-contacts
(fn [db _]
(contacts-by-current-chat filter db)))

View File

@ -0,0 +1,16 @@
(ns syng-im.contacts.views.contact
(:require-macros [syng-im.utils.views :refer [defview]])
(:require [syng-im.components.react :refer [view touchable-highlight]]
[re-frame.core :refer [dispatch subscribe]]
[syng-im.contacts.views.contact-inner :refer [contact-inner-view]]))
(defn on-press [chat whisper-identity]
(if chat
#(dispatch [:navigate-to :chat whisper-identity])
#(dispatch [:start-chat whisper-identity])))
(defview contact-view [{:keys [whisper-identity] :as contact}]
[chat [:get-chat whisper-identity]]
[touchable-highlight
{:onPress (on-press chat whisper-identity)}
[view {} [contact-inner-view contact]]])

View File

@ -0,0 +1,30 @@
(ns syng-im.contacts.views.contact-inner
(:require [clojure.string :as s]
[syng-im.components.react :refer [view image text]]
[syng-im.resources :as res]
[syng-im.contacts.styles :as st]))
(defn contact-photo [{:keys [photo-path]}]
[view st/contact-photo-container
[image {:source (if (s/blank? photo-path)
res/user-no-photo
{:uri photo-path})
:style st/photo-image}]])
(defn contact-online [{:keys [online]}]
(when online
[view st/online-container
[view st/online-dot-left]
[view st/online-dot-right]]))
(defn contact-inner-view [{:keys [name photo-path online]}]
[view st/contact-container
[view st/photo-container
[contact-photo {:photo-path photo-path}]
[contact-online {:online online}]]
[view st/name-container
[text {:style st/name-text}
(if (pos? (count name))
name
;; todo is this correct behaviour?
"Noname")]]])

View File

@ -10,15 +10,16 @@
(def app-db {:identity-password "replace-me-with-user-entered-password"
:identity "me"
:contacts []
:contacts-ids #{}
:current-chat-id "console"
:chat {:command nil
:last-message nil}
:chat {:command nil
:last-message nil}
:chats {}
:chats-updated-signal 0
:show-actions false
:new-group #{}
:new-participants #{}
:signed-up false
:signed-up true
:view-id default-view
:navigation-stack (list default-view)
;; TODO fix hardcoded values
@ -29,12 +30,6 @@
:current-tag nil})
(def protocol-initialized-path [:protocol-initialized])
(def identity-password-path [:identity-password])
(def contact-identity-path [:contact-identity])
(def current-chat-id-path [:current-chat-id])
(def updated-chats-signal-path [:chats-updated-signal])
(defn updated-chat-signal-path [chat-id]
[:chats chat-id :chat-updated-signal])
(defn chat-input-text-path [chat-id]
[:chats chat-id :input-text])
(defn chat-staged-commands-path [chat-id]
@ -49,11 +44,3 @@
[:chats chat-id :command-requests])
(defn chat-command-request-path [chat-id msg-id]
[:chats chat-id :command-requests msg-id])
(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])

View File

@ -0,0 +1,76 @@
(ns syng-im.discovery.handlers
(:require [re-frame.core :refer [register-handler after dispatch enrich]]
[syng-im.protocol.api :as api]
[syng-im.navigation.handlers :as nav]
[syng-im.discovery.model :as discoveries]
[syng-im.utils.handlers :as u]))
(defmethod nav/preload-data! :discovery
[{:keys [discoveries] :as db} _]
(if-not (seq discoveries)
(-> db
(assoc :tags (discoveries/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/discovery-list)))
db))
(register-handler :discovery-response-received
(u/side-effect!
(fn [_ [_ from payload]]
(let [{:keys [name status hashtags location]} payload
location (or location "")
discovery [{:name name
:status status
:whisper-id from
:photo ""
:location location
:tags (map #(hash-map :name %) hashtags)
:last-updated (js/Date.)}]]
(dispatch [:add-discovery discovery])))))
(register-handler :broadcast-status
(u/side-effect!
(fn [{:keys [name]} [_ status hashtags]]
(api/broadcast-discover-status name status hashtags))))
(register-handler :show-discovery-tag
(fn [db [_ tag]]
(dispatch [:navigate-to :discovery-tag])
(assoc db :current-tag tag)))
;; todo remove this
(register-handler :create-fake-discovery!
(u/side-effect!
(fn [_ _]
(let [number (rand-int 999)
discovery {: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 [{:name "tag1"}
{:name "tag2"}
{:name "tag3"}]
:last-updated (new js/Date)}]
(dispatch [:add-discovery discovery])))))
(defn add-discovery
[db [_ discovery]]
(-> db
(assoc :new-discovery discovery)
(update :discoveries conj discovery)))
(defn save-discovery!
[{:keys [new-discovery]} _]
(discoveries/save-discoveries [new-discovery]))
(defn reload-tags!
[db _]
(assoc db :tags (discoveries/all-tags)))
(register-handler :add-discovery
(-> add-discovery
((after save-discovery!))
((enrich reload-tags!))))

View File

@ -0,0 +1,89 @@
(ns syng-im.discovery.model
;syng-im.models.discoveries
(:require [syng-im.utils.logging :as log]
[syng-im.persistence.realm :as realm]
[syng-im.persistence.realm :as r]))
(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 (zero? counter)
(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 [tags] :as discovery}]
(log/debug "Creating discovery: " discovery tags)
(realm/create :discoveries discovery true)
(increase-tags-counter tags))
(defn- update-discovery [{:keys [whisper-id tags] :as discovery}]
(let [old-tags (get-tags whisper-id)
tags (map :name tags)]
(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)
r/collection->map)
(map #(update % :tags vals))))
(defn- add-discoveries [discoveries]
(realm/write (fn []
(let [db-discoveries (discovery-list)]
(mapv (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 all-tags []
(-> (r/get-all :tag)
(r/sorted :count :desc)
r/collection->map))

View File

@ -0,0 +1,52 @@
(ns syng-im.discovery.screen
(:require-macros [syng-im.utils.views :refer [defview]])
(:require
[re-frame.core :refer [dispatch subscribe]]
[syng-im.components.react :refer [view
scroll-view
text
text-input]]
[syng-im.components.toolbar :refer [toolbar]]
[syng-im.discovery.views.popular :refer [popular]]
[syng-im.discovery.views.recent :refer [discovery-recent]]
[syng-im.discovery.styles :as st]))
(defn get-hashtags [status]
(let [hashtags (map #(subs % 1) (re-seq #"#[^ !?,;:.]+" status))]
(or hashtags [])))
(defn title-content [show-search]
[view st/discovery-toolbar-content
(if show-search
[text-input {: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 toogle-search [current-value]
(dispatch [:set ::show-search (not current-value)]))
(defview discovery []
[show-search [:get ::show-search]]
[view st/discovery-container
[toolbar
{:style st/discovery-toolbar
:nav-action {:image {:source {:uri :icon_hamburger}
:style st/hamburger-icon}
:handler #(dispatch [:create-fake-discovery!])}
:custom-content [title-content show-search]
:action {:image {:source {:uri :icon_search}
:style st/search-icon}
:handler #(toogle-search show-search)}}]
[scroll-view {:style {}}
[view st/section-spacing
[text {:style st/discovery-subtitle} "Popular tags"]]
[popular]
[view st/section-spacing
[text {:style st/discovery-subtitle} "Recent"]]
[discovery-recent]]])

View File

@ -1,4 +1,4 @@
(ns syng-im.components.discovery.styles
(ns syng-im.discovery.styles
(:require [syng-im.components.styles :refer [font
title-font
color-white
@ -17,7 +17,7 @@
:borderBottomColor "#eff2f3"})
(def row
{:flexDirection "row"})
{:flexDirection :row})
(def column
{:flexDirection "column"})
@ -39,8 +39,8 @@
(def discovery-title
{:color "#000000de"
:alignSelf "center"
:textAlign "center"
:alignSelf :center
:textAlign :center
:fontFamily "sans-serif"
:fontSize 16})
@ -76,8 +76,8 @@
:fontSize 14
:paddingRight 5
:paddingBottom 2
:alignItems "center"
:justifyContent "center"})
:alignItems :center
:justifyContent :center})
(def tag-name-container
{:flexDirection "column"
@ -91,8 +91,8 @@
:fontSize 12
:paddingRight 5
:paddingBottom 2
:alignItems "center"
:justifyContent "center"})
:alignItems :center
:justifyContent :center})
(def tag-count-container
{:flex 0.2
@ -103,18 +103,18 @@
(def popular-list-container
{:flex 1
:backgroundColor "white"
:backgroundColor :white
:paddingLeft 10
:paddingTop 16})
(def popular-list
{:backgroundColor "white"
{:backgroundColor :white
:paddingTop 13})
;; discover_popular_list_item.cjls
(def popular-list-item
{:flexDirection "row"
{:flexDirection :row
:paddingTop 10
:paddingBottom 10})
@ -137,7 +137,7 @@
(def popular-list-item-avatar-container
{:flex 0.2
:flexDirection "column"
:alignItems "center"
:alignItems :center
:paddingTop 5})
(def popular-list-item-avatar
@ -149,7 +149,7 @@
;; discovery_recent
(def recent-list
{:backgroundColor "white"
{:backgroundColor :white
:paddingLeft 15})
;; discovery_tag
@ -178,3 +178,15 @@
(def icon-search
{:width 17
:height 17})
(def discovery-container
{:flex 1
:backgroundColor :#eef2f5})
(def hamburger-icon
{:width 16
:height 12})
(def search-icon
{:width 17
:height 17})

View File

@ -0,0 +1,20 @@
(ns syng-im.discovery.subs
(:require-macros [reagent.ratom :refer [reaction]])
(:require [re-frame.core :refer [register-sub]]))
(register-sub :get-discoveries-by-tag
(fn [db [_ tag limit]]
(let [discoveries (reaction (:discoveries @db))
tag' (or tag (:current-tag @db))
filter-tag (filter #(some #{tag'} (map :name (:tags %))))
xform (if limit
(comp filter-tag (take limit))
filter-tag)]
(->> @discoveries
(into [] xform)
(reaction)))))
(register-sub :get-popular-tags
(fn [db [_ limit]]
(reaction (take limit (:tags @db)))))

View File

@ -0,0 +1,42 @@
(ns syng-im.discovery.tag
(:require
[re-frame.core :refer [subscribe dispatch]]
[syng-im.utils.logging :as log]
[syng-im.utils.listview :refer [to-datasource]]
[syng-im.components.react :refer [view text list-view list-item]]
[syng-im.components.toolbar :refer [toolbar]]
[syng-im.discovery.views.popular-list-item :refer [popular-list-item]]
[syng-im.discovery.styles :as st]))
(defn render-row [row _ _]
(list-item [popular-list-item row]))
(defn render-separator [_ row-id _]
(list-item [view {:style st/row-separator
:key row-id}]))
(defn title-content [tag]
[view st/tag-title-container
[text {:style st/tag-title} (str " #" tag)]])
(defn discovery-tag []
(let [tag (subscribe [:get :current-tag])
discoveries (subscribe [:get-discoveries-by-tag])]
(log/debug "Got discoveries: " @discoveries)
(fn []
(let [items @discoveries
datasource (to-datasource items)]
[view st/discovery-tag-container
[toolbar {:nav-action {:image {:source {:uri :icon_back}
:style st/icon-back}
:handler #(dispatch [:navigate-back])}
:title "Add Participants"
:content (title-content @tag)
:action {:image {:source {:uri :icon_search}
:style st/icon-search}
:handler (fn [])}}]
[list-view {:dataSource datasource
:renderRow render-row
:renderSeparator render-separator
:style st/recent-list}]]))))

View File

@ -0,0 +1,22 @@
(ns syng-im.discovery.views.popular
(:require-macros [syng-im.utils.views :refer [defview]])
(:require
[re-frame.core :refer [subscribe]]
[syng-im.utils.logging :as log]
[syng-im.components.react :refer [android?
text]]
[syng-im.components.carousel.carousel :refer [carousel]]
[syng-im.discovery.styles :as st]
[syng-im.discovery.views.popular-list :refer [discovery-popular-list]]))
(defn page-width []
(.-width (.get (.. js/React -Dimensions) "window")))
(defview popular []
[popular-tags [:get-popular-tags 3]]
(if (pos? (count popular-tags))
[carousel {:pageStyle st/carousel-page-style
:sneak 20}
(for [{:keys [name count]} popular-tags]
[discovery-popular-list name count])]
[text "None"]))

View File

@ -0,0 +1,34 @@
(ns syng-im.discovery.views.popular-list
(:require-macros [syng-im.utils.views :refer [defview]])
(:require
[re-frame.core :refer [subscribe dispatch]]
[syng-im.components.react :refer [view
list-view
list-item
touchable-highlight
text]]
[syng-im.discovery.styles :as st]
[syng-im.utils.listview :refer [to-datasource]]
[syng-im.discovery.views.popular-list-item :refer [popular-list-item]]))
(defn render-row [row _ _]
(list-item [popular-list-item row]))
(defn render-separator [_ row-id _]
(list-item [view {:style st/row-separator
:key row-id}]))
(defview discovery-popular-list [tag count]
[discoveries [:get-discoveries-by-tag tag 3]]
[view st/popular-list-container
[view st/row
[view st/tag-name-container
[touchable-highlight {:onPress #(dispatch [:show-discovery-tag tag])}
[text {:style st/tag-name} (str " #" (name tag))]]]
[view st/tag-count-container
[text {:style st/tag-count} count]]]
[list-view {:dataSource (to-datasource discoveries)
:enableEmptySections true
:renderRow render-row
:renderSeparator render-separator
:style st/popular-list}]])

View File

@ -0,0 +1,15 @@
(ns syng-im.discovery.views.popular-list-item
(:require [syng-im.components.react :refer [view text image]]
[syng-im.discovery.styles :as st]
[reagent.core :as r]))
(defn popular-list-item
[{:keys [name status]}]
[view st/popular-list-item
[view st/popular-list-item-name-container
[text {:style st/popular-list-item-name} name]
[text {:style st/popular-list-item-status
:numberOfLines 2} status]]
[view st/popular-list-item-avatar-container
[image {:style st/popular-list-item-avatar
:source {:uri :icon_avatar}}]]])

View File

@ -0,0 +1,23 @@
(ns syng-im.discovery.views.recent
(:require-macros [syng-im.utils.views :refer [defview]])
(:require
[re-frame.core :refer [subscribe]]
[syng-im.components.react :refer [view list-view list-item]]
[syng-im.utils.listview :refer [to-datasource]]
[syng-im.discovery.styles :as st]
[syng-im.discovery.views.popular-list-item
:refer [popular-list-item]]))
(defn render-row [row _ _]
(list-item [popular-list-item row]))
(defn render-separator [_ row-id _]
(list-item [view {:style st/row-separator
:key row-id}]))
(defview discovery-recent []
[discoveries [:get :discoveries]]
[list-view {:dataSource (to-datasource discoveries)
:renderRow render-row
:renderSeparator render-separator
:style st/recent-list}])

View File

@ -2,6 +2,7 @@
(:require
[re-frame.core :refer [register-handler after dispatch debug enrich]]
[schema.core :as s :include-macros true]
[syng-im.db :refer [app-db schema]]
[syng-im.persistence.simple-kv-store :as kv]
[syng-im.protocol.state.storage :as storage]
[syng-im.db :as db :refer [app-db schema]]
@ -9,47 +10,19 @@
[syng-im.protocol.protocol-handler :refer [make-handler]]
[syng-im.models.protocol :refer [update-identity
set-initialized]]
[syng-im.models.user-data :as user-data]
[syng-im.models.contacts :as contacts]
[syng-im.models.messages :refer [save-message
update-message!
message-by-id
get-messages]]
[syng-im.models.commands :as commands :refer [set-chat-command
set-response-chat-command
set-chat-command-content
set-chat-command-request
stage-command
unstage-command
set-commands]]
[syng-im.models.messages :refer [save-message update-message!]]
[syng-im.models.commands :refer [set-commands]]
[syng-im.handlers.server :as server]
[syng-im.handlers.contacts :as contacts-service]
[syng-im.handlers.suggestions :refer [get-command
handle-command
get-command-handler
load-commands
apply-staged-commands
check-suggestion
switch-command-suggestions]]
[syng-im.chat.suggestions :refer [load-commands]]
[syng-im.handlers.sign-up :as sign-up-service]
[syng-im.components.discovery.handlers :as discovery]
[syng-im.models.chats :refer [chat-exists?
create-chat
chat-add-participants
chat-remove-participants
set-chat-active
re-join-group-chat
chat-by-id2] :as chats]
[syng-im.models.chat :refer [signal-chat-updated
set-current-chat-id
current-chat-id
update-new-group-selection
update-new-participants-selection
clear-new-group
clear-new-participants
new-group-selection
set-chat-input-text
new-participants-selection]]
chat-by-id2]]
[syng-im.utils.logging :as log]
[syng-im.protocol.api :as api]
[syng-im.constants :refer [text-content-type
@ -59,8 +32,10 @@
nav-pop]]
[syng-im.utils.crypt :refer [gen-random-bytes]]
[syng-im.utils.random :as random]
[clojure.string :as str]
[syng-im.components.react :as r]))
syng-im.chat.handlers
[syng-im.navigation.handlers :as nav]
syng-im.discovery.handlers
syng-im.contacts.handlers))
;; -- Middleware ------------------------------------------------------------
;;
@ -78,10 +53,19 @@
;; -- Common --------------------------------------------------------------
(register-handler :set
(debug
(fn [db [_ k v]]
(assoc db k v))))
(defn preload-data!
[{:keys [view-id] :as db} _]
(nav/preload-data! db [nil view-id]))
(register-handler :initialize-db
(fn [_ _]
(assoc app-db
:signed-up (storage/get kv/kv-store :signed-up))))
:signed-up (storage/get kv/kv-store :signed-up))))
(register-handler :set-loading
(fn [db [_ value]]
@ -119,11 +103,6 @@
(log/debug action commands)
(set-commands db commands)))
(register-handler :set-show-actions
(fn [db [action show-actions]]
(log/debug action)
(assoc-in db db/show-actions-path show-actions)))
;; -- Protocol --------------------------------------------------------------
(register-handler :initialize-protocol
@ -137,55 +116,6 @@
(update-identity identity)
(set-initialized true))))
(defn gen-messages [n]
(mapv (fn [_]
(let [id (random-uuid)]
{:msg-id id
:content (str id
"ooops sdfg dsfg"
"s dfg\ndsfg dfg\ndsfgdsfgdsfg")
:content-type text-content-type
:outgoing false
:from "console"
:to "me"})) (range n)))
(defn store-message!
[{:keys [new-message]} [_ {chat-id :from}]]
(save-message chat-id new-message))
(defn add-message-to-db
[db chat-id message]
(let [messages [:chats chat-id :messages]]
(update-in db messages conj message)))
(defn check-author-direction
[db chat-id {:keys [from outgoing] :as message}]
(let [previous-message (first (get-in db [:chats chat-id :messages]))]
(merge message
{:same-author (if previous-message
(= (:from previous-message) from)
true)
:same-direction (if previous-message
(= (:outgoing previous-message) outgoing)
true)})))
(defn receive-message
[db [_ {chat-id :from :as message}]]
(let [message' (check-author-direction db chat-id message)]
(-> db
(add-message-to-db chat-id message')
(assoc :new-message message'))))
(register-handler :received-msg
(-> receive-message
((after store-message!))))
(register-handler :group-received-msg
(fn [db [action {chat-id :group-id :as msg}]]
(log/debug action "msg" msg)
(save-message chat-id msg)
(signal-chat-updated db chat-id)))
(defn system-message [msg-id content]
{:from "system"
:msg-id msg-id
@ -243,357 +173,109 @@
(register-handler :group-chat-invite-acked
(fn [db [action from group-id ack-msg-id]]
(log/debug action from group-id ack-msg-id)
(joined-chat-msg group-id from ack-msg-id)
(signal-chat-updated db group-id)))
(joined-chat-msg group-id from ack-msg-id)))
(register-handler :participant-removed-from-group
(fn [db [action from group-id identity msg-id]]
(log/debug action msg-id from group-id identity)
(chat-remove-participants group-id [identity])
(participant-removed-from-group-msg group-id identity from msg-id)
(signal-chat-updated db group-id)))
(participant-removed-from-group-msg group-id identity from msg-id)))
(register-handler :you-removed-from-group
(fn [db [action from group-id msg-id]]
(log/debug action msg-id from group-id)
(you-removed-from-group-msg group-id from msg-id)
(set-chat-active group-id false)
(signal-chat-updated db group-id)))
(set-chat-active group-id false)))
(register-handler :participant-left-group
(fn [db [action from group-id msg-id]]
(log/debug action msg-id from group-id)
(if (= (api/my-identity) from)
db
(do (participant-left-group-msg group-id from msg-id)
(signal-chat-updated db group-id)))))
(participant-left-group-msg group-id from msg-id))))
(register-handler :participant-invited-to-group
(fn [db [action from group-id identity msg-id]]
(log/debug action msg-id from group-id identity)
(participant-invited-to-group-msg group-id identity from msg-id)
(signal-chat-updated db group-id)))
(participant-invited-to-group-msg group-id identity from msg-id)))
(register-handler :acked-msg
(fn [db [action from msg-id]]
(log/debug action from msg-id)
(update-message! {:msg-id msg-id
:delivery-status :delivered})
(signal-chat-updated db from)))
:delivery-status :delivered})))
(register-handler :msg-delivery-failed
(fn [db [action msg-id]]
(log/debug action msg-id)
(update-message! {:msg-id msg-id
:delivery-status :failed})
(let [{:keys [chat-id]} (message-by-id msg-id)]
(signal-chat-updated db chat-id))))
(defn console? [s]
(= "console" s))
(def not-console?
(complement console?))
(defn prepare-message
[{:keys [identity current-chat-id] :as db} _]
(let [text (get-in db [:chats current-chat-id :input-text])
{:keys [command]} (check-suggestion db (str text " "))
message (check-author-direction
db current-chat-id
{:msg-id (random/id)
:chat-id current-chat-id
:content text
:to current-chat-id
:from identity
:content-type text-content-type
:outgoing true})]
(if command
(set-chat-command db command)
(assoc db :new-message (when-not (str/blank? text) message)))))
(defn prepare-command [identity chat-id staged-command]
(let [command-key (get-in staged-command [:command :command])
content {:command (name command-key)
:content (:content staged-command)}]
{:msg-id (random/id)
:from identity
:to chat-id
:content content
:content-type content-type-command
:outgoing true
:handler (:handler staged-command)}))
(defn prepare-staged-commans
[{:keys [current-chat-id identity] :as db} _]
(let [staged-commands (get-in db [:chats current-chat-id :staged-commands])]
(->> staged-commands
(map #(prepare-command identity current-chat-id %))
;todo this is wrong :(
(map #(check-author-direction db current-chat-id %))
(assoc db :new-commands))))
(defn add-message
[{:keys [new-message current-chat-id] :as db}]
(if new-message
(add-message-to-db db current-chat-id new-message)
db))
(defn add-commands
[{:keys [new-commands current-chat-id] :as db}]
(reduce
#(add-message-to-db %1 current-chat-id %2)
db
new-commands))
(defn clear-input
[{:keys [current-chat-id new-message] :as db} _]
(if new-message
(assoc-in db [:chats current-chat-id :input-text] nil)
db))
(defn clear-staged-commands
[{:keys [current-chat-id] :as db} _]
(assoc-in db [:chats current-chat-id :staged-commands] []))
(defn send-message!
[{:keys [new-message current-chat-id]} _]
(when (and new-message (not-console? current-chat-id))
(api/send-user-msg {:to current-chat-id
:content (:content new-message)})))
(defn save-message-to-realm!
[{:keys [new-message current-chat-id]} _]
(when new-message
(save-message current-chat-id new-message)))
(defn save-commands-to-realm!
[{:keys [new-commands current-chat-id]} _]
(doseq [new-command new-commands]
(save-message current-chat-id (dissoc new-command :handler))))
(defn handle-commands
[{:keys [new-commands]}]
(doseq [{{content :content} :content
handler :handler} new-commands]
(when handler
(handler content))))
(register-handler :send-chat-msg
(-> prepare-message
((enrich prepare-staged-commans))
((enrich add-message))
((enrich add-commands))
((enrich clear-input))
((enrich clear-staged-commands))
((after (fn [_ _] (r/dismiss-keyboard!))))
((after send-message!))
((after save-message-to-realm!))
((after save-commands-to-realm!))
((after handle-commands))))
:delivery-status :failed})))
(register-handler :leave-group-chat
(fn [db [action navigator]]
(log/debug action)
(let [chat-id (current-chat-id db)]
(let [chat-id (:current-chat-id db)]
(api/leave-group-chat chat-id)
(set-chat-active chat-id false)
(left-chat-msg chat-id)
(signal-chat-updated db chat-id))))
(register-handler :send-group-chat-msg
(fn [db [action chat-id text]]
(log/debug action "chat-id" chat-id "text" text)
(let [{msg-id :msg-id
{from :from} :msg} (api/send-group-user-msg {:group-id chat-id
:content text})
msg {:msg-id msg-id
:from from
:to nil
:content text
:content-type text-content-type
:outgoing true}]
(save-message chat-id msg)
(signal-chat-updated db chat-id))))
(left-chat-msg chat-id))))
;; -- User data --------------------------------------------------------------
(register-handler :set-user-phone-number
(fn [db [_ value]]
(assoc db :user-phone-number value)))
(register-handler :load-user-phone-number
(fn [db [_]]
(user-data/load-phone-number)
db))
;; -- Sign up --------------------------------------------------------------
(register-handler :sign-up
(-> (fn [db [_ phone-number]]
(assoc db :user-phone-number phone-number))
((after (fn [& _] (sign-up-service/on-sign-up-response))))))
(register-handler :sign-up-confirm
(fn [db [_ confirmation-code]]
(sign-up-service/on-send-code-response confirmation-code)
db))
(register-handler :sync-contacts
(fn [db [_ handler]]
(contacts-service/sync-contacts handler)
db))
;; -- Contacts --------------------------------------------------------------
(register-handler :load-syng-contacts
(fn [db [_ value]]
(contacts/load-syng-contacts db)))
(register-handler :show-profile
(fn [db [action identity]]
(log/debug action)
(let [db (contacts/set-contact-identity db identity)]
(dispatch [:navigate-to :profile])
db)))
(register-handler :show-my-profile
(fn [db [action]]
(log/debug action)
(dispatch [:navigate-to :my-profile])
db))
;; todo fetch phone number from db
(assoc db :user-phone-number "123")))
;; -- Chats --------------------------------------------------------------
(register-handler :show-chat
(fn [db [action chat-id navigator nav-type]]
(log/debug action "chat-id" chat-id)
(let [db (-> db
(create-chat chat-id [chat-id] false)
(set-current-chat-id chat-id))]
;; (dispatch [:navigate-to navigator {:view-id :chat} nav-type])
(dispatch [:navigate-to :chat])
db)))
(register-handler :init-console-chat
(fn [db [_]]
(sign-up-service/init db)))
(register-handler :set-signed-up
(fn [db [_ signed-up]]
(sign-up-service/set-signed-up db signed-up)))
;; -- Chat --------------------------------------------------------------
(defn update-text [db [_ text]]
(set-chat-input-text db text))
(defn update-command [db [_ text]]
(let [{:keys [command]} (check-suggestion db text)]
(set-chat-command db command)))
(register-handler :set-chat-input-text
((enrich update-command) update-text))
(register-handler :switch-command-suggestions
(fn [db [_]]
(switch-command-suggestions db)))
(register-handler :set-chat-command
(fn [db [_ command-key]]
;; todo what is going on there?!
(set-chat-command db command-key)))
(register-handler :stage-command
(fn [{:keys [current-chat-id] :as db} _]
(let [db (set-chat-input-text db nil)
{:keys [command content]}
(get-in db [:chats current-chat-id :command-input])
command-info {:command command
:content content
:handler (:handler command)}]
(stage-command db command-info))))
(register-handler :unstage-command
(fn [db [_ staged-command]]
(let []
(unstage-command db staged-command))))
(register-handler :set-response-chat-command
(fn [db [_ to-msg-id command-key]]
(set-response-chat-command db to-msg-id command-key)))
(register-handler :set-chat-command-content
(fn [db [_ content]]
(set-chat-command-content db content)))
(register-handler :set-chat-command-request
(fn [db [_ msg-id handler]]
(set-chat-command-request db msg-id handler)))
(register-handler :show-contacts
(fn [db [action navigator]]
(nav-push navigator {:view-id :contact-list})
db))
(defn update-new-participants-selection [db identity add?]
(update db :new-participants (fn [new-participants]
(if add?
(conj new-participants identity)
(disj new-participants identity)))))
(register-handler :select-new-participant
(fn [db [action identity add?]]
(log/debug action identity add?)
(update-new-participants-selection db identity add?)))
(register-handler :show-remove-participants
(fn [db [action navigator]]
(log/debug action)
(nav-push navigator {:view-id :remove-participants})
(clear-new-participants db)))
(register-handler :remove-selected-participants
(fn [db [action navigator]]
(fn [db [action]]
(log/debug action)
(let [identities (vec (new-participants-selection db))
chat-id (current-chat-id db)]
(let [identities (vec (:new-participants db))
chat-id (:current-chat-id db)]
(chat-remove-participants chat-id identities)
(nav-pop navigator)
(dispatch [:navigate-back])
(doseq [ident identities]
(api/group-remove-participant chat-id ident)
(removed-participant-msg chat-id ident))
(signal-chat-updated db chat-id))))
(register-handler :show-add-participants
(fn [db [action navigator]]
(log/debug action)
(nav-push navigator {:view-id :add-participants})
(clear-new-participants db)))
(removed-participant-msg chat-id ident)))))
(register-handler :add-new-participants
(fn [db [action navigator]]
(log/debug action)
(let [identities (vec (new-participants-selection db))
chat-id (current-chat-id db)]
(let [identities (vec (:new-participants db))
chat-id (:current-chat-id db)]
(chat-add-participants chat-id identities)
(nav-pop navigator)
(doseq [ident identities]
(api/group-add-participant chat-id ident))
db)))
(register-handler :show-group-new
(fn [db [action]]
(log/debug action)
(dispatch [:navigate-to :new-group])
(clear-new-group db)))
(defn update-new-group-selection [db identity add?]
(update-in db :new-group (fn [new-group]
(if add?
(conj new-group identity)
(disj new-group identity)))))
(register-handler :select-for-new-group
(fn [db [action identity add?]]
(log/debug action identity add?)
(fn [db [_ identity add?]]
(update-new-group-selection db identity add?)))
(register-handler :create-new-group
(fn [db [action group-name navigator]]
(fn [db [action group-name]]
(log/debug action)
(let [identities (vec (new-group-selection db))
(let [identities (vec (:new-group db))
group-id (api/start-group-chat identities group-name)
db (create-chat db group-id identities true group-name)]
(dispatch [:show-chat group-id navigator :replace])
(dispatch [:show-chat group-id :replace])
db)))
(register-handler :group-chat-invite-received
@ -602,77 +284,3 @@
(if (chat-exists? group-id)
(re-join-group-chat db group-id identities group-name)
(create-chat db group-id identities true group-name))))
(register-handler :navigate-to
(fn [db [_ view-id]]
(-> db
(assoc :view-id view-id)
(update :navigation-stack conj view-id))))
(register-handler :navigate-back
(fn [{:keys [navigation-stack] :as db} _]
(log/debug :navigate-back)
(if (>= 1 (count navigation-stack))
db
(let [[view-id :as navigation-stack'] (pop navigation-stack)]
(-> db
(assoc :view-id view-id)
(assoc :navigation-stack navigation-stack'))))))
(register-handler :load-more-messages
(fn [db _]
db
;; TODO implement
#_(let [chat-id (get-in db [:chat :current-chat-id])
messages [:chats chat-id :messages]
new-messages (gen-messages 10)]
(update-in db messages concat new-messages))))
(defn load-messages!
[db _]
db
(->> (current-chat-id db)
get-messages
(assoc db :messages)))
(defn init-chat
[{:keys [messages] :as db} _]
(let [id (current-chat-id db)]
(assoc-in db [:chats id :messages] messages)))
(register-handler :init-chat
(-> load-messages!
((enrich init-chat))
debug))
(defn initialize-chats
[{:keys [loaded-chats] :as db} _]
(let [chats (->> loaded-chats
(map (fn [{:keys [chat-id] :as chat}]
[chat-id chat]))
(into {}))]
(-> db
(assoc :chats chats)
(dissoc :loaded-chats))))
(defn load-chats!
[db _]
(assoc db :loaded-chats (chats/chats-list)))
(register-handler :initialize-chats
((enrich initialize-chats) load-chats!))
(defn safe-trim [s]
(when (string? s)
(str/trim s)))
(register-handler :cancel-command
(fn [{:keys [current-chat-id] :as db} _]
(-> db
(assoc-in [:chats current-chat-id :command-input] {})
(update-in [:chats current-chat-id :input-text] safe-trim))))
(register-handler :save-password
(fn [db [_ password]]
(sign-up-service/save-password password)
(assoc db :password-saved true)))

View File

@ -1,64 +0,0 @@
(ns syng-im.handlers.contacts
(:require-macros [cljs.core.async.macros :refer [go]])
(:require [clojure.string :as cstr]
[cljs.core.async :as async :refer [chan put! <!]]
[re-frame.core :refer [subscribe dispatch dispatch-sync]]
[syng-im.utils.utils :refer [log on-error http-post toast]]
[syng-im.utils.crypt :refer [encrypt]]
[syng-im.utils.phone-number :refer [format-phone-number]]
[syng-im.models.contacts :as contacts-model]
[syng-im.utils.logging :as log]))
(defn- get-contact-name [phone-contact]
(cstr/join " "
(remove cstr/blank?
[(:givenName phone-contact)
(:middleName phone-contact)
(:familyName phone-contact)])))
(defn- to-syng-contacts [contacts-by-hash data]
(map (fn [server-contact]
(let [number-info (get contacts-by-hash
(:phone-number-hash server-contact))
phone-contact (:contact number-info)]
{:phone-number (:number number-info)
:whisper-identity (:whisper-identity server-contact)
:name (get-contact-name phone-contact)
:photo-path (:photo-path phone-contact)}))
(js->clj (:contacts data))))
(defn- get-contacts-by-hash [contacts]
(let [numbers-info (reduce (fn [numbers contact]
(into numbers
(map (fn [c]
{:number (format-phone-number (:number c))
:contact contact})
(:phone-numbers contact))))
'()
contacts)]
(reduce (fn [m number-info]
(let [number (:number number-info)
hash (encrypt number)]
(assoc m hash number-info)))
{}
numbers-info)))
(defn- request-syng-contacts [contacts]
(let [contacts-by-hash (get-contacts-by-hash contacts)
data (keys contacts-by-hash)
ch (chan)]
(http-post "get-contacts" {:phone-number-hashes data}
(fn [data]
(put! ch
(to-syng-contacts contacts-by-hash data))))
ch))
(defn sync-contacts [handler]
(go
(let [result (<! (contacts-model/load-phone-contacts))]
(if-let [error (:error result)]
(on-error error)
(let [syng-contacts (<! (request-syng-contacts (:contacts result)))]
(contacts-model/save-syng-contacts syng-contacts)
(dispatch [:load-syng-contacts])
(handler))))))

View File

@ -1,12 +1,11 @@
(ns syng-im.handlers.server
(:require [re-frame.core :refer [subscribe dispatch dispatch-sync]]
[syng-im.models.user-data :as user-data]
[syng-im.utils.utils :refer [log on-error http-post]]
[syng-im.utils.logging :as log]))
(defn sign-up
[db phone-number handler]
(user-data/save-phone-number phone-number)
;(user-data/save-phone-number phone-number)
(http-post "sign-up" {:phone-number phone-number
:whisper-identity (get-in db [:user-identity :public])}
(fn [body]

View File

@ -2,7 +2,6 @@
(:require [re-frame.core :refer [subscribe dispatch dispatch-sync]]
[syng-im.persistence.simple-kv-store :as kv]
[syng-im.protocol.state.storage :as s]
[syng-im.models.chat :refer [set-current-chat-id]]
[syng-im.models.chats :as c]
[syng-im.utils.utils :refer [log on-error http-post toast]]
[syng-im.utils.random :as random]
@ -196,5 +195,5 @@
(-> db
(assoc-in [:chats "console"] console-chat)
(assoc :new-chat console-chat)
(set-current-chat-id "console")
(assoc :current-chat-id "console")
(intro))))))

View File

@ -20,13 +20,13 @@
(defn app-root []
(let [greeting (subscribe [:get-greeting])]
(fn []
[view {:style {:flex-direction "column" :margin 40 :align-items "center"}}
[text {:style {:font-size 30 :font-weight "100" :margin-bottom 20 :text-align "center"}} @greeting]
[view {:style {:flex-direction "column" :margin 40 :align-items :center}}
[text {:style {:font-size 30 :font-weight "100" :margin-bottom 20 :text-align :center}} @greeting]
[image {:source logo-img
:style {:width 80 :height 80 :margin-bottom 30}}]
[touchable-highlight {:style {:background-color "#999" :padding 10 :border-radius 5}
:on-press #(alert "HELLO!")}
[text {:style {:color "white" :text-align "center" :font-weight "bold"}} "press me"]]])))
[text {:style {:color :white :text-align :center :font-weight "bold"}} "press me"]]])))
(defn init []
(dispatch-sync [:initialize-db])

View File

@ -1,55 +0,0 @@
(ns syng-im.models.chat
(:require [syng-im.db :as db]))
(defn set-current-chat-id [db chat-id]
(assoc-in db db/current-chat-id-path chat-id))
(defn current-chat-id [db]
(get-in db db/current-chat-id-path))
(defn signal-chat-updated [db chat-id]
(update-in db (db/updated-chat-signal-path chat-id) (fn [current]
(if current
(inc current)
0))))
(defn chat-updated? [db chat-id]
(get-in db (db/updated-chat-signal-path chat-id)))
(defn update-new-group-selection [db identity add?]
(update-in db db/new-group-path (fn [new-group]
(if add?
(conj new-group identity)
(disj new-group identity)))))
(defn update-new-participants-selection [db identity add?]
(update-in db db/new-participants-path (fn [new-participants]
(if add?
(conj new-participants identity)
(disj new-participants identity)))))
(defn new-group-selection [db]
(get-in db db/new-group-path))
(defn clear-new-group [db]
(assoc-in db db/new-group-path #{}))
(defn new-participants-selection [db]
(get-in db db/new-participants-path))
(defn clear-new-participants [db]
(assoc-in db db/new-participants-path #{}))
(defn set-chat-input-text [db text]
(assoc-in db (db/chat-input-text-path (current-chat-id db)) text))
(defn get-chat-input-text [db]
(get-in db (db/chat-input-text-path (current-chat-id db))))
(comment
(swap! re-frame.db/app-db (fn [db]
(signal-chat-updated db "0x0479a5ed1f38cadfad1db6cd56c4b659b0ebe052bbe9efa950f6660058519fa4ca6be2dda66afa80de96ab00eb97a2605d5267a1e8f4c2a166ab551f6826608cdd")))
(current-chat-id @re-frame.db/app-db)
)

View File

@ -3,21 +3,10 @@
[syng-im.persistence.realm :as r]
[syng-im.utils.random :as random :refer [timestamp]]
[clojure.string :refer [join blank?]]
[syng-im.db :as db]
[syng-im.utils.logging :as log]
[syng-im.constants :refer [content-type-status]]
[syng-im.models.messages :refer [save-message]]
[syng-im.persistence.realm-queries :refer [include-query]]
[syng-im.models.chat :refer [signal-chat-updated]]))
(defn signal-chats-updated [db]
(update-in db db/updated-chats-signal-path (fn [current]
(if current
(inc current)
0))))
(defn chats-updated? [db]
(get-in db db/updated-chats-signal-path))
[syng-im.persistence.realm-queries :refer [include-query]]))
(defn chat-name-from-contacts [identities]
(let [chat-name (->> identities
@ -53,8 +42,6 @@
([{:keys [last-msg-id] :as chat}]
(let [chat (assoc chat :last-msg-id (or last-msg-id ""))]
(r/write #(r/create :chats chat))))
([db chat-id identities group-chat?]
(create-chat db chat-id identities group-chat? nil))
([db chat-id identities group-chat? chat-name]
(if (chat-exists? chat-id)
db
@ -73,7 +60,7 @@
:contacts contacts
:last-msg-id ""}))))
(add-status-message chat-id)
(signal-chats-updated db)))))
db))))
(defn chat-contacts [chat-id]
(-> (r/get-by-field :chats :chat-id chat-id)
@ -96,8 +83,7 @@
:is-active true
:name group-name
:contacts contacts} true))))
(-> (signal-chats-updated db)
(signal-chat-updated group-id)))
db)
(defn normalize-contacts
[chats]
@ -136,13 +122,13 @@
chat (r/single (r/get-by-field :chats :chat-id chat-id))]
(-> (aget chat "contacts")
(r/filtered query)
(.forEach (fn [object index collection]
(.forEach (fn [object _ _]
(aset object "is-in-chat" false))))))))
(defn active-group-chats []
(let [results (r/filtered (r/get-all :chats)
"group-chat = true && is-active = true")]
(js->clj (.map results (fn [object index collection]
(js->clj (.map results (fn [object _ _]
(aget object "chat-id"))))))

View File

@ -1,13 +1,9 @@
(ns syng-im.models.commands
(:require [clojure.string :refer [join split]]
[clojure.walk :refer [stringify-keys keywordize-keys]]
[cljs.core.async :as async :refer [chan put! <! >!]]
[re-frame.core :refer [subscribe dispatch dispatch-sync]]
[re-frame.core :refer [subscribe dispatch]]
[syng-im.db :as db]
[syng-im.models.chat :refer [current-chat-id]]
[syng-im.components.styles :refer [color-blue
color-dark-mint]]
[syng-im.utils.utils :refer [log toast]]))
[syng-im.components.styles :refer [color-blue color-dark-mint]]))
;; todo delete
(def commands [{:command :money
@ -79,52 +75,56 @@
(defn find-command [commands command-key]
(first (filter #(= command-key (:command %)) commands)))
(defn get-chat-command-content [db]
(get-in db (db/chat-command-content-path (current-chat-id db))))
(defn get-chat-command-content
[{:keys [current-chat-id] :as db}]
(get-in db (db/chat-command-content-path current-chat-id)))
(defn set-chat-command-content [db content]
(assoc-in db
[:chats (get-in db db/current-chat-id-path) :command-input :content]
content))
(defn set-chat-command-content
[{:keys [current-chat-id] :as db} content]
(assoc-in db [:chats current-chat-id :command-input :content] content))
(defn get-chat-command [db]
(get-in db (db/chat-command-path (current-chat-id db))))
(defn get-chat-command
[{:keys [current-chat-id] :as db}]
(get-in db (db/chat-command-path current-chat-id)))
(defn set-response-chat-command [db msg-id command-key]
(let [chat-id (current-chat-id db)]
(-> db
(assoc-in [:chats chat-id :command-input :content] nil)
(assoc-in [:chats chat-id :command-input :command]
(get-command db command-key))
(assoc-in [:chats chat-id :command-input :to-msg-id] msg-id))))
(defn set-response-chat-command
[{:keys [current-chat-id] :as db} msg-id command-key]
(update-in db [:chats current-chat-id :command-input] merge
{:content nil
:command (get-command db command-key)
:to-msg-id msg-id}))
(defn set-chat-command [db command-key]
(set-response-chat-command db nil command-key))
(defn get-chat-command-to-msg-id [db]
(get-in db (db/chat-command-to-msg-id-path (current-chat-id db))))
(defn get-chat-command-to-msg-id
[{:keys [current-chat-id] :as db}]
(get-in db (db/chat-command-to-msg-id-path current-chat-id)))
(defn stage-command [db command-info]
(update-in db (db/chat-staged-commands-path (current-chat-id db))
(fn [staged-commands]
(if staged-commands
(conj staged-commands command-info)
[command-info]))))
(defn stage-command
[{:keys [current-chat-id] :as db} command-info]
(update-in db (db/chat-staged-commands-path current-chat-id)
#(if %
(conj % command-info)
[command-info])))
(defn unstage-command [db staged-command]
(update-in db (db/chat-staged-commands-path (current-chat-id db))
(update-in db (db/chat-staged-commands-path (:current-chat-id db))
(fn [staged-commands]
(filterv #(not= % staged-command) staged-commands))))
(defn clear-staged-commands [db]
(assoc-in db (db/chat-staged-commands-path (current-chat-id db)) []))
(defn clear-staged-commands
[{:keys [current-chat-id] :as db}]
(assoc-in db (db/chat-staged-commands-path current-chat-id) []))
(defn get-chat-command-request [db]
(get-in db (db/chat-command-request-path (current-chat-id db)
(defn get-chat-command-request
[{:keys [current-chat-id] :as db}]
(get-in db (db/chat-command-request-path current-chat-id
(get-chat-command-to-msg-id db))))
(defn set-chat-command-request [db msg-id handler]
(update-in db (db/chat-command-requests-path (current-chat-id db))
(defn set-chat-command-request
[{:keys [current-chat-id] :as db} msg-id handler]
(update-in db (db/chat-command-requests-path current-chat-id)
#(assoc % msg-id handler)))
(defn parse-command-msg-content [commands content]

View File

@ -1,92 +1,21 @@
(ns syng-im.models.contacts
(:require [cljs.core.async :as async :refer [chan put! <! >!]]
[re-frame.core :refer [subscribe dispatch dispatch-sync]]
[syng-im.db :as db]
[syng-im.utils.utils :refer [log toast]]
[syng-im.persistence.realm :as realm]
[syng-im.persistence.realm :as r]
(:require [syng-im.persistence.realm :as r]
[syng-im.persistence.realm-queries :refer [include-query
exclude-query]]
[clojure.string :as s]))
exclude-query]]))
;; TODO see https://github.com/rt2zz/react-native-contacts/issues/45
(def fake-phone-contacts? false)
(def fake-contacts? false)
(defn get-contacts []
(-> (r/get-all :contacts)
(r/sorted :name :asc)
r/collection->map))
(def react-native-contacts (js/require "react-native-contacts"))
(defn create-contact [{:keys [name photo-path] :as contact}]
(->> {:name (or name "")
:photo-path (or photo-path "")}
(merge contact)
(r/create :contacts)))
(defn- generate-contact [n]
{:name (str "Contact " n)
:photo-path ""
:phone-numbers [{:label "mobile" :number (apply str (repeat 7 n))}]
:delivery-status (if (< (rand) 0.5) :delivered :seen)
:datetime "15:30"
:new-messages-count (rand-int 3)
:online (< (rand) 0.5)})
(defn- generate-contacts [n]
(map generate-contact (range 1 (inc n))))
(defn load-phone-contacts []
(let [ch (chan)]
(if fake-phone-contacts?
(put! ch {:error nil, :contacts (generate-contacts 10)})
(.getAll react-native-contacts
(fn [error raw-contacts]
(put! ch
{:error error
:contacts
(when (not error)
(log raw-contacts)
(map (fn [contact]
(merge contact
(generate-contact 1)
{:name (:givenName contact)
:photo-path (:thumbnailPath contact)
:phone-numbers (:phoneNumbers contact)}))
(js->clj raw-contacts :keywordize-keys true)))}))))
ch))
(defn- get-contacts []
(if fake-contacts?
[{:phone-number "123"
:whisper-identity "abc"
:name "fake"
:photo-path ""}]
(realm/get-list :contacts)))
(defn load-syng-contacts [db]
(let [contacts (map (fn [contact]
(merge contact
{:delivery-status (if (< (rand) 0.5) :delivered :seen)
:datetime "15:30"
:new-messages-count (rand-int 3)
:online (< (rand) 0.5)}))
(get-contacts))]
(assoc db :contacts contacts)))
(defn- create-contact [{:keys [phone-number whisper-identity name photo-path]}]
(realm/create :contacts
{:phone-number phone-number
:whisper-identity whisper-identity
:name (or name "")
:photo-path (or photo-path "")}))
(defn- contact-exist? [contacts contact]
(some #(= (:phone-number contact) (:phone-number %)) contacts))
(defn- add-contacts [contacts]
(realm/write (fn []
(let [db-contacts (get-contacts)]
(dorun (map (fn [contact]
(if (not (contact-exist? db-contacts contact))
(create-contact contact)
;; TODO else override?
))
contacts))))))
(defn save-syng-contacts [syng-contacts]
(add-contacts syng-contacts))
(defn save-contacts [contacts]
(r/write #(mapv create-contact contacts)))
;;;;;;;;;;;;;;;;;;;;----------------------------------------------
@ -107,45 +36,4 @@
(r/sorted :name :asc))))
(defn contact-by-identity [identity]
(if (= identity "console")
{:phone-number ""
:whisper-identity "console"
:name "Console"
:photo-path ""}
(r/single-cljs (r/get-by-field :contacts :whisper-identity identity))))
;;;;;;;;;;;;;;;;;;;;----------------------------------------------
(defn set-contact-identity [db contact-id]
(assoc-in db db/contact-identity-path contact-id))
(defn contact-identity [db]
(get-in db db/contact-identity-path))
(comment
(r/write #(create-contact {:phone-number "0543072333"
:whisper-identity "0x04e43e861a6dd99ad9eee7bd58af89dcaa430188ebec8698de7b7bad54573324fff4ac5cb9bb277af317efd7abfc917b91bf48cc41e40bf70062fd79400016a1f9"
:name "Splinter"
:photo-path ""}))
(r/write #(create-contact {:phone-number "0544828649"
:whisper-identity "0x0487954e7fa746d8cf787403c2c491aadad540b9bb1f0f7b8184792e91c33b6a394079295f5777ec6d4af9ad5ba24794b3ff1ec8be9ff6a708c85a163733192665"
:name "Exodius"
:photo-path ""}))
(r/write #(create-contact {:phone-number "0522222222"
:whisper-identity "0x0407c278af94e0b4599645023f5bec03cbdb3973bd0ae33b94c6a5885d9d20e82ff3f3c3584a637ba016af40bac2f711fd6028045756f561e36e4b07d0c2b4e623"
:name "Mr. Eagle"
:photo-path ""}))
(r/write #(create-contact {:phone-number "0533333333"
:whisper-identity "0x04512f852558ea09d09419019f3f443ec03ff2c1913c48e567723d70e5abf239ed87fb62486b90b85e12de5d327501c1993c9905a69f2ca7e1bfbaab12dd033313"
:name "Mr. PiggyBear"
:photo-path ""}))
(contacts-list)
(:new-group @re-frame.db/app-db)
)
(r/single-cljs (r/get-by-field :contacts :whisper-identity identity)))

View File

@ -1,124 +0,0 @@
(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.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)))

View File

@ -16,10 +16,6 @@
[s]
(keywordize-keys (apply hash-map (split s #"[;=]"))))
(defn select-chat-last-message [chat]
(when-let [last-msg-id (:last-msg-id chat)]
(r/single-cljs (r/get-by-field :msgs :msg-id last-msg-id))))
(defn save-message
[chat-id {:keys [from to msg-id content content-type outgoing
same-author same-direction]
@ -55,9 +51,6 @@
(update message :content str-to-map)
message)))))
(defn message-by-id [msg-id]
(r/single-cljs (r/get-by-field :msgs :msg-id msg-id)))
(defn update-message! [{:keys [msg-id] :as msg}]
(log/debug "update-message!" msg)
(r/write

View File

@ -6,25 +6,19 @@
[syng-im.utils.types :refer [to-edn-string]]
[re-frame.db :refer [app-db]]
[syng-im.db :as db]
[syng-im.persistence.simple-kv-store :as kv]
[syng-im.utils.logging :as log]))
[syng-im.persistence.simple-kv-store :as kv]))
(defn set-initialized [db initialized?]
(assoc-in db db/protocol-initialized-path initialized?))
(defn update-identity [db identity]
(let [password (get-in db db/identity-password-path)
(let [password (:identity-password db)
encrypted (password-encrypt password (to-edn-string identity))]
(s/put kv/kv-store :identity encrypted)
(assoc db :user-identity identity)))
(defn stored-identity [db]
(let [encrypted (s/get kv/kv-store :identity)
password (get-in db db/identity-password-path)]
password (:identity-password db)]
(when encrypted
(read-string (password-decrypt password encrypted)))))
(comment
(stored-identity @re-frame.db/app-db)
)

View File

@ -1,17 +0,0 @@
(ns syng-im.models.user-data
(:require-macros
[natal-shell.async-storage :refer [get-item set-item]])
(:require [re-frame.core :refer [subscribe dispatch dispatch-sync]]
[syng-im.utils.utils :refer [log on-error toast]]))
(defn save-phone-number [phone-number]
(set-item "user-phone-number" phone-number)
(dispatch [:set-user-phone-number phone-number]))
(defn load-phone-number []
(get-item "user-phone-number"
(fn [error value]
(if error
(on-error error)
(dispatch [:set-user-phone-number (when value
(str value))])))))

View File

@ -0,0 +1,84 @@
(ns syng-im.navigation.handlers
(:require [re-frame.core :refer [register-handler dispatch debug enrich]]))
(defn push-view [db view-id]
(-> db
(update :navigation-stack conj view-id)
(assoc :view-id view-id)))
(defn replace-top-element [stack view-id]
(let [stack' (if (pos? (count stack))
(pop stack)
stack)]
(conj stack' view-id)))
(defn replace-view [db view-id]
(-> db
(update :navigation-stack replace-top-element view-id)
(assoc :view-id view-id)))
(defmulti preload-data! (fn [_ [_ view-id]] view-id))
(defmethod preload-data! :default [db _] db)
(register-handler :navigate-to
(enrich preload-data!)
(fn [db [_ view-id]]
(push-view db view-id)))
(register-handler :navigation-replace
(fn [db [_ view-id]]
(replace-view db view-id)))
(register-handler :navigate-back
(fn [{:keys [navigation-stack] :as db} _]
(if (>= 1 (count navigation-stack))
db
(let [[view-id :as navigation-stack'] (pop navigation-stack)]
(-> db
(assoc :view-id view-id)
(assoc :navigation-stack navigation-stack'))))))
(register-handler :show-group-new
(debug
(fn [db _]
(-> db
(push-view :new-group)
(assoc :new-group #{})))))
(register-handler :show-chat
(fn [db [_ chat-id nav-type]]
(let [update-view-id-fn (if (= :replace nav-type) replace-view push-view)]
(-> db
(update-view-id-fn :chat)
(assoc :current-chat-id chat-id)))))
(register-handler :show-contacts
(fn [db _]
(push-view db :contact-list)))
(defn clear-new-participants [db]
(assoc-in db :new-participants #{}))
(register-handler :show-remove-participants
(fn [db _]
(-> db
(push-view :remove-participants)
clear-new-participants)))
(register-handler :show-add-participants
(fn [db _]
(-> db
(push-view :add-participants)
clear-new-participants)))
(register-handler :show-profile
(debug
(fn [db [_ identity]]
(let [db (assoc db :contact-identity identity)]
(dispatch [:navigate-to :profile])
db))))
(register-handler :show-my-profile
(fn [db _]
(dispatch [:navigate-to :my-profile])
db))

View File

@ -0,0 +1,11 @@
(ns syng-im.navigation.subs
(:require-macros [reagent.ratom :refer [reaction]])
(:require [re-frame.core :refer [register-sub]]))
(register-sub :view-id
(fn [db _]
(reaction (@db :view-id))))
(register-sub :navigation-stack
(fn [db _]
(reaction (:navigation-stack @db))))

View File

@ -0,0 +1,58 @@
(ns syng-im.new-group.screen
(:require [re-frame.core :refer [subscribe dispatch dispatch-sync]]
[syng-im.resources :as res]
[syng-im.components.react :refer [view
text-input
text
image
icon
touchable-highlight
list-view
list-item]]
[syng-im.components.styles :refer [color-purple]]
[syng-im.components.toolbar :refer [toolbar]]
[syng-im.utils.listview :refer [to-datasource]]
[syng-im.new-group.views.contact :refer [new-group-contact]]
[syng-im.new-group.styles :as st]))
(defn new-group-toolbar []
(let [group-name (subscribe [:get ::group-name])]
(fn []
[toolbar
{:title "New group chat"
:action {:image {:source res/v ;; {:uri "icon_search"}
:style st/toolbar-icon}
:handler #(dispatch [:create-new-group @group-name])}}])))
(defn group-name-input []
(let [group-name (subscribe [:get ::group-name])]
(fn []
[text-input
{:underlineColorAndroid color-purple
:style st/group-name-input
:autoFocus true
:placeholder "Group Name"
:onChangeText #(dispatch [:set ::group-name %])
:onSubmitEditing #(dispatch [:set ::group-name nil])}
@group-name])))
(defn new-group []
(let [contacts (subscribe [:all-contacts])]
(fn []
(let [contacts-ds (to-datasource @contacts)]
[view st/new-group-container
[new-group-toolbar]
[view st/chat-name-container
[text {:style st/chat-name-text} "Chat name"]
[group-name-input]
[text {:style st/members-text} "Members"]
[touchable-highlight {:on-press (fn [])}
[view st/add-container
[icon :add_gray st/add-icon]
[text {:style st/add-text} "Add members"]]]
[list-view
{:dataSource contacts-ds
:renderRow (fn [row _ _]
(list-item [new-group-contact row]))
:style st/contacts-list}]]]))))

View File

@ -0,0 +1,67 @@
(ns syng-im.new-group.styles
(:require [syng-im.components.styles :refer [font
title-font
color-white
color-purple
text1-color
text2-color
toolbar-background1]]))
(def toolbar-icon
{:width 20
:height 18})
(def new-group-container
{:flex 1
:flexDirection :column
:backgroundColor color-white})
(def chat-name-container
{:marginHorizontal 16})
(def chat-name-text
{:marginTop 24
:marginBottom 16
:color text2-color
:fontFamily font
:fontSize 14
:lineHeight 20})
(def group-name-input
{:marginLeft -4
:fontSize 14
:fontFamily font
:color text1-color})
(def members-text
{:marginTop 24
:marginBottom 16
:color text2-color
:fontFamily font
:fontSize 14
:lineHeight 20})
(def add-container
{:flexDirection :row
:marginBottom 16})
(def add-icon
{:marginVertical 19
:marginHorizontal 3
:width 17
:height 17})
(def add-text
{:marginTop 18
:marginLeft 32
:color text2-color
:fontFamily font
:fontSize 14
:lineHeight 20})
(def contacts-list
{:backgroundColor :white})
(def contact-container
{:flexDirection :row
:height 56})

View File

@ -0,0 +1,19 @@
(ns syng-im.new-group.views.contact
(:require [re-frame.core :refer [subscribe dispatch dispatch-sync]]
[syng-im.components.react :refer [view]]
[syng-im.contacts.views.contact-inner :refer [contact-inner-view]]
[syng-im.components.item-checkbox :refer [item-checkbox]]
[reagent.core :as r]
[syng-im.new-group.styles :as st]))
(defn new-group-contact [{:keys [whisper-identity] :as contact}]
(let [checked (r/atom false)]
(fn []
[view st/contact-container
[item-checkbox
{:onToggle (fn [checked?]
(reset! checked checked?)
(dispatch [:select-for-new-group whisper-identity checked?]))
:checked @checked
:size 30}]
[contact-inner-view contact]])))

View File

@ -0,0 +1,24 @@
(ns syng-im.participants.styles)
(def participants-container
{:flex 1
:backgroundColor :white})
(def participants-list
{:backgroundColor :white})
(def new-participant-image
{:width 20
:height 18})
(def remove-participants-image
{:width 22
:height 30})
(def participant-container
{:flexDirection :row
:marginTop 5
:marginBottom 5
:paddingLeft 15
:paddingRight 15
:height 75})

View File

@ -0,0 +1,19 @@
(ns syng-im.participants.views.contact
(:require [re-frame.core :refer [subscribe dispatch dispatch-sync]]
[syng-im.components.react :refer [view]]
[syng-im.contacts.views.contact-inner :refer [contact-inner-view]]
[syng-im.components.item-checkbox :refer [item-checkbox]]
[reagent.core :as r]
[syng-im.participants.styles :as st]))
(defn participant-contact [{:keys [whisper-identity] :as contact}]
;; todo must be moved to handlers
(let [checked (r/atom false)]
(fn [{:keys [whisper-identity] :as contact}]
[view st/participant-container
[item-checkbox {:onToggle (fn [checked?]
(reset! checked checked?)
(dispatch [:select-new-participant whisper-identity checked?]))
:checked @checked
:size 30}]
[contact-inner-view contact]])))

View File

@ -0,0 +1,30 @@
(ns syng-im.participants.views.create
(:require [re-frame.core :refer [subscribe dispatch]]
[syng-im.resources :as res]
[syng-im.components.react :refer [view list-view list-item]]
[syng-im.components.toolbar :refer [toolbar]]
[syng-im.utils.listview :refer [to-datasource]]
[syng-im.participants.views.contact :refer [participant-contact]]
[reagent.core :as r]
[syng-im.participants.styles :as st]))
(defn new-participants-toolbar []
[toolbar
{:title "Add Participants"
:action {:image {:source res/v ;; {:uri "icon_search"}
:style st/new-participant-image}
:handler #(dispatch [:add-new-participants])}}])
(defn new-participants-row
[row _ _]
(list-item [participant-contact row]))
(defn new-participants []
(let [contacts (subscribe [:all-new-contacts])]
(fn []
(let [contacts-ds (to-datasource @contacts)]
[view st/participants-container
[new-participants-toolbar]
[list-view {:dataSource contacts-ds
:renderRow new-participants-row
:style st/participants-list}]]))))

View File

@ -0,0 +1,33 @@
(ns syng-im.participants.views.remove
(:require [re-frame.core :refer [subscribe dispatch]]
[syng-im.resources :as res]
[syng-im.components.react :refer [view text-input text image
touchable-highlight list-view
list-item]]
[syng-im.components.toolbar :refer [toolbar]]
[syng-im.utils.listview :refer [to-datasource]]
[syng-im.participants.views.contact
:refer [participant-contact]]
[reagent.core :as r]
[syng-im.participants.styles :as st]))
(defn remove-participants-toolbar []
[toolbar
{:title "Remove Participants"
:action {:handler #(dispatch [:remove-selected-participants])
:image {:source res/trash-icon ;; {:uri "icon_search"}
:style st/remove-participants-image}}}])
(defn remove-participants-row
[row _ _]
(r/as-element [participant-contact row]))
(defn remove-participants []
(let [contacts (subscribe [:current-chat-contacts])]
(fn []
(let [contacts-ds (to-datasource @contacts)]
[view st/participants-container
[remove-participants-toolbar]
[list-view {:dataSource contacts-ds
:renderRow remove-participants-row
:style st/participants-list}]]))))

View File

@ -44,28 +44,28 @@
:properties {:chat-id "string"
:name "string"
:group-chat {:type "bool"
:indexed true}
:is-active "bool"
:timestamp "int"
:contacts {:type "list"
:objectType "chat-contact"}
:indexed true}
: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"}}]})
{: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)))

View File

@ -0,0 +1,98 @@
(ns syng-im.profile.screen
(:require-macros [syng-im.utils.views :refer [defview]])
(:require [clojure.string :as s]
[re-frame.core :refer [subscribe dispatch]]
[syng-im.components.react :refer [view
text
image
icon
scroll-view
touchable-highlight
touchable-opacity]]
[syng-im.resources :as res]
[syng-im.profile.styles :as st]))
(defn user-photo [{:keys [photo-path]}]
[image {:source (if (s/blank? photo-path)
res/user-no-photo
{:uri photo-path})
:style st/user-photo}])
(defn user-online [{:keys [online]}]
(when online
[view st/user-online-container
[view st/user-online-dot-left]
[view st/user-online-dot-right]]))
(defn profile-property-view [{:keys [name value]}]
[view st/profile-property-view-container
[view st/profile-property-view-sub-container
[text {:style st/profile-property-view-label} name]
[text {:style st/profile-property-view-value} value]]])
(defn message-user [identity]
(when identity
(dispatch [:show-chat identity :push])))
(defview profile []
[{:keys [name whisper-identity phone-number]} [:contact]]
[scroll-view {:style st/profile}
[touchable-highlight {:style st/back-btn-touchable
:on-press #(dispatch [:navigate-back])}
[view st/back-btn-container
[icon :back st/back-btn-icon]]]
[view st/status-block
[view st/user-photo-container
[user-photo {}]
[user-online {:online true}]]
[text {:style st/user-name} name]
[text {:style st/status} "!not implemented"]
[view st/btns-container
[touchable-highlight {:onPress #(message-user whisper-identity)}
[view st/message-btn
[text {:style st/message-btn-text} "Message"]]]
[touchable-highlight {:onPress (fn []
;; TODO not implemented
)}
[view st/more-btn
[icon :more_vertical_blue st/more-btn-image]]]]]
[view st/profile-properties-container
[profile-property-view {:name "Username"
:value name}]
[profile-property-view {:name "Phone number"
:value phone-number}]
[profile-property-view {:name "Email"
:value "!not implemented"}]
[view st/report-user-container
[touchable-opacity {}
[text {:style st/report-user-text} "REPORT USER"]]]]])
(defview my-profile []
[username [:get :username]
phone-number [:get :phone-number]
email [:get :email]
status [:get :status]]
[scroll-view {:style st/profile}
[touchable-highlight {:style st/back-btn-touchable
:on-press #(dispatch [:navigate-back])}
[view st/back-btn-container
[icon :back st/back-btn-icon]]]
[touchable-highlight {:style st/actions-btn-touchable
:on-press (fn []
;; TODO not implemented
)}
[view st/actions-btn-container
[icon :dots st/actions-btn-icon]]]
[view st/status-block
[view st/user-photo-container
[user-photo {}]
[user-online {:online true}]]
[text {:style st/user-name} username]
[text {:style st/status} status]]
[view st/profile-properties-container
[profile-property-view {:name "Username"
:value username}]
[profile-property-view {:name "Phone number"
:value phone-number}]
[profile-property-view {:name "Email"
:value email}]]])

View File

@ -1,4 +1,4 @@
(ns syng-im.components.profile-styles
(ns syng-im.profile.styles
(:require [syng-im.components.styles :refer [font
color-light-blue-transparent
color-white

View File

@ -1,204 +1,27 @@
(ns syng-im.subs
(:require-macros [reagent.ratom :refer [reaction]])
(:require [re-frame.core :refer [register-sub]]
[syng-im.db :as db]
[syng-im.components.discovery.subs :as discovery]
[syng-im.models.chat :refer [current-chat-id
chat-updated?]]
[syng-im.models.chats :refer [chats-list
chats-updated?
chat-by-id]]
[syng-im.models.messages :refer [get-messages]]
[syng-im.models.contacts :refer [contacts-list
[syng-im.models.chats :refer [chats-list chat-by-id]]
[syng-im.models.contacts :refer [get-contacts
contacts-list-exclude
contacts-list-include
contact-identity
contact-by-identity]]
[syng-im.models.commands :refer [get-commands
get-chat-command
get-chat-command-content
get-chat-command-request
parse-command-request]]
[syng-im.handlers.suggestions :refer [get-suggestions
typing-command?]]
[syng-im.handlers.content-suggestions :refer [get-content-suggestions]]))
;; -- Chat --------------------------------------------------------------
(register-sub :get-chat-messages
(fn [db _]
(let [chat-id (current-chat-id @db)]
(reaction (get-in @db [:chats chat-id :messages])))))
(register-sub :get-current-chat-id
(fn [db _]
(reaction (current-chat-id @db))))
(register-sub :get-suggestions
(fn [db _]
(let [input-text (->> (current-chat-id @db)
db/chat-input-text-path
(get-in @db)
(reaction))]
(reaction (get-suggestions @db @input-text)))))
(register-sub :typing-command?
(fn [db _]
(reaction (typing-command? @db))))
(register-sub :get-content-suggestions
(fn [db _]
(let [command (reaction (get-chat-command @db))
text (reaction (get-chat-command-content @db))]
(reaction (get-content-suggestions @db @command @text)))))
(register-sub :get-commands
(fn [db _]
(reaction (get-commands @db))))
(register-sub :get-chat-input-text
(fn [db _]
(reaction (get-in @db (db/chat-input-text-path (current-chat-id @db))))))
(register-sub :get-chat-staged-commands
(fn [db _]
(reaction (get-in @db (db/chat-staged-commands-path (current-chat-id @db))))))
(register-sub :get-chat-command
(fn [db _]
(reaction (get-chat-command @db))))
(register-sub :get-chat-command-content
(fn [db _]
(reaction (get-chat-command-content @db))))
(register-sub :chat-command-request
(fn [db _]
(reaction (get-chat-command-request @db))))
syng-im.chat.subs
syng-im.navigation.subs
syng-im.discovery.subs
syng-im.contacts.subs))
;; -- Chats list --------------------------------------------------------------
(register-sub :get-chats
(fn [db _]
(let [chats-updated (reaction (chats-updated? @db))]
(reaction
(let [_ @chats-updated]
(chats-list))))))
(register-sub :get-current-chat
(fn [db _]
(let [current-chat-id (current-chat-id @db)]
(reaction (get-in @db [:chats current-chat-id])))))
(register-sub :get
(fn [db [_ k]]
(reaction (k @db))))
;; -- User data --------------------------------------------------------------
(register-sub :username
(fn [db _]
(reaction
(get @db :username))))
(register-sub :phone-number
(fn [db _]
(reaction
(get @db :phone-number))))
(register-sub :email
(fn [db _]
(reaction
(get @db :email))))
(register-sub :status
(fn [db _]
(reaction
(get @db :status))))
(register-sub
:get-user-identity
(fn [db _]
(reaction
(get @db :user-identity))))
(register-sub
:get-loading
(fn [db _]
(reaction
(get @db :loading))))
(register-sub
:signed-up
(fn [db _]
(reaction
(get @db :signed-up))))
(register-sub
:show-actions
(fn [db _]
(reaction (get-in @db db/show-actions-path))))
(register-sub
:get-contacts
(fn [db _]
(reaction
(get @db :contacts))))
(register-sub :all-contacts
(fn [db _]
(reaction
(contacts-list))))
(register-sub :contact
(fn [db _]
(let [identity (reaction (get-in @db db/contact-identity-path))]
(reaction (contact-by-identity @identity)))))
(register-sub :all-new-contacts
(fn [db _]
(let [current-chat-id (reaction (current-chat-id @db))
chat (reaction (when-let [chat-id @current-chat-id]
(chat-by-id chat-id)))]
(reaction
(when @chat
(let [current-participants (->> @chat
:contacts
(map :identity))]
(contacts-list-exclude current-participants)))))))
(register-sub :current-chat-contacts
(fn [db _]
(let [current-chat-id (reaction (current-chat-id @db))
chat (reaction (when-let [chat-id @current-chat-id]
(chat-by-id chat-id)))]
(reaction
(when @chat
(let [current-participants (->> @chat
:contacts
(map :identity))]
(contacts-list-include current-participants)))))))
(register-sub :view-id
(fn [db _]
(reaction (@db :view-id))))
(register-sub :chat
(fn [db [_ k]]
(-> @db
(get-in [:chats (current-chat-id @db) k])
(reaction))))
(register-sub :navigation-stack
(fn [db _]
(reaction (:navigation-stack @db))))
(reaction (:signed-up @db))))
(register-sub :db
(fn [db _] (reaction @db)))
(register-sub :chat-properties
(fn [db [_ properties]]
(->> properties
(map (fn [k]
[k (-> @db
(get-in [:chats (:current-chat-id @db) k])
(reaction))]))
(into {}))))

Some files were not shown because too many files have changed in this diff Show More