Unverified Commit 94c28569 authored by Rafael Kellermann Streit's avatar Rafael Kellermann Streit Committed by GitHub

Merge pull request #678 from RocketChat/develop

[RELEASE] Merge develop into master
parents 5bbd3f7f 449e639e
...@@ -21,9 +21,10 @@ android { ...@@ -21,9 +21,10 @@ android {
} }
} }
dependencies { dependencies {
compile project(':log-wrapper') api project(':log-wrapper')
compile extraDependencies.okHTTP implementation extraDependencies.okHTTP
compile extraDependencies.rxJava implementation extraDependencies.rxJava
compile extraDependencies.boltTask implementation extraDependencies.rxKotlin
compile supportDependencies.annotation implementation extraDependencies.boltTask
implementation supportDependencies.annotation
} }
\ No newline at end of file
...@@ -84,7 +84,11 @@ public class DDPClient { ...@@ -84,7 +84,11 @@ public class DDPClient {
} }
public void close() { public void close() {
impl.close(REASON_CLOSED_BY_USER, "closed by DDPClient#close()"); close(REASON_CLOSED_BY_USER);
}
public void close(int reason) {
impl.close(reason, "closed by DDPClient#close()");
} }
/** /**
......
...@@ -22,440 +22,467 @@ import io.reactivex.disposables.CompositeDisposable; ...@@ -22,440 +22,467 @@ import io.reactivex.disposables.CompositeDisposable;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
public class DDPClientImpl { public class DDPClientImpl {
private final DDPClient client; private final DDPClient client;
private RxWebSocket websocket; private RxWebSocket websocket;
private Flowable<RxWebSocketCallback.Base> flowable; private Flowable<RxWebSocketCallback.Base> flowable;
private CompositeDisposable disposables; private CompositeDisposable disposables;
private String currentSession; private String currentSession;
public DDPClientImpl(DDPClient self, OkHttpClient client) { /* package */ DDPClientImpl(DDPClient self, OkHttpClient client) {
websocket = new RxWebSocket(client); websocket = new RxWebSocket(client);
this.client = self; this.client = self;
}
private static JSONObject toJson(String s) {
if (TextUtils.isEmpty(s)) {
return null;
} }
try {
return new JSONObject(s); private static JSONObject toJson(String s) {
} catch (JSONException e) { if (TextUtils.isEmpty(s)) {
return null; return null;
}
try {
return new JSONObject(s);
} catch (JSONException e) {
return null;
}
} }
}
private static String extractMsg(JSONObject response) { private static String extractMsg(JSONObject response) {
if (response == null || response.isNull("msg")) { if (response == null || response.isNull("msg")) {
return null; return null;
} else { } else {
return response.optString("msg"); return response.optString("msg");
}
} }
}
/* package */ void connect(final TaskCompletionSource<DDPClientCallback.Connect> task, final String url,
String session) {
try {
flowable = websocket.connect(url).autoConnect(2);
CompositeDisposable disposables = new CompositeDisposable();
disposables.add(
flowable.filter(callback -> callback instanceof RxWebSocketCallback.Open)
.subscribe(
callback ->
sendMessage("connect",
json -> (TextUtils.isEmpty(session) ? json : json.put("session", DDPClientImpl.this.currentSession))
.put(
"version", "pre2")
.put("support", new JSONArray().put("pre2").put("pre1")),
task),
RCLog::e
)
);
disposables.add(
flowable.filter(
callback -> callback instanceof RxWebSocketCallback.Message)
.map(callback -> ((RxWebSocketCallback.Message) callback).responseBodyString)
.map(DDPClientImpl::toJson)
.timeout(7, TimeUnit.SECONDS)
.subscribe(response -> {
String msg = extractMsg(response);
if ("connected".equals(msg) && !response.isNull("session")) {
currentSession = response.optString("session");
task.trySetResult(
new DDPClientCallback.Connect(client, response.optString("session")));
disposables.clear();
} else if ("error".equals(msg) && "Already connected".equals(
response.optString("reason"))) {
task.trySetResult(new DDPClientCallback.Connect(client, null));
disposables.clear();
} else if ("failed".equals(msg)) {
task.trySetError(
new DDPClientCallback.Connect.Failed(client, response.optString("version")));
disposables.clear();
}
},
err -> task.trySetError(new DDPClientCallback.Connect.Timeout(client))
)
);
addErrorCallback(disposables, task); /* package */ void connect(final TaskCompletionSource<DDPClientCallback.Connect> task, final String url,
String session) {
try {
flowable = websocket.connect(url).autoConnect(2);
CompositeDisposable disposables = new CompositeDisposable();
disposables.add(
flowable.filter(callback -> callback instanceof RxWebSocketCallback.Open)
.subscribe(
callback ->
sendMessage("connect",
json -> (TextUtils.isEmpty(session) ? json : json.put("session", DDPClientImpl.this.currentSession))
.put(
"version", "1")
.put("support", new JSONArray().put("1").put("pre2").put("pre1")),
task),
RCLog::e
)
);
disposables.add(
flowable.filter(
callback -> callback instanceof RxWebSocketCallback.Message)
.map(callback -> ((RxWebSocketCallback.Message) callback).responseBodyString)
.map(DDPClientImpl::toJson)
.timeout(7, TimeUnit.SECONDS)
.subscribe(response -> {
String msg = extractMsg(response);
if ("connected".equals(msg) && !response.isNull("session")) {
currentSession = response.optString("session");
task.trySetResult(
new DDPClientCallback.Connect(client, response.optString("session")));
disposables.clear();
} else if ("error".equals(msg) && "Already connected".equals(
response.optString("reason"))) {
task.trySetResult(new DDPClientCallback.Connect(client, null));
disposables.clear();
} else if ("failed".equals(msg)) {
task.trySetError(
new DDPClientCallback.Connect.Failed(client, response.optString("version")));
disposables.clear();
}
},
err -> {
if (err instanceof TimeoutException) {
task.trySetError(new Exception("Your connection seems off…"));
} else {
task.trySetError(new Exception("Ooops. Something's up!"));
}
}
)
);
addErrorCallback(disposables, task);
subscribeBaseListeners(); subscribeBaseListeners();
} catch (Exception e) { } catch (Exception e) {
RCLog.e(e); RCLog.e(e);
}
} }
}
/* package */ Maybe<DDPClientCallback.Base> ping(@Nullable final String id) {
public Maybe<DDPClientCallback.Base> ping(@Nullable final String id) {
final boolean requested = (TextUtils.isEmpty(id)) ?
final boolean requested = (TextUtils.isEmpty(id)) ? sendMessage("ping", null) :
sendMessage("ping", null) : sendMessage("ping", json -> json.put("id", id));
sendMessage("ping", json -> json.put("id", id));
if (requested) {
if (requested) { return flowable.filter(callback -> callback instanceof RxWebSocketCallback.Message)
return flowable.filter(callback -> callback instanceof RxWebSocketCallback.Message) .timeout(8, TimeUnit.SECONDS)
.timeout(8, TimeUnit.SECONDS) .map(callback -> ((RxWebSocketCallback.Message) callback).responseBodyString)
.map(callback -> ((RxWebSocketCallback.Message) callback).responseBodyString) .map(DDPClientImpl::toJson)
.map(DDPClientImpl::toJson) .filter(response -> "pong".equalsIgnoreCase(extractMsg(response)))
.filter(response -> "pong".equalsIgnoreCase(extractMsg(response))) .doOnError(error -> {
.doOnError(error -> { RCLog.e(error, "Heartbeat ping[%s] xxx failed xxx", id);
RCLog.e(error, "Heartbeat ping[%s] xxx failed xxx", id); })
}) .map(response -> {
.map(response -> { String msg = extractMsg(response);
String msg = extractMsg(response); if ("pong".equals(msg)) {
if ("pong".equals(msg)) { RCLog.d("pong[%s] <", id);
RCLog.d("pong[%s] <", id); if (response.isNull("id")) {
if (response.isNull("id")) { return new DDPClientCallback.Ping(client, null);
return new DDPClientCallback.Ping(client, null); } else {
} else { String _id = response.optString("id");
String _id = response.optString("id"); if (id.equals(_id)) {
if (id.equals(_id)) { return new DDPClientCallback.Ping(client, _id);
return new DDPClientCallback.Ping(client, _id); } else {
} else { return new DDPClientCallback.Ping.UnMatched(client, _id);
return new DDPClientCallback.Ping.UnMatched(client, _id); }
} }
} }
} // if we receive anything other than a pong throw an exception
// if we receive anything other than a pong throw an exception throw new DDPClientCallback.RPC.Error(client, id, response);
throw new DDPClientCallback.RPC.Error(client, id, response); }).firstElement();
}).firstElement(); } else {
} else { return Maybe.error(new DDPClientCallback.Closed(client));
return Maybe.error(new DDPClientCallback.Closed(client)); }
} }
}
/* package */void ping(final TaskCompletionSource<DDPClientCallback.Ping> task,
public void ping(final TaskCompletionSource<DDPClientCallback.Ping> task, @Nullable final String id) {
@Nullable final String id) {
final boolean requested = (TextUtils.isEmpty(id)) ?
final boolean requested = (TextUtils.isEmpty(id)) ? sendMessage("ping", null) :
sendMessage("ping", null) : sendMessage("ping", json -> json.put("id", id));
sendMessage("ping", json -> json.put("id", id));
if (requested) {
if (requested) { CompositeDisposable disposables = new CompositeDisposable();
CompositeDisposable disposables = new CompositeDisposable();
disposables.add(
disposables.add( flowable.filter(callback -> callback instanceof RxWebSocketCallback.Message)
flowable.filter(callback -> callback instanceof RxWebSocketCallback.Message) .timeout(8, TimeUnit.SECONDS)
.timeout(8, TimeUnit.SECONDS) .map(callback -> ((RxWebSocketCallback.Message) callback).responseBodyString)
.map(callback -> ((RxWebSocketCallback.Message) callback).responseBodyString) .map(DDPClientImpl::toJson)
.map(DDPClientImpl::toJson) .subscribe(
.subscribe( response -> {
response -> { String msg = extractMsg(response);
String msg = extractMsg(response); if ("pong".equals(msg)) {
if ("pong".equals(msg)) { if (response.isNull("id")) {
if (response.isNull("id")) { task.setResult(new DDPClientCallback.Ping(client, null));
task.setResult(new DDPClientCallback.Ping(client, null)); } else {
} else { String _id = response.optString("id");
String _id = response.optString("id"); if (id.equals(_id)) {
if (id.equals(_id)) { task.setResult(new DDPClientCallback.Ping(client, id));
task.setResult(new DDPClientCallback.Ping(client, id)); }
}
disposables.clear();
}
},
err -> {
if (err instanceof TimeoutException) {
task.trySetError(new Exception("Your connection seems off…"));
} else {
task.trySetError(new Exception("Ooops. Something's up!"));
}
} }
} )
disposables.clear(); );
}
}, addErrorCallback(disposables, task);
err -> task.trySetError(new DDPClientCallback.Ping.Timeout(client)) } else {
) task.trySetError(new DDPClientCallback.Closed(client));
); }
addErrorCallback(disposables, task);
} else {
task.trySetError(new DDPClientCallback.Closed(client));
} }
}
/* package */
public void sub(final TaskCompletionSource<DDPSubscription.Ready> task, String name,
JSONArray params, String id) { void sub(final TaskCompletionSource<DDPSubscription.Ready> task, String name,
final boolean requested = JSONArray params, String id) {
sendMessage("sub", json -> json.put("id", id).put("name", name).put("params", params)); final boolean requested =
sendMessage("sub", json -> json.put("id", id).put("name", name).put("params", params));
if (requested) {
CompositeDisposable disposables = new CompositeDisposable(); if (requested) {
CompositeDisposable disposables = new CompositeDisposable();
disposables.add(
flowable.filter(callback -> callback instanceof RxWebSocketCallback.Message) disposables.add(
.map(callback -> ((RxWebSocketCallback.Message) callback).responseBodyString) flowable.filter(callback -> callback instanceof RxWebSocketCallback.Message)
.map(DDPClientImpl::toJson) .map(callback -> ((RxWebSocketCallback.Message) callback).responseBodyString)
.subscribe( .map(DDPClientImpl::toJson)
response -> { .subscribe(
String msg = extractMsg(response); response -> {
if ("ready".equals(msg) && !response.isNull("subs")) { String msg = extractMsg(response);
JSONArray ids = response.optJSONArray("subs"); if ("ready".equals(msg) && !response.isNull("subs")) {
for (int i = 0; i < ids.length(); i++) { JSONArray ids = response.optJSONArray("subs");
String _id = ids.optString(i); for (int i = 0; i < ids.length(); i++) {
if (id.equals(_id)) { String _id = ids.optString(i);
task.setResult(new DDPSubscription.Ready(client, id)); if (id.equals(_id)) {
disposables.clear(); task.setResult(new DDPSubscription.Ready(client, id));
break; disposables.clear();
} break;
} }
} else if ("nosub".equals(msg) && !response.isNull("id") && !response.isNull( }
"error")) { } else if ("nosub".equals(msg) && !response.isNull("id") && !response.isNull(
String _id = response.optString("id"); "error")) {
if (id.equals(_id)) { String _id = response.optString("id");
task.trySetError(new DDPSubscription.NoSub.Error(client, id, if (id.equals(_id)) {
response.optJSONObject("error"))); task.trySetError(new DDPSubscription.NoSub.Error(client, id,
disposables.clear(); response.optJSONObject("error")));
} disposables.clear();
} }
}, }
RCLog::e },
) err -> {
); if (err instanceof TimeoutException) {
task.trySetError(new Exception("Your connection seems off…"));
addErrorCallback(disposables, task); } else {
} else { task.trySetError(new Exception("Ooops. Something's up!"));
task.trySetError(new DDPClientCallback.Closed(client)); }
}
)
);
addErrorCallback(disposables, task);
} else {
task.trySetError(new DDPClientCallback.Closed(client));
}
} }
}
public void unsub(final TaskCompletionSource<DDPSubscription.NoSub> task, /* package */ void unsub(final TaskCompletionSource<DDPSubscription.NoSub> task,
@Nullable final String id) { @Nullable final String id) {
final boolean requested = sendMessage("unsub", json -> json.put("id", id));
if (requested) {
CompositeDisposable disposables = new CompositeDisposable();
disposables.add(
flowable.filter(callback -> callback instanceof RxWebSocketCallback.Message)
.map(callback -> ((RxWebSocketCallback.Message) callback).responseBodyString)
.map(DDPClientImpl::toJson)
.subscribe(
response -> {
String msg = extractMsg(response);
if ("nosub".equals(msg) && response.isNull("error") && !response.isNull("id")) {
String _id = response.optString("id");
if (id.equals(_id)) {
task.setResult(new DDPSubscription.NoSub(client, id));
disposables.clear();
}
}
},
err -> {
if (err instanceof TimeoutException) {
task.trySetError(new Exception("Your connection seems off…"));
} else {
task.trySetError(new Exception("Ooops. Something's up!"));
}
}
)
);
final boolean requested = sendMessage("unsub", json -> json.put("id", id)); addErrorCallback(disposables, task);
} else {
task.trySetError(new DDPClientCallback.Closed(client));
}
}
if (requested) { /* package */ void rpc(final TaskCompletionSource<DDPClientCallback.RPC> task, String method,
CompositeDisposable disposables = new CompositeDisposable(); JSONArray params, String id, long timeoutMs) {
final boolean requested =
sendMessage("method",
json -> json.put("method", method).put("params", params).put("id", id));
if (requested) {
CompositeDisposable disposables = new CompositeDisposable();
disposables.add(
flowable.filter(callback -> callback instanceof RxWebSocketCallback.Message)
.map(callback -> ((RxWebSocketCallback.Message) callback).responseBodyString)
.map(DDPClientImpl::toJson)
.timeout(timeoutMs, TimeUnit.MILLISECONDS)
.subscribe(
response -> {
String msg = extractMsg(response);
if ("result".equals(msg)) {
String _id = response.optString("id");
if (id.equals(_id)) {
if (!response.isNull("error")) {
task.trySetError(new DDPClientCallback.RPC.Error(client, id,
response.optJSONObject("error")));
} else {
String result = response.optString("result");
task.setResult(new DDPClientCallback.RPC(client, id, result));
}
disposables.clear();
}
}
},
err -> {
if (err instanceof TimeoutException) {
task.trySetError(new Exception("Your connection seems off…"));
} else {
task.trySetError(new Exception("Ooops. Something's up!"));
}
}
)
);
disposables.add( addErrorCallback(disposables, task);
flowable.filter(callback -> callback instanceof RxWebSocketCallback.Message) } else {
.map(callback -> ((RxWebSocketCallback.Message) callback).responseBodyString) task.trySetError(new DDPClientCallback.Closed(client));
.map(DDPClientImpl::toJson) }
.subscribe( }
response -> {
String msg = extractMsg(response); private void subscribeBaseListeners() {
if ("nosub".equals(msg) && response.isNull("error") && !response.isNull("id")) { if (disposables != null &&
String _id = response.optString("id"); disposables.size() > 0 && !disposables.isDisposed()) {
if (id.equals(_id)) { return;
task.setResult(new DDPSubscription.NoSub(client, id)); }
disposables.clear();
} disposables = new CompositeDisposable();
} disposables.add(
}, flowable.filter(callback -> callback instanceof RxWebSocketCallback.Message)
err -> { .map(callback -> ((RxWebSocketCallback.Message) callback).responseBodyString)
} .map(DDPClientImpl::toJson)
) .subscribe(
); response -> {
String msg = extractMsg(response);
addErrorCallback(disposables, task); if ("ping".equals(msg)) {
} else { if (response.isNull("id")) {
task.trySetError(new DDPClientCallback.Closed(client)); sendMessage("pong", null);
} else {
sendMessage("pong", json -> json.put("id", response.getString("id")));
}
}
},
RCLog::e
)
);
} }
}
/* package */ Flowable<DDPSubscription.Event> getDDPSubscription() {
public void rpc(final TaskCompletionSource<DDPClientCallback.RPC> task, String method, String[] targetMsgs = {"added", "changed", "removed", "addedBefore", "movedBefore"};
JSONArray params, String id, long timeoutMs) { return flowable.filter(callback -> callback instanceof RxWebSocketCallback.Message)
final boolean requested = .map(callback -> ((RxWebSocketCallback.Message) callback).responseBodyString)
sendMessage("method", .map(DDPClientImpl::toJson)
json -> json.put("method", method).put("params", params).put("id", id)); .filter(response -> {
if (requested) {
CompositeDisposable disposables = new CompositeDisposable();
disposables.add(
flowable.filter(callback -> callback instanceof RxWebSocketCallback.Message)
.map(callback -> ((RxWebSocketCallback.Message) callback).responseBodyString)
.map(DDPClientImpl::toJson)
.timeout(timeoutMs, TimeUnit.MILLISECONDS)
.subscribe(
response -> {
String msg = extractMsg(response); String msg = extractMsg(response);
if ("result".equals(msg)) { for (String m : targetMsgs) {
String _id = response.optString("id"); if (m.equals(msg)) {
if (id.equals(_id)) { return true;
if (!response.isNull("error")) {
task.trySetError(new DDPClientCallback.RPC.Error(client, id,
response.optJSONObject("error")));
} else {
String result = response.optString("result");
task.setResult(new DDPClientCallback.RPC(client, id, result));
} }
disposables.clear();
}
} }
}, return false;
err -> { })
if (err instanceof TimeoutException) { .map(response -> {
task.trySetError(new DDPClientCallback.RPC.Timeout(client)); String msg = extractMsg(response);
if ("added".equals(msg)) {
return new DDPSubscription.Added(client, response.optString("collection"),
response.optString("id"),
response.isNull("fields") ? null : response.optJSONObject("fields"));
} else if ("addedBefore".equals(msg)) {
return new DDPSubscription.Added.Before(client, response.optString("collection"),
response.optString("id"),
response.isNull("fields") ? null : response.optJSONObject("fields"),
response.isNull("before") ? null : response.optString("before"));
} else if ("changed".equals(msg)) {
return new DDPSubscription.Changed(client, response.optString("collection"),
response.optString("id"),
response.isNull("fields") ? null : response.optJSONObject("fields"),
response.isNull("cleared") ? new JSONArray() : response.optJSONArray("before"));
} else if ("removed".equals(msg)) {
return new DDPSubscription.Removed(client, response.optString("collection"),
response.optString("id"));
} else if ("movedBefore".equals(msg)) {
return new DDPSubscription.MovedBefore(client, response.optString("collection"),
response.optString("id"),
response.isNull("before") ? null : response.optString("before"));
} }
}
)
);
addErrorCallback(disposables, task); return null;
} else { });
task.trySetError(new DDPClientCallback.Closed(client));
} }
}
private void subscribeBaseListeners() { /* package */ void unsubscribeBaseListeners() {
if (disposables != null && if (disposables.size() > 0 || !disposables.isDisposed()) {
disposables.size() > 0 && !disposables.isDisposed()) { disposables.clear();
return; }
} }
disposables = new CompositeDisposable(); /* package */ Task<RxWebSocketCallback.Close> getOnCloseCallback() {
disposables.add( TaskCompletionSource<RxWebSocketCallback.Close> task = new TaskCompletionSource<>();
flowable.filter(callback -> callback instanceof RxWebSocketCallback.Message)
.map(callback -> ((RxWebSocketCallback.Message) callback).responseBodyString) flowable.filter(callback -> callback instanceof RxWebSocketCallback.Close)
.map(DDPClientImpl::toJson) .cast(RxWebSocketCallback.Close.class)
.subscribe( .subscribe(
response -> { task::setResult,
String msg = extractMsg(response); err -> setTaskError(task, err)
if ("ping".equals(msg)) { );
if (response.isNull("id")) {
sendMessage("pong", null); return task.getTask().onSuccessTask(_task -> {
} else { unsubscribeBaseListeners();
sendMessage("pong", json -> json.put("id", response.getString("id"))); return _task;
}
}
},
RCLog::e
)
);
}
public Flowable<DDPSubscription.Event> getDDPSubscription() {
String[] targetMsgs = {"added", "changed", "removed", "addedBefore", "movedBefore"};
return flowable.filter(callback -> callback instanceof RxWebSocketCallback.Message)
.map(callback -> ((RxWebSocketCallback.Message) callback).responseBodyString)
.map(DDPClientImpl::toJson)
.filter(response -> {
String msg = extractMsg(response);
for (String m : targetMsgs) {
if (m.equals(msg)) {
return true;
}
}
return false;
})
.map(response -> {
String msg = extractMsg(response);
if ("added".equals(msg)) {
return new DDPSubscription.Added(client, response.optString("collection"),
response.optString("id"),
response.isNull("fields") ? null : response.optJSONObject("fields"));
} else if ("addedBefore".equals(msg)) {
return new DDPSubscription.Added.Before(client, response.optString("collection"),
response.optString("id"),
response.isNull("fields") ? null : response.optJSONObject("fields"),
response.isNull("before") ? null : response.optString("before"));
} else if ("changed".equals(msg)) {
return new DDPSubscription.Changed(client, response.optString("collection"),
response.optString("id"),
response.isNull("fields") ? null : response.optJSONObject("fields"),
response.isNull("cleared") ? new JSONArray() : response.optJSONArray("before"));
} else if ("removed".equals(msg)) {
return new DDPSubscription.Removed(client, response.optString("collection"),
response.optString("id"));
} else if ("movedBefore".equals(msg)) {
return new DDPSubscription.MovedBefore(client, response.optString("collection"),
response.optString("id"),
response.isNull("before") ? null : response.optString("before"));
}
return null;
}); });
} }
public void unsubscribeBaseListeners() { private boolean sendMessage(String msg, @Nullable JSONBuilder json) {
if (disposables.size() > 0 || !disposables.isDisposed()) { try {
disposables.clear(); JSONObject origJson = new JSONObject().put("msg", msg);
String msg2 = (json == null ? origJson : json.create(origJson)).toString();
return websocket.sendText(msg2);
} catch (Exception e) {
RCLog.e(e);
return false;
}
} }
}
public Task<RxWebSocketCallback.Close> getOnCloseCallback() { private void sendMessage(String msg, @Nullable JSONBuilder json,
TaskCompletionSource<RxWebSocketCallback.Close> task = new TaskCompletionSource<>(); TaskCompletionSource<?> taskForSetError) {
if (!sendMessage(msg, json) && taskForSetError != null) {
taskForSetError.trySetError(new DDPClientCallback.Closed(client));
}
}
flowable.filter(callback -> callback instanceof RxWebSocketCallback.Close) private void addErrorCallback(CompositeDisposable disposables, TaskCompletionSource<?> task) {
.cast(RxWebSocketCallback.Close.class) disposables.add(
.subscribe( flowable.subscribe(
task::setResult, base -> {
err -> setTaskError(task, err) if (base instanceof RxWebSocketCallback.Close) {
task.trySetError(new Exception(((RxWebSocketCallback.Close) base).reason));
}
},
err -> {
setTaskError(task, new Exception(err));
disposables.clear();
}
)
); );
return task.getTask().onSuccessTask(_task -> {
unsubscribeBaseListeners();
return _task;
});
}
private boolean sendMessage(String msg, @Nullable JSONBuilder json) {
try {
JSONObject origJson = new JSONObject().put("msg", msg);
String msg2 = (json == null ? origJson : json.create(origJson)).toString();
return websocket.sendText(msg2);
} catch (Exception e) {
RCLog.e(e);
return false;
} }
}
private void sendMessage(String msg, @Nullable JSONBuilder json, public void close(int code, String reason) {
TaskCompletionSource<?> taskForSetError) { try {
if (!sendMessage(msg, json) && taskForSetError != null) { websocket.close(code, reason);
taskForSetError.trySetError(new DDPClientCallback.Closed(client)); } catch (Exception e) {
} RCLog.e(e);
} }
private void addErrorCallback(CompositeDisposable disposables, TaskCompletionSource<?> task) {
disposables.add(
flowable.subscribe(
base -> {
if (base instanceof RxWebSocketCallback.Close) {
task.trySetError(new Exception(((RxWebSocketCallback.Close) base).reason));
}
},
err -> {
setTaskError(task, new Exception(err));
disposables.clear();
}
)
);
}
public void close(int code, String reason) {
try {
websocket.close(code, reason);
} catch (Exception e) {
RCLog.e(e);
} }
}
private void setTaskError(TaskCompletionSource task, Throwable throwable) { private void setTaskError(TaskCompletionSource task, Throwable throwable) {
if (task.getTask().isCompleted()) { if (task.getTask().isCompleted()) {
return; return;
}
if (throwable instanceof Exception) {
task.setError((Exception) throwable);
} else {
task.setError(new Exception(throwable));
}
} }
if (throwable instanceof Exception) {
task.setError((Exception) throwable);
} else {
task.setError(new Exception(throwable));
}
}
private interface JSONBuilder { private interface JSONBuilder {
@NonNull @NonNull
JSONObject create(JSONObject root) throws JSONException; JSONObject create(JSONObject root) throws JSONException;
} }
} }
...@@ -3,7 +3,8 @@ apply plugin: 'io.fabric' ...@@ -3,7 +3,8 @@ apply plugin: 'io.fabric'
repositories { repositories {
maven { url 'https://maven.fabric.io/public' } maven { url 'https://maven.fabric.io/public' }
maven { url 'https://github.com/uPhyca/stetho-realm/raw/master/maven-repo' } // maven { url 'https://github.com/uPhyca/stetho-realm/raw/master/maven-repo' }
maven { url 'https://github.com/WickeDev/stetho-realm/raw/master/maven-repo' }
} }
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
...@@ -20,8 +21,8 @@ android { ...@@ -20,8 +21,8 @@ android {
applicationId "chat.rocket.android" applicationId "chat.rocket.android"
minSdkVersion 16 minSdkVersion 16
targetSdkVersion rootProject.ext.targetSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 54 versionCode 58
versionName "1.0.31" versionName "1.1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
multiDexEnabled true multiDexEnabled true
...@@ -97,10 +98,9 @@ play { ...@@ -97,10 +98,9 @@ play {
track = "${track}" track = "${track}"
} }
ext { ext {
playLibVersion = '11.6.0'
stethoVersion = '1.5.0' stethoVersion = '1.5.0'
stethoOkhttp3Version = '1.5.0' stethoOkhttp3Version = '1.5.0'
stethoRealmVersion = '2.1.0' stethoRealmVersion = '2.2.2'
rxbindingVersion = '2.0.0' rxbindingVersion = '2.0.0'
rxlifecycleVersion = '2.1.0' rxlifecycleVersion = '2.1.0'
icepickVersion = '3.2.0' icepickVersion = '3.2.0'
...@@ -108,43 +108,49 @@ ext { ...@@ -108,43 +108,49 @@ ext {
} }
dependencies { dependencies {
compile project(':android-ddp') api project(':android-ddp')
compile project(':rocket-chat-android-widgets') api project(':rocket-chat-android-widgets')
compile project(':persistence-realm') api project(':persistence-realm')
compile extraDependencies.okHTTP implementation extraDependencies.okHTTP
compile extraDependencies.rxJava implementation extraDependencies.rxJava
compile extraDependencies.boltTask implementation extraDependencies.rxKotlin
compile supportDependencies.multidex implementation extraDependencies.boltTask
compile supportDependencies.designSupportLibrary implementation supportDependencies.multidex
compile supportDependencies.annotation implementation supportDependencies.designSupportLibrary
compile rxbindingDependencies.rxBinding implementation supportDependencies.annotation
compile rxbindingDependencies.rxBindingSupport implementation rxbindingDependencies.rxBinding
compile rxbindingDependencies.rxBindingAppcompact implementation rxbindingDependencies.rxBindingSupport
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$rootProject.ext.kotlinVersion" implementation rxbindingDependencies.rxBindingAppcompact
compile "com.google.firebase:firebase-core:$playLibVersion" api "org.jetbrains.kotlin:kotlin-stdlib-jre8:$rootProject.ext.kotlinVersion"
compile "com.google.firebase:firebase-crash:$playLibVersion" implementation "com.google.firebase:firebase-core:$rootProject.ext.playLibVersion"
compile "com.google.android.gms:play-services-gcm:$playLibVersion" implementation "com.google.firebase:firebase-crash:$rootProject.ext.playLibVersion"
compile "com.trello.rxlifecycle2:rxlifecycle:$rxlifecycleVersion" implementation "com.google.android.gms:play-services-gcm:$rootProject.ext.playLibVersion"
compile "com.trello.rxlifecycle2:rxlifecycle-android:$rxlifecycleVersion" implementation "com.trello.rxlifecycle2:rxlifecycle:$rxlifecycleVersion"
compile "com.trello.rxlifecycle2:rxlifecycle-components:$rxlifecycleVersion" implementation "com.trello.rxlifecycle2:rxlifecycle-android:$rxlifecycleVersion"
compile 'nl.littlerobots.rxlint:rxlint:1.2' implementation "com.trello.rxlifecycle2:rxlifecycle-components:$rxlifecycleVersion"
compile "frankiesardo:icepick:$icepickVersion" implementation 'nl.littlerobots.rxlint:rxlint:1.2'
implementation "frankiesardo:icepick:$icepickVersion"
annotationProcessor "frankiesardo:icepick-processor:$icepickVersion" annotationProcessor "frankiesardo:icepick-processor:$icepickVersion"
compile "com.github.hotchemi:permissionsdispatcher:$permissionsdispatcherVersion" implementation "com.github.hotchemi:permissionsdispatcher:$permissionsdispatcherVersion"
annotationProcessor "com.github.hotchemi:permissionsdispatcher-processor:$permissionsdispatcherVersion" annotationProcessor "com.github.hotchemi:permissionsdispatcher-processor:$permissionsdispatcherVersion"
compile('com.crashlytics.sdk.android:crashlytics:2.6.8@aar') { implementation('com.crashlytics.sdk.android:crashlytics:2.6.8@aar') {
transitive = true; transitive = true;
} }
debugCompile "com.facebook.stetho:stetho:$stethoVersion" implementation(extraDependencies.crouton) {
exclude group: 'com.android.support', module: 'support-v4'
}
implementation extraDependencies.androidJob
implementation extraDependencies.jstate
debugImplementation "com.facebook.stetho:stetho:$stethoVersion"
debugCompile "com.facebook.stetho:stetho-okhttp3:$stethoOkhttp3Version" debugCompile "com.facebook.stetho:stetho-okhttp3:$stethoOkhttp3Version"
debugCompile "com.uphyca:stetho_realm:$stethoRealmVersion" debugCompile "com.uphyca:stetho_realm:$stethoRealmVersion"
debugCompile "com.tspoon.traceur:traceur:1.0.1" debugCompile "com.tspoon.traceur:traceur:1.0.1"
testCompile 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
testCompile 'org.robolectric:robolectric:3.3' testImplementation 'org.robolectric:robolectric:3.3'
testCompile "org.jetbrains.kotlin:kotlin-test:$rootProject.ext.kotlinVersion" testImplementation "org.jetbrains.kotlin:kotlin-test:$rootProject.ext.kotlinVersion"
testCompile "org.jetbrains.kotlin:kotlin-test-junit:$rootProject.ext.kotlinVersion" testImplementation "org.jetbrains.kotlin:kotlin-test-junit:$rootProject.ext.kotlinVersion"
testCompile "org.jetbrains.kotlin:kotlin-reflect:$rootProject.ext.kotlinVersion" testImplementation "org.jetbrains.kotlin:kotlin-reflect:$rootProject.ext.kotlinVersion"
testCompile "com.nhaarman:mockito-kotlin:1.5.0" testImplementation "com.nhaarman:mockito-kotlin:1.5.0"
testCompile 'org.amshove.kluent:kluent:1.14' testImplementation 'org.amshove.kluent:kluent:1.14'
} }
apply plugin: 'com.google.gms.google-services' apply plugin: 'com.google.gms.google-services'
package chat.rocket.android.helper package chat.rocket.android.helper
import android.content.Context
import chat.rocket.android.RocketChatCache
import chat.rocket.android.api.rest.CookieInterceptor import chat.rocket.android.api.rest.CookieInterceptor
import chat.rocket.android.api.rest.DefaultCookieProvider import chat.rocket.android.api.rest.DefaultCookieProvider
import com.facebook.stetho.okhttp3.StethoInterceptor import com.facebook.stetho.okhttp3.StethoInterceptor
...@@ -24,17 +22,18 @@ object OkHttpHelper { ...@@ -24,17 +22,18 @@ object OkHttpHelper {
return httpClientForUploadFile ?: throw AssertionError("httpClientForUploadFile set to null by another thread") return httpClientForUploadFile ?: throw AssertionError("httpClientForUploadFile set to null by another thread")
} }
fun getClientForDownloadFile(context: Context): OkHttpClient { fun getClientForDownloadFile(): OkHttpClient {
if(httpClientForDownloadFile == null) { if (httpClientForDownloadFile == null) {
httpClientForDownloadFile = OkHttpClient.Builder() httpClientForDownloadFile = OkHttpClient.Builder()
.addNetworkInterceptor(StethoInterceptor()) .addNetworkInterceptor(StethoInterceptor())
.followRedirects(true) .followRedirects(true)
.followSslRedirects(true) .followSslRedirects(true)
.addInterceptor(CookieInterceptor(DefaultCookieProvider(RocketChatCache(context)))) .addInterceptor(CookieInterceptor(DefaultCookieProvider()))
.build() .build()
} }
return httpClientForDownloadFile ?: throw AssertionError("httpClientForDownloadFile set to null by another thread") return httpClientForDownloadFile ?: throw AssertionError("httpClientForDownloadFile set to null by another thread")
} }
/** /**
* Returns the OkHttpClient instance for WebSocket connection. * Returns the OkHttpClient instance for WebSocket connection.
* @return The OkHttpClient WebSocket connection instance. * @return The OkHttpClient WebSocket connection instance.
......
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="chat.rocket.android"> package="chat.rocket.android">
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WAKE_LOCK"/> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.VIBRATE"/> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.VIBRATE" />
<permission <permission
android:name="chat.rocket.android.permission.C2D_MESSAGE" android:name="chat.rocket.android.permission.C2D_MESSAGE"
android:protectionLevel="signature"/> android:protectionLevel="signature" />
<uses-permission android:name="chat.rocket.android.permission.C2D_MESSAGE"/> <uses-permission android:name="chat.rocket.android.permission.C2D_MESSAGE" />
<application <application
android:name=".RocketChatApplication" android:name=".RocketChatApplication"
...@@ -26,39 +27,40 @@ ...@@ -26,39 +27,40 @@
android:configChanges="orientation|screenSize" android:configChanges="orientation|screenSize"
android:windowSoftInputMode="adjustResize"> android:windowSoftInputMode="adjustResize">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity <activity
android:name=".activity.room.RoomActivity" android:name=".activity.room.RoomActivity"
android:configChanges="orientation|screenSize" android:configChanges="orientation|screenSize"
android:windowSoftInputMode="adjustResize"/> android:windowSoftInputMode="adjustResize" />
<activity <activity
android:name=".activity.AddServerActivity" android:name=".activity.AddServerActivity"
android:configChanges="orientation|screenSize" android:configChanges="orientation|screenSize"
android:windowSoftInputMode="adjustResize" android:launchMode="singleTop"
android:launchMode="singleTop"/> android:windowSoftInputMode="adjustResize" />
<activity <activity
android:name=".activity.LoginActivity" android:name=".activity.LoginActivity"
android:configChanges="orientation|screenSize" android:configChanges="orientation|screenSize"
android:windowSoftInputMode="adjustResize"/> android:windowSoftInputMode="adjustResize" />
<service android:name=".service.RocketChatService"/> <service android:name=".service.RocketChatService" />
<receiver <receiver
android:name="com.google.android.gms.gcm.GcmReceiver" android:name="com.google.android.gms.gcm.GcmReceiver"
android:exported="true" android:exported="true"
android:permission="com.google.android.c2dm.permission.SEND"> android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter> <intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE"/> <action android:name="com.google.android.c2dm.intent.RECEIVE" />
<action android:name="com.google.android.c2dm.intent.REGISTRATION"/> <action android:name="com.google.android.c2dm.intent.REGISTRATION" />
<category android:name="chat.rocket.android"/>
<category android:name="chat.rocket.android" />
</intent-filter> </intent-filter>
</receiver> </receiver>
...@@ -67,8 +69,8 @@ ...@@ -67,8 +69,8 @@
android:exported="true" android:exported="true"
android:permission="com.google.android.c2dm.permission.SEND"> android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter> <intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE"/> <action android:name="com.google.android.c2dm.intent.RECEIVE" />
<category android:name="chat.rocket.android"/> <category android:name="chat.rocket.android" />
</intent-filter> </intent-filter>
</receiver> </receiver>
...@@ -76,7 +78,8 @@ ...@@ -76,7 +78,8 @@
android:name="com.google.firebase.iid.FirebaseInstanceIdInternalReceiver" android:name="com.google.firebase.iid.FirebaseInstanceIdInternalReceiver"
android:exported="false" /> android:exported="false" />
<service android:name="com.google.firebase.iid.FirebaseInstanceIdService" <service
android:name="com.google.firebase.iid.FirebaseInstanceIdService"
android:exported="true"> android:exported="true">
<intent-filter android:priority="-500"> <intent-filter android:priority="-500">
<action android:name="com.google.firebase.INSTANCE_ID_EVENT" /> <action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
...@@ -87,7 +90,7 @@ ...@@ -87,7 +90,7 @@
android:name=".push.gcm.GCMIntentService" android:name=".push.gcm.GCMIntentService"
android:exported="false"> android:exported="false">
<intent-filter> <intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE"/> <action android:name="com.google.android.c2dm.intent.RECEIVE" />
</intent-filter> </intent-filter>
</service> </service>
...@@ -95,14 +98,16 @@ ...@@ -95,14 +98,16 @@
android:name=".push.gcm.GcmInstanceIDListenerService" android:name=".push.gcm.GcmInstanceIDListenerService"
android:exported="false"> android:exported="false">
<intent-filter> <intent-filter>
<action android:name="com.google.android.gms.iid.InstanceID"/> <action android:name="com.google.android.gms.iid.InstanceID" />
</intent-filter> </intent-filter>
</service> </service>
<receiver android:name=".push.PushManager$DeleteReceiver" <receiver
android:name=".push.PushManager$DeleteReceiver"
android:exported="false" /> android:exported="false" />
<receiver android:name=".push.PushManager$ReplyReceiver" <receiver
android:name=".push.PushManager$ReplyReceiver"
android:exported="false" /> android:exported="false" />
<meta-data <meta-data
......
package chat.rocket.android
import chat.rocket.android.extensions.printStackTraceOnDebug
import chat.rocket.android.service.KeepAliveJob
import unquietcode.tools.esm.EnumStateMachine
import unquietcode.tools.esm.TransitionException
object ConnectionStatusManager {
enum class State {
OFFLINE, CONNECTING, ONLINE
}
private const val DEBUG = false
private val DEFAULT_CALLBACK = object : TransitionCallback {
override fun onTransitioned(success: Boolean) {
}
}
private val stateMachine: EnumStateMachine<State>
init {
stateMachine = EnumStateMachine(State.OFFLINE)
stateMachine.addTransitions(State.OFFLINE, State.CONNECTING)
stateMachine.addTransitions(State.CONNECTING, State.ONLINE, State.OFFLINE)
stateMachine.addTransitions(State.ONLINE, State.OFFLINE)
}
@Synchronized
fun transitionCount() = stateMachine.transitionCount()
@Synchronized
fun currentState() = stateMachine.currentState()
@Synchronized
fun setOnline(callback: TransitionCallback = DEFAULT_CALLBACK) {
KeepAliveJob.cancelPendingJobRequests()
tryTransitionTo(State.ONLINE, callback)
}
@Synchronized
fun setOnline() {
KeepAliveJob.cancelPendingJobRequests()
tryTransitionTo(State.ONLINE, DEFAULT_CALLBACK)
}
@Synchronized
fun setConnecting(callback: TransitionCallback = DEFAULT_CALLBACK) {
KeepAliveJob.cancelPendingJobRequests()
tryTransitionTo(State.CONNECTING, callback)
}
@Synchronized
fun setConnecting() {
KeepAliveJob.cancelPendingJobRequests()
tryTransitionTo(State.CONNECTING, DEFAULT_CALLBACK)
}
@Synchronized
fun setConnectionError(callback: TransitionCallback = DEFAULT_CALLBACK) {
KeepAliveJob.schedule()
tryTransitionTo(State.OFFLINE, callback)
}
@Synchronized
fun setConnectionError() {
KeepAliveJob.schedule()
tryTransitionTo(State.OFFLINE, DEFAULT_CALLBACK)
}
@Synchronized
fun setOffline() {
stateMachine.reset()
}
private fun tryTransitionTo(newState: State, callback: TransitionCallback) {
try {
stateMachine.transition(newState)
callback.onTransitioned(true)
} catch (e: TransitionException) {
if (DEBUG) {
e.printStackTraceOnDebug()
}
callback.onTransitioned(false)
}
}
interface TransitionCallback {
fun onTransitioned(success: Boolean)
}
}
\ No newline at end of file
...@@ -12,31 +12,31 @@ import chat.rocket.android.activity.MainActivity; ...@@ -12,31 +12,31 @@ import chat.rocket.android.activity.MainActivity;
*/ */
public class LaunchUtil { public class LaunchUtil {
/** /**
* launch MainActivity with proper flags. * launch MainActivity with proper flags.
*/ */
public static void showMainActivity(Context context) { public static void showMainActivity(Context context) {
Intent intent = new Intent(context, MainActivity.class); Intent intent = new Intent(context, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
context.startActivity(intent); context.startActivity(intent);
} }
/** /**
* launch AddServerActivity with proper flags. * launch AddServerActivity with proper flags.
*/ */
public static void showAddServerActivity(Context context) { public static void showAddServerActivity(Context context) {
Intent intent = new Intent(context, AddServerActivity.class); Intent intent = new Intent(context, AddServerActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
context.startActivity(intent); context.startActivity(intent);
} }
/** /**
* launch ServerConfigActivity with proper flags. * launch ServerConfigActivity with proper flags.
*/ */
public static void showLoginActivity(Context context, String hostname) { public static void showLoginActivity(Context context, String hostname) {
Intent intent = new Intent(context, LoginActivity.class); Intent intent = new Intent(context, LoginActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent.putExtra(LoginActivity.KEY_HOSTNAME, hostname); intent.putExtra(LoginActivity.KEY_HOSTNAME, hostname);
context.startActivity(intent); context.startActivity(intent);
} }
} }
...@@ -5,6 +5,7 @@ import android.support.multidex.MultiDexApplication; ...@@ -5,6 +5,7 @@ import android.support.multidex.MultiDexApplication;
import android.support.v7.app.AppCompatDelegate; import android.support.v7.app.AppCompatDelegate;
import com.crashlytics.android.Crashlytics; import com.crashlytics.android.Crashlytics;
import com.evernote.android.job.JobManager;
import java.util.List; import java.util.List;
...@@ -34,6 +35,8 @@ public class RocketChatApplication extends MultiDexApplication { ...@@ -34,6 +35,8 @@ public class RocketChatApplication extends MultiDexApplication {
@Override @Override
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
RocketChatCache.INSTANCE.initialize(this);
JobManager.create(this).addJobCreator(new RocketChatJobCreator());
DDPClient.initialize(OkHttpHelper.INSTANCE.getClientForWebSocket()); DDPClient.initialize(OkHttpHelper.INSTANCE.getClientForWebSocket());
Fabric.with(this, new Crashlytics()); Fabric.with(this, new Crashlytics());
...@@ -44,7 +47,7 @@ public class RocketChatApplication extends MultiDexApplication { ...@@ -44,7 +47,7 @@ public class RocketChatApplication extends MultiDexApplication {
RealmStore.put(serverInfo.getHostname()); RealmStore.put(serverInfo.getHostname());
} }
RocketChatWidgets.initialize(this, OkHttpHelper.INSTANCE.getClientForDownloadFile(this)); RocketChatWidgets.initialize(this, OkHttpHelper.INSTANCE.getClientForDownloadFile());
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true); AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
...@@ -57,7 +60,7 @@ public class RocketChatApplication extends MultiDexApplication { ...@@ -57,7 +60,7 @@ public class RocketChatApplication extends MultiDexApplication {
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
e.printStackTrace(); e.printStackTrace();
} }
Logger.report(e); Logger.INSTANCE.report(e);
}); });
instance = this; instance = this;
......
package chat.rocket.android;
import android.content.Context;
import android.content.SharedPreferences;
import com.hadisatrio.optional.Optional;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import chat.rocket.android.helper.Logger;
import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.log.RCLog;
import chat.rocket.core.utils.Pair;
import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable;
import io.reactivex.annotations.NonNull;
import io.reactivex.annotations.Nullable;
import okhttp3.HttpUrl;
/**
* sharedpreference-based cache.
*/
public class RocketChatCache {
private static final String KEY_SELECTED_SERVER_HOSTNAME = "KEY_SELECTED_SERVER_HOSTNAME";
private static final String KEY_SELECTED_SITE_URL = "KEY_SELECTED_SITE_URL";
private static final String KEY_SELECTED_SITE_NAME = "KEY_SELECTED_SITE_NAME";
private static final String KEY_SELECTED_ROOM_ID = "KEY_SELECTED_ROOM_ID";
private static final String KEY_PUSH_ID = "KEY_PUSH_ID";
private static final String KEY_HOSTNAME_LIST = "KEY_HOSTNAME_LIST";
private Context context;
public RocketChatCache(Context context) {
this.context = context.getApplicationContext();
}
public String getSelectedServerHostname() {
return getString(KEY_SELECTED_SERVER_HOSTNAME, null);
}
public void setSelectedServerHostname(String hostname) {
String newHostname = null;
if (hostname != null) {
newHostname = hostname.toLowerCase();
}
setString(KEY_SELECTED_SERVER_HOSTNAME, newHostname);
}
public void addHostSiteName(@NonNull String currentHostname, @NonNull String siteName) {
try {
String hostSiteNamesJson = getHostSiteNamesJson();
JSONObject jsonObject = (hostSiteNamesJson == null) ?
new JSONObject() : new JSONObject(hostSiteNamesJson);
jsonObject.put(currentHostname, siteName);
setString(KEY_SELECTED_SITE_NAME, jsonObject.toString());
} catch (JSONException e) {
RCLog.e(e);
}
}
public @NonNull String getHostSiteName(@NonNull String host) {
if (host.startsWith("http")) {
HttpUrl url = HttpUrl.parse(host);
if (url != null) {
host = url.host();
}
}
try {
String hostSiteNamesJson = getHostSiteNamesJson();
JSONObject jsonObject = (hostSiteNamesJson == null) ?
new JSONObject() : new JSONObject(hostSiteNamesJson);
host = getSiteUrlFor(host);
return jsonObject.optString(host);
} catch (JSONException e) {
RCLog.e(e);
}
return "";
}
private @Nullable String getHostSiteNamesJson() {
return getString(KEY_SELECTED_SITE_NAME, null);
}
public void addHostnameSiteUrl(@Nullable String hostnameAlias, @NonNull String currentHostname) {
String alias = null;
if (hostnameAlias != null) {
alias = hostnameAlias.toLowerCase();
}
try {
String selectedHostnameAliasJson = getLoginHostnamesJson();
JSONObject jsonObject = selectedHostnameAliasJson == null ?
new JSONObject() : new JSONObject(selectedHostnameAliasJson);
jsonObject.put(alias, currentHostname);
setString(KEY_SELECTED_SITE_URL, jsonObject.toString());
} catch (JSONException e) {
RCLog.e(e);
}
}
public @Nullable String getSiteUrlFor(String hostname) {
try {
String selectedServerHostname = getSelectedServerHostname();
if (getLoginHostnamesJson() == null || getLoginHostnamesJson().isEmpty()) {
return null;
}
return new JSONObject(getLoginHostnamesJson())
.optString(hostname, selectedServerHostname);
} catch (JSONException e) {
RCLog.e(e);
}
return null;
}
private @Nullable String getLoginHostnamesJson() {
return getString(KEY_SELECTED_SITE_URL, null);
}
public void addHostname(@NonNull String hostname, @Nullable String hostnameAvatarUri, String siteName) {
String hostnameList = getString(KEY_HOSTNAME_LIST, null);
try {
JSONObject json;
if (hostnameList == null) {
json = new JSONObject();
} else {
json = new JSONObject(hostnameList);
}
JSONObject serverInfoJson = new JSONObject();
serverInfoJson.put("avatar", hostnameAvatarUri);
serverInfoJson.put("sitename", siteName);
// Replace server avatar uri if exists.
json.put(hostname, hostnameAvatarUri == null ? JSONObject.NULL : serverInfoJson);
setString(KEY_HOSTNAME_LIST, json.toString());
} catch (JSONException e) {
RCLog.e(e);
}
}
public List<Pair<String, Pair<String, String>>> getServerList() {
String json = getString(KEY_HOSTNAME_LIST, null);
if (json == null) {
return Collections.emptyList();
}
try {
JSONObject jsonObj = new JSONObject(json);
List<Pair<String, Pair<String, String>>> serverList = new ArrayList<>();
for (Iterator<String> iter = jsonObj.keys(); iter.hasNext();) {
String hostname = iter.next();
JSONObject serverInfoJson = jsonObj.getJSONObject(hostname);
serverList.add(new Pair<>(hostname, new Pair<>(
"http://" + hostname + "/" + serverInfoJson.getString("avatar"),
serverInfoJson.getString("sitename"))));
}
return serverList;
} catch (JSONException e) {
RCLog.e(e);
}
return Collections.emptyList();
}
public void removeHostname(String hostname) {
String json = getString(KEY_HOSTNAME_LIST, null);
if (TextUtils.isEmpty(json)) {
return;
}
try {
JSONObject jsonObj = new JSONObject(json);
jsonObj.remove(hostname);
String result = jsonObj.length() == 0 ? null : jsonObj.toString();
setString(KEY_HOSTNAME_LIST, result);
} catch (JSONException e) {
RCLog.e(e);
}
}
@Nullable
public String getFirstLoggedHostnameIfAny() {
String json = getString(KEY_HOSTNAME_LIST, null);
if (json != null) {
try {
JSONObject jsonObj = new JSONObject(json);
if (jsonObj.length() > 0 && jsonObj.keys().hasNext()) {
// Returns the first hostname on the list.
return jsonObj.keys().next();
}
} catch (JSONException e) {
RCLog.e(e);
}
}
return null;
}
public String getSelectedRoomId() {
try {
JSONObject jsonObject = getSelectedRoomIdJsonObject();
return jsonObject.optString(getSelectedServerHostname(), null);
} catch (JSONException e) {
RCLog.e(e);
Logger.report(e);
}
return null;
}
public void setSelectedRoomId(String roomId) {
try {
JSONObject jsonObject = getSelectedRoomIdJsonObject();
jsonObject.put(getSelectedServerHostname(), roomId);
setString(KEY_SELECTED_ROOM_ID, jsonObject.toString());
} catch (JSONException e) {
RCLog.e(e);
Logger.report(e);
}
}
private JSONObject getSelectedRoomIdJsonObject() throws JSONException {
String json = getString(KEY_SELECTED_ROOM_ID, null);
if (json == null) {
return new JSONObject();
}
return new JSONObject(json);
}
public String getOrCreatePushId() {
SharedPreferences preferences = getSharedPreferences();
if (!preferences.contains(KEY_PUSH_ID)) {
// generates one and save
String newId = UUID.randomUUID().toString().replace("-", "");
preferences.edit()
.putString(KEY_PUSH_ID, newId)
.apply();
return newId;
}
return preferences.getString(KEY_PUSH_ID, null);
}
public Flowable<Optional<String>> getSelectedServerHostnamePublisher() {
return getValuePublisher(KEY_SELECTED_SERVER_HOSTNAME);
}
public Flowable<Optional<String>> getSelectedRoomIdPublisher() {
return getValuePublisher(KEY_SELECTED_ROOM_ID)
.filter(Optional::isPresent)
.map(Optional::get)
.map(roomValue -> Optional.ofNullable(new JSONObject(roomValue).optString(getSelectedServerHostname(), null)));
}
private SharedPreferences getSharedPreferences() {
return context.getSharedPreferences("cache", Context.MODE_PRIVATE);
}
private SharedPreferences.Editor getEditor() {
return getSharedPreferences().edit();
}
public String getString(String key, String defaultValue) {
return getSharedPreferences().getString(key, defaultValue);
}
private void setString(String key, String value) {
getEditor().putString(key, value).apply();
}
private Flowable<Optional<String>> getValuePublisher(final String key) {
return Flowable.create(emitter -> {
SharedPreferences.OnSharedPreferenceChangeListener
listener = (sharedPreferences, changedKey) -> {
if (key.equals(changedKey) && !emitter.isCancelled()) {
String value = getString(key, null);
emitter.onNext(Optional.ofNullable(value));
}
};
emitter.setCancellable(() -> getSharedPreferences()
.unregisterOnSharedPreferenceChangeListener(listener));
getSharedPreferences().registerOnSharedPreferenceChangeListener(listener);
}, BackpressureStrategy.LATEST);
}
public void removeSelectedRoomId(String currentHostname) {
try {
JSONObject selectedRoomIdJsonObject = getSelectedRoomIdJsonObject();
selectedRoomIdJsonObject.remove(currentHostname);
String result = selectedRoomIdJsonObject.length() == 0 ?
null : selectedRoomIdJsonObject.toString();
setString(KEY_SELECTED_ROOM_ID, result);
} catch (JSONException e) {
Logger.report(e);
RCLog.e(e);
}
}
}
package chat.rocket.android
import android.content.Context
import android.content.SharedPreferences
import android.text.TextUtils
import chat.rocket.android.helper.Logger
import chat.rocket.android.log.RCLog
import chat.rocket.core.utils.Pair
import com.hadisatrio.optional.Optional
import io.reactivex.BackpressureStrategy
import io.reactivex.Flowable
import okhttp3.HttpUrl
import org.json.JSONException
import org.json.JSONObject
import java.util.*
object RocketChatCache {
private val KEY_SELECTED_SERVER_HOSTNAME = "KEY_SELECTED_SERVER_HOSTNAME"
private val KEY_SELECTED_SITE_URL = "KEY_SELECTED_SITE_URL"
private val KEY_SELECTED_SITE_NAME = "KEY_SELECTED_SITE_NAME"
private val KEY_SELECTED_ROOM_ID = "KEY_SELECTED_ROOM_ID"
private val KEY_PUSH_ID = "KEY_PUSH_ID"
private val KEY_HOSTNAME_LIST = "KEY_HOSTNAME_LIST"
private val KEY_OPENED_ROOMS = "KEY_OPENED_ROOMS"
private val KEY_SESSION_TOKEN = "KEY_SESSION_TOKEN"
private val KEY_USER_ID = "KEY_USER_ID"
private val KEY_USER_NAME = "KEY_USER_NAME"
private val KEY_USER_USERNAME = "KEY_USER_USERNAME"
private lateinit var sharedPreferences: SharedPreferences
fun initialize(context: Context) {
sharedPreferences = context.getSharedPreferences("cache", Context.MODE_PRIVATE)
}
fun addOpenedRoom(roomId: String, lastSeen: Long) {
val openedRooms = getOpenedRooms()
try {
val room = JSONObject().put("rid", roomId).put("ls", lastSeen)
openedRooms.put(roomId, room)
} catch (e: JSONException) {
RCLog.e(e)
}
setString(KEY_OPENED_ROOMS, openedRooms.toString())
}
fun removeOpenedRoom(roomId: String) {
val openedRooms = getOpenedRooms()
if (openedRooms.has(roomId)) {
openedRooms.remove(roomId)
}
}
fun getOpenedRooms(): JSONObject {
val openedRooms = getString(KEY_OPENED_ROOMS, "")
openedRooms?.let {
if (openedRooms.isEmpty()) {
return JSONObject()
}
try {
return JSONObject(openedRooms)
} catch (e: JSONException) {
RCLog.e(e)
}
}
return JSONObject()
}
fun getSelectedServerHostname(): String? {
return getString(KEY_SELECTED_SERVER_HOSTNAME, null)
}
fun setSelectedRoomId(roomId: String?) {
try {
val jsonObject = getSelectedRoomIdJsonObject()
jsonObject.put(getSelectedServerHostname(), roomId)
setString(KEY_SELECTED_ROOM_ID, jsonObject.toString())
} catch (e: JSONException) {
RCLog.e(e)
Logger.report(e)
}
}
@Throws(JSONException::class)
private fun getSelectedRoomIdJsonObject(): JSONObject {
val json = getString(KEY_SELECTED_ROOM_ID, null) ?: return JSONObject()
return JSONObject(json)
}
fun getOrCreatePushId(): String? {
val preferences = sharedPreferences
if (!preferences.contains(KEY_PUSH_ID)) {
// generates one and save
val newId = UUID.randomUUID().toString().replace("-", "")
preferences.edit()
.putString(KEY_PUSH_ID, newId)
.apply()
return newId
}
return preferences.getString(KEY_PUSH_ID, null)
}
fun addSiteName(currentHostname: String, siteName: String) {
try {
val hostSiteNamesJson = getSiteName()
val jsonObject = if (hostSiteNamesJson == null)
JSONObject()
else
JSONObject(hostSiteNamesJson)
jsonObject.put(currentHostname, siteName)
setString(KEY_SELECTED_SITE_NAME, jsonObject.toString())
} catch (e: JSONException) {
RCLog.e(e)
}
}
fun getHostSiteName(hostname: String): String {
var host = hostname
if (hostname.startsWith("http")) {
val url = HttpUrl.parse(hostname)
if (url != null) {
host = url.host()
}
}
try {
val hostSiteNamesJson = getSiteName()
val jsonObject = if (hostSiteNamesJson == null)
JSONObject()
else
JSONObject(hostSiteNamesJson)
val siteUrlFor = getSiteUrlFor(host)
return if (siteUrlFor == null) "" else jsonObject.optString(host)
} catch (e: JSONException) {
RCLog.e(e)
}
return ""
}
fun removeSiteName(hostname: String) {
try {
val siteNameJson = getSiteName()
val jsonObject = if (siteNameJson == null)
JSONObject()
else
JSONObject(siteNameJson)
if (jsonObject.has(hostname)) {
jsonObject.remove(hostname)
}
setString(KEY_SELECTED_SITE_NAME, jsonObject.toString())
} catch (e: JSONException) {
RCLog.e(e)
}
}
fun addSiteUrl(hostnameAlias: String?, currentHostname: String) {
var alias: String? = null
if (hostnameAlias != null) {
alias = hostnameAlias.toLowerCase()
}
try {
val selectedHostnameAliasJson = getSiteUrlForAllServers()
val jsonObject = if (selectedHostnameAliasJson == null)
JSONObject()
else
JSONObject(selectedHostnameAliasJson)
jsonObject.put(alias, currentHostname)
setString(KEY_SELECTED_SITE_URL, jsonObject.toString())
} catch (e: JSONException) {
RCLog.e(e)
}
}
fun getSiteUrlFor(hostname: String): String? {
try {
val selectedServerHostname = getSelectedServerHostname()
return if (getSiteUrlForAllServers() == null) null else JSONObject(getSiteUrlForAllServers())
.optString(hostname, selectedServerHostname)
} catch (e: JSONException) {
RCLog.e(e)
}
return null
}
fun addHostname(hostname: String, hostnameAvatarUri: String?, siteName: String) {
val hostnameList = getString(KEY_HOSTNAME_LIST, null)
try {
val json: JSONObject
if (hostnameList == null) {
json = JSONObject()
} else {
json = JSONObject(hostnameList)
}
val serverInfoJson = JSONObject()
serverInfoJson.put("avatar", hostnameAvatarUri)
serverInfoJson.put("sitename", siteName)
// Replace server avatar uri if exists.
json.put(hostname, if (hostnameAvatarUri == null) JSONObject.NULL else serverInfoJson)
setString(KEY_HOSTNAME_LIST, json.toString())
} catch (e: JSONException) {
RCLog.e(e)
}
}
fun getServerList(): List<Pair<String, Pair<String, String>>> {
val json = getString(KEY_HOSTNAME_LIST, null) ?: return emptyList()
try {
val jsonObj = JSONObject(json)
val serverList = ArrayList<Pair<String, Pair<String, String>>>()
val iter = jsonObj.keys()
while (iter.hasNext()) {
val hostname = iter.next()
val serverInfoJson = jsonObj.getJSONObject(hostname)
serverList.add(Pair(hostname, Pair(
"http://" + hostname + "/" + serverInfoJson.getString("avatar"),
serverInfoJson.getString("sitename"))))
}
return serverList
} catch (e: JSONException) {
RCLog.e(e)
}
return emptyList()
}
/**
* Wipe all given hostname entries and references from cache.
*/
fun clearSelectedHostnameReferences() {
val hostname = getSelectedServerHostname()
if (hostname != null) {
setString(KEY_OPENED_ROOMS, null)
removeSiteName(hostname)
removeHostname(hostname)
removeSiteUrl(hostname)
setSelectedServerHostname(getFirstLoggedHostnameIfAny())
}
}
fun removeHostname(hostname: String) {
val json = getString(KEY_HOSTNAME_LIST, null)
if (TextUtils.isEmpty(json)) {
return
}
try {
val jsonObj = JSONObject(json)
jsonObj.remove(hostname)
val result = if (jsonObj.length() == 0) null else jsonObj.toString()
setString(KEY_HOSTNAME_LIST, result)
} catch (e: JSONException) {
RCLog.e(e)
}
}
fun setSelectedServerHostname(hostname: String?) {
var newHostname: String? = null
if (hostname != null) {
newHostname = hostname.toLowerCase()
}
setString(KEY_SELECTED_SERVER_HOSTNAME, newHostname)
}
fun getSelectedRoomId(): String? {
try {
val jsonObject = getSelectedRoomIdJsonObject()
return jsonObject.optString(getSelectedServerHostname(), null)
} catch (e: JSONException) {
RCLog.e(e)
Logger.report(e)
}
return null
}
fun removeSelectedRoomId(currentHostname: String) {
try {
val selectedRoomIdJsonObject = getSelectedRoomIdJsonObject()
selectedRoomIdJsonObject.remove(currentHostname)
val result = if (selectedRoomIdJsonObject.length() == 0) null else selectedRoomIdJsonObject.toString()
setString(KEY_SELECTED_ROOM_ID, result)
} catch (e: JSONException) {
Logger.report(e)
RCLog.e(e)
}
}
fun getFirstLoggedHostnameIfAny(): String? {
val json = getString(KEY_HOSTNAME_LIST, null)
if (json != null) {
try {
val jsonObj = JSONObject(json)
if (jsonObj.length() > 0 && jsonObj.keys().hasNext()) {
// Returns the first hostname on the list.
return jsonObj.keys().next()
}
} catch (e: JSONException) {
RCLog.e(e)
}
}
return null
}
fun setSessionToken(sessionToken: String) {
val selectedServerHostname = getSelectedServerHostname() ?:
throw IllegalStateException("Trying to set sessionToken to null hostname")
val sessions = getString(KEY_SESSION_TOKEN, null)
try {
val jsonObject = if (sessions == null) JSONObject() else JSONObject(sessions)
jsonObject.put(selectedServerHostname, sessionToken)
setString(KEY_SESSION_TOKEN, jsonObject.toString())
} catch (e: JSONException) {
RCLog.e(e)
}
}
fun getSessionToken(): String? {
val selectedServerHostname = getSelectedServerHostname()
val sessions = getString(KEY_SESSION_TOKEN, null)
if (sessions == null || selectedServerHostname == null) {
return null
}
try {
val jsonObject = JSONObject(sessions)
if (jsonObject.has(selectedServerHostname)) {
return jsonObject.optString(selectedServerHostname, null)
}
} catch (e: JSONException) {
RCLog.e(e)
}
return null
}
private fun removeSiteUrl(hostname: String) {
try {
val siteUrlForAllServersJson = getSiteUrlForAllServers()
val jsonObject = if (siteUrlForAllServersJson == null)
JSONObject()
else
JSONObject(siteUrlForAllServersJson)
val keys = jsonObject.keys()
while (keys.hasNext()) {
val alias = keys.next()
if (hostname == jsonObject.getString(alias)) {
jsonObject.remove(alias)
break
}
}
setString(KEY_SELECTED_SITE_URL, jsonObject.toString())
} catch (e: JSONException) {
RCLog.e(e)
}
}
private fun getString(key: String, defaultValue: String?): String? {
return sharedPreferences.getString(key, defaultValue)
}
private fun getSiteUrlForAllServers(): String? {
return getString(KEY_SELECTED_SITE_URL, null)
}
private fun setString(key: String, value: String?) {
getEditor().putString(key, value).apply()
}
private fun getSiteName(): String? {
return getString(KEY_SELECTED_SITE_NAME, null)
}
private fun getEditor(): SharedPreferences.Editor {
return sharedPreferences.edit()
}
fun getSelectedServerHostnamePublisher(): Flowable<Optional<String>> {
return getValuePublisher(KEY_SELECTED_SERVER_HOSTNAME)
}
fun getSelectedRoomIdPublisher(): Flowable<Optional<String>> {
return getValuePublisher(KEY_SELECTED_ROOM_ID)
.filter { it.isPresent() }
.map { it.get() }
.map { roomValue -> Optional.ofNullable(JSONObject(roomValue).optString(getSelectedServerHostname(), null)) }
}
private fun getValuePublisher(key: String): Flowable<Optional<String>> {
return Flowable.create({ emitter ->
val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, changedKey ->
if (key == changedKey && !emitter.isCancelled) {
val value = getString(key, null)
emitter.onNext(Optional.ofNullable(value))
}
}
emitter.setCancellable {
sharedPreferences.unregisterOnSharedPreferenceChangeListener(listener)
}
sharedPreferences.registerOnSharedPreferenceChangeListener(listener)
}, BackpressureStrategy.LATEST)
}
fun setUserId(userId: String?) {
userId?.let {
setString(KEY_USER_ID, userId)
}
}
fun getUserId(): String? = getString(KEY_USER_ID, null)
fun setUserName(name: String?) {
name?.let {
setString(KEY_USER_NAME, name)
}
}
fun getUserName(): String? = getString(KEY_USER_NAME, null)
fun setUserUsername(username: String?) {
username?.let {
setString(KEY_USER_USERNAME, username)
}
}
fun getUserUsername(): String? = getString(KEY_USER_USERNAME, null)
}
\ No newline at end of file
package chat.rocket.android
import chat.rocket.android.service.KeepAliveJob
import com.evernote.android.job.Job
import com.evernote.android.job.JobCreator
class RocketChatJobCreator : JobCreator {
override fun create(tag: String): Job? {
when (tag) {
KeepAliveJob.TAG -> return KeepAliveJob()
else -> return null
}
}
}
\ No newline at end of file
...@@ -24,207 +24,206 @@ import io.reactivex.schedulers.Schedulers; ...@@ -24,207 +24,206 @@ import io.reactivex.schedulers.Schedulers;
import okhttp3.HttpUrl; import okhttp3.HttpUrl;
abstract class AbstractAuthedActivity extends AbstractFragmentActivity { abstract class AbstractAuthedActivity extends AbstractFragmentActivity {
@State protected String hostname; @State
@State protected String roomId; protected String hostname;
@State
protected String roomId;
private RocketChatCache rocketChatCache; private CompositeDisposable compositeDisposable = new CompositeDisposable();
private CompositeDisposable compositeDisposable = new CompositeDisposable(); private boolean isNotification;
private boolean isNotification;
@Override @Override
protected void onCreate(@Nullable Bundle savedInstanceState) { protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
rocketChatCache = new RocketChatCache(this); if (savedInstanceState == null) {
handleIntent(getIntent());
}
updateHostnameIfNeeded(RocketChatCache.INSTANCE.getSelectedServerHostname());
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
handleIntent(intent);
}
private void handleIntent(Intent intent) {
if (intent == null) {
return;
}
if (intent.hasExtra(PushManager.EXTRA_HOSTNAME)) {
String hostname = intent.getStringExtra(PushManager.EXTRA_HOSTNAME);
HttpUrl url = HttpUrl.parse(hostname);
if (url != null) {
String hostnameFromPush = url.host();
String loginHostname = RocketChatCache.INSTANCE.getSiteUrlFor(hostnameFromPush);
RocketChatCache.INSTANCE.setSelectedServerHostname(loginHostname);
if (intent.hasExtra(PushManager.EXTRA_ROOM_ID)) {
RocketChatCache.INSTANCE.setSelectedRoomId(intent.getStringExtra(PushManager.EXTRA_ROOM_ID));
}
}
PushManager.INSTANCE.clearNotificationsByHost(hostname);
} else {
updateHostnameIfNeeded(RocketChatCache.INSTANCE.getSelectedServerHostname());
}
if (intent.hasExtra(PushManager.EXTRA_NOT_ID) && intent.hasExtra(PushManager.EXTRA_HOSTNAME)) {
isNotification = true;
int notificationId = intent.getIntExtra(PushManager.EXTRA_NOT_ID, 0);
String hostname = intent.getStringExtra(PushManager.EXTRA_HOSTNAME);
HttpUrl url = HttpUrl.parse(hostname);
if (url != null) {
String hostnameFromPush = url.host();
String loginHostname = RocketChatCache.INSTANCE.getSiteUrlFor(hostnameFromPush);
PushManager.INSTANCE.clearNotificationsByHostAndNotificationId(loginHostname, notificationId);
} else {
PushManager.INSTANCE.clearNotificationsByNotificationId(notificationId);
}
}
}
private void updateHostnameIfNeeded(String newHostname) {
if (hostname == null) {
if (newHostname != null && assertServerRealmStoreExists(newHostname)) {
updateHostname(newHostname);
updateRoomIdIfNeeded(RocketChatCache.INSTANCE.getSelectedRoomId());
} else {
recoverFromHostnameError();
}
} else {
if (hostname.equals(newHostname)) {
updateHostname(newHostname);
updateRoomIdIfNeeded(RocketChatCache.INSTANCE.getSelectedRoomId());
return;
}
if (assertServerRealmStoreExists(newHostname)) {
Intent intent = new Intent(this, MainActivity.class);
startActivity(intent);
finish();
overridePendingTransition(R.anim.slide_in, R.anim.slide_out);
} else {
recoverFromHostnameError();
}
}
}
private boolean assertServerRealmStoreExists(String hostname) {
return RealmStore.get(hostname) != null;
}
private void updateHostname(String hostname) {
this.hostname = hostname;
onHostnameUpdated();
}
private void recoverFromHostnameError() {
final List<ServerInfo> serverInfoList =
ConnectivityManager.getInstance(getApplicationContext()).getServerList();
if (serverInfoList == null || serverInfoList.size() == 0) {
LaunchUtil.showAddServerActivity(this);
return;
}
// just connect to the first available
final ServerInfo serverInfo = serverInfoList.get(0);
if (savedInstanceState == null) { RocketChatCache.INSTANCE.setSelectedServerHostname(serverInfo.getHostname());
handleIntent(getIntent()); RocketChatCache.INSTANCE.setSelectedRoomId(null);
} }
updateHostnameIfNeeded(rocketChatCache.getSelectedServerHostname()); private void updateRoomIdIfNeeded(String newRoomId) {
} if (roomId == null) {
if (newRoomId != null && assertRoomSubscriptionExists(newRoomId)) {
updateRoomId(newRoomId);
}
} else {
if (!roomId.equals(newRoomId) && assertRoomSubscriptionExists(newRoomId)) {
updateRoomId(newRoomId);
}
}
}
@Override private boolean assertRoomSubscriptionExists(String roomId) {
protected void onNewIntent(Intent intent) { if (!assertServerRealmStoreExists(hostname)) {
super.onNewIntent(intent); return false;
handleIntent(intent); }
}
private void handleIntent(Intent intent) { RealmRoom room = RealmStore.get(hostname).executeTransactionForRead(realm ->
if (intent == null) { realm.where(RealmRoom.class).equalTo(RealmRoom.ROOM_ID, roomId).findFirst());
return; if (room == null) {
RocketChatCache.INSTANCE.setSelectedRoomId(null);
return false;
}
return true;
} }
if (intent.hasExtra(PushManager.EXTRA_HOSTNAME)) { private void updateRoomId(String roomId) {
String hostname = intent.getStringExtra(PushManager.EXTRA_HOSTNAME); this.roomId = roomId;
HttpUrl url = HttpUrl.parse(hostname); onRoomIdUpdated();
if (url != null) { }
String hostnameFromPush = url.host();
String loginHostname = rocketChatCache.getSiteUrlFor(hostnameFromPush); protected void onHostnameUpdated() {
rocketChatCache.setSelectedServerHostname(loginHostname); }
protected void onRoomIdUpdated() {
}
if (intent.hasExtra(PushManager.EXTRA_ROOM_ID)) { @Override
rocketChatCache.setSelectedRoomId(intent.getStringExtra(PushManager.EXTRA_ROOM_ID)); protected void onResume() {
super.onResume();
subscribeToConfigChanges();
ConnectivityManager.getInstance(getApplicationContext()).keepAliveServer();
if (isNotification) {
updateHostnameIfNeeded(RocketChatCache.INSTANCE.getSelectedServerHostname());
updateRoomIdIfNeeded(RocketChatCache.INSTANCE.getSelectedRoomId());
isNotification = false;
} }
} }
PushManager.INSTANCE.clearNotificationsByHost(hostname);
} else { @Override
updateHostnameIfNeeded(rocketChatCache.getSelectedServerHostname()); protected void onPause() {
} compositeDisposable.clear();
if (intent.hasExtra(PushManager.EXTRA_NOT_ID) && intent.hasExtra(PushManager.EXTRA_HOSTNAME)) { super.onPause();
isNotification = true; }
int notificationId = intent.getIntExtra(PushManager.EXTRA_NOT_ID, 0);
String hostname = intent.getStringExtra(PushManager.EXTRA_HOSTNAME); @Override
HttpUrl url = HttpUrl.parse(hostname); protected void onSaveInstanceState(Bundle outState) {
if (url != null) { super.onSaveInstanceState(outState);
String hostnameFromPush = url.host(); }
String loginHostname = rocketChatCache.getSiteUrlFor(hostnameFromPush);
PushManager.INSTANCE.clearNotificationsByHostAndNotificationId(loginHostname, notificationId); private void subscribeToConfigChanges() {
} else { compositeDisposable.add(
PushManager.INSTANCE.clearNotificationsByNotificationId(notificationId); RocketChatCache.INSTANCE.getSelectedServerHostnamePublisher()
} .map(Optional::get)
.distinctUntilChanged()
} .subscribeOn(Schedulers.io())
} .observeOn(AndroidSchedulers.mainThread())
.subscribe(
private void updateHostnameIfNeeded(String newHostname) { this::updateHostnameIfNeeded,
if (hostname == null) { Logger.INSTANCE::report
if (newHostname != null && assertServerRealmStoreExists(newHostname)) { )
updateHostname(newHostname); );
updateRoomIdIfNeeded(rocketChatCache.getSelectedRoomId());
} else { compositeDisposable.add(
recoverFromHostnameError(); RocketChatCache.INSTANCE.getSelectedRoomIdPublisher()
} .filter(Optional::isPresent)
} else { .map(Optional::get)
if (hostname.equals(newHostname)) { .subscribeOn(Schedulers.io())
updateHostname(newHostname); .observeOn(AndroidSchedulers.mainThread())
updateRoomIdIfNeeded(rocketChatCache.getSelectedRoomId()); .subscribe(
return; this::updateRoomIdIfNeeded,
} Logger.INSTANCE::report
)
if (assertServerRealmStoreExists(newHostname)) { );
Intent intent = new Intent(this, MainActivity.class); }
startActivity(intent);
finish();
overridePendingTransition(R.anim.slide_in, R.anim.slide_out);
} else {
recoverFromHostnameError();
}
}
}
private boolean assertServerRealmStoreExists(String hostname) {
return RealmStore.get(hostname) != null;
}
private void updateHostname(String hostname) {
this.hostname = hostname;
onHostnameUpdated();
}
private void recoverFromHostnameError() {
final List<ServerInfo> serverInfoList =
ConnectivityManager.getInstance(getApplicationContext()).getServerList();
if (serverInfoList == null || serverInfoList.size() == 0) {
LaunchUtil.showAddServerActivity(this);
return;
}
// just connect to the first available
final ServerInfo serverInfo = serverInfoList.get(0);
rocketChatCache.setSelectedServerHostname(serverInfo.getHostname());
rocketChatCache.setSelectedRoomId(null);
}
private void updateRoomIdIfNeeded(String newRoomId) {
if (roomId == null) {
if (newRoomId != null && assertRoomSubscriptionExists(newRoomId)) {
updateRoomId(newRoomId);
}
} else {
if (!roomId.equals(newRoomId) && assertRoomSubscriptionExists(newRoomId)) {
updateRoomId(newRoomId);
}
}
}
private boolean assertRoomSubscriptionExists(String roomId) {
if (!assertServerRealmStoreExists(hostname)) {
return false;
}
RealmRoom room = RealmStore.get(hostname).executeTransactionForRead(realm ->
realm.where(RealmRoom.class).equalTo(RealmRoom.ROOM_ID, roomId).findFirst());
if (room == null) {
rocketChatCache.setSelectedRoomId(null);
return false;
}
return true;
}
private void updateRoomId(String roomId) {
this.roomId = roomId;
onRoomIdUpdated();
}
protected void onHostnameUpdated() {
}
protected void onRoomIdUpdated() {
}
@Override
protected void onResume() {
super.onResume();
subscribeToConfigChanges();
ConnectivityManager.getInstance(getApplicationContext()).keepAliveServer();
if (isNotification) {
updateHostnameIfNeeded(rocketChatCache.getSelectedServerHostname());
updateRoomIdIfNeeded(rocketChatCache.getSelectedRoomId());
isNotification = false;
}
}
@Override
protected void onPause() {
compositeDisposable.clear();
super.onPause();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
}
private void subscribeToConfigChanges() {
compositeDisposable.add(
rocketChatCache.getSelectedServerHostnamePublisher()
.map(Optional::get)
.distinctUntilChanged()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
this::updateHostnameIfNeeded,
Logger::report
)
);
compositeDisposable.add(
rocketChatCache.getSelectedRoomIdPublisher()
.filter(Optional::isPresent)
.map(Optional::get)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
this::updateRoomIdIfNeeded,
Logger::report
)
);
}
} }
...@@ -8,6 +8,7 @@ import android.support.v4.app.Fragment; ...@@ -8,6 +8,7 @@ import android.support.v4.app.Fragment;
import chat.rocket.android.R; import chat.rocket.android.R;
import chat.rocket.android.fragment.server_config.LoginFragment; import chat.rocket.android.fragment.server_config.LoginFragment;
import chat.rocket.android.fragment.server_config.RetryLoginFragment; import chat.rocket.android.fragment.server_config.RetryLoginFragment;
import chat.rocket.android.helper.BackStackHelper;
import chat.rocket.android.service.ConnectivityManager; import chat.rocket.android.service.ConnectivityManager;
import chat.rocket.core.interactors.SessionInteractor; import chat.rocket.core.interactors.SessionInteractor;
import chat.rocket.persistence.realm.repositories.RealmSessionRepository; import chat.rocket.persistence.realm.repositories.RealmSessionRepository;
...@@ -16,77 +17,90 @@ import chat.rocket.persistence.realm.repositories.RealmSessionRepository; ...@@ -16,77 +17,90 @@ import chat.rocket.persistence.realm.repositories.RealmSessionRepository;
* Activity for Login, Sign-up, and Retry connecting... * Activity for Login, Sign-up, and Retry connecting...
*/ */
public class LoginActivity extends AbstractFragmentActivity implements LoginContract.View { public class LoginActivity extends AbstractFragmentActivity implements LoginContract.View {
public static final String KEY_HOSTNAME = "hostname"; public static final String KEY_HOSTNAME = "hostname";
private LoginContract.Presenter presenter; private LoginContract.Presenter presenter;
@Override @Override
protected int getLayoutContainerForFragment() { protected int getLayoutContainerForFragment() {
return R.id.content; return R.id.content;
} }
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String hostname = null;
Intent intent = getIntent();
if (intent != null && intent.getExtras() != null) {
hostname = intent.getStringExtra(KEY_HOSTNAME);
}
presenter = new LoginPresenter(
hostname,
new SessionInteractor(new RealmSessionRepository(hostname)),
ConnectivityManager.getInstance(getApplicationContext())
);
}
@Override
protected void onResume() {
super.onResume();
presenter.bindView(this);
}
@Override
protected void onDestroy() {
presenter.release();
super.onDestroy();
}
private void showFragment(Fragment fragment, String hostname) {
setContentView(R.layout.simple_screen);
injectHostnameArgTo(fragment, hostname);
super.showFragment(fragment);
}
private void injectHostnameArgTo(Fragment fragment, String hostname) {
Bundle args = fragment.getArguments();
if (args == null) {
args = new Bundle();
}
args.putString(LoginActivity.KEY_HOSTNAME, hostname);
fragment.setArguments(args);
}
@Override
protected void onBackPressedNotHandled() {
moveTaskToBack(true);
}
@Override
public void showLogin(String hostname) {
showFragment(new LoginFragment(), hostname);
}
@Override @Override
protected void onCreate(@Nullable Bundle savedInstanceState) { public void showRetryLogin(String hostname) {
super.onCreate(savedInstanceState); showFragment(new RetryLoginFragment(), hostname);
}
String hostname = null; @Override
Intent intent = getIntent(); public void closeView() {
if (intent != null && intent.getExtras() != null) { finish();
hostname = intent.getStringExtra(KEY_HOSTNAME); overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
} }
presenter = new LoginPresenter( @Override
hostname, protected boolean onBackPress() {
new SessionInteractor(new RealmSessionRepository(hostname)), if (BackStackHelper.FRAGMENT_TAG.equals("internal")) {
ConnectivityManager.getInstance(getApplicationContext()) super.onBackPress();
); BackStackHelper.FRAGMENT_TAG = "login";
} } else if (BackStackHelper.FRAGMENT_TAG.equals("login")) {
LoginFragment loginFragment = (LoginFragment) getSupportFragmentManager()
@Override .findFragmentById(getLayoutContainerForFragment());
protected void onResume() { loginFragment.goBack();
super.onResume(); }
presenter.bindView(this); return true;
}
@Override
protected void onDestroy() {
presenter.release();
super.onDestroy();
}
private void showFragment(Fragment fragment, String hostname) {
setContentView(R.layout.simple_screen);
injectHostnameArgTo(fragment, hostname);
super.showFragment(fragment);
}
private void injectHostnameArgTo(Fragment fragment, String hostname) {
Bundle args = fragment.getArguments();
if (args == null) {
args = new Bundle();
} }
args.putString(LoginActivity.KEY_HOSTNAME, hostname);
fragment.setArguments(args);
}
@Override
protected void onBackPressedNotHandled() {
moveTaskToBack(true);
}
@Override
public void showLogin(String hostname) {
showFragment(new LoginFragment(), hostname);
}
@Override
public void showRetryLogin(String hostname) {
showFragment(new RetryLoginFragment(), hostname);
}
@Override
public void closeView() {
finish();
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
}
} }
...@@ -66,7 +66,7 @@ public class LoginPresenter extends BasePresenter<LoginContract.View> ...@@ -66,7 +66,7 @@ public class LoginPresenter extends BasePresenter<LoginContract.View>
view.closeView(); view.closeView();
} }
}, },
Logger::report Logger.INSTANCE::report
); );
addSubscription(subscription); addSubscription(subscription);
......
...@@ -5,7 +5,7 @@ import android.graphics.drawable.Drawable; ...@@ -5,7 +5,7 @@ import android.graphics.drawable.Drawable;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar; import android.support.graphics.drawable.AnimatedVectorDrawableCompat;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import android.support.v4.widget.SlidingPaneLayout; import android.support.v4.widget.SlidingPaneLayout;
...@@ -18,7 +18,9 @@ import android.widget.TextView; ...@@ -18,7 +18,9 @@ import android.widget.TextView;
import com.facebook.drawee.view.SimpleDraweeView; import com.facebook.drawee.view.SimpleDraweeView;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import chat.rocket.android.ConnectionStatusManager;
import chat.rocket.android.LaunchUtil; import chat.rocket.android.LaunchUtil;
import chat.rocket.android.R; import chat.rocket.android.R;
import chat.rocket.android.RocketChatCache; import chat.rocket.android.RocketChatCache;
...@@ -30,6 +32,7 @@ import chat.rocket.android.helper.KeyboardHelper; ...@@ -30,6 +32,7 @@ import chat.rocket.android.helper.KeyboardHelper;
import chat.rocket.android.service.ConnectivityManager; import chat.rocket.android.service.ConnectivityManager;
import chat.rocket.android.service.ConnectivityManagerApi; import chat.rocket.android.service.ConnectivityManagerApi;
import chat.rocket.android.widget.RoomToolbar; import chat.rocket.android.widget.RoomToolbar;
import chat.rocket.android.widget.helper.DebouncingOnClickListener;
import chat.rocket.android.widget.helper.FrescoHelper; import chat.rocket.android.widget.helper.FrescoHelper;
import chat.rocket.core.interactors.CanCreateRoomInteractor; import chat.rocket.core.interactors.CanCreateRoomInteractor;
import chat.rocket.core.interactors.RoomInteractor; import chat.rocket.core.interactors.RoomInteractor;
...@@ -40,6 +43,8 @@ import chat.rocket.persistence.realm.repositories.RealmPublicSettingRepository; ...@@ -40,6 +43,8 @@ import chat.rocket.persistence.realm.repositories.RealmPublicSettingRepository;
import chat.rocket.persistence.realm.repositories.RealmRoomRepository; import chat.rocket.persistence.realm.repositories.RealmRoomRepository;
import chat.rocket.persistence.realm.repositories.RealmSessionRepository; import chat.rocket.persistence.realm.repositories.RealmSessionRepository;
import chat.rocket.persistence.realm.repositories.RealmUserRepository; import chat.rocket.persistence.realm.repositories.RealmUserRepository;
import de.keyboardsurfer.android.widget.crouton.Configuration;
import de.keyboardsurfer.android.widget.crouton.Crouton;
import hugo.weaving.DebugLog; import hugo.weaving.DebugLog;
/** /**
...@@ -49,7 +54,11 @@ public class MainActivity extends AbstractAuthedActivity implements MainContract ...@@ -49,7 +54,11 @@ public class MainActivity extends AbstractAuthedActivity implements MainContract
private RoomToolbar toolbar; private RoomToolbar toolbar;
private SlidingPaneLayout pane; private SlidingPaneLayout pane;
private MainContract.Presenter presenter; private MainContract.Presenter presenter;
private volatile Snackbar statusTicker; private volatile AtomicReference<Crouton> croutonStatusTicker = new AtomicReference<>();
private View croutonView;
private ImageView croutonTryAgainImage;
private TextView croutonText;
private AnimatedVectorDrawableCompat tryAgainSpinnerAnimatedDrawable;
@Override @Override
public int getLayoutContainerForFragment() { public int getLayoutContainerForFragment() {
...@@ -62,6 +71,7 @@ public class MainActivity extends AbstractAuthedActivity implements MainContract ...@@ -62,6 +71,7 @@ public class MainActivity extends AbstractAuthedActivity implements MainContract
setContentView(R.layout.activity_main); setContentView(R.layout.activity_main);
toolbar = findViewById(R.id.activity_main_toolbar); toolbar = findViewById(R.id.activity_main_toolbar);
pane = findViewById(R.id.sliding_pane); pane = findViewById(R.id.sliding_pane);
loadCroutonViewIfNeeded();
setupToolbar(); setupToolbar();
} }
...@@ -71,7 +81,7 @@ public class MainActivity extends AbstractAuthedActivity implements MainContract ...@@ -71,7 +81,7 @@ public class MainActivity extends AbstractAuthedActivity implements MainContract
ConnectivityManagerApi connectivityManager = ConnectivityManager.getInstance(getApplicationContext()); ConnectivityManagerApi connectivityManager = ConnectivityManager.getInstance(getApplicationContext());
if (hostname == null || presenter == null) { if (hostname == null || presenter == null) {
String previousHostname = hostname; String previousHostname = hostname;
hostname = new RocketChatCache(getApplicationContext()).getSelectedServerHostname(); hostname = RocketChatCache.INSTANCE.getSelectedServerHostname();
if (hostname == null) { if (hostname == null) {
showAddServerScreen(); showAddServerScreen();
} else { } else {
...@@ -85,7 +95,7 @@ public class MainActivity extends AbstractAuthedActivity implements MainContract ...@@ -85,7 +95,7 @@ public class MainActivity extends AbstractAuthedActivity implements MainContract
connectivityManager.keepAliveServer(); connectivityManager.keepAliveServer();
presenter.bindView(this); presenter.bindView(this);
presenter.loadSignedInServers(hostname); presenter.loadSignedInServers(hostname);
roomId = new RocketChatCache(getApplicationContext()).getSelectedRoomId(); roomId = RocketChatCache.INSTANCE.getSelectedRoomId();
} }
} }
...@@ -94,8 +104,7 @@ public class MainActivity extends AbstractAuthedActivity implements MainContract ...@@ -94,8 +104,7 @@ public class MainActivity extends AbstractAuthedActivity implements MainContract
if (presenter != null) { if (presenter != null) {
presenter.release(); presenter.release();
} }
// Dismiss any status ticker Crouton.cancelAllCroutons();
if (statusTicker != null) statusTicker.dismiss();
super.onPause(); super.onPause();
} }
...@@ -177,15 +186,12 @@ public class MainActivity extends AbstractAuthedActivity implements MainContract ...@@ -177,15 +186,12 @@ public class MainActivity extends AbstractAuthedActivity implements MainContract
PublicSettingRepository publicSettingRepository = new RealmPublicSettingRepository(hostname); PublicSettingRepository publicSettingRepository = new RealmPublicSettingRepository(hostname);
RocketChatCache rocketChatCache = new RocketChatCache(this);
presenter = new MainPresenter( presenter = new MainPresenter(
roomInteractor, roomInteractor,
createRoomInteractor, createRoomInteractor,
sessionInteractor, sessionInteractor,
new MethodCallHelper(this, hostname), new MethodCallHelper(this, hostname),
ConnectivityManager.getInstance(getApplicationContext()), ConnectivityManager.getInstance(getApplicationContext()),
rocketChatCache,
publicSettingRepository publicSettingRepository
); );
...@@ -194,12 +200,12 @@ public class MainActivity extends AbstractAuthedActivity implements MainContract ...@@ -194,12 +200,12 @@ public class MainActivity extends AbstractAuthedActivity implements MainContract
presenter.bindView(this); presenter.bindView(this);
presenter.loadSignedInServers(hostname); presenter.loadSignedInServers(hostname);
roomId = rocketChatCache.getSelectedRoomId(); roomId = RocketChatCache.INSTANCE.getSelectedRoomId();
} }
private void updateSidebarMainFragment() { private void updateSidebarMainFragment() {
closeSidebarIfNeeded(); closeSidebarIfNeeded();
String selectedServerHostname = new RocketChatCache(this).getSelectedServerHostname(); String selectedServerHostname = RocketChatCache.INSTANCE.getSelectedServerHostname();
Fragment sidebarFragment = findFragmentByTag(selectedServerHostname); Fragment sidebarFragment = findFragmentByTag(selectedServerHostname);
if (sidebarFragment == null) { if (sidebarFragment == null) {
sidebarFragment = SidebarMainFragment.create(selectedServerHostname); sidebarFragment = SidebarMainFragment.create(selectedServerHostname);
...@@ -250,34 +256,84 @@ public class MainActivity extends AbstractAuthedActivity implements MainContract ...@@ -250,34 +256,84 @@ public class MainActivity extends AbstractAuthedActivity implements MainContract
} }
@Override @Override
public synchronized void showConnectionError() { public void showConnectionError() {
dismissStatusTickerIfShowing(); ConnectionStatusManager.INSTANCE.setConnectionError(this::showConnectionErrorCrouton);
statusTicker = Snackbar.make(findViewById(getLayoutContainerForFragment()),
R.string.fragment_retry_login_error_title, Snackbar.LENGTH_INDEFINITE)
.setAction(R.string.fragment_retry_login_retry_title, view ->
ConnectivityManager.getInstance(getApplicationContext()).keepAliveServer());
statusTicker.show();
} }
@Override @Override
public synchronized void showConnecting() { public void showConnecting() {
dismissStatusTickerIfShowing(); ConnectionStatusManager.INSTANCE.setConnecting(this::showConnectingCrouton);
statusTicker = Snackbar.make(findViewById(getLayoutContainerForFragment()),
R.string.server_config_activity_authenticating, Snackbar.LENGTH_INDEFINITE);
statusTicker.show();
} }
@Override @Override
public synchronized void showConnectionOk() { public void showConnectionOk() {
dismissStatusTickerIfShowing(); ConnectionStatusManager.INSTANCE.setOnline(this::dismissStatusTickerIfShowing);
}
private void showConnectingCrouton(boolean success) {
if (success) {
croutonText.setText(R.string.server_config_activity_authenticating);
croutonTryAgainImage.setOnClickListener(null);
tryAgainSpinnerAnimatedDrawable.start();
Crouton.cancelAllCroutons();
updateCrouton();
croutonStatusTicker.get().show();
}
} }
private void dismissStatusTickerIfShowing() { private void showConnectionErrorCrouton(boolean success) {
if (statusTicker != null) { if (success) {
statusTicker.dismiss(); tryAgainSpinnerAnimatedDrawable.stop();
croutonText.setText(R.string.fragment_retry_login_error_title);
croutonTryAgainImage.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View v) {
retryConnection();
}
});
Crouton.cancelAllCroutons();
updateCrouton();
croutonStatusTicker.get().show();
}
}
private void loadCroutonViewIfNeeded() {
if (croutonView == null) {
croutonView = LayoutInflater.from(this).inflate(R.layout.crouton_status_ticker, null);
croutonTryAgainImage = croutonView.findViewById(R.id.try_again_image);
croutonText = croutonView.findViewById(R.id.text_view_status);
tryAgainSpinnerAnimatedDrawable =
AnimatedVectorDrawableCompat.create(this, R.drawable.ic_loading_animated);
croutonTryAgainImage.setImageDrawable(tryAgainSpinnerAnimatedDrawable);
updateCrouton();
} }
} }
private void updateCrouton() {
Configuration configuration = new Configuration.Builder()
.setDuration(Configuration.DURATION_INFINITE).build();
Crouton crouton = Crouton.make(this, croutonView, getLayoutContainerForFragment())
.setConfiguration(configuration);
croutonStatusTicker.set(crouton);
}
private void dismissStatusTickerIfShowing(boolean success) {
if (success && croutonStatusTicker.get() != null) {
croutonStatusTicker.get().hide();
}
}
private void retryConnection() {
croutonStatusTicker.set(null);
showConnecting();
ConnectivityManager.getInstance(getApplicationContext()).keepAliveServer();
}
@Override @Override
public void showSignedInServers(List<Pair<String, Pair<String, String>>> serverList) { public void showSignedInServers(List<Pair<String, Pair<String, String>>> serverList) {
final SlidingPaneLayout subPane = findViewById(R.id.sub_sliding_pane); final SlidingPaneLayout subPane = findViewById(R.id.sub_sliding_pane);
...@@ -334,28 +390,24 @@ public class MainActivity extends AbstractAuthedActivity implements MainContract ...@@ -334,28 +390,24 @@ public class MainActivity extends AbstractAuthedActivity implements MainContract
Fragment fragment = getSupportFragmentManager().findFragmentById(getLayoutContainerForFragment()); Fragment fragment = getSupportFragmentManager().findFragmentById(getLayoutContainerForFragment());
if (fragment != null && fragment instanceof RoomFragment) { if (fragment != null && fragment instanceof RoomFragment) {
RoomFragment roomFragment = (RoomFragment) fragment; RoomFragment roomFragment = (RoomFragment) fragment;
roomFragment.loadMessages(); roomFragment.loadMissedMessages();
} }
} }
private void changeServerIfNeeded(String serverHostname) { private void changeServerIfNeeded(String serverHostname) {
if (!hostname.equalsIgnoreCase(serverHostname)) { if (!hostname.equalsIgnoreCase(serverHostname)) {
RocketChatCache rocketChatCache = new RocketChatCache(getApplicationContext()); RocketChatCache.INSTANCE.setSelectedServerHostname(serverHostname);
rocketChatCache.setSelectedServerHostname(serverHostname);
} }
} }
@DebugLog @DebugLog
public void onLogout() { public void onLogout() {
if (new RocketChatCache(getApplicationContext()).getSelectedServerHostname() == null) { presenter.prepareToLogout();
if (RocketChatCache.INSTANCE.getSelectedServerHostname() == null) {
finish();
LaunchUtil.showMainActivity(this); LaunchUtil.showMainActivity(this);
} else { } else {
onHostnameUpdated(); onHostnameUpdated();
} }
} }
@DebugLog
public void beforeLogoutCleanUp() {
presenter.beforeLogoutCleanUp();
}
} }
...@@ -40,6 +40,6 @@ public interface MainContract { ...@@ -40,6 +40,6 @@ public interface MainContract {
void loadSignedInServers(String hostname); void loadSignedInServers(String hostname);
void beforeLogoutCleanUp(); void prepareToLogout();
} }
} }
...@@ -28,6 +28,7 @@ import chat.rocket.core.models.Session; ...@@ -28,6 +28,7 @@ import chat.rocket.core.models.Session;
import chat.rocket.core.models.User; import chat.rocket.core.models.User;
import chat.rocket.core.repositories.PublicSettingRepository; import chat.rocket.core.repositories.PublicSettingRepository;
import chat.rocket.core.utils.Pair; import chat.rocket.core.utils.Pair;
import hugo.weaving.DebugLog;
import io.reactivex.Flowable; import io.reactivex.Flowable;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable; import io.reactivex.disposables.Disposable;
...@@ -40,7 +41,6 @@ public class MainPresenter extends BasePresenter<MainContract.View> ...@@ -40,7 +41,6 @@ public class MainPresenter extends BasePresenter<MainContract.View>
private final SessionInteractor sessionInteractor; private final SessionInteractor sessionInteractor;
private final MethodCallHelper methodCallHelper; private final MethodCallHelper methodCallHelper;
private final ConnectivityManagerApi connectivityManagerApi; private final ConnectivityManagerApi connectivityManagerApi;
private final RocketChatCache rocketChatCache;
private final PublicSettingRepository publicSettingRepository; private final PublicSettingRepository publicSettingRepository;
public MainPresenter(RoomInteractor roomInteractor, public MainPresenter(RoomInteractor roomInteractor,
...@@ -48,13 +48,12 @@ public class MainPresenter extends BasePresenter<MainContract.View> ...@@ -48,13 +48,12 @@ public class MainPresenter extends BasePresenter<MainContract.View>
SessionInteractor sessionInteractor, SessionInteractor sessionInteractor,
MethodCallHelper methodCallHelper, MethodCallHelper methodCallHelper,
ConnectivityManagerApi connectivityManagerApi, ConnectivityManagerApi connectivityManagerApi,
RocketChatCache rocketChatCache, PublicSettingRepository publicSettingRepository) { PublicSettingRepository publicSettingRepository) {
this.roomInteractor = roomInteractor; this.roomInteractor = roomInteractor;
this.canCreateRoomInteractor = canCreateRoomInteractor; this.canCreateRoomInteractor = canCreateRoomInteractor;
this.sessionInteractor = sessionInteractor; this.sessionInteractor = sessionInteractor;
this.methodCallHelper = methodCallHelper; this.methodCallHelper = methodCallHelper;
this.connectivityManagerApi = connectivityManagerApi; this.connectivityManagerApi = connectivityManagerApi;
this.rocketChatCache = rocketChatCache;
this.publicSettingRepository = publicSettingRepository; this.publicSettingRepository = publicSettingRepository;
} }
...@@ -96,12 +95,13 @@ public class MainPresenter extends BasePresenter<MainContract.View> ...@@ -96,12 +95,13 @@ public class MainPresenter extends BasePresenter<MainContract.View>
subscribeToNetworkChanges(); subscribeToNetworkChanges();
subscribeToUnreadCount(); subscribeToUnreadCount();
subscribeToSession(); subscribeToSession();
setUserOnline();
} }
@Override @Override
public void release() { public void release() {
setUserAway(); if (RocketChatCache.INSTANCE.getSessionToken() != null) {
setUserAway();
}
super.release(); super.release();
} }
...@@ -119,7 +119,7 @@ public class MainPresenter extends BasePresenter<MainContract.View> ...@@ -119,7 +119,7 @@ public class MainPresenter extends BasePresenter<MainContract.View>
view.showHome(); view.showHome();
} }
}, },
Logger::report Logger.INSTANCE::report
); );
addSubscription(subscription); addSubscription(subscription);
...@@ -133,8 +133,9 @@ public class MainPresenter extends BasePresenter<MainContract.View> ...@@ -133,8 +133,9 @@ public class MainPresenter extends BasePresenter<MainContract.View>
addSubscription(subscription); addSubscription(subscription);
} }
@DebugLog
@Override @Override
public void beforeLogoutCleanUp() { public void prepareToLogout() {
clearSubscriptions(); clearSubscriptions();
} }
...@@ -155,13 +156,13 @@ public class MainPresenter extends BasePresenter<MainContract.View> ...@@ -155,13 +156,13 @@ public class MainPresenter extends BasePresenter<MainContract.View>
String logoUrl = (jsonObject.has("url")) ? String logoUrl = (jsonObject.has("url")) ?
jsonObject.optString("url") : jsonObject.optString("defaultUrl"); jsonObject.optString("url") : jsonObject.optString("defaultUrl");
String siteName = serverInfoPair.second; String siteName = serverInfoPair.second;
rocketChatCache.addHostname(hostname.toLowerCase(), logoUrl, siteName); RocketChatCache.INSTANCE.addHostname(hostname.toLowerCase(), logoUrl, siteName);
return rocketChatCache.getServerList(); return RocketChatCache.INSTANCE.getServerList();
} }
private void openRoom() { private void openRoom() {
String hostname = rocketChatCache.getSelectedServerHostname(); String hostname = RocketChatCache.INSTANCE.getSelectedServerHostname();
String roomId = rocketChatCache.getSelectedRoomId(); String roomId = RocketChatCache.INSTANCE.getSelectedRoomId();
if (roomId == null || roomId.length() == 0) { if (roomId == null || roomId.length() == 0) {
view.showHome(); view.showHome();
...@@ -181,7 +182,7 @@ public class MainPresenter extends BasePresenter<MainContract.View> ...@@ -181,7 +182,7 @@ public class MainPresenter extends BasePresenter<MainContract.View>
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe( .subscribe(
pair -> view.showUnreadCount(pair.first, pair.second), pair -> view.showUnreadCount(pair.first, pair.second),
Logger::report Logger.INSTANCE::report
); );
addSubscription(subscription); addSubscription(subscription);
...@@ -209,10 +210,11 @@ public class MainPresenter extends BasePresenter<MainContract.View> ...@@ -209,10 +210,11 @@ public class MainPresenter extends BasePresenter<MainContract.View>
view.showConnecting(); view.showConnecting();
return; return;
} }
// TODO: Should we remove below and above calls to view?
view.showConnectionOk(); // view.showConnectionOk();
RocketChatCache.INSTANCE.setSessionToken(session.getToken());
}, },
Logger::report Logger.INSTANCE::report
); );
addSubscription(subscription); addSubscription(subscription);
...@@ -225,17 +227,21 @@ public class MainPresenter extends BasePresenter<MainContract.View> ...@@ -225,17 +227,21 @@ public class MainPresenter extends BasePresenter<MainContract.View>
.subscribe( .subscribe(
connectivity -> { connectivity -> {
if (connectivity.state == ServerConnectivity.STATE_CONNECTED) { if (connectivity.state == ServerConnectivity.STATE_CONNECTED) {
view.showConnectionOk(); //TODO: notify almost connected or something like that.
view.refreshRoom(); // view.showConnectionOk();
} else if (connectivity.state == ServerConnectivity.STATE_DISCONNECTED) { } else if (connectivity.state == ServerConnectivity.STATE_DISCONNECTED) {
if (connectivity.code == DDPClient.REASON_NETWORK_ERROR) { if (connectivity.code == DDPClient.REASON_NETWORK_ERROR) {
view.showConnectionError(); view.showConnectionError();
} }
} else if (connectivity.state == ServerConnectivity.STATE_SESSION_ESTABLISHED) {
setUserOnline();
view.refreshRoom();
view.showConnectionOk();
} else { } else {
view.showConnecting(); view.showConnecting();
} }
}, },
Logger::report RCLog::e
); );
addSubscription(disposable); addSubscription(disposable);
......
...@@ -7,6 +7,7 @@ import org.json.JSONArray; ...@@ -7,6 +7,7 @@ import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import java.util.Iterator;
import java.util.UUID; import java.util.UUID;
import bolts.Continuation; import bolts.Continuation;
...@@ -14,6 +15,7 @@ import bolts.Task; ...@@ -14,6 +15,7 @@ import bolts.Task;
import chat.rocket.android.RocketChatCache; import chat.rocket.android.RocketChatCache;
import chat.rocket.android.helper.CheckSum; import chat.rocket.android.helper.CheckSum;
import chat.rocket.android.helper.TextUtils; import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.log.RCLog;
import chat.rocket.android.service.ConnectivityManager; import chat.rocket.android.service.ConnectivityManager;
import chat.rocket.android_ddp.DDPClient; import chat.rocket.android_ddp.DDPClient;
import chat.rocket.android_ddp.DDPClientCallback; import chat.rocket.android_ddp.DDPClientCallback;
...@@ -32,564 +34,611 @@ import chat.rocket.persistence.realm.models.ddp.RealmSpotlightUser; ...@@ -32,564 +34,611 @@ import chat.rocket.persistence.realm.models.ddp.RealmSpotlightUser;
import chat.rocket.persistence.realm.models.internal.MethodCall; import chat.rocket.persistence.realm.models.internal.MethodCall;
import chat.rocket.persistence.realm.models.internal.RealmSession; import chat.rocket.persistence.realm.models.internal.RealmSession;
import hugo.weaving.DebugLog; import hugo.weaving.DebugLog;
import io.realm.RealmQuery;
import okhttp3.HttpUrl; import okhttp3.HttpUrl;
/** /**
* Utility class for creating/handling MethodCall or RPC. * Utility class for creating/handling MethodCall or RPC.
* * <p>
* TODO: separate method into several manager classes (SubscriptionManager, MessageManager, ...). * TODO: separate method into several manager classes (SubscriptionManager, MessageManager, ...).
*/ */
public class MethodCallHelper { public class MethodCallHelper {
protected static final long TIMEOUT_MS = 20000; protected static final long TIMEOUT_MS = 20000;
protected static final Continuation<String, Task<JSONObject>> CONVERT_TO_JSON_OBJECT = protected static final Continuation<String, Task<JSONObject>> CONVERT_TO_JSON_OBJECT =
task -> Task.forResult(new JSONObject(task.getResult())); task -> Task.forResult(new JSONObject(task.getResult()));
protected static final Continuation<String, Task<JSONArray>> CONVERT_TO_JSON_ARRAY = protected static final Continuation<String, Task<JSONArray>> CONVERT_TO_JSON_ARRAY =
task -> Task.forResult(new JSONArray(task.getResult())); task -> Task.forResult(new JSONArray(task.getResult()));
protected final Context context; protected final Context context;
protected final RealmHelper realmHelper; protected final RealmHelper realmHelper;
/** /**
* initialize with Context and hostname. * initialize with Context and hostname.
*/ */
public MethodCallHelper(Context context, String hostname) { public MethodCallHelper(Context context, String hostname) {
this.context = context.getApplicationContext(); this.context = context.getApplicationContext();
this.realmHelper = RealmStore.getOrCreate(hostname); this.realmHelper = RealmStore.getOrCreate(hostname);
} }
/** /**
* initialize with RealmHelper and DDPClient. * initialize with RealmHelper and DDPClient.
*/ */
public MethodCallHelper(RealmHelper realmHelper) { public MethodCallHelper(RealmHelper realmHelper) {
this.context = null; this.context = null;
this.realmHelper = realmHelper; this.realmHelper = realmHelper;
} }
public MethodCallHelper(Context context, RealmHelper realmHelper) { public MethodCallHelper(Context context, RealmHelper realmHelper) {
this.context = context.getApplicationContext(); this.context = context.getApplicationContext();
this.realmHelper = realmHelper; this.realmHelper = realmHelper;
} }
@DebugLog @DebugLog
private Task<String> executeMethodCall(String methodName, String param, long timeout) { private Task<String> executeMethodCall(String methodName, String param, long timeout) {
if (DDPClient.get() != null) { if (DDPClient.get() != null) {
return DDPClient.get().rpc(UUID.randomUUID().toString(), methodName, param, timeout) return DDPClient.get().rpc(UUID.randomUUID().toString(), methodName, param, timeout)
.onSuccessTask(task -> Task.forResult(task.getResult().result)) .onSuccessTask(task -> Task.forResult(task.getResult().result))
.continueWithTask(task_ -> { .continueWithTask(task_ -> {
if (task_.isFaulted()) { if (task_.isFaulted()) {
return Task.forError(task_.getError()); return Task.forError(task_.getError());
} }
return Task.forResult(task_.getResult()); return Task.forResult(task_.getResult());
}); });
} else {
return MethodCall.execute(realmHelper, methodName, param, timeout)
.onSuccessTask(task -> {
ConnectivityManager.getInstance(context.getApplicationContext())
.keepAliveServer();
return task;
});
}
}
private Task<String> injectErrorHandler(Task<String> task) {
return task.continueWithTask(_task -> {
if (_task.isFaulted()) {
Exception exception = _task.getError();
if (exception instanceof MethodCall.Error || exception instanceof DDPClientCallback.RPC.Error) {
String errMessageJson;
if (exception instanceof DDPClientCallback.RPC.Error) {
errMessageJson = ((DDPClientCallback.RPC.Error) exception).error.toString();
} else {
errMessageJson = exception.getMessage();
}
if (TextUtils.isEmpty(errMessageJson)) {
return Task.forError(exception);
}
String errType = new JSONObject(errMessageJson).optString("error");
String errMessage = new JSONObject(errMessageJson).getString("message");
if (TwoStepAuthException.TYPE.equals(errType)) {
return Task.forError(new TwoStepAuthException(errMessage));
}
return Task.forError(new Exception(errMessage));
} else if (exception instanceof DDPClientCallback.RPC.Timeout) {
return Task.forError(new MethodCall.Timeout());
} else if (exception instanceof DDPClientCallback.Closed) {
return Task.forError(new Exception("Oops, your connection seems off..."));
} else { } else {
return Task.forError(exception); return MethodCall.execute(realmHelper, methodName, param, timeout)
.onSuccessTask(task -> {
ConnectivityManager.getInstance(context.getApplicationContext())
.keepAliveServer();
return task;
});
} }
} else { }
return _task;
}
});
}
protected final Task<String> call(String methodName, long timeout) {
return injectErrorHandler(executeMethodCall(methodName, null, timeout));
}
protected final Task<String> call(String methodName, long timeout, ParamBuilder paramBuilder) {
try {
final JSONArray params = paramBuilder.buildParam();
return injectErrorHandler(executeMethodCall(methodName,
params != null ? params.toString() : null, timeout));
} catch (JSONException exception) {
return Task.forError(exception);
}
}
/**
* Register RealmUser.
*/
public Task<String> registerUser(final String name, final String email,
final String password, final String confirmPassword) {
return call("registerUser", TIMEOUT_MS, () -> new JSONArray().put(new JSONObject()
.put("name", name)
.put("email", email)
.put("pass", password)
.put("confirm-pass", confirmPassword))); // nothing to do.
}
private Task<Void> saveToken(Task<String> task) {
return realmHelper.executeTransaction(realm ->
realm.createOrUpdateObjectFromJson(RealmSession.class, new JSONObject()
.put("sessionId", RealmSession.DEFAULT_ID)
.put("token", task.getResult())
.put("tokenVerified", true)
.put("error", JSONObject.NULL)
));
}
/**
* set current user's name.
*/
public Task<String> setUsername(final String username) {
return call("setUsername", TIMEOUT_MS, () -> new JSONArray().put(username));
}
public Task<Void> joinDefaultChannels() {
return call("joinDefaultChannels", TIMEOUT_MS)
.onSuccessTask(task -> Task.forResult(null));
}
public Task<Void> joinRoom(String roomId) {
return call("joinRoom", TIMEOUT_MS, () -> new JSONArray().put(roomId))
.onSuccessTask(task -> Task.forResult(null));
}
/**
* Login with username/email and password.
*/
public Task<Void> loginWithEmail(final String usernameOrEmail, final String password) {
return call("login", TIMEOUT_MS, () -> {
JSONObject param = new JSONObject();
if (Patterns.EMAIL_ADDRESS.matcher(usernameOrEmail).matches()) {
param.put("user", new JSONObject().put("email", usernameOrEmail));
} else {
param.put("user", new JSONObject().put("username", usernameOrEmail));
}
param.put("password", new JSONObject()
.put("digest", CheckSum.sha256(password))
.put("algorithm", "sha-256"));
return new JSONArray().put(param);
}).onSuccessTask(CONVERT_TO_JSON_OBJECT)
.onSuccessTask(task -> Task.forResult(task.getResult().getString("token")))
.onSuccessTask(this::saveToken);
}
public Task<Void> loginWithLdap(final String username, final String password) {
return call("login", TIMEOUT_MS, () -> {
JSONObject param = new JSONObject();
param.put("ldap", true);
param.put("username", username);
param.put("ldapPass", password);
param.put("ldapOptions", new JSONObject());
return new JSONArray().put(param);
}).onSuccessTask(CONVERT_TO_JSON_OBJECT)
.onSuccessTask(task -> Task.forResult(task.getResult().getString("token")))
.onSuccessTask(this::saveToken);
}
/**
* Login with OAuth.
*/
public Task<Void> loginWithOAuth(final String credentialToken,
final String credentialSecret) {
return call("login", TIMEOUT_MS, () -> new JSONArray().put(new JSONObject()
.put("oauth", new JSONObject()
.put("credentialToken", credentialToken)
.put("credentialSecret", credentialSecret))
)).onSuccessTask(CONVERT_TO_JSON_OBJECT)
.onSuccessTask(task -> Task.forResult(task.getResult().getString("token")))
.onSuccessTask(this::saveToken);
}
/**
* Login with token.
*/
public Task<Void> loginWithToken(final String token) {
return call("login", TIMEOUT_MS, () -> new JSONArray().put(new JSONObject()
.put("resume", token)
)).onSuccessTask(CONVERT_TO_JSON_OBJECT)
.onSuccessTask(task -> Task.forResult(task.getResult().getString("token")))
.onSuccessTask(this::saveToken)
.continueWithTask(task -> {
if (task.isFaulted()) {
RealmSession.logError(realmHelper, task.getError());
}
return task;
});
}
public Task<Void> twoStepCodeLogin(final String usernameOrEmail, final String password,
final String twoStepCode) {
return call("login", TIMEOUT_MS, () -> {
JSONObject loginParam = new JSONObject();
if (Patterns.EMAIL_ADDRESS.matcher(usernameOrEmail).matches()) {
loginParam.put("user", new JSONObject().put("email", usernameOrEmail));
} else {
loginParam.put("user", new JSONObject().put("username", usernameOrEmail));
}
loginParam.put("password", new JSONObject()
.put("digest", CheckSum.sha256(password))
.put("algorithm", "sha-256"));
JSONObject twoStepParam = new JSONObject();
twoStepParam.put("login", loginParam);
twoStepParam.put("code", twoStepCode);
JSONObject param = new JSONObject();
param.put("totp", twoStepParam);
return new JSONArray().put(param);
}).onSuccessTask(CONVERT_TO_JSON_OBJECT)
.onSuccessTask(task -> Task.forResult(task.getResult().getString("token")))
.onSuccessTask(this::saveToken);
}
/**
* Logout.
*/
public Task<Void> logout() {
return call("logout", TIMEOUT_MS).onSuccessTask(task -> {
if (task.isFaulted()) {
return Task.forError(task.getError());
}
return null;
});
}
/**
* request "subscriptions/get".
*/
public Task<Void> getRoomSubscriptions() {
return call("subscriptions/get", TIMEOUT_MS).onSuccessTask(CONVERT_TO_JSON_ARRAY)
.onSuccessTask(task -> {
final JSONArray result = task.getResult();
try {
for (int i = 0; i < result.length(); i++) {
RealmRoom.customizeJson(result.getJSONObject(i));
}
return realmHelper.executeTransaction(realm -> { private Task<String> injectErrorHandler(Task<String> task) {
realm.delete(RealmRoom.class); return task.continueWithTask(_task -> {
realm.createOrUpdateAllFromJson( if (_task.isFaulted()) {
RealmRoom.class, result); Exception exception = _task.getError();
return null; if (exception instanceof MethodCall.Error || exception instanceof DDPClientCallback.RPC.Error) {
}); String errMessageJson;
} catch (JSONException exception) { if (exception instanceof DDPClientCallback.RPC.Error) {
return Task.forError(exception); errMessageJson = ((DDPClientCallback.RPC.Error) exception).error.toString();
} } else {
}); errMessageJson = exception.getMessage();
} }
if (TextUtils.isEmpty(errMessageJson)) {
/** return Task.forError(exception);
* Load messages for room. }
*/ String errType = new JSONObject(errMessageJson).optString("error");
public Task<JSONArray> loadHistory(final String roomId, final long timestamp, String errMessage = new JSONObject(errMessageJson).getString("message");
final int count, final long lastSeen) {
return call("loadHistory", TIMEOUT_MS, () -> new JSONArray() if (TwoStepAuthException.TYPE.equals(errType)) {
.put(roomId) return Task.forError(new TwoStepAuthException(errMessage));
.put(timestamp > 0 ? new JSONObject().put("$date", timestamp) : JSONObject.NULL) }
.put(count) return Task.forError(new Exception(errMessage));
.put(lastSeen > 0 ? new JSONObject().put("$date", lastSeen) : JSONObject.NULL) } else if (exception instanceof DDPClientCallback.RPC.Timeout) {
).onSuccessTask(CONVERT_TO_JSON_OBJECT) return Task.forError(new MethodCall.Timeout());
.onSuccessTask(task -> { } else if (exception instanceof DDPClientCallback.Closed) {
JSONObject result = task.getResult(); return Task.forError(new Exception(exception.getMessage()));
final JSONArray messages = result.getJSONArray("messages"); } else {
for (int i = 0; i < messages.length(); i++) { return Task.forError(exception);
RealmMessage.customizeJson(messages.getJSONObject(i)); }
} } else {
return _task;
return realmHelper.executeTransaction(realm -> {
if (timestamp == 0) {
realm.where(RealmMessage.class)
.equalTo("rid", roomId)
.equalTo("syncstate", SyncState.SYNCED)
.findAll().deleteAllFromRealm();
}
if (messages.length() > 0) {
realm.createOrUpdateAllFromJson(RealmMessage.class, messages);
} }
return null;
}).onSuccessTask(_task -> Task.forResult(messages));
}); });
} }
/** protected final Task<String> call(String methodName, long timeout) {
* update user's status. return injectErrorHandler(executeMethodCall(methodName, null, timeout));
*/ }
public Task<Void> setUserStatus(final String status) {
return call("UserPresence:setDefaultStatus", TIMEOUT_MS, () -> new JSONArray().put(status)) protected final Task<String> call(String methodName, long timeout, ParamBuilder paramBuilder) {
.onSuccessTask(task -> Task.forResult(null)); try {
} final JSONArray params = paramBuilder.buildParam();
return injectErrorHandler(executeMethodCall(methodName,
public Task<Void> setUserPresence(final String status) { params != null ? params.toString() : null, timeout));
return call("UserPresence:" + status, TIMEOUT_MS) } catch (JSONException exception) {
.onSuccessTask(task -> Task.forResult(null)); return Task.forError(exception);
} }
}
public Task<JSONObject> getUsersOfRoom(final String roomId, final boolean showAll) {
return call("getUsersOfRoom", TIMEOUT_MS, () -> new JSONArray().put(roomId).put(showAll)) /**
.onSuccessTask(CONVERT_TO_JSON_OBJECT); * Register RealmUser.
} */
public Task<String> registerUser(final String name, final String email,
public Task<Void> createChannel(final String name, final boolean readOnly) { final String password, final String confirmPassword) {
return call("createChannel", TIMEOUT_MS, () -> new JSONArray() return call("registerUser", TIMEOUT_MS, () -> new JSONArray().put(new JSONObject()
.put(name) .put("name", name)
.put(new JSONArray()) .put("email", email)
.put(readOnly)) .put("pass", password)
.onSuccessTask(task -> Task.forResult(null)); .put("confirm-pass", confirmPassword))); // nothing to do.
} }
public Task<Void> createPrivateGroup(final String name, final boolean readOnly) { private Task<Void> saveToken(Task<String> task) {
return call("createPrivateGroup", TIMEOUT_MS, () -> new JSONArray() return realmHelper.executeTransaction(realm ->
.put(name) realm.createOrUpdateObjectFromJson(RealmSession.class, new JSONObject()
.put(new JSONArray()) .put("sessionId", RealmSession.DEFAULT_ID)
.put(readOnly)) .put("token", task.getResult())
.onSuccessTask(task -> Task.forResult(null)); .put("tokenVerified", true)
} .put("error", JSONObject.NULL)
));
public Task<String> createDirectMessage(final String username) { }
return call("createDirectMessage", TIMEOUT_MS, () -> new JSONArray().put(username))
.onSuccessTask(CONVERT_TO_JSON_OBJECT) /**
.onSuccessTask(task -> Task.forResult(task.getResult().getString("rid"))); * set current user's name.
} */
public Task<String> setUsername(final String username) {
/** return call("setUsername", TIMEOUT_MS, () -> new JSONArray().put(username));
* send message. }
*/
public Task<Void> sendMessage(String messageId, String roomId, String msg, long editedAt) { public Task<Void> joinDefaultChannels() {
try { return call("joinDefaultChannels", TIMEOUT_MS)
JSONObject messageJson = new JSONObject() .onSuccessTask(task -> Task.forResult(null));
.put("_id", messageId) }
.put("rid", roomId)
.put("msg", msg); public Task<Void> joinRoom(String roomId) {
return call("joinRoom", TIMEOUT_MS, () -> new JSONArray().put(roomId))
if (editedAt == 0) { .onSuccessTask(task -> Task.forResult(null));
return sendMessage(messageJson); }
} else {
return updateMessage(messageJson); /**
} * Login with username/email and password.
} catch (JSONException exception) { */
return Task.forError(exception); public Task<Void> loginWithEmail(final String usernameOrEmail, final String password) {
} return call("login", TIMEOUT_MS, () -> {
} JSONObject param = new JSONObject();
if (Patterns.EMAIL_ADDRESS.matcher(usernameOrEmail).matches()) {
public Task<Void> deleteMessage(String messageID) { param.put("user", new JSONObject().put("email", usernameOrEmail));
try { } else {
JSONObject messageJson = new JSONObject() param.put("user", new JSONObject().put("username", usernameOrEmail));
.put("_id", messageID);
return deleteMessage(messageJson);
} catch(JSONException exception) {
return Task.forError(exception);
}
}
/**
* Send message object.
*/
private Task<Void> sendMessage(final JSONObject messageJson) {
return call("sendMessage", TIMEOUT_MS, () -> new JSONArray().put(messageJson))
.onSuccessTask(task -> Task.forResult(null));
}
private Task<Void> updateMessage(final JSONObject messageJson) {
return call("updateMessage", TIMEOUT_MS, () -> new JSONArray().put(messageJson))
.onSuccessTask(task -> Task.forResult(null));
}
private Task<Void> deleteMessage(final JSONObject messageJson) {
return call("deleteMessage", TIMEOUT_MS, () -> new JSONArray().put(messageJson))
.onSuccessTask(task -> Task.forResult(null));
}
/**
* mark all messages are read in the room.
*/
public Task<Void> readMessages(final String roomId) {
return call("readMessages", TIMEOUT_MS, () -> new JSONArray().put(roomId))
.onSuccessTask(task -> Task.forResult(null));
}
public Task<Void> getPublicSettings(String currentHostname) {
return call("public-settings/get", TIMEOUT_MS)
.onSuccessTask(CONVERT_TO_JSON_ARRAY)
.onSuccessTask(task -> {
final JSONArray settings = task.getResult();
String siteUrl = null;
String siteName = null;
for (int i = 0; i < settings.length(); i++) {
JSONObject jsonObject = settings.getJSONObject(i);
RealmPublicSetting.customizeJson(jsonObject);
if (isPublicSetting(jsonObject, PublicSettingsConstants.General.SITE_URL)) {
siteUrl = jsonObject.getString(RealmPublicSetting.VALUE);
} else if (isPublicSetting(jsonObject, PublicSettingsConstants.General.SITE_NAME)) {
siteName = jsonObject.getString(RealmPublicSetting.VALUE);
} }
} param.put("password", new JSONObject()
.put("digest", CheckSum.sha256(password))
if (siteName != null && siteUrl != null) { .put("algorithm", "sha-256"));
HttpUrl httpSiteUrl = HttpUrl.parse(siteUrl); return new JSONArray().put(param);
if (httpSiteUrl != null) { }).onSuccessTask(CONVERT_TO_JSON_OBJECT)
String host = httpSiteUrl.host(); .onSuccessTask(task -> Task.forResult(task.getResult().getString("token")))
RocketChatCache rocketChatCache = new RocketChatCache(context); .onSuccessTask(this::saveToken);
rocketChatCache.addHostnameSiteUrl(host, currentHostname); }
rocketChatCache.addHostSiteName(currentHostname, siteName);
public Task<Void> loginWithLdap(final String username, final String password) {
return call("login", TIMEOUT_MS, () -> {
JSONObject param = new JSONObject();
param.put("ldap", true);
param.put("username", username);
param.put("ldapPass", password);
param.put("ldapOptions", new JSONObject());
return new JSONArray().put(param);
}).onSuccessTask(CONVERT_TO_JSON_OBJECT)
.onSuccessTask(task -> Task.forResult(task.getResult().getString("token")))
.onSuccessTask(this::saveToken);
}
/**
* Login with OAuth.
*/
public Task<Void> loginWithOAuth(final String credentialToken,
final String credentialSecret) {
return call("login", TIMEOUT_MS, () -> new JSONArray().put(new JSONObject()
.put("oauth", new JSONObject()
.put("credentialToken", credentialToken)
.put("credentialSecret", credentialSecret))
)).onSuccessTask(CONVERT_TO_JSON_OBJECT)
.onSuccessTask(task -> Task.forResult(task.getResult().getString("token")))
.onSuccessTask(this::saveToken);
}
/**
* Login with token.
*/
public Task<Void> loginWithToken(final String token) {
return call("login", TIMEOUT_MS, () -> new JSONArray().put(new JSONObject()
.put("resume", token)
)).onSuccessTask(CONVERT_TO_JSON_OBJECT)
.onSuccessTask(task -> Task.forResult(task.getResult().getString("token")))
.onSuccessTask(this::saveToken)
.continueWithTask(task -> {
if (task.isFaulted()) {
RealmSession.logError(realmHelper, task.getError());
}
return task;
});
}
public Task<Void> twoStepCodeLogin(final String usernameOrEmail, final String password,
final String twoStepCode) {
return call("login", TIMEOUT_MS, () -> {
JSONObject loginParam = new JSONObject();
if (Patterns.EMAIL_ADDRESS.matcher(usernameOrEmail).matches()) {
loginParam.put("user", new JSONObject().put("email", usernameOrEmail));
} else {
loginParam.put("user", new JSONObject().put("username", usernameOrEmail));
} }
} loginParam.put("password", new JSONObject()
.put("digest", CheckSum.sha256(password))
.put("algorithm", "sha-256"));
return realmHelper.executeTransaction(realm -> { JSONObject twoStepParam = new JSONObject();
realm.delete(RealmPublicSetting.class); twoStepParam.put("login", loginParam);
realm.createOrUpdateAllFromJson(RealmPublicSetting.class, settings); twoStepParam.put("code", twoStepCode);
return null;
}); JSONObject param = new JSONObject();
}); param.put("totp", twoStepParam);
}
return new JSONArray().put(param);
private boolean isPublicSetting(JSONObject jsonObject, String id) { }).onSuccessTask(CONVERT_TO_JSON_OBJECT)
return jsonObject.optString(RealmPublicSetting.ID).equalsIgnoreCase(id); .onSuccessTask(task -> Task.forResult(task.getResult().getString("token")))
} .onSuccessTask(this::saveToken);
}
public Task<Void> getPermissions() {
return call("permissions/get", TIMEOUT_MS) /**
.onSuccessTask(CONVERT_TO_JSON_ARRAY) * Logout.
.onSuccessTask(task -> { */
final JSONArray permissions = task.getResult(); public Task<Void> logout() {
for (int i = 0; i < permissions.length(); i++) { return call("logout", TIMEOUT_MS).onSuccessTask(task -> {
RealmPermission.customizeJson(permissions.getJSONObject(i)); if (task.isFaulted()) {
} return Task.forError(task.getError());
}
return realmHelper.executeTransaction(realm -> {
realm.delete(RealmPermission.class);
realm.createOrUpdateAllFromJson(RealmPermission.class, permissions);
return null;
});
});
}
public Task<Void> getRoomRoles(final String roomId) {
return call("getRoomRoles", TIMEOUT_MS, () -> new JSONArray().put(roomId))
.onSuccessTask(CONVERT_TO_JSON_ARRAY)
.onSuccessTask(task -> {
final JSONArray roomRoles = task.getResult();
for (int i = 0; i < roomRoles.length(); i++) {
RealmRoomRole.customizeJson(roomRoles.getJSONObject(i));
}
return realmHelper.executeTransaction(realm -> {
realm.delete(RealmRoomRole.class);
realm.createOrUpdateAllFromJson(RealmRoomRole.class, roomRoles);
return null; return null;
});
}); });
} }
public Task<Void> searchSpotlightUsers(String term) { /**
return searchSpotlight(RealmSpotlightUser.class, "users", term); * request "subscriptions/get".
} */
public Task<Void> getRoomSubscriptions() {
public Task<Void> searchSpotlightRooms(String term) { return call("subscriptions/get", TIMEOUT_MS).onSuccessTask(CONVERT_TO_JSON_ARRAY)
return searchSpotlight(RealmSpotlightRoom.class, "rooms", term); .onSuccessTask(task -> {
} final JSONArray result = task.getResult();
try {
public Task<Void> searchSpotlight(String term) { for (int i = 0; i < result.length(); i++) {
return call("spotlight", TIMEOUT_MS, () -> RealmRoom.customizeJson(result.getJSONObject(i));
new JSONArray() }
.put(term)
.put(JSONObject.NULL) return realmHelper.executeTransaction(realm -> {
.put(new JSONObject().put("rooms", true).put("users", true)) realm.delete(RealmRoom.class);
).onSuccessTask(CONVERT_TO_JSON_OBJECT) realm.createOrUpdateAllFromJson(
.onSuccessTask(task -> { RealmRoom.class, result);
String jsonString = null;
final JSONObject result = task.getResult(); JSONObject openedRooms = RocketChatCache.INSTANCE.getOpenedRooms();
JSONArray roomJsonArray = (JSONArray) result.get("rooms"); RealmQuery<RealmRoom> query = realm.where(RealmRoom.class);
int roomTotal = roomJsonArray.length(); Iterator<String> keys = openedRooms.keys();
if (roomTotal > 0) { while (keys.hasNext()) {
for (int i = 0; i < roomTotal; ++i) { String rid = keys.next();
RealmSpotlight.Companion.customizeRoomJSONObject(roomJsonArray.getJSONObject(i)); RealmRoom realmRoom = query.equalTo(RealmRoom.ID, rid).findFirst();
} if (realmRoom == null) {
jsonString = roomJsonArray.toString(); RocketChatCache.INSTANCE.removeOpenedRoom(rid);
} } else {
loadMissedMessages(rid, realmRoom.getLastSeen())
JSONArray userJsonArray = (JSONArray) result.get("users"); .continueWithTask(task1 -> {
int usersTotal = userJsonArray.length(); if (task1.isFaulted()) {
if (usersTotal > 0) { Exception error = task1.getError();
for (int i = 0; i < usersTotal; ++i) { RCLog.e(error);
RealmSpotlight.Companion.customizeUserJSONObject(userJsonArray.getJSONObject(i)); }
} return null;
});
}
}
return null;
});
} catch (JSONException exception) {
return Task.forError(exception);
}
});
}
public Task<JSONArray> loadMissedMessages(final String roomId, final long timestamp) {
return call("loadMissedMessages", TIMEOUT_MS, () -> new JSONArray()
.put(roomId)
.put(timestamp > 0 ? new JSONObject().put("$date", timestamp) : JSONObject.NULL)
).onSuccessTask(CONVERT_TO_JSON_ARRAY)
.onSuccessTask(task -> {
JSONArray result = task.getResult();
for (int i = 0; i < result.length(); i++) {
RealmMessage.customizeJson(result.getJSONObject(i));
}
return realmHelper.executeTransaction(realm -> {
if (timestamp == 0) {
realm.where(RealmMessage.class)
.equalTo("rid", roomId)
.equalTo("syncstate", SyncState.SYNCED)
.findAll().deleteAllFromRealm();
}
if (result.length() > 0) {
realm.createOrUpdateAllFromJson(RealmMessage.class, result);
}
return null;
}).onSuccessTask(_task -> Task.forResult(result));
});
}
/**
* Load messages for room.
*/
public Task<JSONArray> loadHistory(final String roomId, final long timestamp,
final int count, final long lastSeen) {
return call("loadHistory", TIMEOUT_MS, () -> new JSONArray()
.put(roomId)
.put(timestamp > 0 ? new JSONObject().put("$date", timestamp) : JSONObject.NULL)
.put(count)
.put(lastSeen > 0 ? new JSONObject().put("$date", lastSeen) : JSONObject.NULL)
).onSuccessTask(CONVERT_TO_JSON_OBJECT)
.onSuccessTask(task -> {
JSONObject result = task.getResult();
final JSONArray messages = result.getJSONArray("messages");
for (int i = 0; i < messages.length(); i++) {
RealmMessage.customizeJson(messages.getJSONObject(i));
}
return realmHelper.executeTransaction(realm -> {
if (timestamp == 0) {
realm.where(RealmMessage.class)
.equalTo("rid", roomId)
.equalTo("syncstate", SyncState.SYNCED)
.findAll().deleteAllFromRealm();
}
if (messages.length() > 0) {
realm.createOrUpdateAllFromJson(RealmMessage.class, messages);
}
return null;
}).onSuccessTask(_task -> Task.forResult(messages));
});
}
/**
* update user's status.
*/
public Task<Void> setUserStatus(final String status) {
return call("UserPresence:setDefaultStatus", TIMEOUT_MS, () -> new JSONArray().put(status))
.onSuccessTask(task -> Task.forResult(null));
}
public Task<Void> setUserPresence(final String status) {
return call("UserPresence:" + status, TIMEOUT_MS)
.onSuccessTask(task -> Task.forResult(null));
}
public Task<JSONObject> getUsersOfRoom(final String roomId, final boolean showAll) {
return call("getUsersOfRoom", TIMEOUT_MS, () -> new JSONArray().put(roomId).put(showAll))
.onSuccessTask(CONVERT_TO_JSON_OBJECT);
}
if (jsonString == null) { public Task<Void> createChannel(final String name, final boolean readOnly) {
jsonString = userJsonArray.toString(); return call("createChannel", TIMEOUT_MS, () -> new JSONArray()
.put(name)
.put(new JSONArray())
.put(readOnly))
.onSuccessTask(task -> Task.forResult(null));
}
public Task<Void> createPrivateGroup(final String name, final boolean readOnly) {
return call("createPrivateGroup", TIMEOUT_MS, () -> new JSONArray()
.put(name)
.put(new JSONArray())
.put(readOnly))
.onSuccessTask(task -> Task.forResult(null));
}
public Task<String> createDirectMessage(final String username) {
return call("createDirectMessage", TIMEOUT_MS, () -> new JSONArray().put(username))
.onSuccessTask(CONVERT_TO_JSON_OBJECT)
.onSuccessTask(task -> Task.forResult(task.getResult().getString("rid")));
}
/**
* send message.
*/
public Task<Void> sendMessage(String messageId, String roomId, String msg, long editedAt) {
try {
JSONObject messageJson = new JSONObject()
.put("_id", messageId)
.put("rid", roomId)
.put("msg", msg);
if (editedAt == 0) {
return sendMessage(messageJson);
} else { } else {
jsonString = jsonString.replace("]", "") + "," + userJsonArray.toString().replace("[", ""); return updateMessage(messageJson);
} }
} } catch (JSONException exception) {
return Task.forError(exception);
if (jsonString != null) { }
String jsonStringResults = jsonString; }
realmHelper.executeTransaction(realm -> {
realm.delete(RealmSpotlight.class);
realm.createOrUpdateAllFromJson(RealmSpotlight.class, jsonStringResults);
return null;
});
}
return null;
});
}
private Task<Void> searchSpotlight(Class clazz, String key, String term) {
return call("spotlight", TIMEOUT_MS, () -> new JSONArray()
.put(term)
.put(JSONObject.NULL)
.put(new JSONObject().put(key, true)))
.onSuccessTask(CONVERT_TO_JSON_OBJECT)
.onSuccessTask(task -> {
final JSONObject result = task.getResult();
if (!result.has(key)) {
return null;
}
Object items = result.get(key); public Task<Void> deleteMessage(String messageID) {
if (!(items instanceof JSONArray)) { try {
return null; JSONObject messageJson = new JSONObject()
} .put("_id", messageID);
return realmHelper.executeTransaction(realm -> { return deleteMessage(messageJson);
realm.delete(clazz); } catch (JSONException exception) {
realm.createOrUpdateAllFromJson(clazz, (JSONArray) items); return Task.forError(exception);
return null; }
}); }
});
} /**
* Send message object.
*/
private Task<Void> sendMessage(final JSONObject messageJson) {
return call("sendMessage", TIMEOUT_MS, () -> new JSONArray().put(messageJson))
.onSuccessTask(task -> Task.forResult(null));
}
private Task<Void> updateMessage(final JSONObject messageJson) {
return call("updateMessage", TIMEOUT_MS, () -> new JSONArray().put(messageJson))
.onSuccessTask(task -> Task.forResult(null));
}
private Task<Void> deleteMessage(final JSONObject messageJson) {
return call("deleteMessage", TIMEOUT_MS, () -> new JSONArray().put(messageJson))
.onSuccessTask(task -> Task.forResult(null));
}
/**
* mark all messages are read in the room.
*/
public Task<Void> readMessages(final String roomId) {
return call("readMessages", TIMEOUT_MS, () -> new JSONArray().put(roomId))
.onSuccessTask(task -> Task.forResult(null));
}
public Task<Void> getPublicSettings(String currentHostname) {
return call("public-settings/get", TIMEOUT_MS)
.onSuccessTask(CONVERT_TO_JSON_ARRAY)
.onSuccessTask(task -> {
final JSONArray settings = task.getResult();
String siteUrl = null;
String siteName = null;
for (int i = 0; i < settings.length(); i++) {
JSONObject jsonObject = settings.getJSONObject(i);
RealmPublicSetting.customizeJson(jsonObject);
if (isPublicSetting(jsonObject, PublicSettingsConstants.General.SITE_URL)) {
siteUrl = jsonObject.getString(RealmPublicSetting.VALUE);
} else if (isPublicSetting(jsonObject, PublicSettingsConstants.General.SITE_NAME)) {
siteName = jsonObject.getString(RealmPublicSetting.VALUE);
}
}
if (siteName != null && siteUrl != null) {
HttpUrl httpSiteUrl = HttpUrl.parse(siteUrl);
if (httpSiteUrl != null) {
String host = httpSiteUrl.host();
RocketChatCache.INSTANCE.addSiteUrl(host, currentHostname);
RocketChatCache.INSTANCE.addSiteName(currentHostname, siteName);
}
}
return realmHelper.executeTransaction(realm -> {
realm.delete(RealmPublicSetting.class);
realm.createOrUpdateAllFromJson(RealmPublicSetting.class, settings);
return null;
});
});
}
private boolean isPublicSetting(JSONObject jsonObject, String id) {
return jsonObject.optString(RealmPublicSetting.ID).equalsIgnoreCase(id);
}
public Task<Void> getPermissions() {
return call("permissions/get", TIMEOUT_MS)
.onSuccessTask(CONVERT_TO_JSON_ARRAY)
.onSuccessTask(task -> {
final JSONArray permissions = task.getResult();
for (int i = 0; i < permissions.length(); i++) {
RealmPermission.customizeJson(permissions.getJSONObject(i));
}
return realmHelper.executeTransaction(realm -> {
realm.delete(RealmPermission.class);
realm.createOrUpdateAllFromJson(RealmPermission.class, permissions);
return null;
});
});
}
public Task<Void> getRoomRoles(final String roomId) {
return call("getRoomRoles", TIMEOUT_MS, () -> new JSONArray().put(roomId))
.onSuccessTask(CONVERT_TO_JSON_ARRAY)
.onSuccessTask(task -> {
final JSONArray roomRoles = task.getResult();
for (int i = 0; i < roomRoles.length(); i++) {
RealmRoomRole.customizeJson(roomRoles.getJSONObject(i));
}
return realmHelper.executeTransaction(realm -> {
realm.delete(RealmRoomRole.class);
realm.createOrUpdateAllFromJson(RealmRoomRole.class, roomRoles);
return null;
});
});
}
public Task<Void> searchSpotlightUsers(String term) {
return searchSpotlight(RealmSpotlightUser.class, "users", term);
}
protected interface ParamBuilder { public Task<Void> searchSpotlightRooms(String term) {
JSONArray buildParam() throws JSONException; return searchSpotlight(RealmSpotlightRoom.class, "rooms", term);
} }
public Task<Void> searchSpotlight(String term) {
return call("spotlight", TIMEOUT_MS, () ->
new JSONArray()
.put(term)
.put(JSONObject.NULL)
.put(new JSONObject().put("rooms", true).put("users", true))
).onSuccessTask(CONVERT_TO_JSON_OBJECT)
.onSuccessTask(task -> {
String jsonString = null;
final JSONObject result = task.getResult();
JSONArray roomJsonArray = (JSONArray) result.get("rooms");
int roomTotal = roomJsonArray.length();
if (roomTotal > 0) {
for (int i = 0; i < roomTotal; ++i) {
RealmSpotlight.Companion.customizeRoomJSONObject(roomJsonArray.getJSONObject(i));
}
jsonString = roomJsonArray.toString();
}
JSONArray userJsonArray = (JSONArray) result.get("users");
int usersTotal = userJsonArray.length();
if (usersTotal > 0) {
for (int i = 0; i < usersTotal; ++i) {
RealmSpotlight.Companion.customizeUserJSONObject(userJsonArray.getJSONObject(i));
}
if (jsonString == null) {
jsonString = userJsonArray.toString();
} else {
jsonString = jsonString.replace("]", "") + "," + userJsonArray.toString().replace("[", "");
}
}
if (jsonString != null) {
String jsonStringResults = jsonString;
realmHelper.executeTransaction(realm -> {
realm.delete(RealmSpotlight.class);
realm.createOrUpdateAllFromJson(RealmSpotlight.class, jsonStringResults);
return null;
});
}
return null;
});
}
private Task<Void> searchSpotlight(Class clazz, String key, String term) {
return call("spotlight", TIMEOUT_MS, () -> new JSONArray()
.put(term)
.put(JSONObject.NULL)
.put(new JSONObject().put(key, true)))
.onSuccessTask(CONVERT_TO_JSON_OBJECT)
.onSuccessTask(task -> {
final JSONObject result = task.getResult();
if (!result.has(key)) {
return null;
}
Object items = result.get(key);
if (!(items instanceof JSONArray)) {
return null;
}
return realmHelper.executeTransaction(realm -> {
realm.delete(clazz);
realm.createOrUpdateAllFromJson(clazz, (JSONArray) items);
return null;
});
});
}
protected interface ParamBuilder {
JSONArray buildParam() throws JSONException;
}
} }
...@@ -8,42 +8,37 @@ import chat.rocket.persistence.realm.models.internal.RealmSession; ...@@ -8,42 +8,37 @@ import chat.rocket.persistence.realm.models.internal.RealmSession;
public class DefaultCookieProvider implements CookieProvider { public class DefaultCookieProvider implements CookieProvider {
private RocketChatCache rocketChatCache;
public DefaultCookieProvider(RocketChatCache rocketChatCache) {
this.rocketChatCache = rocketChatCache;
}
@Override
public String getHostname() {
return getHostnameFromCache();
}
@Override
public String getCookie() {
final String hostname = getHostnameFromCache();
if (hostname == null) {
return "";
}
final RealmHelper realmHelper = RealmStore.get(getHostnameFromCache()); @Override
if (realmHelper == null) { public String getHostname() {
return ""; return getHostnameFromCache();
} }
final RealmUser user = realmHelper.executeTransactionForRead(realm -> @Override
RealmUser.queryCurrentUser(realm).findFirst()); public String getCookie() {
final RealmSession session = realmHelper.executeTransactionForRead(realm -> final String hostname = getHostnameFromCache();
RealmSession.queryDefaultSession(realm).findFirst()); if (hostname == null) {
return "";
}
if (user == null || session == null) { final RealmHelper realmHelper = RealmStore.get(getHostnameFromCache());
return ""; if (realmHelper == null) {
} return "";
}
final RealmUser user = realmHelper.executeTransactionForRead(realm ->
RealmUser.queryCurrentUser(realm).findFirst());
final RealmSession session = realmHelper.executeTransactionForRead(realm ->
RealmSession.queryDefaultSession(realm).findFirst());
return "rc_uid=" + user.getId() + ";rc_token=" + session.getToken(); if (user == null || session == null) {
} return "";
}
private String getHostnameFromCache() { return "rc_uid=" + user.getId() + ";rc_token=" + session.getToken();
return rocketChatCache.getSelectedServerHostname(); }
}
private String getHostnameFromCache() {
return RocketChatCache.INSTANCE.getSelectedServerHostname();
}
} }
package chat.rocket.android.api.rest package chat.rocket.android.api.rest
import chat.rocket.android.R
import chat.rocket.android.RocketChatApplication
import chat.rocket.android.helper.UrlHelper import chat.rocket.android.helper.UrlHelper
import chat.rocket.android.push.gcm.GcmPushHelper
import chat.rocket.core.models.Room import chat.rocket.core.models.Room
import com.google.android.gms.gcm.GoogleCloudMessaging
import com.google.android.gms.iid.InstanceID
import okhttp3.HttpUrl import okhttp3.HttpUrl
import okhttp3.MediaType
import okhttp3.Request import okhttp3.Request
import okhttp3.RequestBody
import org.json.JSONObject
import java.io.IOException
/** /**
* Helper class for dealing with Rest API calls. * Helper class for dealing with Rest API calls.
...@@ -139,6 +148,30 @@ object RestApiHelper { ...@@ -139,6 +148,30 @@ object RestApiHelper {
.build() .build()
} }
fun getRequestForPushTokenRegistration(hostname: String,
token: String,
userId: String): Request {
val parsedHttpUrl = HttpUrl.parse(getEndpointUrlForPushToken(hostname))
?.newBuilder()
?.build()
val json = JSONObject()
.put("type", "gcm")
.put("appName", "main")
.put("value", GcmPushHelper.getGcmToken())
val requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"),
json.toString())
return Request.Builder()
.url(parsedHttpUrl)
.post(requestBody)
.addHeader("X-Auth-Token", token)
.addHeader("X-User-Id", userId)
.addHeader("Content-Type", "application/json")
.build()
}
/** /**
* Returns a Rest API endpoint URL for favorite or pinned messages accordingly with the room type and the server hostname. * Returns a Rest API endpoint URL for favorite or pinned messages accordingly with the room type and the server hostname.
* *
...@@ -169,6 +202,9 @@ object RestApiHelper { ...@@ -169,6 +202,9 @@ object RestApiHelper {
fun getEndpointUrlForMemberList(roomType: String, hostname: String): String = fun getEndpointUrlForMemberList(roomType: String, hostname: String): String =
UrlHelper.getSafeHostname(hostname) + getRestApiUrlForMemberList(roomType) UrlHelper.getSafeHostname(hostname) + getRestApiUrlForMemberList(roomType)
fun getEndpointUrlForPushToken(hostname: String): String =
UrlHelper.getSafeHostname(hostname) + getRestApiUrlForPushToken()
/** /**
* Returns the correspondent Rest API URL accordingly with the room type to get its favorite or pinned messages. * Returns the correspondent Rest API URL accordingly with the room type to get its favorite or pinned messages.
* *
...@@ -216,4 +252,21 @@ object RestApiHelper { ...@@ -216,4 +252,21 @@ object RestApiHelper {
} }
return restApiUrl return restApiUrl
} }
/**
* Returns the correspondent Rest API URL for registration/deletion of Gcm Registration token.
*/
fun getRestApiUrlForPushToken(): String {
return "/api/v1/push.token"
}
@Throws(IOException::class)
private fun getGcmToken(senderId: String): String {
return InstanceID.getInstance(RocketChatApplication.getInstance())
.getToken(senderId, GoogleCloudMessaging.INSTANCE_ID_SCOPE, null)
}
private fun getSenderId(): String {
return RocketChatApplication.getInstance().getString(R.string.gcm_sender_id)
}
} }
\ No newline at end of file
package chat.rocket.android.extensions
import chat.rocket.android.BuildConfig
fun Throwable.printStackTraceOnDebug() {
if (BuildConfig.DEBUG) {
this.printStackTrace()
}
}
\ No newline at end of file
...@@ -7,7 +7,7 @@ public interface InputHostnameContract { ...@@ -7,7 +7,7 @@ public interface InputHostnameContract {
interface View extends BaseContract.View { interface View extends BaseContract.View {
void showLoader(); void showLoader();
void hideLoader(); void hideLoader(Boolean isValidServerUrl);
void showInvalidServerError(); void showInvalidServerError();
......
...@@ -6,12 +6,12 @@ import android.support.annotation.Nullable; ...@@ -6,12 +6,12 @@ import android.support.annotation.Nullable;
import android.support.constraint.ConstraintLayout; import android.support.constraint.ConstraintLayout;
import android.support.design.widget.Snackbar; import android.support.design.widget.Snackbar;
import android.view.View; import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.TextView; import android.widget.TextView;
import chat.rocket.android.BuildConfig; import chat.rocket.android.BuildConfig;
import chat.rocket.android.LaunchUtil; import chat.rocket.android.LaunchUtil;
import chat.rocket.android.R; import chat.rocket.android.R;
import chat.rocket.android.RocketChatCache;
import chat.rocket.android.fragment.AbstractFragment; import chat.rocket.android.fragment.AbstractFragment;
import chat.rocket.android.helper.TextUtils; import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.service.ConnectivityManager; import chat.rocket.android.service.ConnectivityManager;
...@@ -21,90 +21,101 @@ import chat.rocket.android.service.ConnectivityManager; ...@@ -21,90 +21,101 @@ import chat.rocket.android.service.ConnectivityManager;
*/ */
public class InputHostnameFragment extends AbstractFragment implements InputHostnameContract.View { public class InputHostnameFragment extends AbstractFragment implements InputHostnameContract.View {
private InputHostnameContract.Presenter presenter; private InputHostnameContract.Presenter presenter;
private ConstraintLayout container; private ConstraintLayout container;
private View waitingView; private View waitingView;
public InputHostnameFragment() {} public InputHostnameFragment() {
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) { @Override
super.onCreate(savedInstanceState); public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Context appContext = getContext().getApplicationContext();
presenter = new InputHostnamePresenter(new RocketChatCache(appContext), ConnectivityManager.getInstance(appContext)); Context appContext = getContext().getApplicationContext();
} presenter = new InputHostnamePresenter(ConnectivityManager.getInstance(appContext));
}
@Override
protected int getLayout() { @Override
return R.layout.fragment_input_hostname; protected int getLayout() {
} return R.layout.fragment_input_hostname;
}
@Override
protected void onSetupView() { @Override
setupVersionInfo(); protected void onSetupView() {
setupVersionInfo();
container = rootView.findViewById(R.id.container);
waitingView = rootView.findViewById(R.id.waiting); container = rootView.findViewById(R.id.container);
rootView.findViewById(R.id.btn_connect).setOnClickListener(view -> handleConnect()); waitingView = rootView.findViewById(R.id.waiting);
} rootView.findViewById(R.id.btn_connect).setOnClickListener(view -> handleConnect());
}
private void setupVersionInfo() {
TextView versionInfoView = (TextView) rootView.findViewById(R.id.version_info); private void setupVersionInfo() {
versionInfoView.setText(getString(R.string.version_info_text, BuildConfig.VERSION_NAME)); TextView versionInfoView = rootView.findViewById(R.id.version_info);
} versionInfoView.setText(getString(R.string.version_info_text, BuildConfig.VERSION_NAME));
}
private void handleConnect() {
presenter.connectTo(getHostname()); private void handleConnect() {
} hideSoftKeyboard();
presenter.connectTo(getHostname());
@Override }
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState); private void hideSoftKeyboard() {
presenter.bindView(this); InputMethodManager inputManager = (InputMethodManager)
} getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
inputManager.hideSoftInputFromWindow(getActivity().getCurrentFocus().getWindowToken(),
@Override InputMethodManager.HIDE_NOT_ALWAYS);
public void onDestroyView() { }
presenter.release();
super.onDestroyView(); @Override
} public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
private String getHostname() { presenter.bindView(this);
final TextView editor = (TextView) rootView.findViewById(R.id.editor_hostname); }
return TextUtils.or(TextUtils.or(editor.getText(), editor.getHint()), "").toString().toLowerCase(); @Override
} public void onDestroyView() {
presenter.release();
private void showError(String errString) { super.onDestroyView();
Snackbar.make(rootView, errString, Snackbar.LENGTH_LONG).show(); }
}
private String getHostname() {
@Override final TextView editor = (TextView) rootView.findViewById(R.id.editor_hostname);
public void showLoader() {
container.setVisibility(View.GONE); return TextUtils.or(TextUtils.or(editor.getText(), editor.getHint()), "").toString().toLowerCase();
waitingView.setVisibility(View.VISIBLE); }
}
private void showError(String errString) {
@Override Snackbar.make(rootView, errString, Snackbar.LENGTH_LONG).show();
public void hideLoader() { }
waitingView.setVisibility(View.GONE);
container.setVisibility(View.VISIBLE); @Override
} public void showLoader() {
container.setVisibility(View.GONE);
@Override waitingView.setVisibility(View.VISIBLE);
public void showInvalidServerError() { }
showError(getString(R.string.input_hostname_invalid_server_message));
} @Override
public void hideLoader(Boolean isValidServerUrl) {
@Override if(!isValidServerUrl) {
public void showConnectionError() { waitingView.setVisibility(View.GONE);
showError(getString(R.string.connection_error_try_later)); container.setVisibility(View.VISIBLE);
} }
}
@Override
public void showHome() { @Override
LaunchUtil.showMainActivity(getContext()); public void showInvalidServerError() {
getActivity().overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); showError(getString(R.string.input_hostname_invalid_server_message));
} }
@Override
public void showConnectionError() {
showError(getString(R.string.connection_error_try_later));
}
@Override
public void showHome() {
LaunchUtil.showMainActivity(getContext());
getActivity().overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
}
} }
...@@ -14,11 +14,10 @@ import io.reactivex.android.schedulers.AndroidSchedulers; ...@@ -14,11 +14,10 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable; import io.reactivex.disposables.Disposable;
public class InputHostnamePresenter extends BasePresenter<InputHostnameContract.View> implements InputHostnameContract.Presenter { public class InputHostnamePresenter extends BasePresenter<InputHostnameContract.View> implements InputHostnameContract.Presenter {
private final RocketChatCache rocketChatCache;
private final ConnectivityManagerApi connectivityManager; private final ConnectivityManagerApi connectivityManager;
private boolean isValidServerUrl;
public InputHostnamePresenter(RocketChatCache rocketChatCache, ConnectivityManagerApi connectivityManager) { public InputHostnamePresenter(ConnectivityManagerApi connectivityManager) {
this.rocketChatCache = rocketChatCache;
this.connectivityManager = connectivityManager; this.connectivityManager = connectivityManager;
} }
...@@ -37,24 +36,25 @@ public class InputHostnamePresenter extends BasePresenter<InputHostnameContract. ...@@ -37,24 +36,25 @@ public class InputHostnamePresenter extends BasePresenter<InputHostnameContract.
final Disposable subscription = ServerPolicyHelper.isApiVersionValid(validationHelper) final Disposable subscription = ServerPolicyHelper.isApiVersionValid(validationHelper)
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get())) .subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.doOnTerminate(() -> view.hideLoader()) .doOnTerminate(() -> view.hideLoader(isValidServerUrl))
.subscribe( .subscribe(
serverValidation -> { serverValidation -> {
if (serverValidation.isValid()) { if (serverValidation.isValid()) {
isValidServerUrl=true;
onServerValid(hostname, serverValidation.usesSecureConnection()); onServerValid(hostname, serverValidation.usesSecureConnection());
} else { } else {
view.showInvalidServerError(); view.showInvalidServerError();
} }
}, },
throwable -> { throwable -> {
Logger.report(throwable); Logger.INSTANCE.report(throwable);
view.showConnectionError(); view.showConnectionError();
}); });
addSubscription(subscription); addSubscription(subscription);
} }
private void onServerValid(String hostname, boolean usesSecureConnection) { private void onServerValid(String hostname, boolean usesSecureConnection) {
rocketChatCache.setSelectedServerHostname(hostname); RocketChatCache.INSTANCE.setSelectedServerHostname(hostname);
String server = hostname.replace("/", "."); String server = hostname.replace("/", ".");
connectivityManager.addOrUpdateServer(server, server, !usesSecureConnection); connectivityManager.addOrUpdateServer(server, server, !usesSecureConnection);
......
...@@ -11,7 +11,7 @@ import chat.rocket.android.fragment.AbstractFragment; ...@@ -11,7 +11,7 @@ import chat.rocket.android.fragment.AbstractFragment;
import chat.rocket.android.widget.RoomToolbar; import chat.rocket.android.widget.RoomToolbar;
import chat.rocket.core.models.User; import chat.rocket.core.models.User;
abstract class AbstractChatRoomFragment extends AbstractFragment { public abstract class AbstractChatRoomFragment extends AbstractFragment {
private RoomToolbar roomToolbar; private RoomToolbar roomToolbar;
@Nullable @Nullable
......
package chat.rocket.android.fragment.chatroom;
import chat.rocket.android.R;
public class HomeFragment extends AbstractChatRoomFragment {
public HomeFragment() {}
@Override
protected int getLayout() {
return R.layout.fragment_home;
}
@Override
protected void onSetupView() {
setToolbarTitle(getText(R.string.home_fragment_title));
}
}
\ No newline at end of file
package chat.rocket.android.fragment.chatroom
import chat.rocket.android.R
class HomeFragment : AbstractChatRoomFragment() {
override fun getLayout(): Int {
return R.layout.fragment_home
}
override fun onSetupView() {
setToolbarTitle(getText(R.string.home_fragment_title))
}
}
\ No newline at end of file
package chat.rocket.android.fragment.chatroom;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.util.List;
import chat.rocket.android.shared.BaseContract;
import chat.rocket.android.widget.AbsoluteUrl;
import chat.rocket.core.models.Message;
import chat.rocket.core.models.Room;
import chat.rocket.core.models.User;
public interface RoomContract {
interface View extends BaseContract.View {
void setupWith(RocketChatAbsoluteUrl rocketChatAbsoluteUrl);
void render(Room room);
void showUserStatus(User user);
void updateHistoryState(boolean hasNext, boolean isLoaded);
void onMessageSendSuccessfully();
void disableMessageInput();
void enableMessageInput();
void showUnreadCount(int count);
void showMessages(List<Message> messages);
void showMessageSendFailure(Message message);
void showMessageDeleteFailure(Message message);
void autoloadImages();
void manualLoadImages();
void onReply(AbsoluteUrl absoluteUrl, String markdown, Message message);
void onCopy(String message);
void showMessageActions(Message message);
}
interface Presenter extends BaseContract.Presenter<View> {
void loadMessages();
void loadMoreMessages();
void onMessageSelected(@Nullable Message message);
void onMessageTap(@Nullable Message message);
void sendMessage(String messageText);
void resendMessage(@NonNull Message message);
void updateMessage(@NonNull Message message, String content);
void deleteMessage(@NonNull Message message);
void onUnreadCount();
void onMarkAsRead();
void refreshRoom();
void replyMessage(@NonNull Message message, boolean justQuote);
void acceptMessageDeleteFailure(Message message);
}
}
package chat.rocket.android.fragment.chatroom
import chat.rocket.android.shared.BaseContract
import chat.rocket.android.widget.AbsoluteUrl
import chat.rocket.core.models.Message
import chat.rocket.core.models.Room
import chat.rocket.core.models.User
interface RoomContract {
interface View : BaseContract.View {
fun setupWith(rocketChatAbsoluteUrl: RocketChatAbsoluteUrl)
fun render(room: Room)
fun showUserStatus(user: User)
fun updateHistoryState(hasNext: Boolean, isLoaded: Boolean)
fun onMessageSendSuccessfully()
fun disableMessageInput()
fun enableMessageInput()
fun showUnreadCount(count: Int)
fun showMessages(messages: List<Message>)
fun showMessageSendFailure(message: Message)
fun showMessageDeleteFailure(message: Message)
fun autoloadImages()
fun manualLoadImages()
fun onReply(absoluteUrl: AbsoluteUrl, markdown: String, message: Message)
fun onCopy(message: String)
fun showMessageActions(message: Message)
}
interface Presenter : BaseContract.Presenter<View> {
fun loadMessages()
fun loadMoreMessages()
fun onMessageSelected(message: Message?)
fun onMessageTap(message: Message?)
fun sendMessage(messageText: String)
fun resendMessage(message: Message)
fun updateMessage(message: Message, content: String)
fun deleteMessage(message: Message)
fun onUnreadCount()
fun onMarkAsRead()
fun refreshRoom()
fun replyMessage(message: Message, justQuote: Boolean)
fun acceptMessageDeleteFailure(message: Message)
fun loadMissedMessages()
}
}
...@@ -131,7 +131,7 @@ public class RoomFragment extends AbstractChatRoomFragment implements ...@@ -131,7 +131,7 @@ public class RoomFragment extends AbstractChatRoomFragment implements
private MethodCallHelper methodCallHelper; private MethodCallHelper methodCallHelper;
private AbsoluteUrlHelper absoluteUrlHelper; private AbsoluteUrlHelper absoluteUrlHelper;
private Message edittingMessage = null; private Message editingMessage = null;
private RoomToolbar toolbar; private RoomToolbar toolbar;
...@@ -344,7 +344,7 @@ public class RoomFragment extends AbstractChatRoomFragment implements ...@@ -344,7 +344,7 @@ public class RoomFragment extends AbstractChatRoomFragment implements
optionalPane.ifPresent(pane -> pane.setPanelSlideListener(new SlidingPaneLayout.PanelSlideListener() { optionalPane.ifPresent(pane -> pane.setPanelSlideListener(new SlidingPaneLayout.PanelSlideListener() {
@Override @Override
public void onPanelSlide(View view, float v) { public void onPanelSlide(@NonNull View view, float v) {
messageFormManager.enableComposingText(false); messageFormManager.enableComposingText(false);
sidebarFragment.clearSearchViewFocus(); sidebarFragment.clearSearchViewFocus();
//Ref: ActionBarDrawerToggle#setProgress //Ref: ActionBarDrawerToggle#setProgress
...@@ -352,12 +352,12 @@ public class RoomFragment extends AbstractChatRoomFragment implements ...@@ -352,12 +352,12 @@ public class RoomFragment extends AbstractChatRoomFragment implements
} }
@Override @Override
public void onPanelOpened(View view) { public void onPanelOpened(@NonNull View view) {
toolbar.setNavigationIconVerticalMirror(true); toolbar.setNavigationIconVerticalMirror(true);
} }
@Override @Override
public void onPanelClosed(View view) { public void onPanelClosed(@NonNull View view) {
messageFormManager.enableComposingText(true); messageFormManager.enableComposingText(true);
toolbar.setNavigationIconVerticalMirror(false); toolbar.setNavigationIconVerticalMirror(false);
subPane.closePane(); subPane.closePane();
...@@ -487,8 +487,8 @@ public class RoomFragment extends AbstractChatRoomFragment implements ...@@ -487,8 +487,8 @@ public class RoomFragment extends AbstractChatRoomFragment implements
@Override @Override
public boolean onBackPressed() { public boolean onBackPressed() {
if (edittingMessage != null) { if (editingMessage != null) {
edittingMessage = null; editingMessage = null;
messageFormManager.clearComposingText(); messageFormManager.clearComposingText();
} }
return false; return false;
...@@ -540,22 +540,22 @@ public class RoomFragment extends AbstractChatRoomFragment implements ...@@ -540,22 +540,22 @@ public class RoomFragment extends AbstractChatRoomFragment implements
inputContentInfo.releasePermission(); inputContentInfo.releasePermission();
} catch (Exception e) { } catch (Exception e) {
RCLog.e(e); RCLog.e(e);
Logger.report(e); Logger.INSTANCE.report(e);
} }
return true; return true;
} }
private void sendMessage(String messageText) { private void sendMessage(String messageText) {
if (edittingMessage == null) { if (editingMessage == null) {
presenter.sendMessage(messageText); presenter.sendMessage(messageText);
} else { } else {
presenter.updateMessage(edittingMessage, messageText); presenter.updateMessage(editingMessage, messageText);
} }
} }
@Override @Override
public void setupWith(RocketChatAbsoluteUrl rocketChatAbsoluteUrl) { public void setupWith(@NonNull RocketChatAbsoluteUrl rocketChatAbsoluteUrl) {
if (rocketChatAbsoluteUrl != null) { if (rocketChatAbsoluteUrl != null) {
token = rocketChatAbsoluteUrl.getToken(); token = rocketChatAbsoluteUrl.getToken();
userId = rocketChatAbsoluteUrl.getUserId(); userId = rocketChatAbsoluteUrl.getUserId();
...@@ -564,7 +564,7 @@ public class RoomFragment extends AbstractChatRoomFragment implements ...@@ -564,7 +564,7 @@ public class RoomFragment extends AbstractChatRoomFragment implements
} }
@Override @Override
public void render(Room room) { public void render(@NonNull Room room) {
roomType = room.getType(); roomType = room.getType();
setToolbarTitle(room.getName()); setToolbarTitle(room.getName());
...@@ -589,7 +589,7 @@ public class RoomFragment extends AbstractChatRoomFragment implements ...@@ -589,7 +589,7 @@ public class RoomFragment extends AbstractChatRoomFragment implements
} }
@Override @Override
public void showUserStatus(User user) { public void showUserStatus(@NonNull User user) {
showToolbarUserStatuslIcon(user.getStatus()); showToolbarUserStatuslIcon(user.getStatus());
} }
...@@ -610,7 +610,7 @@ public class RoomFragment extends AbstractChatRoomFragment implements ...@@ -610,7 +610,7 @@ public class RoomFragment extends AbstractChatRoomFragment implements
public void onMessageSendSuccessfully() { public void onMessageSendSuccessfully() {
scrollToLatestMessage(); scrollToLatestMessage();
messageFormManager.onMessageSend(); messageFormManager.onMessageSend();
edittingMessage = null; editingMessage = null;
} }
@Override @Override
...@@ -629,15 +629,16 @@ public class RoomFragment extends AbstractChatRoomFragment implements ...@@ -629,15 +629,16 @@ public class RoomFragment extends AbstractChatRoomFragment implements
} }
@Override @Override
public void showMessages(List<Message> messages) { public void showMessages(@NonNull List<? extends Message> messages) {
if (messageListAdapter == null) { if (messageListAdapter == null) {
return; return;
} }
messageListAdapter.updateData(messages);
messageListAdapter.updateData((List<Message>) messages);
} }
@Override @Override
public void showMessageSendFailure(Message message) { public void showMessageSendFailure(@NonNull Message message) {
new AlertDialog.Builder(getContext()) new AlertDialog.Builder(getContext())
.setPositiveButton(R.string.resend, .setPositiveButton(R.string.resend,
(dialog, which) -> presenter.resendMessage(message)) (dialog, which) -> presenter.resendMessage(message))
...@@ -648,7 +649,7 @@ public class RoomFragment extends AbstractChatRoomFragment implements ...@@ -648,7 +649,7 @@ public class RoomFragment extends AbstractChatRoomFragment implements
} }
@Override @Override
public void showMessageDeleteFailure(Message message) { public void showMessageDeleteFailure(@NonNull Message message) {
new AlertDialog.Builder(getContext()) new AlertDialog.Builder(getContext())
.setTitle(getContext().getString(R.string.failed_to_delete)) .setTitle(getContext().getString(R.string.failed_to_delete))
.setMessage(getContext().getString(R.string.failed_to_delete_message)) .setMessage(getContext().getString(R.string.failed_to_delete_message))
...@@ -667,12 +668,12 @@ public class RoomFragment extends AbstractChatRoomFragment implements ...@@ -667,12 +668,12 @@ public class RoomFragment extends AbstractChatRoomFragment implements
} }
@Override @Override
public void onReply(AbsoluteUrl absoluteUrl, String markdown, Message message) { public void onReply(@NonNull AbsoluteUrl absoluteUrl, @NonNull String markdown, @NonNull Message message) {
messageFormManager.setReply(absoluteUrl, markdown, message); messageFormManager.setReply(absoluteUrl, markdown, message);
} }
@Override @Override
public void onCopy(String message) { public void onCopy(@NonNull String message) {
RocketChatApplication context = RocketChatApplication.getInstance(); RocketChatApplication context = RocketChatApplication.getInstance();
ClipboardManager clipboardManager = ClipboardManager clipboardManager =
(ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
...@@ -680,7 +681,7 @@ public class RoomFragment extends AbstractChatRoomFragment implements ...@@ -680,7 +681,7 @@ public class RoomFragment extends AbstractChatRoomFragment implements
} }
@Override @Override
public void showMessageActions(Message message) { public void showMessageActions(@NonNull Message message) {
Activity context = getActivity(); Activity context = getActivity();
if (context != null && context instanceof MainActivity) { if (context != null && context instanceof MainActivity) {
MessagePopup.take(message) MessagePopup.take(message)
...@@ -694,7 +695,7 @@ public class RoomFragment extends AbstractChatRoomFragment implements ...@@ -694,7 +695,7 @@ public class RoomFragment extends AbstractChatRoomFragment implements
} }
private void onEditMessage(Message message) { private void onEditMessage(Message message) {
edittingMessage = message; editingMessage = message;
messageFormManager.setEditMessage(message.getMessage()); messageFormManager.setEditMessage(message.getMessage());
} }
...@@ -716,7 +717,7 @@ public class RoomFragment extends AbstractChatRoomFragment implements ...@@ -716,7 +717,7 @@ public class RoomFragment extends AbstractChatRoomFragment implements
} }
} }
public void loadMessages() { public void loadMissedMessages() {
presenter.loadMessages(); presenter.loadMissedMessages();
} }
} }
\ No newline at end of file
...@@ -6,11 +6,17 @@ import android.support.v4.util.Pair; ...@@ -6,11 +6,17 @@ import android.support.v4.util.Pair;
import com.hadisatrio.optional.Optional; import com.hadisatrio.optional.Optional;
import org.json.JSONException;
import org.json.JSONObject;
import chat.rocket.android.BackgroundLooper; import chat.rocket.android.BackgroundLooper;
import chat.rocket.android.RocketChatApplication;
import chat.rocket.android.RocketChatCache;
import chat.rocket.android.api.MethodCallHelper; import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.android.helper.AbsoluteUrlHelper; import chat.rocket.android.helper.AbsoluteUrlHelper;
import chat.rocket.android.helper.LogIfError; import chat.rocket.android.helper.LogIfError;
import chat.rocket.android.helper.Logger; import chat.rocket.android.helper.Logger;
import chat.rocket.android.log.RCLog;
import chat.rocket.android.service.ConnectivityManagerApi; import chat.rocket.android.service.ConnectivityManagerApi;
import chat.rocket.android.shared.BasePresenter; import chat.rocket.android.shared.BasePresenter;
import chat.rocket.core.SyncState; import chat.rocket.core.SyncState;
...@@ -21,391 +27,420 @@ import chat.rocket.core.models.Settings; ...@@ -21,391 +27,420 @@ import chat.rocket.core.models.Settings;
import chat.rocket.core.models.User; import chat.rocket.core.models.User;
import chat.rocket.core.repositories.RoomRepository; import chat.rocket.core.repositories.RoomRepository;
import chat.rocket.core.repositories.UserRepository; import chat.rocket.core.repositories.UserRepository;
import io.reactivex.Flowable;
import io.reactivex.Single; import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable; import io.reactivex.disposables.Disposable;
public class RoomPresenter extends BasePresenter<RoomContract.View> public class RoomPresenter extends BasePresenter<RoomContract.View>
implements RoomContract.Presenter { implements RoomContract.Presenter {
private final String roomId; private final String roomId;
private final MessageInteractor messageInteractor; private final MessageInteractor messageInteractor;
private final UserRepository userRepository; private final UserRepository userRepository;
private final RoomRepository roomRepository; private final RoomRepository roomRepository;
private final AbsoluteUrlHelper absoluteUrlHelper; private final AbsoluteUrlHelper absoluteUrlHelper;
private final MethodCallHelper methodCallHelper; private final MethodCallHelper methodCallHelper;
private final ConnectivityManagerApi connectivityManagerApi; private final ConnectivityManagerApi connectivityManagerApi;
private Room currentRoom; private Room currentRoom;
public RoomPresenter(String roomId, /* package */RoomPresenter(String roomId,
UserRepository userRepository, UserRepository userRepository,
MessageInteractor messageInteractor, MessageInteractor messageInteractor,
RoomRepository roomRepository, RoomRepository roomRepository,
AbsoluteUrlHelper absoluteUrlHelper, AbsoluteUrlHelper absoluteUrlHelper,
MethodCallHelper methodCallHelper, MethodCallHelper methodCallHelper,
ConnectivityManagerApi connectivityManagerApi) { ConnectivityManagerApi connectivityManagerApi) {
this.roomId = roomId; this.roomId = roomId;
this.userRepository = userRepository; this.userRepository = userRepository;
this.messageInteractor = messageInteractor; this.messageInteractor = messageInteractor;
this.roomRepository = roomRepository; this.roomRepository = roomRepository;
this.absoluteUrlHelper = absoluteUrlHelper; this.absoluteUrlHelper = absoluteUrlHelper;
this.methodCallHelper = methodCallHelper; this.methodCallHelper = methodCallHelper;
this.connectivityManagerApi = connectivityManagerApi; this.connectivityManagerApi = connectivityManagerApi;
} }
@Override
public void bindView(@NonNull RoomContract.View view) {
super.bindView(view);
refreshRoom();
}
@Override
public void refreshRoom() {
getRoomRoles();
getRoomInfo();
getRoomHistoryStateInfo();
getMessages();
getUserPreferences();
getAbsoluteUrl();
}
@Override
public void loadMessages() {
final Disposable subscription = getSingleRoom()
.flatMap(messageInteractor::loadMessages)
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
success -> {
if (success) {
connectivityManagerApi.keepAliveServer();
}
},
Logger::report
);
addSubscription(subscription); @Override
} public void bindView(@NonNull RoomContract.View view) {
super.bindView(view);
@Override refreshRoom();
public void loadMoreMessages() { }
final Disposable subscription = getSingleRoom()
.flatMap(messageInteractor::loadMoreMessages)
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
success -> {
if (success) {
connectivityManagerApi.keepAliveServer();
}
},
Logger::report
);
addSubscription(subscription); @Override
} public void refreshRoom() {
getRoomRoles();
getRoomInfo();
getRoomHistoryStateInfo();
getMessages();
getUserPreferences();
}
@Override @Override
public void onMessageSelected(@Nullable Message message) { public void loadMessages() {
if (message == null) { final Disposable subscription = getSingleRoom()
return; .flatMap(messageInteractor::loadMessages)
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
success -> {
if (!success) {
connectivityManagerApi.keepAliveServer();
}
},
Logger.INSTANCE::report
);
addSubscription(subscription);
} }
if (message.getSyncState() == SyncState.DELETE_FAILED) { @Override
view.showMessageDeleteFailure(message); public void loadMoreMessages() {
} else if (message.getSyncState() == SyncState.FAILED) { final Disposable subscription = getSingleRoom()
view.showMessageSendFailure(message); .flatMap(messageInteractor::loadMoreMessages)
} else if (message.getType() == null && message.getSyncState() == SyncState.SYNCED) { .subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
// If message is not a system message show applicable actions. .observeOn(AndroidSchedulers.mainThread())
view.showMessageActions(message); .subscribe(
success -> {
if (!success) {
connectivityManagerApi.keepAliveServer();
}
},
Logger.INSTANCE::report
);
addSubscription(subscription);
} }
}
@Override @Override
public void onMessageTap(@Nullable Message message) { public void onMessageSelected(@Nullable Message message) {
if (message == null) { if (message == null) {
return; return;
}
if (message.getSyncState() == SyncState.DELETE_FAILED) {
view.showMessageDeleteFailure(message);
} else if (message.getSyncState() == SyncState.FAILED) {
view.showMessageSendFailure(message);
} else if (message.getType() == null && message.getSyncState() == SyncState.SYNCED) {
// If message is not a system message show applicable actions.
view.showMessageActions(message);
}
} }
if (message.getSyncState() == SyncState.FAILED) { @Override
view.showMessageSendFailure(message); public void onMessageTap(@Nullable Message message) {
if (message == null) {
return;
}
if (message.getSyncState() == SyncState.FAILED) {
view.showMessageSendFailure(message);
}
} }
}
@Override
@Override public void replyMessage(@NonNull Message message, boolean justQuote) {
public void replyMessage(@NonNull Message message, boolean justQuote) { final Disposable subscription = this.absoluteUrlHelper.getRocketChatAbsoluteUrl()
this.absoluteUrlHelper.getRocketChatAbsoluteUrl() .cache()
.cache() .subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get())) .observeOn(AndroidSchedulers.mainThread())
.observeOn(AndroidSchedulers.mainThread()) .subscribe(
.subscribe( serverUrl -> {
serverUrl -> { if (serverUrl.isPresent()) {
if (serverUrl.isPresent()) { RocketChatAbsoluteUrl absoluteUrl = serverUrl.get();
RocketChatAbsoluteUrl absoluteUrl = serverUrl.get(); String baseUrl = absoluteUrl.getBaseUrl();
String baseUrl = absoluteUrl.getBaseUrl(); view.onReply(absoluteUrl, buildReplyOrQuoteMarkdown(baseUrl, message, justQuote), message);
view.onReply(absoluteUrl, buildReplyOrQuoteMarkdown(baseUrl, message, justQuote), message); }
} },
}, Logger.INSTANCE::report
Logger::report );
);
} addSubscription(subscription);
public void acceptMessageDeleteFailure(Message message) {
final Disposable subscription = messageInteractor.acceptDeleteFailure(message)
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe();
addSubscription(subscription);
}
private String buildReplyOrQuoteMarkdown(String baseUrl, Message message, boolean justQuote) {
if (currentRoom == null || message.getUser() == null) {
return "";
} }
if (currentRoom.isDirectMessage()) { public void acceptMessageDeleteFailure(Message message) {
return String.format("[ ](%s/direct/%s?msg=%s) ", baseUrl, final Disposable subscription = messageInteractor.acceptDeleteFailure(message)
message.getUser().getUsername(), .subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
message.getId()); .observeOn(AndroidSchedulers.mainThread())
} else { .subscribe();
return String.format("[ ](%s/channel/%s?msg=%s) %s", baseUrl,
currentRoom.getName(), addSubscription(subscription);
message.getId(),
justQuote ? "" : "@" + message.getUser().getUsername() + " ");
} }
}
@Override
public void sendMessage(String messageText) {
view.disableMessageInput();
final Disposable subscription = getRoomUserPair()
.flatMap(pair -> messageInteractor.send(pair.first, pair.second, messageText))
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
success -> {
if (success) {
view.onMessageSendSuccessfully();
}
view.enableMessageInput();
},
throwable -> {
view.enableMessageInput();
Logger.report(throwable);
}
);
addSubscription(subscription); @Override
} public void loadMissedMessages() {
RocketChatApplication appContext = RocketChatApplication.getInstance();
@Override JSONObject openedRooms = RocketChatCache.INSTANCE.getOpenedRooms();
public void resendMessage(@NonNull Message message) { if (openedRooms.has(roomId)) {
final Disposable subscription = getCurrentUser() try {
.flatMap(user -> messageInteractor.resend(message, user)) JSONObject room = openedRooms.getJSONObject(roomId);
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get())) String rid = room.optString("rid");
.observeOn(AndroidSchedulers.mainThread()) long ls = room.optLong("ls");
.subscribe(); methodCallHelper.loadMissedMessages(rid, ls)
.continueWith(new LogIfError());
addSubscription(subscription); } catch (JSONException e) {
} RCLog.e(e);
@Override
public void updateMessage(@NonNull Message message, String content) {
view.disableMessageInput();
final Disposable subscription = getCurrentUser()
.flatMap(user -> messageInteractor.update(message, user, content))
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
success -> {
if (success) {
view.onMessageSendSuccessfully();
}
view.enableMessageInput();
},
throwable -> {
view.enableMessageInput();
Logger.report(throwable);
} }
); }
}
addSubscription(subscription); private String buildReplyOrQuoteMarkdown(String baseUrl, Message message, boolean justQuote) {
} if (currentRoom == null || message.getUser() == null) {
return "";
@Override }
public void deleteMessage(@NonNull Message message) {
final Disposable subscription = messageInteractor.delete(message) if (currentRoom.isDirectMessage()) {
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get())) return String.format("[ ](%s/direct/%s?msg=%s) ", baseUrl,
.observeOn(AndroidSchedulers.mainThread()) message.getUser().getUsername(),
.subscribe(); message.getId());
} else {
addSubscription(subscription); return String.format("[ ](%s/channel/%s?msg=%s) %s", baseUrl,
} currentRoom.getName(),
message.getId(),
@Override justQuote ? "" : "@" + message.getUser().getUsername() + " ");
public void onUnreadCount() { }
final Disposable subscription = getRoomUserPair() }
.flatMap(roomUserPair -> messageInteractor
.unreadCountFor(roomUserPair.first, roomUserPair.second))
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
count -> view.showUnreadCount(count),
Logger::report
);
addSubscription(subscription); @Override
} public void sendMessage(String messageText) {
view.disableMessageInput();
@Override final Disposable subscription = getRoomUserPair()
public void onMarkAsRead() { .flatMap(pair -> messageInteractor.send(pair.first, pair.second, messageText))
final Disposable subscription = roomRepository.getById(roomId) .subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.filter(Optional::isPresent) .observeOn(AndroidSchedulers.mainThread())
.map(Optional::get) .subscribe(
.firstElement() success -> {
.filter(Room::isAlert) if (success) {
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get())) view.onMessageSendSuccessfully();
.observeOn(AndroidSchedulers.mainThread()) }
.subscribe( view.enableMessageInput();
room -> methodCallHelper.readMessages(room.getRoomId()) },
.continueWith(new LogIfError()), throwable -> {
Logger::report view.enableMessageInput();
); Logger.INSTANCE.report(throwable);
}
);
addSubscription(subscription);
}
@Override
public void resendMessage(@NonNull Message message) {
final Disposable subscription = getCurrentUser()
.flatMap(user -> messageInteractor.resend(message, user))
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe();
addSubscription(subscription); addSubscription(subscription);
}
private void getRoomRoles() {
methodCallHelper.getRoomRoles(roomId);
}
private void getRoomInfo() {
final Disposable subscription = roomRepository.getById(roomId)
.distinctUntilChanged()
.filter(Optional::isPresent)
.map(Optional::get)
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::processRoom, Logger::report);
addSubscription(subscription);
}
private void processRoom(Room room) {
this.currentRoom = room;
view.render(room);
if (room.isDirectMessage()) {
getUserByUsername(room.getName());
} }
}
private void getUserByUsername(String username) {
final Disposable disposable = userRepository.getByUsername(username)
.distinctUntilChanged()
.filter(Optional::isPresent)
.map(Optional::get)
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(view::showUserStatus, Logger::report);
addSubscription(disposable);
}
private void getRoomHistoryStateInfo() {
final Disposable subscription = roomRepository.getHistoryStateByRoomId(roomId)
.distinctUntilChanged()
.filter(Optional::isPresent)
.map(Optional::get)
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
roomHistoryState -> {
int syncState = roomHistoryState.getSyncState();
view.updateHistoryState(
!roomHistoryState.isComplete(),
syncState == SyncState.SYNCED || syncState == SyncState.FAILED
);
},
Logger::report
);
addSubscription(subscription); @Override
} public void updateMessage(@NonNull Message message, String content) {
view.disableMessageInput();
private void getMessages() { final Disposable subscription = getCurrentUser()
final Disposable subscription = roomRepository.getById(roomId) .flatMap(user -> messageInteractor.update(message, user, content))
.filter(Optional::isPresent) .subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.map(Optional::get) .observeOn(AndroidSchedulers.mainThread())
.flatMap(messageInteractor::getAllFrom) .subscribe(
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get())) success -> {
.observeOn(AndroidSchedulers.mainThread()) if (success) {
.subscribe( view.onMessageSendSuccessfully();
messages -> view.showMessages(messages), }
Logger::report view.enableMessageInput();
); },
throwable -> {
view.enableMessageInput();
Logger.INSTANCE.report(throwable);
}
);
addSubscription(subscription);
}
addSubscription(subscription); @Override
} public void deleteMessage(@NonNull Message message) {
final Disposable subscription = messageInteractor.delete(message)
private void getUserPreferences() { .subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
final Disposable subscription = userRepository.getCurrent() .observeOn(AndroidSchedulers.mainThread())
.filter(Optional::isPresent) .subscribe();
.map(Optional::get)
.filter(user -> user.getSettings() != null)
.map(User::getSettings)
.filter(settings -> settings.getPreferences() != null)
.map(Settings::getPreferences)
.distinctUntilChanged()
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
preferences -> {
if (preferences.isAutoImageLoad()) {
view.autoloadImages();
} else {
view.manualLoadImages();
}
},
Logger::report
);
addSubscription(subscription); addSubscription(subscription);
} }
@Override
public void onUnreadCount() {
final Disposable subscription = getRoomUserPair()
.flatMap(roomUserPair -> messageInteractor
.unreadCountFor(roomUserPair.first, roomUserPair.second))
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
count -> view.showUnreadCount(count),
Logger.INSTANCE::report
);
addSubscription(subscription);
}
private void getAbsoluteUrl() { @Override
final Disposable subscription = absoluteUrlHelper.getRocketChatAbsoluteUrl() public void onMarkAsRead() {
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get())) final Disposable subscription = roomRepository.getById(roomId)
.observeOn(AndroidSchedulers.mainThread()) .filter(Optional::isPresent)
.subscribe( .map(Optional::get)
it -> view.setupWith(it.orNull()), .firstElement()
Logger::report .filter(Room::isAlert)
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
room -> methodCallHelper.readMessages(room.getRoomId())
.continueWith(new LogIfError()),
Logger.INSTANCE::report
);
addSubscription(subscription);
}
private void getRoomRoles() {
methodCallHelper.getRoomRoles(roomId);
}
private void getRoomInfo() {
final Disposable subscription = roomRepository.getById(roomId)
.distinctUntilChanged()
.filter(Optional::isPresent)
.map(Optional::get)
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::processRoom, Logger.INSTANCE::report);
addSubscription(subscription);
}
private void processRoom(Room room) {
this.currentRoom = room;
view.render(room);
if (room.isDirectMessage()) {
getUserByUsername(room.getName());
}
}
private void getUserByUsername(String username) {
final Disposable disposable = userRepository.getByUsername(username)
.distinctUntilChanged()
.filter(Optional::isPresent)
.map(Optional::get)
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(view::showUserStatus, Logger.INSTANCE::report);
addSubscription(disposable);
}
private void getRoomHistoryStateInfo() {
final Disposable subscription = roomRepository.getHistoryStateByRoomId(roomId)
.distinctUntilChanged()
.filter(Optional::isPresent)
.map(Optional::get)
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
roomHistoryState -> {
int syncState = roomHistoryState.getSyncState();
view.updateHistoryState(
!roomHistoryState.isComplete(),
syncState == SyncState.SYNCED || syncState == SyncState.FAILED
);
},
Logger.INSTANCE::report
);
addSubscription(subscription);
}
private void getMessages() {
final Disposable subscription = Flowable.zip(roomRepository.getById(roomId),
absoluteUrlHelper.getRocketChatAbsoluteUrl().toFlowable().cache(), Pair::new)
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.map(pair -> {
view.setupWith(pair.second.orNull());
return pair.first;
})
.filter(Optional::isPresent)
.map(Optional::get)
.map(room -> {
RocketChatCache.INSTANCE.addOpenedRoom(room.getRoomId(), room.getLastSeen());
return room;
})
.flatMap(messageInteractor::getAllFrom)
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
view::showMessages,
Logger.INSTANCE::report
);
addSubscription(subscription);
}
private void getUserPreferences() {
final Disposable subscription = userRepository.getCurrent()
.filter(Optional::isPresent)
.map(Optional::get)
.filter(user -> user.getSettings() != null)
.map(User::getSettings)
.filter(settings -> settings.getPreferences() != null)
.map(Settings::getPreferences)
.distinctUntilChanged()
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
preferences -> {
if (preferences.isAutoImageLoad()) {
view.autoloadImages();
} else {
view.manualLoadImages();
}
},
Logger.INSTANCE::report
);
addSubscription(subscription);
}
private void getAbsoluteUrl() {
final Disposable subscription = absoluteUrlHelper.getRocketChatAbsoluteUrl()
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
it -> view.setupWith(it.orNull()),
Logger.INSTANCE::report
);
addSubscription(subscription);
}
private Single<Pair<Room, User>> getRoomUserPair() {
return Single.zip(
getSingleRoom(),
getCurrentUser(),
Pair::new
); );
}
addSubscription(subscription); private Single<Room> getSingleRoom() {
} return roomRepository.getById(roomId)
.filter(Optional::isPresent)
private Single<Pair<Room, User>> getRoomUserPair() { .map(Optional::get)
return Single.zip( .firstElement()
getSingleRoom(), .toSingle();
getCurrentUser(), }
Pair::new
); private Single<User> getCurrentUser() {
} return userRepository.getCurrent()
.filter(Optional::isPresent)
private Single<Room> getSingleRoom() { .map(Optional::get)
return roomRepository.getById(roomId) .firstElement()
.filter(Optional::isPresent) .toSingle();
.map(Optional::get) }
.firstElement()
.toSingle();
}
private Single<User> getCurrentUser() {
return userRepository.getCurrent()
.filter(Optional::isPresent)
.map(Optional::get)
.firstElement()
.toSingle();
}
} }
...@@ -36,134 +36,132 @@ import io.reactivex.disposables.Disposable; ...@@ -36,134 +36,132 @@ import io.reactivex.disposables.Disposable;
public class MessageOptionsDialogFragment extends BottomSheetDialogFragment { public class MessageOptionsDialogFragment extends BottomSheetDialogFragment {
public final static String ARG_MESSAGE_ID = "messageId"; public final static String ARG_MESSAGE_ID = "messageId";
private CompositeDisposable compositeDisposable = new CompositeDisposable();
private OnMessageOptionSelectedListener internalListener = new OnMessageOptionSelectedListener() {
@Override
public void onEdit(Message message) {
if (externalListener != null) {
externalListener.onEdit(message);
}
}
};
private CompositeDisposable compositeDisposable = new CompositeDisposable(); private OnMessageOptionSelectedListener externalListener = null;
private OnMessageOptionSelectedListener internalListener = new OnMessageOptionSelectedListener() {
@Override
public void onEdit(Message message) {
if (externalListener != null) {
externalListener.onEdit(message);
}
}
};
private OnMessageOptionSelectedListener externalListener = null; public static MessageOptionsDialogFragment create(@NonNull Message message) {
Bundle bundle = new Bundle();
bundle.putString(ARG_MESSAGE_ID, message.getId());
public static MessageOptionsDialogFragment create(@NonNull Message message) { MessageOptionsDialogFragment messageOptionsDialogFragment = new MessageOptionsDialogFragment();
Bundle bundle = new Bundle(); messageOptionsDialogFragment.setArguments(bundle);
bundle.putString(ARG_MESSAGE_ID, message.getId());
MessageOptionsDialogFragment messageOptionsDialogFragment = new MessageOptionsDialogFragment(); return messageOptionsDialogFragment;
messageOptionsDialogFragment.setArguments(bundle); }
return messageOptionsDialogFragment; public void setOnMessageOptionSelectedListener(
} OnMessageOptionSelectedListener onMessageOptionSelectedListener) {
externalListener = onMessageOptionSelectedListener;
}
public void setOnMessageOptionSelectedListener( @NonNull
OnMessageOptionSelectedListener onMessageOptionSelectedListener) { @Override
externalListener = onMessageOptionSelectedListener; public Dialog onCreateDialog(Bundle savedInstanceState) {
} BottomSheetDialog bottomSheetDialog = new BottomSheetDialog(getContext());
@NonNull bottomSheetDialog.setContentView(R.layout.dialog_message_options);
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
BottomSheetDialog bottomSheetDialog = new BottomSheetDialog(getContext());
bottomSheetDialog.setContentView(R.layout.dialog_message_options); TextView info = (TextView) bottomSheetDialog.findViewById(R.id.message_options_info);
TextView info = (TextView) bottomSheetDialog.findViewById(R.id.message_options_info); Bundle args = getArguments();
if (args == null || !args.containsKey(ARG_MESSAGE_ID)) {
info.setText(R.string.message_options_no_message_info);
} else {
setUpDialog(bottomSheetDialog, args.getString(ARG_MESSAGE_ID));
}
Bundle args = getArguments(); return bottomSheetDialog;
if (args == null || !args.containsKey(ARG_MESSAGE_ID)) {
info.setText(R.string.message_options_no_message_info);
} else {
setUpDialog(bottomSheetDialog, args.getString(ARG_MESSAGE_ID));
} }
return bottomSheetDialog; @Override
} public void onDismiss(DialogInterface dialog) {
compositeDisposable.clear();
@Override super.onDismiss(dialog);
public void onDismiss(DialogInterface dialog) { }
compositeDisposable.clear();
super.onDismiss(dialog); private void setUpDialog(final BottomSheetDialog bottomSheetDialog, String messageId) {
} String hostname = RocketChatCache.INSTANCE.getSelectedServerHostname();
private void setUpDialog(final BottomSheetDialog bottomSheetDialog, String messageId) { EditMessageInteractor editMessageInteractor = getEditMessageInteractor(hostname);
RocketChatCache cache = new RocketChatCache(bottomSheetDialog.getContext());
MessageRepository messageRepository = new RealmMessageRepository(hostname);
String hostname = cache.getSelectedServerHostname();
Disposable disposable = messageRepository.getById(messageId)
EditMessageInteractor editMessageInteractor = getEditMessageInteractor(hostname); .flatMap(it -> {
if (!it.isPresent()) {
MessageRepository messageRepository = new RealmMessageRepository(hostname); return Single.just(Pair.<Message, Boolean>create(null, false));
}
Disposable disposable = messageRepository.getById(messageId)
.flatMap(it -> { Message message = it.get();
if (!it.isPresent()) {
return Single.just(Pair.<Message, Boolean>create(null, false)); return Single.zip(
} Single.just(message),
editMessageInteractor.isAllowed(message),
Message message = it.get(); Pair::create
);
return Single.zip( })
Single.just(message), .subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
editMessageInteractor.isAllowed(message), .observeOn(AndroidSchedulers.mainThread())
Pair::create .subscribe(
); pair -> {
}) if (pair.second) {
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get())) bottomSheetDialog.findViewById(R.id.message_options_info)
.observeOn(AndroidSchedulers.mainThread()) .setVisibility(View.GONE);
.subscribe( View editView = bottomSheetDialog.findViewById(R.id.message_options_edit_action);
pair -> { editView.setVisibility(View.VISIBLE);
if (pair.second) { editView.setOnClickListener(view -> internalListener.onEdit(pair.first));
bottomSheetDialog.findViewById(R.id.message_options_info) } else {
.setVisibility(View.GONE); ((TextView) bottomSheetDialog.findViewById(R.id.message_options_info))
View editView = bottomSheetDialog.findViewById(R.id.message_options_edit_action); .setText(R.string.message_options_no_permissions_info);
editView.setVisibility(View.VISIBLE); }
editView.setOnClickListener(view -> internalListener.onEdit(pair.first)); },
} else { throwable -> {
((TextView) bottomSheetDialog.findViewById(R.id.message_options_info)) ((TextView) bottomSheetDialog.findViewById(R.id.message_options_info))
.setText(R.string.message_options_no_permissions_info); .setText(R.string.message_options_no_message_info);
}
}, Logger.INSTANCE.report(throwable);
throwable -> { }
((TextView) bottomSheetDialog.findViewById(R.id.message_options_info)) );
.setText(R.string.message_options_no_message_info);
compositeDisposable.add(disposable);
Logger.report(throwable); }
}
private EditMessageInteractor getEditMessageInteractor(String hostname) {
UserRepository userRepository = new RealmUserRepository(hostname);
RoomRoleRepository roomRoleRepository = new RealmRoomRoleRepository(hostname);
PermissionRepository permissionRepository = new RealmPermissionRepository(hostname);
PermissionInteractor permissionInteractor = new PermissionInteractor(
userRepository,
roomRoleRepository,
permissionRepository
); );
compositeDisposable.add(disposable); MessageRepository messageRepository = new RealmMessageRepository(hostname);
} RoomRepository roomRepository = new RealmRoomRepository(hostname);
PublicSettingRepository publicSettingRepository = new RealmPublicSettingRepository(hostname);
private EditMessageInteractor getEditMessageInteractor(String hostname) {
UserRepository userRepository = new RealmUserRepository(hostname); return new EditMessageInteractor(
RoomRoleRepository roomRoleRepository = new RealmRoomRoleRepository(hostname); permissionInteractor,
PermissionRepository permissionRepository = new RealmPermissionRepository(hostname); userRepository,
messageRepository,
PermissionInteractor permissionInteractor = new PermissionInteractor( roomRepository,
userRepository, publicSettingRepository
roomRoleRepository, );
permissionRepository }
);
public interface OnMessageOptionSelectedListener {
MessageRepository messageRepository = new RealmMessageRepository(hostname); void onEdit(Message message);
RoomRepository roomRepository = new RealmRoomRepository(hostname); }
PublicSettingRepository publicSettingRepository = new RealmPublicSettingRepository(hostname);
return new EditMessageInteractor(
permissionInteractor,
userRepository,
messageRepository,
roomRepository,
publicSettingRepository
);
}
public interface OnMessageOptionSelectedListener {
void onEdit(Message message);
}
} }
...@@ -23,13 +23,14 @@ import java.sql.Timestamp ...@@ -23,13 +23,14 @@ import java.sql.Timestamp
* Created by Filipe de Lima Brito (filipedelimabrito@gmail.com) on 9/22/17. * Created by Filipe de Lima Brito (filipedelimabrito@gmail.com) on 9/22/17.
*/ */
class RoomListPresenter(val context: Context, val view: RoomListContract.View) : RoomListContract.Presenter { class RoomListPresenter(val context: Context, val view: RoomListContract.View) : RoomListContract.Presenter {
private lateinit var TAG: String
override fun requestPinnedMessages(roomId: String, override fun requestPinnedMessages(roomId: String,
roomType: String, roomType: String,
hostname: String, hostname: String,
token: String, token: String,
userId: String, userId: String,
offset: Int) { offset: Int) {
TAG = "pinned"
view.showWaitingView(true) view.showWaitingView(true)
OkHttpHelper.getClient() OkHttpHelper.getClient()
.newCall(RestApiHelper.getRequestForPinnedMessages(roomId, .newCall(RestApiHelper.getRequestForPinnedMessages(roomId,
...@@ -53,7 +54,7 @@ class RoomListPresenter(val context: Context, val view: RoomListContract.View) : ...@@ -53,7 +54,7 @@ class RoomListPresenter(val context: Context, val view: RoomListContract.View) :
if (response.isSuccessful) { if (response.isSuccessful) {
val result = response.body()?.string() val result = response.body()?.string()
if (result != null) { if (result != null) {
handleMessagesJson(result, true) handleMessagesJson(result, true, TAG)
} }
} else { } else {
showErrorMessage(response.message()) showErrorMessage(response.message())
...@@ -68,6 +69,7 @@ class RoomListPresenter(val context: Context, val view: RoomListContract.View) : ...@@ -68,6 +69,7 @@ class RoomListPresenter(val context: Context, val view: RoomListContract.View) :
token: String, token: String,
userId: String, userId: String,
offset: Int) { offset: Int) {
TAG = "favorite"
view.showWaitingView(true) view.showWaitingView(true)
OkHttpHelper.getClient() OkHttpHelper.getClient()
.newCall(RestApiHelper.getRequestForFavoriteMessages(roomId, .newCall(RestApiHelper.getRequestForFavoriteMessages(roomId,
...@@ -91,7 +93,7 @@ class RoomListPresenter(val context: Context, val view: RoomListContract.View) : ...@@ -91,7 +93,7 @@ class RoomListPresenter(val context: Context, val view: RoomListContract.View) :
if (response.isSuccessful) { if (response.isSuccessful) {
val result = response.body()?.string() val result = response.body()?.string()
if (result != null) { if (result != null) {
handleMessagesJson(result, false) handleMessagesJson(result, false, TAG)
} }
} else { } else {
showErrorMessage(response.message()) showErrorMessage(response.message())
...@@ -180,7 +182,7 @@ class RoomListPresenter(val context: Context, val view: RoomListContract.View) : ...@@ -180,7 +182,7 @@ class RoomListPresenter(val context: Context, val view: RoomListContract.View) :
OkHttpHelper.getClient().dispatcher().cancelAll() OkHttpHelper.getClient().dispatcher().cancelAll()
} }
private fun handleMessagesJson(json: String, isPinnedMessage: Boolean) { private fun handleMessagesJson(json: String, isPinnedMessage: Boolean, TAG: String) {
try { try {
val jSONObject = JSONObject(json) val jSONObject = JSONObject(json)
val messagesJSONArray = jSONObject.getJSONArray("messages") val messagesJSONArray = jSONObject.getJSONArray("messages")
...@@ -204,7 +206,10 @@ class RoomListPresenter(val context: Context, val view: RoomListContract.View) : ...@@ -204,7 +206,10 @@ class RoomListPresenter(val context: Context, val view: RoomListContract.View) :
} }
if (dataSet.isEmpty() && !hasItem) { if (dataSet.isEmpty() && !hasItem) {
showEmptyViewMessage(context.getString(R.string.fragment_room_list_no_favorite_message_to_show)) if (TAG.equals("favorite"))
showEmptyViewMessage(context.getString(R.string.fragment_room_list_no_favorite_message_to_show))
else if (TAG.equals("pinned"))
showEmptyViewMessage(context.getString(R.string.fragment_room_list_no_pinned_message_to_show))
} else { } else {
if (dataSet.isNotEmpty()) { if (dataSet.isNotEmpty()) {
hasItem = true hasItem = true
......
...@@ -37,7 +37,7 @@ public class OAuthPresenter extends BasePresenter<OAuthContract.View> ...@@ -37,7 +37,7 @@ public class OAuthPresenter extends BasePresenter<OAuthContract.View>
view.close(); view.close();
} }
}, },
Logger::report Logger.INSTANCE::report
) )
); );
} }
......
...@@ -6,39 +6,41 @@ import android.support.v4.app.Fragment; ...@@ -6,39 +6,41 @@ import android.support.v4.app.Fragment;
import chat.rocket.android.R; import chat.rocket.android.R;
import chat.rocket.android.fragment.AbstractFragment; import chat.rocket.android.fragment.AbstractFragment;
import chat.rocket.android.helper.BackStackHelper;
import chat.rocket.android.helper.TextUtils; import chat.rocket.android.helper.TextUtils;
abstract class AbstractServerConfigFragment extends AbstractFragment { public abstract class AbstractServerConfigFragment extends AbstractFragment {
public static final String KEY_HOSTNAME = "hostname"; public static final String KEY_HOSTNAME = "hostname";
protected String hostname; protected String hostname;
@Override @Override
public void onCreate(@Nullable Bundle savedInstanceState) { public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
Bundle args = getArguments(); Bundle args = getArguments();
if (args == null) { if (args == null) {
finish(); finish();
return; return;
}
hostname = args.getString(KEY_HOSTNAME);
if (TextUtils.isEmpty(hostname)) {
finish();
}
} }
hostname = args.getString(KEY_HOSTNAME); protected void showFragment(Fragment fragment) {
if (TextUtils.isEmpty(hostname)) { getFragmentManager().beginTransaction()
finish(); .add(R.id.content, fragment)
.commit();
}
protected void showFragmentWithBackStack(Fragment fragment) {
BackStackHelper.FRAGMENT_TAG = "internal";
getFragmentManager().beginTransaction()
.add(R.id.content, fragment)
.addToBackStack(null)
.commit();
} }
}
protected void showFragment(Fragment fragment) {
getFragmentManager().beginTransaction()
.add(R.id.content, fragment)
.commit();
}
protected void showFragmentWithBackStack(Fragment fragment) {
getFragmentManager().beginTransaction()
.add(R.id.content, fragment)
.addToBackStack(null)
.commit();
}
} }
package chat.rocket.android.fragment.server_config; package chat.rocket.android.fragment.server_config;
import android.content.Context;
import java.util.List; import java.util.List;
import chat.rocket.android.shared.BaseContract; import chat.rocket.android.shared.BaseContract;
...@@ -7,21 +10,29 @@ import chat.rocket.core.models.LoginServiceConfiguration; ...@@ -7,21 +10,29 @@ import chat.rocket.core.models.LoginServiceConfiguration;
public interface LoginContract { public interface LoginContract {
interface View extends BaseContract.View { interface View extends BaseContract.View {
void showLoader();
void hideLoader();
void showErrorInUsernameEditText();
void showErrorInPasswordEditText();
void showLoader(); void showError(String message);
void hideLoader(); void showLoginServices(List<LoginServiceConfiguration> loginServiceList);
void showError(String message); void showTwoStepAuth();
void showLoginServices(List<LoginServiceConfiguration> loginServiceList); void goBack();
}
void showTwoStepAuth(); interface Presenter extends BaseContract.Presenter<View> {
}
interface Presenter extends BaseContract.Presenter<View> { void login(String username, String password);
void login(String username, String password); void goBack(Context ctx);
} }
} }
package chat.rocket.android.fragment.server_config;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.constraint.ConstraintLayout;
import android.support.design.widget.Snackbar;
import android.support.v4.app.Fragment;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import java.util.HashMap;
import java.util.List;
import chat.rocket.android.R;
import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.android.layouthelper.oauth.OAuthProviderInfo;
import chat.rocket.android.log.RCLog;
import chat.rocket.core.models.LoginServiceConfiguration;
import chat.rocket.persistence.realm.repositories.RealmLoginServiceConfigurationRepository;
import chat.rocket.persistence.realm.repositories.RealmPublicSettingRepository;
/**
* Login screen.
*/
public class LoginFragment extends AbstractServerConfigFragment implements LoginContract.View {
private LoginContract.Presenter presenter;
private ConstraintLayout container;
private View waitingView;
private TextView txtUsername;
private TextView txtPasswd;
@Override
protected int getLayout() {
return R.layout.fragment_login;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
presenter = new LoginPresenter(
new RealmLoginServiceConfigurationRepository(hostname),
new RealmPublicSettingRepository(hostname),
new MethodCallHelper(getContext(), hostname)
);
}
@Override
protected void onSetupView() {
container = rootView.findViewById(R.id.container);
Button btnEmail = rootView.findViewById(R.id.btn_login_with_email);
Button btnUserRegistration = rootView.findViewById(R.id.btn_user_registration);
txtUsername = rootView.findViewById(R.id.editor_username);
txtPasswd = rootView.findViewById(R.id.editor_passwd);
waitingView = rootView.findViewById(R.id.waiting);
btnEmail.setOnClickListener(view ->
presenter.login(txtUsername.getText().toString(), txtPasswd.getText().toString()));
btnUserRegistration.setOnClickListener(view ->
UserRegistrationDialogFragment.create(hostname, txtUsername.getText().toString(), txtPasswd.getText().toString())
.show(getFragmentManager(), "UserRegistrationDialogFragment"));
}
@Override
public void showLoader() {
container.setVisibility(View.GONE);
waitingView.setVisibility(View.VISIBLE);
}
@Override
public void hideLoader() {
waitingView.setVisibility(View.GONE);
container.setVisibility(View.VISIBLE);
}
@Override
public void showError(String message) {
Snackbar.make(rootView, message, Snackbar.LENGTH_SHORT).show();
}
@Override
public void showLoginServices(List<LoginServiceConfiguration> loginServiceList) {
HashMap<String, View> viewMap = new HashMap<>();
HashMap<String, Boolean> supportedMap = new HashMap<>();
for (OAuthProviderInfo info : OAuthProviderInfo.LIST) {
viewMap.put(info.serviceName, rootView.findViewById(info.buttonId));
supportedMap.put(info.serviceName, false);
}
for (LoginServiceConfiguration authProvider : loginServiceList) {
for (OAuthProviderInfo info : OAuthProviderInfo.LIST) {
if (!supportedMap.get(info.serviceName)
&& info.serviceName.equals(authProvider.getService())) {
supportedMap.put(info.serviceName, true);
viewMap.get(info.serviceName).setOnClickListener(view -> {
Fragment fragment = null;
try {
fragment = info.fragmentClass.newInstance();
} catch (Exception exception) {
RCLog.w(exception, "failed to build new Fragment");
}
if (fragment != null) {
Bundle args = new Bundle();
args.putString("hostname", hostname);
fragment.setArguments(args);
showFragmentWithBackStack(fragment);
}
});
viewMap.get(info.serviceName).setVisibility(View.VISIBLE);
}
}
}
for (OAuthProviderInfo info : OAuthProviderInfo.LIST) {
if (!supportedMap.get(info.serviceName)) {
viewMap.get(info.serviceName).setVisibility(View.GONE);
}
}
}
@Override
public void showTwoStepAuth() {
showFragmentWithBackStack(TwoStepAuthFragment.create(
hostname, txtUsername.getText().toString(), txtPasswd.getText().toString()
));
}
@Override
public void onResume() {
super.onResume();
presenter.bindView(this);
}
@Override
public void onPause() {
presenter.release();
super.onPause();
}
}
package chat.rocket.android.fragment.server_config
import android.os.Bundle
import android.support.constraint.ConstraintLayout
import android.support.design.widget.Snackbar
import android.support.design.widget.TextInputLayout
import android.support.v4.app.Fragment
import android.text.TextUtils
import android.view.View
import android.widget.Button
import android.widget.TextView
import chat.rocket.android.R
import chat.rocket.android.api.MethodCallHelper
import chat.rocket.android.layouthelper.oauth.OAuthProviderInfo
import chat.rocket.android.log.RCLog
import chat.rocket.core.models.LoginServiceConfiguration
import chat.rocket.persistence.realm.repositories.RealmLoginServiceConfigurationRepository
import chat.rocket.persistence.realm.repositories.RealmPublicSettingRepository
import com.jakewharton.rxbinding2.widget.RxTextView
import java.util.*
/**
* Login screen.
*/
class LoginFragment : AbstractServerConfigFragment(), LoginContract.View {
private lateinit var presenter: LoginContract.Presenter
private lateinit var container: ConstraintLayout
private lateinit var waitingView: View
private lateinit var txtUsername: TextView
private lateinit var txtPasswd: TextView
private lateinit var textInputUsername: TextInputLayout
private lateinit var textInputPassword: TextInputLayout
override fun getLayout(): Int {
return R.layout.fragment_login
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
presenter = LoginPresenter(
RealmLoginServiceConfigurationRepository(hostname),
RealmPublicSettingRepository(hostname),
MethodCallHelper(context, hostname)
)
}
override fun onSetupView() {
container = rootView.findViewById(R.id.container)
val btnEmail = rootView.findViewById<Button>(R.id.btn_login_with_email)
val btnUserRegistration = rootView.findViewById<Button>(R.id.btn_user_registration)
txtUsername = rootView.findViewById(R.id.editor_username)
txtPasswd = rootView.findViewById(R.id.editor_passwd)
textInputUsername = rootView.findViewById(R.id.text_input_username)
textInputPassword = rootView.findViewById(R.id.text_input_passwd)
setUpRxBinders()
waitingView = rootView.findViewById(R.id.waiting)
btnEmail.setOnClickListener { _ -> presenter.login(txtUsername.text.toString(), txtPasswd.text.toString()) }
btnUserRegistration.setOnClickListener { _ ->
UserRegistrationDialogFragment.create(hostname, txtUsername.text.toString(), txtPasswd.text.toString())
.show(fragmentManager!!, "UserRegistrationDialogFragment")
}
}
fun setUpRxBinders() {
RxTextView.textChanges(txtUsername).subscribe { text ->
if (!TextUtils.isEmpty(text) && textInputUsername.isErrorEnabled)
textInputUsername.setErrorEnabled(false)
}
RxTextView.textChanges(txtPasswd).subscribe { text ->
if (!TextUtils.isEmpty(text) && textInputPassword.isErrorEnabled)
textInputPassword.setErrorEnabled(false)
}
}
override fun showLoader() {
container.visibility = View.GONE
waitingView.visibility = View.VISIBLE
}
override fun showErrorInUsernameEditText() {
textInputUsername.setErrorEnabled(true);
textInputUsername.setError("Enter a Username")
}
override fun showErrorInPasswordEditText() {
textInputPassword.setErrorEnabled(true);
textInputPassword.setError("Enter a Password")
}
override fun hideLoader() {
waitingView.visibility = View.GONE
container.visibility = View.VISIBLE
}
override fun showError(message: String) {
Snackbar.make(rootView, message, Snackbar.LENGTH_SHORT).show()
}
override fun showLoginServices(loginServiceList: List<LoginServiceConfiguration>) {
val viewMap = HashMap<String, View>()
val supportedMap = HashMap<String, Boolean>()
for (info in OAuthProviderInfo.LIST) {
viewMap.put(info.serviceName, rootView.findViewById(info.buttonId))
supportedMap.put(info.serviceName, false)
}
for (authProvider in loginServiceList) {
for (info in OAuthProviderInfo.LIST) {
if (supportedMap[info.serviceName] == false && info.serviceName == authProvider.service) {
supportedMap.put(info.serviceName, true)
viewMap[info.serviceName]?.setOnClickListener { _ ->
var fragment: Fragment? = null
try {
fragment = info.fragmentClass.newInstance()
} catch (exception: Exception) {
RCLog.w(exception, "failed to build new Fragment")
}
fragment?.let {
val args = Bundle()
args.putString("hostname", hostname)
fragment.arguments = args
showFragmentWithBackStack(fragment)
}
}
viewMap[info.serviceName]?.visibility = View.VISIBLE
}
}
}
for (info in OAuthProviderInfo.LIST) {
if (supportedMap[info.serviceName] == false) {
viewMap[info.serviceName]?.visibility = View.GONE
}
}
}
override fun showTwoStepAuth() {
showFragmentWithBackStack(TwoStepAuthFragment.create(
hostname, txtUsername.text.toString(), txtPasswd.text.toString()
))
}
override fun onResume() {
super.onResume()
presenter.bindView(this)
}
override fun onPause() {
presenter.release()
super.onPause()
}
override fun goBack() {
presenter.goBack(context)
}
}
package chat.rocket.android.fragment.server_config;
import android.support.annotation.NonNull;
import com.hadisatrio.optional.Optional;
import bolts.Task;
import chat.rocket.android.BackgroundLooper;
import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.android.api.TwoStepAuthException;
import chat.rocket.android.helper.Logger;
import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.shared.BasePresenter;
import chat.rocket.core.PublicSettingsConstants;
import chat.rocket.core.models.PublicSetting;
import chat.rocket.core.repositories.LoginServiceConfigurationRepository;
import chat.rocket.core.repositories.PublicSettingRepository;
import io.reactivex.android.schedulers.AndroidSchedulers;
public class LoginPresenter extends BasePresenter<LoginContract.View>
implements LoginContract.Presenter {
private final LoginServiceConfigurationRepository loginServiceConfigurationRepository;
private final PublicSettingRepository publicSettingRepository;
private final MethodCallHelper methodCallHelper;
public LoginPresenter(LoginServiceConfigurationRepository loginServiceConfigurationRepository,
PublicSettingRepository publicSettingRepository,
MethodCallHelper methodCallHelper) {
this.loginServiceConfigurationRepository = loginServiceConfigurationRepository;
this.publicSettingRepository = publicSettingRepository;
this.methodCallHelper = methodCallHelper;
}
@Override
public void bindView(@NonNull LoginContract.View view) {
super.bindView(view);
getLoginServices();
}
@Override
public void login(String username, String password) {
if (TextUtils.isEmpty(username) || TextUtils.isEmpty(password)) {
return;
}
view.showLoader();
addSubscription(
publicSettingRepository.getById(PublicSettingsConstants.LDAP.ENABLE)
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
publicSettingOptional -> doLogin(username, password, publicSettingOptional),
Logger::report
)
);
}
private void getLoginServices() {
addSubscription(
loginServiceConfigurationRepository.getAll()
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
loginServiceConfigurations -> view.showLoginServices(loginServiceConfigurations),
Logger::report
)
);
}
private void doLogin(String username, String password, Optional<PublicSetting> optional) {
call(username, password, optional)
.continueWith(task -> {
if (task.isFaulted()) {
view.hideLoader();
final Exception error = task.getError();
if (error instanceof TwoStepAuthException) {
view.showTwoStepAuth();
} else {
view.showError(error.getMessage());
}
}
return null;
}, Task.UI_THREAD_EXECUTOR);
}
private Task<Void> call(String username, String password, Optional<PublicSetting> optional) {
if (optional.isPresent() && optional.get().getValueAsBoolean()) {
return methodCallHelper.loginWithLdap(username, password);
}
return methodCallHelper.loginWithEmail(username, password);
}
}
package chat.rocket.android.fragment.server_config
import android.content.Context
import bolts.Continuation
import bolts.Task
import chat.rocket.android.BackgroundLooper
import chat.rocket.android.LaunchUtil
import chat.rocket.android.RocketChatApplication
import chat.rocket.android.RocketChatCache
import chat.rocket.android.api.MethodCallHelper
import chat.rocket.android.api.TwoStepAuthException
import chat.rocket.android.helper.Logger
import chat.rocket.android.helper.TextUtils
import chat.rocket.android.service.ConnectivityManager
import chat.rocket.android.shared.BasePresenter
import chat.rocket.core.PublicSettingsConstants
import chat.rocket.core.models.PublicSetting
import chat.rocket.core.repositories.LoginServiceConfigurationRepository
import chat.rocket.core.repositories.PublicSettingRepository
import com.hadisatrio.optional.Optional
import io.reactivex.Completable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.rxkotlin.subscribeBy
class LoginPresenter(private val loginServiceConfigurationRepository: LoginServiceConfigurationRepository,
private val publicSettingRepository: PublicSettingRepository,
private val methodCallHelper: MethodCallHelper) : BasePresenter<LoginContract.View>(), LoginContract.Presenter {
override fun bindView(view: LoginContract.View) {
super.bindView(view)
getLoginServices()
}
override fun goBack(ctx: Context?) {
val context = RocketChatApplication.getInstance()
val hostname = RocketChatCache.getSelectedServerHostname()
hostname?.let {
ConnectivityManager.getInstance(context).removeServer(hostname)
RocketChatCache.clearSelectedHostnameReferences()
}
LaunchUtil.showMainActivity(ctx)
}
override fun login(username: String, password: String) {
//set error to edit texts
if (TextUtils.isEmpty(username) && TextUtils.isEmpty(password)) {
view.showErrorInUsernameEditText()
view.showErrorInPasswordEditText()
return
}
if (TextUtils.isEmpty(username)) {
view.showErrorInUsernameEditText()
return
}
if (TextUtils.isEmpty(password)) {
view.showErrorInPasswordEditText()
return
}
view.showLoader()
addSubscription(
publicSettingRepository.getById(PublicSettingsConstants.LDAP.ENABLE)
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribeBy(
onSuccess = { publicSettingOptional -> doLogin(username, password, publicSettingOptional) },
onError = { Logger.report(it) }
)
)
}
private fun getLoginServices() {
addSubscription(
loginServiceConfigurationRepository.all
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribeBy(
onNext = { loginServiceConfigurations ->
view.showLoginServices(loginServiceConfigurations);
},
onError = { Logger.report(it) }
)
)
}
private fun doLogin(username: String, password: String, optional: Optional<PublicSetting>) {
addSubscription(
Completable.create {
call(username, password, optional)
.continueWith(object : Continuation<Void, Any?> {
override fun then(task: Task<Void>?): Any? {
if (task != null && task.isFaulted()) {
view.hideLoader()
val error = task.getError()
error?.let {
if (error is TwoStepAuthException) {
view.showTwoStepAuth()
} else {
view.showError(error.message)
}
}
return Completable.complete()
}
return null
}
}, Task.UI_THREAD_EXECUTOR)
}.subscribeBy(
onError = {
view.showError(it.message)
}
)
)
}
private fun call(username: String, password: String, optional: Optional<PublicSetting>): Task<Void> {
return if (optional.isPresent && optional.get().valueAsBoolean) {
methodCallHelper.loginWithLdap(username, password)
} else methodCallHelper.loginWithEmail(username, password)
}
}
...@@ -52,7 +52,7 @@ public class RetryLoginPresenter extends BasePresenter<RetryLoginContract.View> ...@@ -52,7 +52,7 @@ public class RetryLoginPresenter extends BasePresenter<RetryLoginContract.View>
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe( .subscribe(
this::onSession, this::onSession,
Logger::report Logger.INSTANCE::report
) )
); );
} }
......
...@@ -4,14 +4,17 @@ import android.app.Dialog; ...@@ -4,14 +4,17 @@ import android.app.Dialog;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.design.widget.TextInputEditText;
import android.support.design.widget.TextInputLayout;
import android.support.v4.app.DialogFragment; import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog; import android.support.v7.app.AlertDialog;
import android.util.Patterns; import android.util.Patterns;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import com.jakewharton.rxbinding2.widget.RxTextView;
import chat.rocket.android.R; import chat.rocket.android.R;
import chat.rocket.android.api.MethodCallHelper; import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.android.helper.TextUtils; import chat.rocket.android.helper.TextUtils;
...@@ -20,122 +23,174 @@ import chat.rocket.android.helper.TextUtils; ...@@ -20,122 +23,174 @@ import chat.rocket.android.helper.TextUtils;
* Dialog for user registration. * Dialog for user registration.
*/ */
public class UserRegistrationDialogFragment extends DialogFragment { public class UserRegistrationDialogFragment extends DialogFragment {
private String hostname; private String hostname;
private String username; private String username;
private String email; private String email;
private String password; private String password;
private TextInputEditText txtUsername;
public UserRegistrationDialogFragment() { private TextInputEditText txtEmail;
super(); private TextInputEditText txtPasswd;
} private TextInputLayout textInputUsername;
private TextInputLayout textInputEmail;
/** private TextInputLayout textInputPassword;
* build UserRegistrationDialogFragment with auto-detect email/username. private View waitingView;
*/ public UserRegistrationDialogFragment() {
public static UserRegistrationDialogFragment create(String hostname, super();
String usernameOrEmail, String password) {
if (Patterns.EMAIL_ADDRESS.matcher(usernameOrEmail).matches()) {
return create(hostname, null, usernameOrEmail, password);
} else {
return create(hostname, usernameOrEmail, null, password);
} }
}
/**
/** * build UserRegistrationDialogFragment with auto-detect email/username.
* build UserRegistrationDialogFragment. */
*/ public static UserRegistrationDialogFragment create(String hostname,
public static UserRegistrationDialogFragment create(String hostname, String usernameOrEmail, String password) {
String username, String email, if (Patterns.EMAIL_ADDRESS.matcher(usernameOrEmail).matches()) {
String password) { return create(hostname, null, usernameOrEmail, password);
Bundle args = new Bundle(); } else {
args.putString("hostname", hostname); return create(hostname, usernameOrEmail, null, password);
if (!TextUtils.isEmpty(username)) { }
args.putString("username", username);
} }
if (!TextUtils.isEmpty(email)) {
args.putString("email", email); /**
* build UserRegistrationDialogFragment.
*/
public static UserRegistrationDialogFragment create(String hostname,
String username, String email,
String password) {
Bundle args = new Bundle();
args.putString("hostname", hostname);
if (!TextUtils.isEmpty(username)) {
args.putString("username", username);
}
if (!TextUtils.isEmpty(email)) {
args.putString("email", email);
}
if (!TextUtils.isEmpty(password)) {
args.putString("password", password);
}
UserRegistrationDialogFragment dialog = new UserRegistrationDialogFragment();
dialog.setArguments(args);
return dialog;
} }
if (!TextUtils.isEmpty(password)) {
args.putString("password", password); @Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle args = getArguments();
if (args != null) {
hostname = args.getString("hostname");
username = args.getString("username");
email = args.getString("email");
password = args.getString("password");
}
} }
UserRegistrationDialogFragment dialog = new UserRegistrationDialogFragment();
dialog.setArguments(args); @NonNull
return dialog; @Override
} public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getContext(), R.style.AppTheme_Dialog)
@Override .setView(createDialogView())
public void onCreate(@Nullable Bundle savedInstanceState) { .create();
super.onCreate(savedInstanceState);
Bundle args = getArguments();
if (args != null) {
hostname = args.getString("hostname");
username = args.getString("username");
email = args.getString("email");
password = args.getString("password");
} }
}
private View createDialogView() {
@NonNull View dialog = LayoutInflater.from(getContext())
@Override .inflate(R.layout.dialog_user_registration, null, false);
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getContext(), R.style.AppTheme_Dialog) initViews(dialog);
.setView(createDialogView()) setUpRxBinders();
.create();
} if (!TextUtils.isEmpty(username)) {
txtUsername.setText(username);
private View createDialogView() { }
View dialog = LayoutInflater.from(getContext()) if (!TextUtils.isEmpty(email)) {
.inflate(R.layout.dialog_user_registration, null, false); txtEmail.setText(email);
}
final TextView txtUsername = (TextView) dialog.findViewById(R.id.editor_username); if (!TextUtils.isEmpty(password)) {
final TextView txtEmail = (TextView) dialog.findViewById(R.id.editor_email); txtPasswd.setText(password);
final TextView txtPasswd = (TextView) dialog.findViewById(R.id.editor_passwd); }
if (!TextUtils.isEmpty(username)) { waitingView.setVisibility(View.GONE);
txtUsername.setText(username);
dialog.findViewById(R.id.btn_register_user).setOnClickListener(registerButton -> {
if (checkIfEditTextsEmpty())
return;
registerButton.setEnabled(false);
registerButton.setAlpha(0.5f);
waitingView.setVisibility(View.VISIBLE);
username = txtUsername.getText().toString();
email = txtEmail.getText().toString();
password = txtPasswd.getText().toString();
MethodCallHelper methodCallHelper = new MethodCallHelper(getContext(), hostname);
methodCallHelper.registerUser(username, email, password, password)
.onSuccessTask(task -> methodCallHelper.loginWithEmail(email, password))
.onSuccessTask(task -> methodCallHelper.setUsername(username)) //TODO: should prompt!
.onSuccessTask(task -> methodCallHelper.joinDefaultChannels())
.onSuccessTask(task -> {
dismiss();
return task;
})
.continueWith(task -> {
if (task.isFaulted()) {
Exception exception = task.getError();
showError(exception.getMessage());
registerButton.setEnabled(true);
waitingView.setVisibility(View.GONE);
}
return null;
});
});
return dialog;
} }
if (!TextUtils.isEmpty(email)) {
txtEmail.setText(email); private void initViews(View dialog) {
txtUsername = dialog.findViewById(R.id.editor_username);
txtEmail = dialog.findViewById(R.id.editor_email);
txtPasswd = dialog.findViewById(R.id.editor_passwd);
textInputEmail = dialog.findViewById(R.id.text_input_email);
textInputUsername = dialog.findViewById(R.id.text_input_username);
textInputPassword = dialog.findViewById(R.id.text_input_passwd);
waitingView = dialog.findViewById(R.id.waiting);
} }
if (!TextUtils.isEmpty(password)) {
txtPasswd.setText(password); private boolean checkIfEditTextsEmpty() {
boolean check = false;
if (TextUtils.isEmpty(txtEmail.getText().toString())) {
textInputEmail.setError("Enter an email address");
textInputEmail.setErrorEnabled(true);
check = true;
}
if (TextUtils.isEmpty(txtUsername.getText().toString())) {
textInputUsername.setError("Enter a username");
textInputUsername.setErrorEnabled(true);
check = true;
}
if (TextUtils.isEmpty(txtPasswd.getText().toString())) {
textInputPassword.setError("Enter a password");
textInputPassword.setErrorEnabled(true);
check = true;
}
return check;
} }
final View waitingView = dialog.findViewById(R.id.waiting); private void setUpRxBinders() {
waitingView.setVisibility(View.GONE); RxTextView.textChanges(txtUsername).subscribe(text -> {
if (!TextUtils.isEmpty(text) && textInputUsername.isErrorEnabled())
dialog.findViewById(R.id.btn_register_user).setOnClickListener(view -> { textInputUsername.setErrorEnabled(false);
view.setEnabled(false); });
waitingView.setVisibility(View.VISIBLE); RxTextView.textChanges(txtEmail).subscribe(text -> {
if (!TextUtils.isEmpty(text) && textInputEmail.isErrorEnabled())
username = txtUsername.getText().toString(); textInputEmail.setErrorEnabled(false);
email = txtEmail.getText().toString(); });
password = txtPasswd.getText().toString(); RxTextView.textChanges(txtPasswd).subscribe(text -> {
if (!TextUtils.isEmpty(text) && textInputPassword.isErrorEnabled())
MethodCallHelper methodCallHelper = new MethodCallHelper(getContext(), hostname); textInputPassword.setErrorEnabled(false);
methodCallHelper.registerUser(username, email, password, password) });
.onSuccessTask(task -> methodCallHelper.loginWithEmail(email, password)) }
.onSuccessTask(task -> methodCallHelper.setUsername(username)) //TODO: should prompt!
.onSuccessTask(task -> methodCallHelper.joinDefaultChannels()) private void showError(String errMessage) {
.onSuccessTask(task -> { Toast.makeText(getContext(), errMessage, Toast.LENGTH_SHORT).show();
dismiss(); }
return task;
})
.continueWith(task -> {
if (task.isFaulted()) {
Exception exception = task.getError();
showError(exception.getMessage());
view.setEnabled(true);
waitingView.setVisibility(View.GONE);
}
return null;
});
});
return dialog;
}
private void showError(String errMessage) {
Toast.makeText(getContext(), errMessage, Toast.LENGTH_SHORT).show();
}
} }
...@@ -25,7 +25,7 @@ public interface SidebarMainContract { ...@@ -25,7 +25,7 @@ public interface SidebarMainContract {
void show(User user); void show(User user);
void onLogoutCleanUp(); void onPreparedToLogOut();
} }
interface Presenter extends BaseContract.Presenter<View> { interface Presenter extends BaseContract.Presenter<View> {
...@@ -48,6 +48,6 @@ public interface SidebarMainContract { ...@@ -48,6 +48,6 @@ public interface SidebarMainContract {
void onLogout(Continuation<Void, Object> continuation); void onLogout(Continuation<Void, Object> continuation);
void beforeLogoutCleanUp(); void prepareToLogOut();
} }
} }
\ No newline at end of file
...@@ -22,10 +22,8 @@ import java.util.ArrayList; ...@@ -22,10 +22,8 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import bolts.Task;
import chat.rocket.android.BuildConfig; import chat.rocket.android.BuildConfig;
import chat.rocket.android.R; import chat.rocket.android.R;
import chat.rocket.android.RocketChatCache;
import chat.rocket.android.activity.MainActivity; import chat.rocket.android.activity.MainActivity;
import chat.rocket.android.api.MethodCallHelper; import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.android.fragment.AbstractFragment; import chat.rocket.android.fragment.AbstractFragment;
...@@ -51,323 +49,313 @@ import chat.rocket.persistence.realm.repositories.RealmServerInfoRepository; ...@@ -51,323 +49,313 @@ import chat.rocket.persistence.realm.repositories.RealmServerInfoRepository;
import chat.rocket.persistence.realm.repositories.RealmSessionRepository; import chat.rocket.persistence.realm.repositories.RealmSessionRepository;
import chat.rocket.persistence.realm.repositories.RealmSpotlightRepository; import chat.rocket.persistence.realm.repositories.RealmSpotlightRepository;
import chat.rocket.persistence.realm.repositories.RealmUserRepository; import chat.rocket.persistence.realm.repositories.RealmUserRepository;
import hugo.weaving.DebugLog;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable; import io.reactivex.disposables.Disposable;
public class SidebarMainFragment extends AbstractFragment implements SidebarMainContract.View { public class SidebarMainFragment extends AbstractFragment implements SidebarMainContract.View {
private SidebarMainContract.Presenter presenter; private SidebarMainContract.Presenter presenter;
private RoomListAdapter adapter; private RoomListAdapter adapter;
private SearchView searchView; private SearchView searchView;
private TextView loadMoreResultsText; private TextView loadMoreResultsText;
private List<RoomSidebar> roomSidebarList = Collections.emptyList(); private List<RoomSidebar> roomSidebarList = Collections.emptyList();
private Disposable spotlightDisposable; private Disposable spotlightDisposable;
private String hostname; private String hostname;
private static final String HOSTNAME = "hostname"; private static final String HOSTNAME = "hostname";
public SidebarMainFragment() {} public SidebarMainFragment() {
}
/**
* build SidebarMainFragment with hostname.
*/
public static SidebarMainFragment create(String hostname) {
Bundle args = new Bundle();
args.putString(HOSTNAME, hostname);
SidebarMainFragment fragment = new SidebarMainFragment();
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
hostname = getArguments().getString(HOSTNAME);
RealmUserRepository userRepository = new RealmUserRepository(hostname);
AbsoluteUrlHelper absoluteUrlHelper = new AbsoluteUrlHelper(
hostname,
new RealmServerInfoRepository(),
userRepository,
new SessionInteractor(new RealmSessionRepository(hostname))
);
RocketChatCache rocketChatCache = new RocketChatCache(getContext().getApplicationContext());
presenter = new SidebarMainPresenter(
hostname,
new RoomInteractor(new RealmRoomRepository(hostname)),
userRepository,
rocketChatCache,
absoluteUrlHelper,
new MethodCallHelper(getContext(), hostname),
new RealmSpotlightRepository(hostname)
);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = super.onCreateView(inflater, container, savedInstanceState);
presenter.bindView(this);
return view;
}
@Override
public void onDestroyView() {
presenter.release();
super.onDestroyView();
}
@Override
public void onResume() {
super.onResume();
}
@Override
public void onPause() {
super.onPause();
}
@Override
protected int getLayout() {
return R.layout.fragment_sidebar_main;
}
@SuppressLint("RxLeakedSubscription")
@Override
protected void onSetupView() {
setupUserActionToggle();
setupUserStatusButtons();
setupLogoutButton();
setupVersionInfo();
searchView = rootView.findViewById(R.id.search);
adapter = new RoomListAdapter();
adapter.setOnItemClickListener(new RoomListAdapter.OnItemClickListener() {
@Override
public void onItemClick(RoomSidebar roomSidebar) {
searchView.setQuery(null, false);
searchView.clearFocus();
presenter.onRoomSelected(roomSidebar);
}
@Override /**
public void onItemClick(Spotlight spotlight) { * build SidebarMainFragment with hostname.
searchView.setQuery(null, false); */
searchView.clearFocus(); public static SidebarMainFragment create(String hostname) {
presenter.onSpotlightSelected(spotlight); Bundle args = new Bundle();
} args.putString(HOSTNAME, hostname);
});
SidebarMainFragment fragment = new SidebarMainFragment();
RecyclerView recyclerView = rootView.findViewById(R.id.room_list_container); fragment.setArguments(args);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false));
recyclerView.setAdapter(adapter); return fragment;
}
loadMoreResultsText = rootView.findViewById(R.id.text_load_more_results);
@Override
RxSearchView.queryTextChanges(searchView) public void onCreate(@Nullable Bundle savedInstanceState) {
.observeOn(AndroidSchedulers.mainThread()) super.onCreate(savedInstanceState);
.subscribe(charSequence -> {
if (spotlightDisposable != null && !spotlightDisposable.isDisposed()) { hostname = getArguments().getString(HOSTNAME);
spotlightDisposable.dispose(); RealmUserRepository userRepository = new RealmUserRepository(hostname);
}
presenter.disposeSubscriptions(); AbsoluteUrlHelper absoluteUrlHelper = new AbsoluteUrlHelper(
if (charSequence.length() == 0) { hostname,
loadMoreResultsText.setVisibility(View.GONE); new RealmServerInfoRepository(),
adapter.setMode(RoomListAdapter.MODE_ROOM); userRepository,
presenter.bindView(this); new SessionInteractor(new RealmSessionRepository(hostname))
} else {
filterRoomSidebarList(charSequence);
}
});
loadMoreResultsText.setOnClickListener(view -> loadMoreResults());
}
@Override
public void showRoomSidebarList(@NonNull List<RoomSidebar> roomSidebarList) {
this.roomSidebarList = roomSidebarList;
adapter.setRoomSidebarList(roomSidebarList);
}
@Override
public void filterRoomSidebarList(CharSequence term) {
List<RoomSidebar> filteredRoomSidebarList = new ArrayList<>();
for (RoomSidebar roomSidebar: roomSidebarList) {
if (roomSidebar.getRoomName().contains(term)) {
filteredRoomSidebarList.add(roomSidebar);
}
}
if (filteredRoomSidebarList.isEmpty()) {
loadMoreResults();
} else {
loadMoreResultsText.setVisibility(View.VISIBLE);
adapter.setMode(RoomListAdapter.MODE_ROOM);
adapter.setRoomSidebarList(filteredRoomSidebarList);
}
}
private void loadMoreResults() {
spotlightDisposable = presenter.searchSpotlight(searchView.getQuery().toString())
.toObservable()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::showSearchSuggestions);
}
private void showSearchSuggestions(List<Spotlight> spotlightList) {
loadMoreResultsText.setVisibility(View.GONE);
adapter.setMode(RoomListAdapter.MODE_SPOTLIGHT);
adapter.setSpotlightList(spotlightList);
}
@SuppressLint("RxLeakedSubscription")
private void setupUserActionToggle() {
final CompoundButton toggleUserAction = rootView.findViewById(R.id.toggle_user_action);
toggleUserAction.setFocusableInTouchMode(false);
rootView.findViewById(R.id.user_info_container).setOnClickListener(view -> toggleUserAction.toggle());
RxCompoundButton.checkedChanges(toggleUserAction)
.compose(bindToLifecycle())
.subscribe(
this::showUserActionContainer,
Logger::report
); );
}
public void showUserActionContainer(boolean show) { presenter = new SidebarMainPresenter(
rootView.findViewById(R.id.user_action_outer_container) hostname,
.setVisibility(show ? View.VISIBLE : View.GONE); new RoomInteractor(new RealmRoomRepository(hostname)),
} userRepository,
absoluteUrlHelper,
public void toggleUserActionContainer(boolean checked) { new MethodCallHelper(getContext(), hostname),
CompoundButton toggleUserAction = rootView.findViewById(R.id.toggle_user_action); new RealmSpotlightRepository(hostname)
toggleUserAction.setChecked(checked); );
} }
@Override @Nullable
public void showScreen() { @Override
rootView.setVisibility(View.VISIBLE); public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
} View view = super.onCreateView(inflater, container, savedInstanceState);
presenter.bindView(this);
@Override return view;
public void showEmptyScreen() { }
rootView.setVisibility(View.INVISIBLE);
} @Override
public void onDestroyView() {
@Override presenter.release();
public void show(User user) { super.onDestroyView();
onRenderCurrentUser(user); }
updateRoomListMode();
} @Override
public void onResume() {
private void setupUserStatusButtons() { super.onResume();
rootView.findViewById(R.id.btn_status_online).setOnClickListener(view -> {
presenter.onUserOnline(); }
closeUserActionContainer();
}); @Override
rootView.findViewById(R.id.btn_status_away).setOnClickListener(view -> { public void onPause() {
presenter.onUserAway();
closeUserActionContainer(); super.onPause();
}); }
rootView.findViewById(R.id.btn_status_busy).setOnClickListener(view -> {
presenter.onUserBusy(); @Override
closeUserActionContainer(); protected int getLayout() {
}); return R.layout.fragment_sidebar_main;
rootView.findViewById(R.id.btn_status_invisible).setOnClickListener(view -> { }
presenter.onUserOffline();
closeUserActionContainer(); @SuppressLint("RxLeakedSubscription")
}); @Override
} protected void onSetupView() {
setupUserActionToggle();
private void onRenderCurrentUser(User user) { setupUserStatusButtons();
if (user != null) { setupLogoutButton();
UserRenderer userRenderer = new UserRenderer(user); setupVersionInfo();
userRenderer.showAvatar(rootView.findViewById(R.id.current_user_avatar), hostname);
userRenderer.showUsername(rootView.findViewById(R.id.current_user_name)); searchView = rootView.findViewById(R.id.search);
userRenderer.showStatusColor(rootView.findViewById(R.id.current_user_status));
adapter = new RoomListAdapter();
adapter.setOnItemClickListener(new RoomListAdapter.OnItemClickListener() {
@Override
public void onItemClick(RoomSidebar roomSidebar) {
searchView.setQuery(null, false);
searchView.clearFocus();
presenter.onRoomSelected(roomSidebar);
}
@Override
public void onItemClick(Spotlight spotlight) {
searchView.setQuery(null, false);
searchView.clearFocus();
presenter.onSpotlightSelected(spotlight);
}
});
RecyclerView recyclerView = rootView.findViewById(R.id.room_list_container);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false));
recyclerView.setAdapter(adapter);
loadMoreResultsText = rootView.findViewById(R.id.text_load_more_results);
RxSearchView.queryTextChanges(searchView)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(charSequence -> {
if (spotlightDisposable != null && !spotlightDisposable.isDisposed()) {
spotlightDisposable.dispose();
}
presenter.disposeSubscriptions();
if (charSequence.length() == 0) {
loadMoreResultsText.setVisibility(View.GONE);
adapter.setMode(RoomListAdapter.MODE_ROOM);
presenter.bindView(this);
} else {
filterRoomSidebarList(charSequence);
}
});
loadMoreResultsText.setOnClickListener(view -> loadMoreResults());
}
@Override
public void showRoomSidebarList(@NonNull List<RoomSidebar> roomSidebarList) {
this.roomSidebarList = roomSidebarList;
adapter.setRoomSidebarList(roomSidebarList);
}
@Override
public void filterRoomSidebarList(CharSequence term) {
List<RoomSidebar> filteredRoomSidebarList = new ArrayList<>();
for (RoomSidebar roomSidebar : roomSidebarList) {
if (roomSidebar.getRoomName().contains(term)) {
filteredRoomSidebarList.add(roomSidebar);
}
}
if (filteredRoomSidebarList.isEmpty()) {
loadMoreResults();
} else {
loadMoreResultsText.setVisibility(View.VISIBLE);
adapter.setMode(RoomListAdapter.MODE_ROOM);
adapter.setRoomSidebarList(filteredRoomSidebarList);
}
}
private void loadMoreResults() {
spotlightDisposable = presenter.searchSpotlight(searchView.getQuery().toString())
.toObservable()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::showSearchSuggestions);
}
private void showSearchSuggestions(List<Spotlight> spotlightList) {
loadMoreResultsText.setVisibility(View.GONE);
adapter.setMode(RoomListAdapter.MODE_SPOTLIGHT);
adapter.setSpotlightList(spotlightList);
}
@SuppressLint("RxLeakedSubscription")
private void setupUserActionToggle() {
final CompoundButton toggleUserAction = rootView.findViewById(R.id.toggle_user_action);
toggleUserAction.setFocusableInTouchMode(false);
rootView.findViewById(R.id.user_info_container).setOnClickListener(view -> toggleUserAction.toggle());
RxCompoundButton.checkedChanges(toggleUserAction)
.compose(bindToLifecycle())
.subscribe(
this::showUserActionContainer,
Logger.INSTANCE::report
);
} }
}
public void showUserActionContainer(boolean show) {
private void updateRoomListMode() { rootView.findViewById(R.id.user_action_outer_container)
final List<RoomListHeader> roomListHeaders = new ArrayList<>(); .setVisibility(show ? View.VISIBLE : View.GONE);
}
roomListHeaders.add(new UnreadRoomListHeader(
getString(R.string.fragment_sidebar_main_unread_rooms_title) public void toggleUserActionContainer(boolean checked) {
)); CompoundButton toggleUserAction = rootView.findViewById(R.id.toggle_user_action);
toggleUserAction.setChecked(checked);
roomListHeaders.add(new FavoriteRoomListHeader( }
getString(R.string.fragment_sidebar_main_favorite_title)
)); @Override
public void showScreen() {
roomListHeaders.add(new LivechatRoomListHeader( rootView.setVisibility(View.VISIBLE);
getString(R.string.fragment_sidebar_main_livechat_title) }
));
@Override
roomListHeaders.add(new ChannelRoomListHeader( public void showEmptyScreen() {
getString(R.string.fragment_sidebar_main_channels_title), rootView.setVisibility(View.INVISIBLE);
() -> showAddRoomDialog(AddChannelDialogFragment.create(hostname)) }
));
roomListHeaders.add(new DirectMessageRoomListHeader( @Override
getString(R.string.fragment_sidebar_main_direct_messages_title), public void show(User user) {
() -> showAddRoomDialog(AddDirectMessageDialogFragment.create(hostname)) onRenderCurrentUser(user);
)); updateRoomListMode();
}
adapter.setRoomListHeaders(roomListHeaders);
} private void setupUserStatusButtons() {
rootView.findViewById(R.id.btn_status_online).setOnClickListener(view -> {
@Override presenter.onUserOnline();
public void onLogoutCleanUp() { closeUserActionContainer();
Activity activity = getActivity(); });
if (activity != null && activity instanceof MainActivity) { rootView.findViewById(R.id.btn_status_away).setOnClickListener(view -> {
((MainActivity) activity).onLogout(); presenter.onUserAway();
presenter.onLogout(task -> { closeUserActionContainer();
if (task.isFaulted()) { });
return Task.forError(task.getError()); rootView.findViewById(R.id.btn_status_busy).setOnClickListener(view -> {
presenter.onUserBusy();
closeUserActionContainer();
});
rootView.findViewById(R.id.btn_status_invisible).setOnClickListener(view -> {
presenter.onUserOffline();
closeUserActionContainer();
});
}
private void onRenderCurrentUser(User user) {
if (user != null) {
UserRenderer userRenderer = new UserRenderer(user);
userRenderer.showAvatar(rootView.findViewById(R.id.current_user_avatar), hostname);
userRenderer.showUsername(rootView.findViewById(R.id.current_user_name));
userRenderer.showStatusColor(rootView.findViewById(R.id.current_user_status));
} }
return null;
});
} }
}
private void updateRoomListMode() {
private void setupLogoutButton() { final List<RoomListHeader> roomListHeaders = new ArrayList<>();
rootView.findViewById(R.id.btn_logout).setOnClickListener(view -> {
closeUserActionContainer(); roomListHeaders.add(new UnreadRoomListHeader(
// Clear relative data and set new hostname if any. getString(R.string.fragment_sidebar_main_unread_rooms_title)
presenter.beforeLogoutCleanUp(); ));
final Activity activity = getActivity();
if (activity != null && activity instanceof MainActivity) { roomListHeaders.add(new FavoriteRoomListHeader(
// Clear subscriptions on MainPresenter. getString(R.string.fragment_sidebar_main_favorite_title)
((MainActivity) activity).beforeLogoutCleanUp(); ));
}
}); roomListHeaders.add(new LivechatRoomListHeader(
} getString(R.string.fragment_sidebar_main_livechat_title)
));
public void clearSearchViewFocus() {
searchView.clearFocus(); roomListHeaders.add(new ChannelRoomListHeader(
} getString(R.string.fragment_sidebar_main_channels_title),
() -> showAddRoomDialog(AddChannelDialogFragment.create(hostname))
public void closeUserActionContainer() { ));
final CompoundButton toggleUserAction = rootView.findViewById(R.id.toggle_user_action); roomListHeaders.add(new DirectMessageRoomListHeader(
if (toggleUserAction != null && toggleUserAction.isChecked()) { getString(R.string.fragment_sidebar_main_direct_messages_title),
toggleUserAction.setChecked(false); () -> showAddRoomDialog(AddDirectMessageDialogFragment.create(hostname))
));
adapter.setRoomListHeaders(roomListHeaders);
}
@DebugLog
@Override
public void onPreparedToLogOut() {
final Activity activity = getActivity();
if (activity != null && activity instanceof MainActivity) {
((MainActivity) activity).onLogout();
}
}
private void setupLogoutButton() {
rootView.findViewById(R.id.btn_logout).setOnClickListener(view -> {
closeUserActionContainer();
// Clear relative data and set new hostname if any.
presenter.prepareToLogOut();
});
} }
}
private void setupVersionInfo() { public void clearSearchViewFocus() {
TextView versionInfoView = rootView.findViewById(R.id.version_info); searchView.clearFocus();
versionInfoView.setText(getString(R.string.version_info_text, BuildConfig.VERSION_NAME)); }
}
private void showAddRoomDialog(DialogFragment dialog) { public void closeUserActionContainer() {
dialog.show(getFragmentManager(), "AbstractAddRoomDialogFragment"); final CompoundButton toggleUserAction = rootView.findViewById(R.id.toggle_user_action);
} if (toggleUserAction != null && toggleUserAction.isChecked()) {
toggleUserAction.setChecked(false);
}
}
private void setupVersionInfo() {
TextView versionInfoView = rootView.findViewById(R.id.version_info);
versionInfoView.setText(getString(R.string.version_info_text, BuildConfig.VERSION_NAME));
}
private void showAddRoomDialog(DialogFragment dialog) {
dialog.show(getFragmentManager(), "AbstractAddRoomDialogFragment");
}
} }
\ No newline at end of file
...@@ -18,7 +18,6 @@ import chat.rocket.android.helper.LogIfError; ...@@ -18,7 +18,6 @@ import chat.rocket.android.helper.LogIfError;
import chat.rocket.android.helper.Logger; import chat.rocket.android.helper.Logger;
import chat.rocket.android.helper.TextUtils; import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.service.ConnectivityManager; import chat.rocket.android.service.ConnectivityManager;
import chat.rocket.android.service.ConnectivityManagerApi;
import chat.rocket.android.shared.BasePresenter; import chat.rocket.android.shared.BasePresenter;
import chat.rocket.core.interactors.RoomInteractor; import chat.rocket.core.interactors.RoomInteractor;
import chat.rocket.core.models.Room; import chat.rocket.core.models.Room;
...@@ -33,12 +32,12 @@ import chat.rocket.persistence.realm.repositories.RealmSpotlightRepository; ...@@ -33,12 +32,12 @@ import chat.rocket.persistence.realm.repositories.RealmSpotlightRepository;
import io.reactivex.Flowable; import io.reactivex.Flowable;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable; import io.reactivex.disposables.Disposable;
import io.realm.Realm;
public class SidebarMainPresenter extends BasePresenter<SidebarMainContract.View> implements SidebarMainContract.Presenter { public class SidebarMainPresenter extends BasePresenter<SidebarMainContract.View> implements SidebarMainContract.Presenter {
private final String hostname; private final String hostname;
private final RoomInteractor roomInteractor; private final RoomInteractor roomInteractor;
private final UserRepository userRepository; private final UserRepository userRepository;
private final RocketChatCache rocketChatCache;
private final AbsoluteUrlHelper absoluteUrlHelper; private final AbsoluteUrlHelper absoluteUrlHelper;
private final MethodCallHelper methodCallHelper; private final MethodCallHelper methodCallHelper;
private SpotlightRepository realmSpotlightRepository; private SpotlightRepository realmSpotlightRepository;
...@@ -47,14 +46,12 @@ public class SidebarMainPresenter extends BasePresenter<SidebarMainContract.View ...@@ -47,14 +46,12 @@ public class SidebarMainPresenter extends BasePresenter<SidebarMainContract.View
public SidebarMainPresenter(String hostname, public SidebarMainPresenter(String hostname,
RoomInteractor roomInteractor, RoomInteractor roomInteractor,
UserRepository userRepository, UserRepository userRepository,
RocketChatCache rocketChatCache,
AbsoluteUrlHelper absoluteUrlHelper, AbsoluteUrlHelper absoluteUrlHelper,
MethodCallHelper methodCallHelper, MethodCallHelper methodCallHelper,
RealmSpotlightRepository realmSpotlightRepository) { RealmSpotlightRepository realmSpotlightRepository) {
this.hostname = hostname; this.hostname = hostname;
this.roomInteractor = roomInteractor; this.roomInteractor = roomInteractor;
this.userRepository = userRepository; this.userRepository = userRepository;
this.rocketChatCache = rocketChatCache;
this.absoluteUrlHelper = absoluteUrlHelper; this.absoluteUrlHelper = absoluteUrlHelper;
this.methodCallHelper = methodCallHelper; this.methodCallHelper = methodCallHelper;
this.realmSpotlightRepository = realmSpotlightRepository; this.realmSpotlightRepository = realmSpotlightRepository;
...@@ -80,14 +77,14 @@ public class SidebarMainPresenter extends BasePresenter<SidebarMainContract.View ...@@ -80,14 +77,14 @@ public class SidebarMainPresenter extends BasePresenter<SidebarMainContract.View
) )
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get())) .subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(pair -> view.show(pair.first.orNull()), Logger::report); .subscribe(pair -> view.show(pair.first.orNull()), Logger.INSTANCE::report);
addSubscription(subscription); addSubscription(subscription);
} }
@Override @Override
public void onRoomSelected(RoomSidebar roomSidebar) { public void onRoomSelected(RoomSidebar roomSidebar) {
rocketChatCache.setSelectedRoomId(roomSidebar.getRoomId()); RocketChatCache.INSTANCE.setSelectedRoomId(roomSidebar.getRoomId());
} }
@Override @Override
...@@ -103,7 +100,7 @@ public class SidebarMainPresenter extends BasePresenter<SidebarMainContract.View ...@@ -103,7 +100,7 @@ public class SidebarMainPresenter extends BasePresenter<SidebarMainContract.View
methodCallHelper.createDirectMessage(username) methodCallHelper.createDirectMessage(username)
.continueWithTask(task -> { .continueWithTask(task -> {
if (task.isCompleted()) { if (task.isCompleted()) {
rocketChatCache.setSelectedRoomId(task.getResult()); RocketChatCache.INSTANCE.setSelectedRoomId(task.getResult());
} }
return null; return null;
}); });
...@@ -111,7 +108,7 @@ public class SidebarMainPresenter extends BasePresenter<SidebarMainContract.View ...@@ -111,7 +108,7 @@ public class SidebarMainPresenter extends BasePresenter<SidebarMainContract.View
methodCallHelper.joinRoom(spotlight.getId()) methodCallHelper.joinRoom(spotlight.getId())
.continueWithTask(task -> { .continueWithTask(task -> {
if (task.isCompleted()) { if (task.isCompleted()) {
rocketChatCache.setSelectedRoomId(spotlight.getId()); RocketChatCache.INSTANCE.setSelectedRoomId(spotlight.getId());
} }
return null; return null;
}); });
...@@ -142,7 +139,7 @@ public class SidebarMainPresenter extends BasePresenter<SidebarMainContract.View ...@@ -142,7 +139,7 @@ public class SidebarMainPresenter extends BasePresenter<SidebarMainContract.View
public void onLogout(Continuation<Void, Object> continuation) { public void onLogout(Continuation<Void, Object> continuation) {
methodCallHelper.logout().continueWith(task -> { methodCallHelper.logout().continueWith(task -> {
if (task.isFaulted()) { if (task.isFaulted()) {
Logger.report(task.getError()); Logger.INSTANCE.report(task.getError());
return Task.forError(task.getError()); return Task.forError(task.getError());
} }
return task.onSuccess(continuation); return task.onSuccess(continuation);
...@@ -150,20 +147,26 @@ public class SidebarMainPresenter extends BasePresenter<SidebarMainContract.View ...@@ -150,20 +147,26 @@ public class SidebarMainPresenter extends BasePresenter<SidebarMainContract.View
} }
@Override @Override
public void beforeLogoutCleanUp() { public void prepareToLogOut() {
clearSubscriptions(); onLogout(task -> {
String currentHostname = rocketChatCache.getSelectedServerHostname(); if (task.isFaulted()) {
RealmHelper realmHelper = RealmStore.getOrCreate(currentHostname); return Task.forError(task.getError());
realmHelper.executeTransaction(realm -> { }
realm.executeTransactionAsync(realmObj -> realmObj.deleteAll());
CookieManager.getInstance().removeAllCookie(); clearSubscriptions();
ConnectivityManagerApi connectivityManagerApi = ConnectivityManager.getInstance(RocketChatApplication.getInstance()); String currentHostname = RocketChatCache.INSTANCE.getSelectedServerHostname();
connectivityManagerApi.removeServer(currentHostname); RealmHelper realmHelper = RealmStore.getOrCreate(currentHostname);
rocketChatCache.removeHostname(currentHostname); return realmHelper.executeTransaction(realm -> {
rocketChatCache.removeSelectedRoomId(currentHostname); RocketChatCache.INSTANCE.removeHostname(currentHostname);
rocketChatCache.setSelectedServerHostname(rocketChatCache.getFirstLoggedHostnameIfAny()); RocketChatCache.INSTANCE.removeSelectedRoomId(currentHostname);
view.onLogoutCleanUp(); RocketChatCache.INSTANCE.setSelectedServerHostname(RocketChatCache.INSTANCE.getFirstLoggedHostnameIfAny());
return null; realm.executeTransactionAsync(Realm::deleteAll);
view.onPreparedToLogOut();
ConnectivityManager.getInstance(RocketChatApplication.getInstance())
.removeServer(hostname);
CookieManager.getInstance().removeAllCookie();
return null;
});
}); });
} }
...@@ -177,7 +180,7 @@ public class SidebarMainPresenter extends BasePresenter<SidebarMainContract.View ...@@ -177,7 +180,7 @@ public class SidebarMainPresenter extends BasePresenter<SidebarMainContract.View
.distinctUntilChanged() .distinctUntilChanged()
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get())) .subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(this::processRooms, Logger::report); .subscribe(this::processRooms, Logger.INSTANCE::report);
addSubscription(subscription); addSubscription(subscription);
} }
...@@ -221,13 +224,13 @@ public class SidebarMainPresenter extends BasePresenter<SidebarMainContract.View ...@@ -221,13 +224,13 @@ public class SidebarMainPresenter extends BasePresenter<SidebarMainContract.View
.distinctUntilChanged() .distinctUntilChanged()
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get())) .subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(this::processUsers, Logger::report); .subscribe(this::processUsers, Logger.INSTANCE::report);
addSubscription(subscription); addSubscription(subscription);
} }
private void processUsers(List<User> userList) { private void processUsers(List<User> userList) {
for (User user: userList) { for (User user : userList) {
for(RoomSidebar roomSidebar: roomSidebarList) { for (RoomSidebar roomSidebar : roomSidebarList) {
if (roomSidebar.getRoomName().equals(user.getUsername())) { if (roomSidebar.getRoomName().equals(user.getUsername())) {
roomSidebar.setUserStatus(user.getStatus()); roomSidebar.setUserStatus(user.getStatus());
} }
......
...@@ -47,7 +47,7 @@ public class AddChannelDialogFragment extends AbstractAddRoomDialogFragment { ...@@ -47,7 +47,7 @@ public class AddChannelDialogFragment extends AbstractAddRoomDialogFragment {
.compose(bindToLifecycle()) .compose(bindToLifecycle())
.subscribe( .subscribe(
buttonAddChannel::setEnabled, buttonAddChannel::setEnabled,
Logger::report Logger.INSTANCE::report
); );
buttonAddChannel.setOnClickListener(view -> createRoom()); buttonAddChannel.setOnClickListener(view -> createRoom());
......
...@@ -68,7 +68,7 @@ public class AddDirectMessageDialogFragment extends AbstractAddRoomDialogFragmen ...@@ -68,7 +68,7 @@ public class AddDirectMessageDialogFragment extends AbstractAddRoomDialogFragmen
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe( .subscribe(
this::setupView, this::setupView,
Logger::report Logger.INSTANCE::report
) )
); );
...@@ -77,7 +77,7 @@ public class AddDirectMessageDialogFragment extends AbstractAddRoomDialogFragmen ...@@ -77,7 +77,7 @@ public class AddDirectMessageDialogFragment extends AbstractAddRoomDialogFragmen
.compose(bindToLifecycle()) .compose(bindToLifecycle())
.subscribe( .subscribe(
buttonAddDirectMessage::setEnabled, buttonAddDirectMessage::setEnabled,
Logger::report Logger.INSTANCE::report
); );
buttonAddDirectMessage.setOnClickListener(view -> createRoom()); buttonAddDirectMessage.setOnClickListener(view -> createRoom());
......
package chat.rocket.android.helper;
public class BackStackHelper {
public static String FRAGMENT_TAG = "login";
}
package chat.rocket.android.helper;
import com.crashlytics.android.Crashlytics;
import com.google.firebase.crash.FirebaseCrash;
public class Logger {
public static void report(Throwable throwable) {
FirebaseCrash.report(throwable);
Crashlytics.logException(throwable);
}
}
package chat.rocket.android.helper
import chat.rocket.android.BuildConfig
import com.crashlytics.android.Crashlytics
import com.google.firebase.crash.FirebaseCrash
object Logger {
fun report(throwable: Throwable) {
if (BuildConfig.DEBUG) {
throwable.printStackTrace()
}
FirebaseCrash.report(throwable)
Crashlytics.logException(throwable)
}
}
...@@ -48,9 +48,7 @@ public class MessagePopup { ...@@ -48,9 +48,7 @@ public class MessagePopup {
} }
private void showAvailableActionsOnly(Context context) { private void showAvailableActionsOnly(Context context) {
RocketChatCache cache = new RocketChatCache(context.getApplicationContext()); String hostname = RocketChatCache.INSTANCE.getSelectedServerHostname();
String hostname = cache.getSelectedServerHostname();
EditMessageInteractor editMessageInteractor = getEditMessageInteractor(hostname); EditMessageInteractor editMessageInteractor = getEditMessageInteractor(hostname);
...@@ -102,7 +100,7 @@ public class MessagePopup { ...@@ -102,7 +100,7 @@ public class MessagePopup {
.create() .create()
.show(); .show();
}, },
Logger::report Logger.INSTANCE::report
); );
compositeDisposable.add(disposable); compositeDisposable.add(disposable);
} }
...@@ -167,7 +165,7 @@ public class MessagePopup { ...@@ -167,7 +165,7 @@ public class MessagePopup {
} }
public MessagePopup setDeleteAction(ActionListener actionListener) { public MessagePopup setDeleteAction(ActionListener actionListener) {
DELETE_ACTION_INFO.actionListener= actionListener; DELETE_ACTION_INFO.actionListener = actionListener;
return singleton; return singleton;
} }
......
...@@ -101,7 +101,7 @@ public class RoomListItemViewHolder extends RecyclerView.ViewHolder { ...@@ -101,7 +101,7 @@ public class RoomListItemViewHolder extends RecyclerView.ViewHolder {
break; break;
default: { default: {
itemView.showPrivateChannelIcon(); itemView.showPrivateChannelIcon();
Logger.report(new AssertionError("Room type doesn't satisfies the method documentation. Room type is:" + roomType)); Logger.INSTANCE.report(new AssertionError("Room type doesn't satisfies the method documentation. Room type is:" + roomType));
} }
} }
} }
......
package chat.rocket.android.push package chat.rocket.android.push
import android.annotation.SuppressLint
import android.app.Notification import android.app.Notification
import android.app.NotificationChannel import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
...@@ -137,6 +138,7 @@ object PushManager { ...@@ -137,6 +138,7 @@ object PushManager {
return group return group
} }
@SuppressLint("NewApi")
internal fun showNotification(context: Context, lastPushMessage: PushMessage) { internal fun showNotification(context: Context, lastPushMessage: PushMessage) {
if (lastPushMessage.host == null || lastPushMessage.message == null || lastPushMessage.title == null) { if (lastPushMessage.host == null || lastPushMessage.message == null || lastPushMessage.title == null) {
return return
...@@ -202,7 +204,7 @@ object PushManager { ...@@ -202,7 +204,7 @@ object PushManager {
.setDeleteIntent(deleteIntent) .setDeleteIntent(deleteIntent)
.setMessageNotification() .setMessageNotification()
val subText = RocketChatCache(context).getHostSiteName(host) val subText = RocketChatCache.getHostSiteName(host)
if (subText.isNotEmpty()) { if (subText.isNotEmpty()) {
builder.setSubText(subText) builder.setSubText(subText)
} }
...@@ -257,6 +259,7 @@ object PushManager { ...@@ -257,6 +259,7 @@ object PushManager {
} }
} }
@SuppressLint("NewApi")
@RequiresApi(Build.VERSION_CODES.N) @RequiresApi(Build.VERSION_CODES.N)
internal fun createGroupNotificationForNougatAndAbove(context: Context, lastPushMessage: PushMessage): Notification? { internal fun createGroupNotificationForNougatAndAbove(context: Context, lastPushMessage: PushMessage): Notification? {
with(lastPushMessage) { with(lastPushMessage) {
...@@ -289,7 +292,7 @@ object PushManager { ...@@ -289,7 +292,7 @@ object PushManager {
manager.createNotificationChannel(groupChannel) manager.createNotificationChannel(groupChannel)
} }
val subText = RocketChatCache(context).getHostSiteName(host) val subText = RocketChatCache.getHostSiteName(host)
if (subText.isNotEmpty()) { if (subText.isNotEmpty()) {
builder.setSubText(subText) builder.setSubText(subText)
} }
...@@ -344,7 +347,7 @@ object PushManager { ...@@ -344,7 +347,7 @@ object PushManager {
.setContentIntent(contentIntent) .setContentIntent(contentIntent)
.setMessageNotification() .setMessageNotification()
val subText = RocketChatCache(context).getHostSiteName(host) val subText = RocketChatCache.getHostSiteName(host)
if (subText.isNotEmpty()) { if (subText.isNotEmpty()) {
builder.setSubText(subText) builder.setSubText(subText)
} }
...@@ -370,6 +373,7 @@ object PushManager { ...@@ -370,6 +373,7 @@ object PushManager {
} }
} }
@SuppressLint("NewApi")
@RequiresApi(Build.VERSION_CODES.N) @RequiresApi(Build.VERSION_CODES.N)
internal fun createSingleNotificationForNougatAndAbove(context: Context, lastPushMessage: PushMessage): Notification? { internal fun createSingleNotificationForNougatAndAbove(context: Context, lastPushMessage: PushMessage): Notification? {
val manager: NotificationManager = val manager: NotificationManager =
...@@ -404,7 +408,7 @@ object PushManager { ...@@ -404,7 +408,7 @@ object PushManager {
manager.createNotificationChannel(channel) manager.createNotificationChannel(channel)
} }
val subText = RocketChatCache(context).getHostSiteName(host) val subText = RocketChatCache.getHostSiteName(host)
if (subText.isNotEmpty()) { if (subText.isNotEmpty()) {
builder.setSubText(subText) builder.setSubText(subText)
} }
...@@ -503,6 +507,7 @@ object PushManager { ...@@ -503,6 +507,7 @@ object PushManager {
setAutoCancel(true) setAutoCancel(true)
setShowWhen(true) setShowWhen(true)
setColor(res.getColor(R.color.colorRed400, ctx.theme)) setColor(res.getColor(R.color.colorRed400, ctx.theme))
setDefaults(Notification.DEFAULT_ALL)
setSmallIcon(smallIcon) setSmallIcon(smallIcon)
}) })
return this return this
...@@ -646,7 +651,7 @@ object PushManager { ...@@ -646,7 +651,7 @@ object PushManager {
} }
val httpUrl = HttpUrl.parse(pushMessage.host) val httpUrl = HttpUrl.parse(pushMessage.host)
httpUrl?.let { httpUrl?.let {
val siteUrl = RocketChatCache(context).getSiteUrlFor(httpUrl.host()) val siteUrl = RocketChatCache.getSiteUrlFor(httpUrl.host())
if (siteUrl != null) { if (siteUrl != null) {
sendMessage(siteUrl, message, pushMessage.rid) sendMessage(siteUrl, message, pushMessage.rid)
} }
...@@ -693,7 +698,6 @@ object PushManager { ...@@ -693,7 +698,6 @@ object PushManager {
.subscribe({ _ -> .subscribe({ _ ->
// Empty // Empty
}, { throwable -> }, { throwable ->
throwable.printStackTrace()
Logger.report(throwable) Logger.report(throwable)
}) })
} }
......
package chat.rocket.android.push.gcm
import bolts.Task
import chat.rocket.android.R
import chat.rocket.android.RocketChatApplication
import chat.rocket.android.RocketChatCache
import chat.rocket.android.api.RaixPushHelper
import chat.rocket.persistence.realm.RealmHelper
import chat.rocket.persistence.realm.models.ddp.RealmUser
import com.google.android.gms.gcm.GoogleCloudMessaging
import com.google.android.gms.iid.InstanceID
import java.io.IOException
object GcmPushHelper {
fun getGcmToken(): String? = getGcmToken(getSenderId())
@Throws(IOException::class)
private fun registerGcmTokenForServer(realmHelper: RealmHelper): Task<Void> {
val gcmToken = getGcmToken(getSenderId())
val currentUser = realmHelper.executeTransactionForRead({ realm -> RealmUser.queryCurrentUser(realm).findFirst() })
val userId = if (currentUser != null) currentUser.getId() else null
val pushId = RocketChatCache.getOrCreatePushId()
return RaixPushHelper(realmHelper)
.pushUpdate(pushId!!, gcmToken, userId)
}
@Throws(IOException::class)
private fun getGcmToken(senderId: String): String {
return InstanceID.getInstance(RocketChatApplication.getInstance())
.getToken(senderId, GoogleCloudMessaging.INSTANCE_ID_SCOPE, null)
}
private fun getSenderId(): String {
return RocketChatApplication.getInstance().getString(R.string.gcm_sender_id)
}
}
\ No newline at end of file
...@@ -28,4 +28,8 @@ public interface ConnectivityManagerApi { ...@@ -28,4 +28,8 @@ public interface ConnectivityManagerApi {
int getConnectivityState(@NonNull String hostname); int getConnectivityState(@NonNull String hostname);
void resetConnectivityStateList(); void resetConnectivityStateList();
void notifySessionEstablished(String hostname);
void notifyConnecting(String hostname);
} }
package chat.rocket.android.service
import chat.rocket.android.ConnectionStatusManager
import chat.rocket.android.RocketChatApplication
import com.evernote.android.job.Job
import com.evernote.android.job.JobManager
import com.evernote.android.job.JobRequest
import java.util.concurrent.TimeUnit
class KeepAliveJob : Job() {
private val connectivityManager: ConnectivityManagerApi
companion object {
val TAG = "chat.rocket.android.service.KeepAliveJob"
fun schedule() {
JobRequest.Builder(TAG)
.setExecutionWindow(TimeUnit.SECONDS.toMillis(3L), TimeUnit.SECONDS.toMillis(10L))
.setBackoffCriteria(10L, JobRequest.BackoffPolicy.EXPONENTIAL)
.setUpdateCurrent(true)
.setRequiredNetworkType(JobRequest.NetworkType.CONNECTED)
.setRequiresCharging(false)
.setRequirementsEnforced(true)
.build()
.schedule()
}
fun cancelPendingJobRequests() {
val allJobRequests = JobManager.instance().getAllJobRequestsForTag(TAG)
allJobRequests.forEach { jobRequest ->
jobRequest.cancelAndEdit()
}
}
}
init {
val context = RocketChatApplication.getInstance()
connectivityManager = ConnectivityManager.getInstance(context)
}
override fun onRunJob(params: Params): Result {
if (ConnectionStatusManager.transitionCount() == 0L) {
return Result.SUCCESS
}
when (ConnectionStatusManager.currentState()) {
ConnectionStatusManager.State.CONNECTING, ConnectionStatusManager.State.ONLINE -> {
cancel()
}
else -> connectivityManager.keepAliveServer()
}
return Result.SUCCESS
}
}
\ No newline at end of file
...@@ -14,6 +14,7 @@ import java.util.Map; ...@@ -14,6 +14,7 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import chat.rocket.android.ConnectionStatusManager;
import chat.rocket.android.RocketChatCache; import chat.rocket.android.RocketChatCache;
import chat.rocket.android.helper.RxHelper; import chat.rocket.android.helper.RxHelper;
import chat.rocket.android.log.RCLog; import chat.rocket.android.log.RCLog;
...@@ -74,7 +75,7 @@ import io.reactivex.subjects.BehaviorSubject; ...@@ -74,7 +75,7 @@ import io.reactivex.subjects.BehaviorSubject;
@DebugLog @DebugLog
@Override @Override
public void ensureConnections() { public void ensureConnections() {
String hostname = new RocketChatCache(appContext).getSelectedServerHostname(); String hostname = RocketChatCache.INSTANCE.getSelectedServerHostname();
if (hostname == null) { if (hostname == null) {
return; return;
} }
...@@ -107,7 +108,7 @@ import io.reactivex.subjects.BehaviorSubject; ...@@ -107,7 +108,7 @@ import io.reactivex.subjects.BehaviorSubject;
public void removeServer(String hostname) { public void removeServer(String hostname) {
RealmBasedServerInfo.remove(hostname); RealmBasedServerInfo.remove(hostname);
if (serverConnectivityList.containsKey(hostname)) { if (serverConnectivityList.containsKey(hostname)) {
disconnectFromServerIfNeeded(hostname) disconnectFromServerIfNeeded(hostname, DDPClient.REASON_CLOSED_BY_USER)
.subscribe(_val -> { .subscribe(_val -> {
}, RCLog::e); }, RCLog::e);
} }
...@@ -136,6 +137,13 @@ import io.reactivex.subjects.BehaviorSubject; ...@@ -136,6 +137,13 @@ import io.reactivex.subjects.BehaviorSubject;
return list; return list;
} }
@Override
public void notifySessionEstablished(String hostname) {
serverConnectivityList.put(hostname, ServerConnectivity.STATE_SESSION_ESTABLISHED);
connectivitySubject.onNext(
new ServerConnectivity(hostname, ServerConnectivity.STATE_SESSION_ESTABLISHED));
}
@DebugLog @DebugLog
@Override @Override
public void notifyConnectionEstablished(String hostname, String session) { public void notifyConnectionEstablished(String hostname, String session) {
...@@ -200,7 +208,7 @@ import io.reactivex.subjects.BehaviorSubject; ...@@ -200,7 +208,7 @@ import io.reactivex.subjects.BehaviorSubject;
}); });
} }
private Single<Boolean> disconnectFromServerIfNeeded(String hostname) { private Single<Boolean> disconnectFromServerIfNeeded(String hostname, int reason) {
return Single.defer(() -> { return Single.defer(() -> {
final int connectivity = serverConnectivityList.get(hostname); final int connectivity = serverConnectivityList.get(hostname);
if (connectivity == ServerConnectivity.STATE_DISCONNECTED) { if (connectivity == ServerConnectivity.STATE_DISCONNECTED) {
...@@ -209,8 +217,8 @@ import io.reactivex.subjects.BehaviorSubject; ...@@ -209,8 +217,8 @@ import io.reactivex.subjects.BehaviorSubject;
if (connectivity == ServerConnectivity.STATE_CONNECTING) { if (connectivity == ServerConnectivity.STATE_CONNECTING) {
return waitForConnected(hostname) return waitForConnected(hostname)
.doOnError(err -> notifyConnectionLost(hostname, DDPClient.REASON_NETWORK_ERROR)) // .doOnError(err -> notifyConnectionLost(hostname, DDPClient.REASON_CLOSED_BY_USER))
.flatMap(_val -> disconnectFromServerIfNeeded(hostname)); .flatMap(_val -> disconnectFromServerIfNeeded(hostname, DDPClient.REASON_CLOSED_BY_USER));
} }
if (connectivity == ServerConnectivity.STATE_DISCONNECTING) { if (connectivity == ServerConnectivity.STATE_DISCONNECTING) {
...@@ -253,6 +261,7 @@ import io.reactivex.subjects.BehaviorSubject; ...@@ -253,6 +261,7 @@ import io.reactivex.subjects.BehaviorSubject;
private Single<Boolean> connectToServer(String hostname) { private Single<Boolean> connectToServer(String hostname) {
return Single.defer(() -> { return Single.defer(() -> {
if (!serverConnectivityList.containsKey(hostname)) { if (!serverConnectivityList.containsKey(hostname)) {
ConnectionStatusManager.INSTANCE.setConnectionError();
return Single.error(new IllegalArgumentException("hostname not found")); return Single.error(new IllegalArgumentException("hostname not found"));
} }
...@@ -263,8 +272,10 @@ import io.reactivex.subjects.BehaviorSubject; ...@@ -263,8 +272,10 @@ import io.reactivex.subjects.BehaviorSubject;
} }
if (serviceInterface != null) { if (serviceInterface != null) {
ConnectionStatusManager.INSTANCE.setConnecting();
return serviceInterface.ensureConnectionToServer(hostname); return serviceInterface.ensureConnectionToServer(hostname);
} else { } else {
ConnectionStatusManager.INSTANCE.setConnectionError();
return Single.error(new ThreadLooperNotPreparedException("not prepared")); return Single.error(new ThreadLooperNotPreparedException("not prepared"));
} }
}); });
...@@ -279,8 +290,11 @@ import io.reactivex.subjects.BehaviorSubject; ...@@ -279,8 +290,11 @@ import io.reactivex.subjects.BehaviorSubject;
if (serviceInterface != null) { if (serviceInterface != null) {
return serviceInterface.disconnectFromServer(hostname) return serviceInterface.disconnectFromServer(hostname)
// //after disconnection from server, remove HOSTNAME key from HashMap //after disconnection from server, remove HOSTNAME key from HashMap
.doAfterTerminate(() -> serverConnectivityList.remove(hostname)); .doAfterTerminate(() -> {
serverConnectivityList.remove(hostname);
serverConnectivityList.put(hostname, ServerConnectivity.STATE_DISCONNECTED);
});
} else { } else {
return Single.error(new IllegalStateException("not prepared")); return Single.error(new IllegalStateException("not prepared"));
} }
......
...@@ -38,7 +38,8 @@ public class RocketChatService extends Service implements ConnectivityServiceInt ...@@ -38,7 +38,8 @@ public class RocketChatService extends Service implements ConnectivityServiceInt
/** /**
* ensure RocketChatService alive. * ensure RocketChatService alive.
*/ */
/*package*/static void keepAlive(Context context) { /*package*/
static void keepAlive(Context context) {
context.startService(new Intent(context, RocketChatService.class)); context.startService(new Intent(context, RocketChatService.class));
} }
...@@ -80,7 +81,7 @@ public class RocketChatService extends Service implements ConnectivityServiceInt ...@@ -80,7 +81,7 @@ public class RocketChatService extends Service implements ConnectivityServiceInt
} }
if (currentWebSocketThread != null) { if (currentWebSocketThread != null) {
return currentWebSocketThread.terminate() return currentWebSocketThread.terminate(false)
// after disconnection from server // after disconnection from server
.doAfterTerminate(() -> { .doAfterTerminate(() -> {
currentWebSocketThread = null; currentWebSocketThread = null;
...@@ -99,14 +100,15 @@ public class RocketChatService extends Service implements ConnectivityServiceInt ...@@ -99,14 +100,15 @@ public class RocketChatService extends Service implements ConnectivityServiceInt
return Single.defer(() -> { return Single.defer(() -> {
webSocketThreadLock.acquire(); webSocketThreadLock.acquire();
int connectivityState = ConnectivityManager.getInstance(getApplicationContext()).getConnectivityState(hostname); int connectivityState = ConnectivityManager.getInstance(getApplicationContext()).getConnectivityState(hostname);
boolean isDisconnected = connectivityState != ServerConnectivity.STATE_CONNECTED; boolean isDisconnected = connectivityState < ServerConnectivity.STATE_CONNECTED;
if (currentWebSocketThread != null && existsThreadForHostname(hostname) && !isDisconnected) { if (currentWebSocketThread != null && existsThreadForHostname(hostname) && !isDisconnected) {
webSocketThreadLock.release(); webSocketThreadLock.release();
return Single.just(currentWebSocketThread); return Single.just(currentWebSocketThread);
} }
if (currentWebSocketThread != null) { if (currentWebSocketThread != null) {
return currentWebSocketThread.terminate() boolean hasFailed = existsThreadForHostname(hostname);
return currentWebSocketThread.terminate(hasFailed)
.doAfterTerminate(() -> currentWebSocketThread = null) .doAfterTerminate(() -> currentWebSocketThread = null)
.flatMap(terminated -> .flatMap(terminated ->
RocketChatWebSocketThread.getStarted(getApplicationContext(), hostname) RocketChatWebSocketThread.getStarted(getApplicationContext(), hostname)
...@@ -117,7 +119,7 @@ public class RocketChatService extends Service implements ConnectivityServiceInt ...@@ -117,7 +119,7 @@ public class RocketChatService extends Service implements ConnectivityServiceInt
.doOnError(throwable -> { .doOnError(throwable -> {
currentWebSocketThread = null; currentWebSocketThread = null;
RCLog.e(throwable); RCLog.e(throwable);
Logger.report(throwable); Logger.INSTANCE.report(throwable);
webSocketThreadLock.release(); webSocketThreadLock.release();
}) })
); );
...@@ -131,7 +133,7 @@ public class RocketChatService extends Service implements ConnectivityServiceInt ...@@ -131,7 +133,7 @@ public class RocketChatService extends Service implements ConnectivityServiceInt
.doOnError(throwable -> { .doOnError(throwable -> {
currentWebSocketThread = null; currentWebSocketThread = null;
RCLog.e(throwable); RCLog.e(throwable);
Logger.report(throwable); Logger.INSTANCE.report(throwable);
webSocketThreadLock.release(); webSocketThreadLock.release();
}); });
}); });
......
...@@ -14,6 +14,7 @@ import java.util.concurrent.TimeUnit; ...@@ -14,6 +14,7 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import bolts.Task; import bolts.Task;
import chat.rocket.android.ConnectionStatusManager;
import chat.rocket.android.api.MethodCallHelper; import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.android.helper.LogIfError; import chat.rocket.android.helper.LogIfError;
import chat.rocket.android.helper.RxHelper; import chat.rocket.android.helper.RxHelper;
...@@ -29,7 +30,6 @@ import chat.rocket.android.service.observer.FileUploadingWithUfsObserver; ...@@ -29,7 +30,6 @@ import chat.rocket.android.service.observer.FileUploadingWithUfsObserver;
import chat.rocket.android.service.observer.GcmPushRegistrationObserver; import chat.rocket.android.service.observer.GcmPushRegistrationObserver;
import chat.rocket.android.service.observer.GetUsersOfRoomsProcedureObserver; import chat.rocket.android.service.observer.GetUsersOfRoomsProcedureObserver;
import chat.rocket.android.service.observer.LoadMessageProcedureObserver; import chat.rocket.android.service.observer.LoadMessageProcedureObserver;
import chat.rocket.android.service.observer.MethodCallObserver;
import chat.rocket.android.service.observer.NewMessageObserver; import chat.rocket.android.service.observer.NewMessageObserver;
import chat.rocket.android.service.observer.PushSettingsObserver; import chat.rocket.android.service.observer.PushSettingsObserver;
import chat.rocket.android.service.observer.SessionObserver; import chat.rocket.android.service.observer.SessionObserver;
...@@ -54,8 +54,7 @@ public class RocketChatWebSocketThread extends HandlerThread { ...@@ -54,8 +54,7 @@ public class RocketChatWebSocketThread extends HandlerThread {
LoginServiceConfigurationSubscriber.class, LoginServiceConfigurationSubscriber.class,
ActiveUsersSubscriber.class, ActiveUsersSubscriber.class,
UserDataSubscriber.class, UserDataSubscriber.class,
MethodCallObserver.class, // MethodCallObserver.class,
SessionObserver.class,
LoadMessageProcedureObserver.class, LoadMessageProcedureObserver.class,
GetUsersOfRoomsProcedureObserver.class, GetUsersOfRoomsProcedureObserver.class,
NewMessageObserver.class, NewMessageObserver.class,
...@@ -71,10 +70,10 @@ public class RocketChatWebSocketThread extends HandlerThread { ...@@ -71,10 +70,10 @@ public class RocketChatWebSocketThread extends HandlerThread {
private final String hostname; private final String hostname;
private final RealmHelper realmHelper; private final RealmHelper realmHelper;
private final ConnectivityManagerInternal connectivityManager; private final ConnectivityManagerInternal connectivityManager;
private final ArrayList<Registrable> listeners = new ArrayList<>(); private volatile ArrayList<Registrable> listeners = new ArrayList<>();
private final CompositeDisposable heartbeatDisposable = new CompositeDisposable(); private final CompositeDisposable heartbeatDisposable = new CompositeDisposable();
private final CompositeDisposable reconnectDisposable = new CompositeDisposable(); private final CompositeDisposable reconnectDisposable = new CompositeDisposable();
private boolean listenersRegistered; private volatile boolean listenersRegistered;
private RocketChatWebSocketThread(Context appContext, String hostname) { private RocketChatWebSocketThread(Context appContext, String hostname) {
super("RC_thread_" + hostname); super("RC_thread_" + hostname);
...@@ -125,21 +124,26 @@ public class RocketChatWebSocketThread extends HandlerThread { ...@@ -125,21 +124,26 @@ public class RocketChatWebSocketThread extends HandlerThread {
} }
/** /**
* terminate WebSocket thread. * Terminate WebSocket thread. If {@code hasFailed} is {@code true} it means that a connection was
* in progress but failed and got offline. If it's {@code false} means that the user explicitly
* disconnected from server either by logging out or by means of changing servers.
*
* @param hasFailed {@code true} if the termination is due to a network error, otherwise
* return false
*/ */
@DebugLog @DebugLog
/* package */ Single<Boolean> terminate() { /* package */ Single<Boolean> terminate(boolean hasFailed) {
if (isAlive()) { if (isAlive()) {
return Single.create(emitter -> { return Single.create(emitter -> new Handler(getLooper()).post(() -> {
new Handler(getLooper()).post(() -> { RCLog.d("thread %s: terminated()", Thread.currentThread().getId());
RCLog.d("thread %s: terminated()", Thread.currentThread().getId()); int reason = (hasFailed) ?
unregisterListenersAndClose(); DDPClient.REASON_NETWORK_ERROR : DDPClient.REASON_CLOSED_BY_USER;
connectivityManager.notifyConnectionLost(hostname, unregisterListenersAndClose(reason);
DDPClient.REASON_CLOSED_BY_USER); connectivityManager.notifyConnectionLost(hostname, reason);
RocketChatWebSocketThread.super.quit(); RocketChatWebSocketThread.super.quit();
emitter.onSuccess(true); ConnectionStatusManager.INSTANCE.setOffline();
}); emitter.onSuccess(true);
}); }));
} else { } else {
connectivityManager.notifyConnectionLost(hostname, connectivityManager.notifyConnectionLost(hostname,
DDPClient.REASON_NETWORK_ERROR); DDPClient.REASON_NETWORK_ERROR);
...@@ -163,7 +167,7 @@ public class RocketChatWebSocketThread extends HandlerThread { ...@@ -163,7 +167,7 @@ public class RocketChatWebSocketThread extends HandlerThread {
@DebugLog @DebugLog
/* package */ Single<Boolean> keepAlive() { /* package */ Single<Boolean> keepAlive() {
return checkIfConnectionAlive() return checkIfConnectionAlive()
.flatMap(alive -> alive ? Single.just(true) : connectWithExponentialBackoff()); .flatMap(alive -> connectWithExponentialBackoff());
} }
@DebugLog @DebugLog
...@@ -224,7 +228,7 @@ public class RocketChatWebSocketThread extends HandlerThread { ...@@ -224,7 +228,7 @@ public class RocketChatWebSocketThread extends HandlerThread {
.onSuccessTask(task -> { .onSuccessTask(task -> {
final String newSession = task.getResult().session; final String newSession = task.getResult().session;
connectivityManager.notifyConnectionEstablished(hostname, newSession); connectivityManager.notifyConnectionEstablished(hostname, newSession);
// handling WebSocket#onClose() callback.
task.getResult().client.getOnCloseCallback().onSuccess(_task -> { task.getResult().client.getOnCloseCallback().onSuccess(_task -> {
RxWebSocketCallback.Close result = _task.getResult(); RxWebSocketCallback.Close result = _task.getResult();
if (result.code == DDPClient.REASON_NETWORK_ERROR) { if (result.code == DDPClient.REASON_NETWORK_ERROR) {
...@@ -333,8 +337,8 @@ public class RocketChatWebSocketThread extends HandlerThread { ...@@ -333,8 +337,8 @@ public class RocketChatWebSocketThread extends HandlerThread {
.findAll()); .findAll());
if (sessions != null && sessions.size() > 0) { if (sessions != null && sessions.size() > 0) {
// if we have a session try to resume it. At this point we're probably recovering from // If we have a session try to resume it. At this point we're probably recovering from
// a disconnection state // a disconnection state.
final CompositeDisposable disposables = new CompositeDisposable(); final CompositeDisposable disposables = new CompositeDisposable();
MethodCallHelper methodCall = new MethodCallHelper(realmHelper); MethodCallHelper methodCall = new MethodCallHelper(realmHelper);
disposables.add( disposables.add(
...@@ -351,7 +355,11 @@ public class RocketChatWebSocketThread extends HandlerThread { ...@@ -351,7 +355,11 @@ public class RocketChatWebSocketThread extends HandlerThread {
createObserversAndRegister(); createObserversAndRegister();
disposables.clear(); disposables.clear();
}, },
error -> logErrorAndUnsubscribe(disposables, error) error -> {
logErrorAndUnsubscribe(disposables, error);
connectivityManager.notifyConnectionLost(hostname,
DDPClient.REASON_NETWORK_ERROR);
}
) )
); );
} else { } else {
...@@ -376,6 +384,10 @@ public class RocketChatWebSocketThread extends HandlerThread { ...@@ -376,6 +384,10 @@ public class RocketChatWebSocketThread extends HandlerThread {
RCLog.w(exception, "Failed to register listeners!!"); RCLog.w(exception, "Failed to register listeners!!");
} }
} }
// Register SessionObserver late.
SessionObserver sessionObserver = new SessionObserver(appContext, hostname, realmHelper);
sessionObserver.register();
listeners.add(sessionObserver);
listenersRegistered = true; listenersRegistered = true;
startHeartBeat(); startHeartBeat();
} }
...@@ -404,9 +416,9 @@ public class RocketChatWebSocketThread extends HandlerThread { ...@@ -404,9 +416,9 @@ public class RocketChatWebSocketThread extends HandlerThread {
} }
@DebugLog @DebugLog
private void unregisterListenersAndClose() { private void unregisterListenersAndClose(int reason) {
unregisterListeners(); unregisterListeners();
DDPClient.get().close(); DDPClient.get().close(reason);
} }
@DebugLog @DebugLog
......
...@@ -5,50 +5,50 @@ package chat.rocket.android.service; ...@@ -5,50 +5,50 @@ package chat.rocket.android.service;
*/ */
public class ServerConnectivity { public class ServerConnectivity {
public static final int STATE_CONNECTED = 1; public static final int STATE_DISCONNECTED = 0;
/* package */ static final int STATE_DISCONNECTING = 1;
public static final int STATE_DISCONNECTED = 2; /* package */ static final int STATE_CONNECTING = 2;
public static final int STATE_CONNECTING = 3; public static final int STATE_CONNECTED = 3;
/*package*/ static final int STATE_DISCONNECTING = 4; public static final int STATE_SESSION_ESTABLISHED = 4;
public static final ServerConnectivity CONNECTED = new ServerConnectivity(null, STATE_CONNECTED); /* package */ static final ServerConnectivity CONNECTED = new ServerConnectivity(null, STATE_CONNECTED);
public final String hostname; public final String hostname;
public final int state; public final int state;
public final int code; public final int code;
ServerConnectivity(String hostname, int state) { ServerConnectivity(String hostname, int state) {
this.hostname = hostname; this.hostname = hostname;
this.state = state; this.state = state;
this.code = -1; this.code = -1;
} }
ServerConnectivity(String hostname, int state, int code) { ServerConnectivity(String hostname, int state, int code) {
this.hostname = hostname; this.hostname = hostname;
this.state = state; this.state = state;
this.code = code; this.code = code;
} }
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false; if (o == null || getClass() != o.getClass()) return false;
ServerConnectivity that = (ServerConnectivity) o; ServerConnectivity that = (ServerConnectivity) o;
return state == that.state; return state == that.state;
} }
@Override @Override
public int hashCode() { public int hashCode() {
return state; return state;
} }
/** /**
* This exception should be thrown when connection is lost during waiting for CONNECTED. * This exception should be thrown when connection is lost during waiting for CONNECTED.
*/ */
public static class DisconnectedException extends Exception { /* package */static class DisconnectedException extends Exception {
public DisconnectedException() { /* package */DisconnectedException() {
super("Disconnected"); super("Disconnected");
}
} }
}
} }
package chat.rocket.android.service.internal; package chat.rocket.android.service.internal;
import android.content.Context;
import com.hadisatrio.optional.Optional; import com.hadisatrio.optional.Optional;
import chat.rocket.android.RocketChatCache; import chat.rocket.android.RocketChatCache;
...@@ -13,13 +11,11 @@ import chat.rocket.persistence.realm.models.ddp.RealmRoom; ...@@ -13,13 +11,11 @@ import chat.rocket.persistence.realm.models.ddp.RealmRoom;
import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.CompositeDisposable;
public abstract class AbstractRocketChatCacheObserver implements Registrable { public abstract class AbstractRocketChatCacheObserver implements Registrable {
private final Context context;
private final RealmHelper realmHelper; private final RealmHelper realmHelper;
private String roomId; private String roomId;
private CompositeDisposable compositeDisposable = new CompositeDisposable(); private CompositeDisposable compositeDisposable = new CompositeDisposable();
protected AbstractRocketChatCacheObserver(Context context, RealmHelper realmHelper) { protected AbstractRocketChatCacheObserver(RealmHelper realmHelper) {
this.context = context;
this.realmHelper = realmHelper; this.realmHelper = realmHelper;
} }
...@@ -47,7 +43,7 @@ public abstract class AbstractRocketChatCacheObserver implements Registrable { ...@@ -47,7 +43,7 @@ public abstract class AbstractRocketChatCacheObserver implements Registrable {
@Override @Override
public final void register() { public final void register() {
compositeDisposable.add( compositeDisposable.add(
new RocketChatCache(context) RocketChatCache.INSTANCE
.getSelectedRoomIdPublisher() .getSelectedRoomIdPublisher()
.filter(Optional::isPresent) .filter(Optional::isPresent)
.map(Optional::get) .map(Optional::get)
......
...@@ -13,60 +13,58 @@ import chat.rocket.persistence.realm.RealmHelper; ...@@ -13,60 +13,58 @@ import chat.rocket.persistence.realm.RealmHelper;
* wrapper for managing stream-notify-message depending on RocketChatCache. * wrapper for managing stream-notify-message depending on RocketChatCache.
*/ */
public class StreamRoomMessageManager implements Registrable { public class StreamRoomMessageManager implements Registrable {
private final Context context; private final Context context;
private final String hostname; private final String hostname;
private final RealmHelper realmHelper; private final RealmHelper realmHelper;
private final AbstractRocketChatCacheObserver cacheObserver; private final AbstractRocketChatCacheObserver cacheObserver;
private final Handler handler; private final Handler handler;
private final RocketChatCache rocketChatCache; private StreamRoomMessage streamRoomMessage;
private StreamRoomMessage streamRoomMessage;
public StreamRoomMessageManager(Context context, String hostname, public StreamRoomMessageManager(Context context, String hostname,
RealmHelper realmHelper) { RealmHelper realmHelper) {
this.context = context; this.context = context;
this.hostname = hostname; this.hostname = hostname;
this.realmHelper = realmHelper; this.realmHelper = realmHelper;
this.rocketChatCache = new RocketChatCache(context);
cacheObserver = new AbstractRocketChatCacheObserver(context, realmHelper) { cacheObserver = new AbstractRocketChatCacheObserver(realmHelper) {
@Override @Override
protected void onRoomIdUpdated(String roomId) { protected void onRoomIdUpdated(String roomId) {
unregisterStreamNotifyMessageIfNeeded(); unregisterStreamNotifyMessageIfNeeded();
registerStreamNotifyMessage(roomId); registerStreamNotifyMessage(roomId);
} }
}; };
handler = new Handler(Looper.myLooper()); handler = new Handler(Looper.myLooper());
} }
private void registerStreamNotifyMessage(String roomId) { private void registerStreamNotifyMessage(String roomId) {
handler.post(() -> { handler.post(() -> {
streamRoomMessage = new StreamRoomMessage(context, hostname, realmHelper, roomId); streamRoomMessage = new StreamRoomMessage(context, hostname, realmHelper, roomId);
streamRoomMessage.register(); streamRoomMessage.register();
}); });
} }
private void unregisterStreamNotifyMessageIfNeeded() { private void unregisterStreamNotifyMessageIfNeeded() {
handler.post(() -> { handler.post(() -> {
if (streamRoomMessage != null) { if (streamRoomMessage != null) {
streamRoomMessage.unregister(); streamRoomMessage.unregister();
streamRoomMessage = null; streamRoomMessage = null;
} }
}); });
} }
@Override @Override
public void register() { public void register() {
cacheObserver.register(); cacheObserver.register();
String selectedRoomId = rocketChatCache.getSelectedRoomId(); String selectedRoomId = RocketChatCache.INSTANCE.getSelectedRoomId();
if (selectedRoomId == null) { if (selectedRoomId == null) {
return; return;
}
registerStreamNotifyMessage(selectedRoomId);
} }
registerStreamNotifyMessage(selectedRoomId);
}
@Override @Override
public void unregister() { public void unregister() {
unregisterStreamNotifyMessageIfNeeded(); unregisterStreamNotifyMessageIfNeeded();
cacheObserver.unregister(); cacheObserver.unregister();
} }
} }
...@@ -5,6 +5,7 @@ import android.content.Context; ...@@ -5,6 +5,7 @@ import android.content.Context;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import chat.rocket.android.RocketChatCache;
import chat.rocket.android.api.MethodCallHelper; import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.android.helper.LogIfError; import chat.rocket.android.helper.LogIfError;
import chat.rocket.android.service.Registrable; import chat.rocket.android.service.Registrable;
...@@ -19,64 +20,71 @@ import io.realm.RealmResults; ...@@ -19,64 +20,71 @@ import io.realm.RealmResults;
* observe the user with emails. * observe the user with emails.
*/ */
public class CurrentUserObserver extends AbstractModelObserver<RealmUser> { public class CurrentUserObserver extends AbstractModelObserver<RealmUser> {
private final MethodCallHelper methodCall; private final MethodCallHelper methodCall;
private boolean currentUserExists; private boolean currentUserExists;
private ArrayList<Registrable> listeners; private ArrayList<Registrable> listeners;
public CurrentUserObserver(Context context, String hostname, public CurrentUserObserver(Context context, String hostname,
RealmHelper realmHelper) { RealmHelper realmHelper) {
super(context, hostname, realmHelper); super(context, hostname, realmHelper);
methodCall = new MethodCallHelper(realmHelper); methodCall = new MethodCallHelper(realmHelper);
currentUserExists = false; currentUserExists = false;
} }
@Override @Override
public RealmResults<RealmUser> queryItems(Realm realm) { public RealmResults<RealmUser> queryItems(Realm realm) {
return RealmUser.queryCurrentUser(realm).findAll(); return RealmUser.queryCurrentUser(realm).findAll();
} }
@Override @Override
public void onUpdateResults(List<RealmUser> results) { public void onUpdateResults(List<RealmUser> results) {
boolean exists = !results.isEmpty(); boolean exists = !results.isEmpty();
if (currentUserExists != exists) { if (currentUserExists != exists) {
if (exists) { if (exists) {
onLogin(results.get(0)); onLogin(results.get(0));
} else { } else {
onLogout(); onLogout();
} }
currentUserExists = exists; currentUserExists = exists;
}
} }
}
@DebugLog @DebugLog
private void onLogin(RealmUser user) { private void onLogin(RealmUser user) {
if (listeners != null) { if (listeners != null) {
onLogout(); onLogout();
} }
listeners = new ArrayList<>(); listeners = new ArrayList<>();
final String userId = user.getId(); String userId = user.getId();
String username = user.getUsername();
String name = user.getName();
// get and observe Room subscriptions.
methodCall.getRoomSubscriptions().onSuccess(task -> {
if (listeners != null) {
Registrable listener = new StreamNotifyUserSubscriptionsChanged(
context, hostname, realmHelper, userId);
listener.register();
listeners.add(listener);
}
return null;
}).continueWith(new LogIfError());
}
@DebugLog RocketChatCache.INSTANCE.setUserId(userId);
private void onLogout() { RocketChatCache.INSTANCE.setUserUsername(username);
if (listeners != null) { RocketChatCache.INSTANCE.setUserName(name);
for (Registrable listener : listeners) {
listener.unregister(); // get and observe Room subscriptions.
} methodCall.getRoomSubscriptions().onSuccess(task -> {
if (listeners != null) {
Registrable listener = new StreamNotifyUserSubscriptionsChanged(
context, hostname, realmHelper, userId);
listener.register();
listeners.add(listener);
}
return null;
}).continueWith(new LogIfError());
}
@DebugLog
private void onLogout() {
if (listeners != null) {
for (Registrable listener : listeners) {
listener.unregister();
}
}
listeners = null;
} }
listeners = null;
}
} }
...@@ -13,6 +13,7 @@ import chat.rocket.android.R; ...@@ -13,6 +13,7 @@ import chat.rocket.android.R;
import chat.rocket.android.RocketChatCache; import chat.rocket.android.RocketChatCache;
import chat.rocket.android.api.RaixPushHelper; import chat.rocket.android.api.RaixPushHelper;
import chat.rocket.android.helper.LogIfError; import chat.rocket.android.helper.LogIfError;
import chat.rocket.android.push.gcm.GcmPushHelper;
import chat.rocket.core.SyncState; import chat.rocket.core.SyncState;
import chat.rocket.persistence.realm.RealmHelper; import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.persistence.realm.models.ddp.RealmUser; import chat.rocket.persistence.realm.models.ddp.RealmUser;
...@@ -24,63 +25,75 @@ import io.realm.RealmResults; ...@@ -24,63 +25,75 @@ import io.realm.RealmResults;
* call raix:push-update if needed. * call raix:push-update if needed.
*/ */
public class GcmPushRegistrationObserver extends AbstractModelObserver<GcmPushRegistration> { public class GcmPushRegistrationObserver extends AbstractModelObserver<GcmPushRegistration> {
public GcmPushRegistrationObserver(Context context, String hostname,
RealmHelper realmHelper) {
super(context, hostname, realmHelper);
}
@Override public GcmPushRegistrationObserver(Context context, String hostname,
public RealmResults<GcmPushRegistration> queryItems(Realm realm) { RealmHelper realmHelper) {
return GcmPushRegistration.queryDefault(realm) super(context, hostname, realmHelper);
.equalTo(GcmPushRegistration.SYNC_STATE, SyncState.NOT_SYNCED) }
.equalTo(GcmPushRegistration.GCM_PUSH_ENABLED, true)
.findAll();
}
@Override @Override
public void onUpdateResults(List<GcmPushRegistration> results) { public RealmResults<GcmPushRegistration> queryItems(Realm realm) {
if (results.isEmpty()) { return GcmPushRegistration.queryDefault(realm)
return; .equalTo(GcmPushRegistration.SYNC_STATE, SyncState.NOT_SYNCED)
.equalTo(GcmPushRegistration.GCM_PUSH_ENABLED, true)
.findAll();
} }
realmHelper.executeTransaction(realm -> { @Override
GcmPushRegistration.queryDefault(realm).findFirst().setSyncState(SyncState.SYNCING); public void onUpdateResults(List<GcmPushRegistration> results) {
return null; String gcmToken = GcmPushHelper.INSTANCE.getGcmToken();
}).onSuccessTask(_task -> registerGcmTokenForServer() if (gcmToken != null && !gcmToken.isEmpty()) {
).onSuccessTask(_task -> // We already have gcm token, so try to register it.
realmHelper.executeTransaction(realm -> { try {
GcmPushRegistration.queryDefault(realm).findFirst().setSyncState(SyncState.SYNCED); tryToRegisterToken();
return null; } catch (Exception e) {
}) e.printStackTrace();
).continueWith(task -> { }
if (task.isFaulted()) { }
realmHelper.executeTransaction(realm -> { }
GcmPushRegistration.queryDefault(realm).findFirst().setSyncState(SyncState.FAILED);
return null;
}).continueWith(new LogIfError());
}
return null;
});
}
private Task<Void> registerGcmTokenForServer() throws IOException { private Task<Void> tryToRegisterToken() {
final String gcmToken = getGcmToken(getSenderId()); return realmHelper.executeTransaction(realm -> {
final RealmUser currentUser = realmHelper.executeTransactionForRead(realm -> GcmPushRegistration.queryDefault(realm).findFirst().setSyncState(SyncState.SYNCING);
RealmUser.queryCurrentUser(realm).findFirst()); return null;
final String userId = currentUser != null ? currentUser.getId() : null; }).onSuccessTask(_task -> registerGcmTokenForServer()
final String pushId = new RocketChatCache(context).getOrCreatePushId(); ).onSuccessTask(_task ->
realmHelper.executeTransaction(realm -> {
GcmPushRegistration.queryDefault(realm).findFirst().setSyncState(SyncState.SYNCED);
return null;
})
).continueWith(task -> {
if (task.isFaulted()) {
realmHelper.executeTransaction(realm -> {
GcmPushRegistration gcmPushRegistration = GcmPushRegistration.queryDefault(realm).findFirst();
if (gcmPushRegistration != null) {
gcmPushRegistration.setSyncState(SyncState.FAILED);
}
return null;
}).continueWith(new LogIfError());
}
return null;
});
}
return new RaixPushHelper(realmHelper) private Task<Void> registerGcmTokenForServer() throws IOException {
.pushUpdate(pushId, gcmToken, userId); final String gcmToken = getGcmToken(getSenderId());
} final RealmUser currentUser = realmHelper.executeTransactionForRead(realm ->
RealmUser.queryCurrentUser(realm).findFirst());
final String userId = currentUser != null ? currentUser.getId() : null;
final String pushId = RocketChatCache.INSTANCE.getOrCreatePushId();
private String getGcmToken(String senderId) throws IOException { return new RaixPushHelper(realmHelper)
return InstanceID.getInstance(context) .pushUpdate(pushId, gcmToken, userId);
.getToken(senderId, GoogleCloudMessaging.INSTANCE_ID_SCOPE, null); }
}
private String getSenderId() { private String getGcmToken(String senderId) throws IOException {
return context.getString(R.string.gcm_sender_id); return InstanceID.getInstance(context)
} .getToken(senderId, GoogleCloudMessaging.INSTANCE_ID_SCOPE, null);
}
private String getSenderId() {
return context.getString(R.string.gcm_sender_id);
}
} }
...@@ -7,6 +7,7 @@ import java.util.List; ...@@ -7,6 +7,7 @@ import java.util.List;
import chat.rocket.android.RocketChatCache; import chat.rocket.android.RocketChatCache;
import chat.rocket.android.api.RaixPushHelper; import chat.rocket.android.api.RaixPushHelper;
import chat.rocket.android.helper.LogIfError; import chat.rocket.android.helper.LogIfError;
import chat.rocket.android.service.ConnectivityManager;
import chat.rocket.android.service.internal.StreamRoomMessageManager; import chat.rocket.android.service.internal.StreamRoomMessageManager;
import chat.rocket.persistence.realm.RealmHelper; import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.persistence.realm.models.internal.GetUsersOfRoomsProcedure; import chat.rocket.persistence.realm.models.internal.GetUsersOfRoomsProcedure;
...@@ -73,8 +74,10 @@ public class SessionObserver extends AbstractModelObserver<RealmSession> { ...@@ -73,8 +74,10 @@ public class SessionObserver extends AbstractModelObserver<RealmSession> {
// update push info // update push info
pushHelper pushHelper
.pushSetUser(new RocketChatCache(context).getOrCreatePushId()) .pushSetUser(RocketChatCache.INSTANCE.getOrCreatePushId())
.continueWith(new LogIfError()); .continueWith(new LogIfError());
ConnectivityManager.getInstance(context).notifySessionEstablished(hostname);
} }
@DebugLog @DebugLog
......
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator
android:duration="1000"
android:propertyName="rotation"
android:repeatCount="-1"
android:valueFrom="0"
android:valueTo="360" />
</set>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="426.88dp"
android:height="343.23dp"
android:viewportWidth="426.88"
android:viewportHeight="343.23">
<path
android:fillColor="#e1e5e8"
android:pathData="M210.44,32.71 A134.94,134.94,0,0,0,103.67,85.13 L107.49,85.13
A131.91,131.91,0,0,1,210.44,35.71 C283.28,35.71,342.54,94.97,342.54,167.81
S283.28,299.91,210.44,299.91 S78.34,240.65,78.34,167.81
Q78.34,166.28,78.34,164.75 C77.34,164,76.52,162.83,75.42,162.26
Q75.31,165.02,75.31,167.8 C75.31,242.29,135.91,302.9,210.41,302.9
S345.51,242.3,345.51,167.8 S284.93,32.71,210.44,32.71 Z" />
<path
android:fillColor="#fff"
android:strokeColor="#175cc4"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M163.17,223.3 C192.933,223.3,217.06,247.427,217.06,277.19
C217.06,306.953,192.933,331.08,163.17,331.08
C133.407,331.08,109.28,306.953,109.28,277.19
C109.28,247.427,133.407,223.3,163.17,223.3 Z" />
<path
android:strokeColor="#e1e5e8"
android:strokeWidth="3"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M50.16,153.04 L39.79,153.04" />
<path
android:strokeColor="#e1e5e8"
android:strokeWidth="3"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M44.98,147.86 L44.98,158.22" />
<path
android:strokeColor="#e1e5e8"
android:strokeWidth="3"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M103.73,53.06 C106.293,53.06,108.37,55.1374,108.37,57.7
C108.37,60.2626,106.293,62.34,103.73,62.34
C101.167,62.34,99.09,60.2626,99.09,57.7
C99.09,55.1374,101.167,53.06,103.73,53.06 Z" />
<path
android:strokeColor="#e1e5e8"
android:strokeWidth="3"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M35.47,57.7 L11.88,57.7" />
<path
android:strokeColor="#e1e5e8"
android:strokeWidth="3"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M86.97,57.7 L44.98,57.7" />
<path
android:strokeColor="#e1e5e8"
android:strokeWidth="3"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M385.19,57.71 C387.753,57.71,389.83,59.7874,389.83,62.35
C389.83,64.9126,387.753,66.99,385.19,66.99
C382.627,66.99,380.55,64.9126,380.55,62.35
C380.55,59.7874,382.627,57.71,385.19,57.71 Z" />
<path
android:strokeColor="#e1e5e8"
android:strokeWidth="3"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M287.28,143.64 C289.843,143.64,291.92,145.717,291.92,148.28
C291.92,150.843,289.843,152.92,287.28,152.92
C284.717,152.92,282.64,150.843,282.64,148.28
C282.64,145.717,284.717,143.64,287.28,143.64 Z" />
<path
android:strokeColor="#e1e5e8"
android:strokeWidth="3"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M173.56,113.07 C176.123,113.07,178.2,115.147,178.2,117.71
C178.2,120.273,176.123,122.35,173.56,122.35
C170.997,122.35,168.92,120.273,168.92,117.71
C168.92,115.147,170.997,113.07,173.56,113.07 Z" />
<path
android:strokeColor="#e1e5e8"
android:strokeWidth="3"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M111.61,223.8 C114.173,223.8,116.25,225.877,116.25,228.44
C116.25,231.003,114.173,233.08,111.61,233.08
C109.047,233.08,106.97,231.003,106.97,228.44
C106.97,225.877,109.047,223.8,111.61,223.8 Z" />
<path
android:strokeColor="#e1e5e8"
android:strokeWidth="3"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M370.48,62.35 L325.7,62.35" />
<path
android:strokeColor="#e1e5e8"
android:strokeWidth="3"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M214.13,19.55 L203.77,19.55" />
<path
android:strokeColor="#e1e5e8"
android:strokeWidth="3"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M208.95,14.37 L208.95,24.73" />
<path
android:strokeColor="#e1e5e8"
android:strokeWidth="3"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M247.92,19.55 L218.56,19.55" />
<path
android:strokeColor="#e1e5e8"
android:strokeWidth="3"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M415.22,152.57 L404.86,152.57" />
<path
android:strokeColor="#e1e5e8"
android:strokeWidth="3"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M410.04,147.39 L410.04,157.75" />
<path
android:strokeColor="#e1e5e8"
android:strokeWidth="3"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M395.53,152.57 L349.72,152.57" />
<path
android:fillColor="#fff"
android:strokeColor="#175cc4"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M334.39,170.87 C354.372,170.87,370.57,187.068,370.57,207.05
C370.57,227.032,354.372,243.23,334.39,243.23
C314.408,243.23,298.21,227.032,298.21,207.05
C298.21,187.068,314.408,170.87,334.39,170.87 Z" />
<path
android:fillColor="#fff"
android:strokeColor="#175cc4"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M222.52,146.58 C239.503,146.58,253.27,160.347,253.27,177.33
C253.27,194.313,239.503,208.08,222.52,208.08
C205.537,208.08,191.77,194.313,191.77,177.33
C191.77,160.347,205.537,146.58,222.52,146.58 Z" />
<path
android:fillColor="#fff"
android:strokeColor="#175cc4"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M287.28,230.33 C293.316,230.33,298.21,235.224,298.21,241.26
C298.21,247.296,293.316,252.19,287.28,252.19
C281.244,252.19,276.35,247.296,276.35,241.26
C276.35,235.224,281.244,230.33,287.28,230.33 Z" />
<path
android:fillColor="#f5455c"
android:pathData="M284.28,232.31 C290.316,232.31,295.21,237.204,295.21,243.24
C295.21,249.276,290.316,254.17,284.28,254.17
C278.244,254.17,273.35,249.276,273.35,243.24
C273.35,237.204,278.244,232.31,284.28,232.31 Z" />
<path
android:fillColor="#f5455c"
android:pathData="M331.39,174.17 C351.206,174.17,367.27,190.234,367.27,210.05
C367.27,229.866,351.206,245.93,331.39,245.93
C311.574,245.93,295.51,229.866,295.51,210.05
C295.51,190.234,311.574,174.17,331.39,174.17 Z" />
<path
android:fillColor="#fff"
android:strokeColor="#175cc4"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M311.94,250.72 C315.613,250.72,318.59,253.697,318.59,257.37
C318.59,261.043,315.613,264.02,311.94,264.02
C308.267,264.02,305.29,261.043,305.29,257.37
C305.29,253.697,308.267,250.72,311.94,250.72 Z" />
<path
android:strokeColor="#175cc4"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M229.74,181.04 C234.125,181.04,237.68,184.595,237.68,188.98
C237.68,193.365,234.125,196.92,229.74,196.92
C225.355,196.92,221.8,193.365,221.8,188.98
C221.8,184.595,225.355,181.04,229.74,181.04 Z" />
<path
android:strokeColor="#175cc4"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M244.19,175.09 C246.896,175.09,249.09,177.284,249.09,179.99
C249.09,182.696,246.896,184.89,244.19,184.89
C241.484,184.89,239.29,182.696,239.29,179.99
C239.29,177.284,241.484,175.09,244.19,175.09 Z" />
<path
android:strokeColor="#175cc4"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M218.74,154.14 C220.673,154.14,222.24,155.707,222.24,157.64
C222.24,159.573,220.673,161.14,218.74,161.14
C216.807,161.14,215.24,159.573,215.24,157.64
C215.24,155.707,216.807,154.14,218.74,154.14 Z" />
<path
android:pathData="M201.57,63.28 C203,57.68,213,55.49,226.86,55.78 A49.61,49.61,0,0,1,231.62,48.78
C228.17,48.57,224.83,48.45,221.62,48.45 Q218.53,48.45,215.62,48.59
C199.24,49.35,188.93,53.39,187.33,59.68 S192.85,74.44,206.87,82.94
A149.7,149.7,0,0,0,221.63,90.71 A49.64,49.64,0,0,1,220.55,82.16
C208,75.72,200.13,68.93,201.57,63.28 Z" />
<path
android:pathData="M225.84,57.71 L224.74,57.71 C212.23,57.71,204.48,59.96,203.51,63.78
S208.71,73.71,220.51,79.93 A50,50,0,0,1,225.85,57.72 Z" />
<path
android:pathData="M314.74,103.46 C326.88,103.4,334.74,101.19,335.66,97.46
S330.94,88.08,320.46,82.3 A50,50,0,0,1,314.74,103.51 Z" />
<path
android:pathData="M337.6,97.91 C336.02,104.13,324.32,105.47,314.55,105.47 L313.63,105.47
A49.64,49.64,0,0,1,308.47,112.71 C333.15,114.31,351.17,110.6,353.38,101.93
S341.78,81.61,319.69,71.27 A49.59,49.59,0,0,1,320.5,80
C329.2,84.67,339.21,91.57,337.6,97.91 Z" />
<path
android:fillColor="#175cc4"
android:pathData="M206.86,82.92 C192.86,74.42,185.72,65.92,187.32,59.66 S199.23,49.34,215.61,48.57
Q218.53,48.43,221.61,48.43 C224.82,48.43,228.16,48.54,231.61,48.76
Q232.4,47.76,233.23,46.86 A153.06,153.06,0,0,0,215.51,46.57
C197.94,47.39,187.24,51.86,185.38,59.16 S190.78,75.5,205.83,84.62
A154.6,154.6,0,0,0,222.23,93.15 Q221.9,91.93,221.63,90.68
A149.7,149.7,0,0,1,206.86,82.92 Z" />
<path
android:fillColor="#175cc4"
android:pathData="M319.15,68.69 Q319.45,69.97,319.69,71.27 C341.78,81.6,355.56,93.34,353.38,101.93
S333.15,114.31,308.47,112.71 Q307.59,113.71,306.66,114.71
C311.03,115.04,315.21,115.21,319.14,115.21
C339.36,115.21,353.14,110.86,355.32,102.43
C357.85,92.45,343.31,79.69,319.15,68.69 Z" />
<path
android:fillColor="#175cc4"
android:pathData="M264.08,105.42 A249.58,249.58,0,0,1,221.63,90.69 Q221.9,91.93,222.23,93.16
A254,254,0,0,0,263.59,107.36 A255.47,255.47,0,0,0,306.66,114.7
Q307.59,113.7,308.47,112.7 A248.15,248.15,0,0,1,264.08,105.42 Z" />
<path
android:fillColor="#175cc4"
android:pathData="M320.46,82.25 C330.94,88.03,336.62,93.67,335.66,97.41
S326.88,103.41,314.74,103.41 Q314.21,104.41,313.63,105.41 L314.55,105.41
C324.32,105.41,336.02,104.07,337.6,97.85 S329.2,84.67,320.51,80
C320.51,80.75,320.49,81.5,320.46,82.25 Z" />
<path
android:fillColor="#175cc4"
android:pathData="M201.57,63.28 C200.13,68.93,208.03,75.71,220.57,82.13
C220.57,81.4,220.57,80.66,220.57,79.92 C208.78,73.7,202.57,67.74,203.57,63.77
S212.29,57.7,224.8,57.7 L225.9,57.7 C226.23,57.05,226.56,56.4,226.9,55.77
C213,55.49,203,57.68,201.57,63.28 Z" />
<path
android:fillColor="#175cc4"
android:pathData="M314,103.47 C300.61,103.4,283.42,100.86,265.59,96.32
C246.72,91.52,231.29,85.61,220.51,79.93 C220.51,80.67,220.51,81.41,220.51,82.14
C232.51,88.29,248.7,94.14,265.05,98.26 C282.88,102.8,300.05,105.35,313.59,105.46
Q314.17,104.46,314.7,103.46 Z" />
<path
android:fillColor="#fff"
android:strokeColor="#175cc4"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M270.53,131.18 A51,51,0,0,1,221.26,93.41 L220.71,91.41 L222.64,92.28
A254.16,254.16,0,0,0,263.83,106.43 A256.15,256.15,0,0,0,306.73,113.74
L308.84,113.9 L307.38,115.43 A51.26,51.26,0,0,1,270.53,131.22 Z" />
<path
android:fillColor="#175cc4"
android:pathData="M222.23,93.15 A254,254,0,0,0,263.59,107.35 A255.47,255.47,0,0,0,306.66,114.69
A50,50,0,0,1,222.23,93.14 M219.23,89.57 L220.33,93.66 A52,52,0,0,0,308.14,116.07
L311,113 L306.78,112.68 A255.19,255.19,0,0,1,264.05,105.39
A253.16,253.16,0,0,1,223.05,91.3 L219.19,89.56 Z" />
<path
android:fillColor="#fff"
android:strokeColor="#175cc4"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M314,104.47 C300.53,104.4,283.25,101.85,265.34,97.29
C247.81,92.83,231.72,87,220,80.81 L219.46,80.53 L219.46,79.92
A51,51,0,0,1,225,57.26 C225.33,56.59,225.68,55.94,226,55.26
A50.81,50.81,0,0,1,230.85,48.1 Q231.65,47.1,232.5,46.16
A51,51,0,0,1,320.14,68.43 C320.34,69.28,320.53,70.17,320.69,71.06
A51,51,0,0,1,321.52,79.96 C321.52,80.73,321.52,81.49,321.47,82.26
A51.07,51.07,0,0,1,315.64,103.89 L315.36,104.42 L314,104.42 Z" />
<path
android:fillColor="#175cc4"
android:pathData="M270.48,30.18 A50,50,0,0,1,319.16,68.7 Q319.46,69.98,319.7,71.28
A49.59,49.59,0,0,1,320.51,80.01 C320.51,80.76,320.51,81.51,320.51,82.27
A50,50,0,0,1,314.79,103.48 L314,103.48 C300.61,103.41,283.42,100.87,265.59,96.33
C246.72,91.53,231.29,85.62,220.51,79.94 A50,50,0,0,1,225.85,57.73
C226.18,57.08,226.51,56.43,226.85,55.8 A49.6,49.6,0,0,1,231.61,48.8
Q232.4,47.8,233.23,46.9 A50.1,50.1,0,0,1,270.47,30.21 M270.47,28.21
A52.08,52.08,0,0,0,231.74,45.56 C231.17,46.2,230.61,46.86,230.06,47.56
A51.78,51.78,0,0,0,225.11,54.87 C224.77,55.48,224.42,56.14,224.05,56.87
A52,52,0,0,0,218.5,79.97 L218.5,81.18 L219.57,81.75
C231.32,87.95,247.48,93.83,265.09,98.31
C283.09,102.89,300.44,105.45,313.98,105.52 L315.98,105.52 L316.54,104.46
A52,52,0,0,0,322.48,82.41 C322.48,81.55,322.53,80.78,322.53,80.06
A51.77,51.77,0,0,0,321.68,70.98 C321.51,70.07,321.33,69.17,321.12,68.3
A52,52,0,0,0,270.5,28.25 Z" />
<path
android:strokeColor="#175cc4"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M95.95,197.11 L107.17,197.11 Q112.06,197.11,112.06,202 L112.06,202.01
Q112.06,206.9,107.17,206.9 L95.95,206.9 Q91.06,206.9,91.06,202.01 L91.06,202
Q91.06,197.11,95.95,197.11 Z" />
<path
android:strokeColor="#175cc4"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M52.07,176.33 L63.29,176.33 Q68.18,176.33,68.18,181.22 L68.18,181.23
Q68.18,186.12,63.29,186.12 L52.07,186.12 Q47.18,186.12,47.18,181.23
L47.18,181.22 Q47.18,176.33,52.07,176.33 Z" />
<path
android:fillColor="#2de0a5"
android:pathData="M93.17,198.71 L104.39,198.71 Q109.28,198.71,109.28,203.6 L109.28,203.61
Q109.28,208.5,104.39,208.5 L93.17,208.5 Q88.28,208.5,88.28,203.61 L88.28,203.6
Q88.28,198.71,93.17,198.71 Z" />
<path
android:fillColor="#2de0a5"
android:pathData="M49.29,177.93 L60.51,177.93 Q65.4,177.93,65.4,182.82 L65.4,182.83
Q65.4,187.72,60.51,187.72 L49.29,187.72 Q44.4,187.72,44.4,182.83 L44.4,182.82
Q44.4,177.93,49.29,177.93 Z" />
<path
android:strokeColor="#175cc4"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M67.17,185.71 L72.99,188.47" />
<path
android:strokeColor="#175cc4"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M86.08,194.67 L91.9,197.43" />
<path
android:strokeColor="#175cc4"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M229,147.52 C228.62,147.45,228.25,147.39,227.87,147.33
A8.28,8.28,0,0,0,227.1,149.49 A7.66,7.66,0,0,0,232.98,158.69
A7.81,7.81,0,0,0,241.69,153.51 A28.09,28.09,0,0,0,229,147.52 Z" />
<path
android:fillColor="#2de0a5"
android:pathData="M228.58,151 C228.2,150.93,227.83,150.87,227.45,150.81
A8.28,8.28,0,0,0,226.68,152.97 A7.66,7.66,0,0,0,232.56,162.17
A7.81,7.81,0,0,0,241.27,156.99 A28.09,28.09,0,0,0,228.58,151 Z" />
<path
android:strokeColor="#175cc4"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M106.62,85.7 C130.97,85.7,150.71,105.44,150.71,129.79
C150.71,154.14,130.97,173.88,106.62,173.88
C82.2698,173.88,62.53,154.14,62.53,129.79 C62.53,105.44,82.2698,85.7,106.62,85.7
Z" />
<path
android:fillColor="#2de0a5"
android:pathData="M217.34,95 A50,50,0,0,0,301.77,116.55 A255.47,255.47,0,0,1,258.7,109.21
A254,254,0,0,1,217.34,95 Z" />
<path
android:fillColor="#2de0a5"
android:pathData="M278,33.55 A50,50,0,0,0,228.4,48.68 Q227.57,49.61,226.78,50.58
A49.61,49.61,0,0,0,222.02,57.58 C221.67,58.21,221.33,58.86,221.02,59.51
A50.14,50.14,0,0,0,217.23,69.64 A33.54,33.54,0,0,0,215.75,79.47
A207.67,207.67,0,0,0,260.75,96.47 C278.58,101.01,295.75,105.2,309.14,105.27
L309.89,105.27 A50,50,0,0,0,315.61,84.06 C315.61,83.31,315.66,82.55,315.61,81.8
A49.59,49.59,0,0,0,314.8,73.07 Q314.56,71.77,314.26,70.49 A50,50,0,0,0,278,33.55
Z" />
<path
android:fillColor="#f5455c"
android:pathData="M309.68,252.29 C313.353,252.29,316.33,255.267,316.33,258.94
C316.33,262.613,313.353,265.59,309.68,265.59
C306.007,265.59,303.03,262.613,303.03,258.94
C303.03,255.267,306.007,252.29,309.68,252.29 Z" />
<path
android:fillColor="#f5455c"
android:pathData="M340.53,228.63 C343.584,228.63,346.06,231.106,346.06,234.16
C346.06,237.214,343.584,239.69,340.53,239.69
C337.476,239.69,335,237.214,335,234.16 C335,231.106,337.476,228.63,340.53,228.63
Z" />
<path
android:fillColor="#2de0a5"
android:pathData="M219.34,148.56 C236.323,148.56,250.09,162.327,250.09,179.31
C250.09,196.293,236.323,210.06,219.34,210.06
C202.357,210.06,188.59,196.293,188.59,179.31
C188.59,162.327,202.357,148.56,219.34,148.56 Z" />
<path
android:fillColor="#2de0a5"
android:pathData="M217.34,155.92 C219.411,155.92,221.09,157.599,221.09,159.67
C221.09,161.741,219.411,163.42,217.34,163.42
C215.269,163.42,213.59,161.741,213.59,159.67
C213.59,157.599,215.269,155.92,217.34,155.92 Z" />
<path
android:fillColor="#2de0a5"
android:pathData="M243.02,176.34 C245.726,176.34,247.92,178.534,247.92,181.24
C247.92,183.946,245.726,186.14,243.02,186.14
C240.314,186.14,238.12,183.946,238.12,181.24
C238.12,178.534,240.314,176.34,243.02,176.34 Z" />
<path
android:fillColor="#2de0a5"
android:pathData="M228.93,183.33 C233.508,183.33,237.22,187.042,237.22,191.62
C237.22,196.198,233.508,199.91,228.93,199.91
C224.352,199.91,220.64,196.198,220.64,191.62
C220.64,187.042,224.352,183.33,228.93,183.33 Z" />
<path
android:fillColor="#fff"
android:strokeColor="#175cc4"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M235.28,69.97 L227.96,77.29" />
<path
android:fillColor="#fff"
android:strokeColor="#175cc4"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M227.96,69.97 L235.28,77.29" />
<path
android:strokeColor="#175cc4"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M342.41,227.67 C344.973,227.67,347.05,229.747,347.05,232.31
C347.05,234.873,344.973,236.95,342.41,236.95
C339.847,236.95,337.77,234.873,337.77,232.31
C337.77,229.747,339.847,227.67,342.41,227.67 Z" />
<path
android:fillColor="#fff"
android:strokeColor="#175cc4"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M146.28,56.68 S124.16,42.33,131.84,15.31 C131.84,15.31,151.65,22.06,152.95,52.66
C152.94,52.66,147.26,54.32,146.28,56.68 Z" />
<path
android:strokeColor="#175cc4"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M140.09,32.98 C141.868,32.98,143.31,34.4216,143.31,36.2
C143.31,37.9784,141.868,39.42,140.09,39.42
C138.312,39.42,136.87,37.9784,136.87,36.2
C136.87,34.4216,138.312,32.98,140.09,32.98 Z" />
<path
android:fillColor="#f5455c"
android:pathData="M147.68,56.07 S145.11,61.93,155.48,69.35 A15,15,0,0,0,152,53.52 Z" />
<path
android:fillColor="#fff"
android:strokeColor="#175cc4"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M134.87,44 A18.27,18.27,0,0,0,138.62,64.87 S140.94,58.36,146.28,56.71
C146.28,56.68,139.77,53.89,134.87,44 Z" />
<path
android:fillColor="#fff"
android:strokeColor="#175cc4"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M150,36 A18.27,18.27,0,0,1,163.14,52.65 S156.73,50.07,151.95,52.97
C151.94,53,154.16,46.27,150,36 Z" />
<path
android:strokeColor="#175cc4"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M130.68,32.18 L135.91,28.95" />
<path
android:strokeColor="#175cc4"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M152.19,59.98 L144.66,45.18" />
<path
android:strokeColor="#175cc4"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M302.42,223.3 L321.45,223.3" />
<path
android:strokeColor="#175cc4"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M300.73,217.56 L311.94,217.56" />
<path
android:strokeColor="#e1e5e8"
android:strokeWidth="3"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M126.06,175.93 S159.22,186.63,145.35,221.13" />
<path
android:strokeColor="#e1e5e8"
android:strokeWidth="3"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M320.51,265.59 S352.51,273.25,351.51,245.94" />
<path
android:strokeColor="#e1e5e8"
android:strokeWidth="3"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M256.67,167.81 S285.94,155.64,298.2,184.38" />
<path
android:strokeColor="#e1e5e8"
android:strokeWidth="3"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M192.77,155.55 S180.14,114,218.56,108.3" />
<path
android:strokeColor="#e1e5e8"
android:strokeWidth="3"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M301.78,126.48 S332.53,137.99,325.26,167.81" />
<path
android:strokeColor="#e1e5e8"
android:strokeWidth="3"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M222.52,213.45 S219.24,250.71,270.52,247.67" />
<path
android:strokeColor="#e1e5e8"
android:strokeWidth="3"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M187.1,223.3 S180.1,205.38,192.78,202.01" />
<path
android:strokeColor="#e1e5e8"
android:strokeWidth="3"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M220.51,280 S271.23,284.17,273.35,254.13" />
<path
android:strokeColor="#e1e5e8"
android:strokeWidth="3"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M185.16,170.86 S181.39,145.33,152.67,148.56" />
<path
android:strokeColor="#e1e5e8"
android:strokeWidth="3"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M150.71,108.3 S169.3,77.08,210.44,92.17" />
<path
android:strokeColor="#175cc4"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M102.6,115.51 C98.73,116.22,95.33,117.77,92.92,121.04
C87.51,128.36,90.84,136.68,99.83,138.33
C102.83,138.88,107.54,136.68,108.48,139.82
C109.71,143.92,111.98,148.17,110.92,152.82
C110.32,155.45,111.67,157.71,113.05,159.68 S116.15,163.94,119.25,161.17
C123.16,157.69,127.47,154.77,127.56,148.67
C127.63,143.26,132.04,139.87,134.75,135.03 A16.08,16.08,0,0,1,126.31,124.5
C130.19,124.15,134.41,128.42,137.11,123.74
C138.41,121.49,135.58,120.17,135.11,118.24" />
<path
android:strokeColor="#175cc4"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M103.29,115.51 C108.62,115.51,112.9,119.79,118.52,119.08
C120.52,118.83,122.13,118.51,123.52,116.66
C122.52,113.24,119.82,112.1,116.45,112.06
C112.3,112.06,108.15,111.9,104.01,112.11
C102.01,112.22,101.41,113.36,103.32,114.81" />
<path
android:strokeColor="#175cc4"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M63.47,125.89 C67.29,126.68,66.14,129.72,66.21,132.11
C66.31,135.57,66.1,138.58,70.29,140.61 A7.26,7.26,0,0,1,73.78,150.75
C72.67,153.1,73.52,155.4,73.15,157.69" />
<path
android:strokeColor="#175cc4"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M120.57,88.54 C119.38,90.75,118.13,93,120,95
C118.49,98.36,115.44,95.82,113.54,97.48 C112.54,98.37,109.62,98.48,109.64,98.93
C109.91,106.4,101.03,105.93,99.08,111.34
C99.82,112.24,100.55,113.08,101.86,112.77" />
<path
android:strokeColor="#175cc4"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M82.32,93.06 C84.54,98.61,81.32,101.39,76.75,102.61 A15,15,0,0,0,67.1,109.61" />
<path
android:strokeColor="#175cc4"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M111.61,173 C105.89,170,99.88,170.79,93.86,171.63" />
<path
android:strokeColor="#175cc4"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M93,94.36 C98.24,94.58,101.39,91.21,104.46,87.69" />
<path
android:strokeColor="#175cc4"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M94.1,87.6 C93.83,94.33,88.3,94.99,83.64,96.6" />
<path
android:strokeColor="#175cc4"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M148.26,116.93 L148.26,125.76 C143.07,122.52,141.48,115.86,135.26,118.98" />
<path
android:fillColor="#2de0a5"
android:pathData="M103.06,84.63 C127.41,84.63,147.15,104.37,147.15,128.72
C147.15,153.07,127.41,172.81,103.06,172.81
C78.7098,172.81,58.97,153.07,58.97,128.72
C58.97,104.37,78.7098,84.63,103.06,84.63 Z" />
<path
android:fillColor="#2de0a5"
android:pathData="M71,102.06 L71,102.06 A8.77,8.77,0,0,1,73.93,100.28 L77.1,99.13
A4.12,4.12,0,0,0,78.39,98.37 L80.92,96.18 A2.44,2.44,0,0,0,81.71,94.83
L81.81,94.36 A1.77,1.77,0,0,1,83.28,93 L84.67,92.83 A5.34,5.34,0,0,0,86.05,92.47
L89.29,91.14 L90.37,90.87 A11.44,11.44,0,0,1,92.12,90.58 L93.03,90.5
A7.54,7.54,0,0,0,96.03,89.57 L99.12,87.84 A5.57,5.57,0,0,0,100.69,86.53
L102.82,83.95 L100.76,83.95 A39.41,39.41,0,0,0,84.9,89.09
C76.63,93.88,74.9,96.46,70.68,101.73 C70.43,102,71,102.06,71,102.06 Z" />
<path
android:fillColor="#2de0a5"
android:pathData="M62.46,123.26 A1.42,1.42,0,0,1,64.14,124.19
C64.71,125.46,65.03,124.76,64.71,131.19 S70.17,138.47,70.77,138.98
C70.77,138.98,74.31,141.68,72.71,146.87 C72.71,146.87,71.28,148.01,71.57,151.76
L71.57,153.15 S68.21,149.39,64.65,140.58 C62.71,135.77,62.42,124.29,62.42,124.29
Z" />
<path
android:fillColor="#2de0a5"
android:pathData="M91.76,169.07 S104.16,165.61,109.56,170.66
C109.56,170.66,102.11,172.07,91.76,169.07 Z" />
<path
android:fillColor="#2de0a5"
android:pathData="M120.62,85.7 S129.77,90.33,134.39,94.83 S139.07,99.2,143.25,105.65
A37.79,37.79,0,0,1,146.37,112.54 A14.62,14.62,0,0,1,146.37,120.54
S146.37,121.17,145.37,119.95 S140.37,114.17,138.28,114.04
S133.99,114.14,134.04,114.76 S133.13,114.68,135.58,118.47
S131.75,122.82,131.75,122.82 S131.32,123.33,126.38,121.17
C126.38,121.17,124.14,120.62,125.54,123.41 S127.19,127.93,133.14,132.1
C133.14,132.1,133.73,132.1,131.75,134.34 A28.21,28.21,0,0,0,126.56,142.86
S126.56,148.57,124.03,151.86 A54,54,0,0,1,117.53,158.62 A3,3,0,0,1,112.95,157.87
C110.95,154.87,109.47,153.6,109.8,150.12 S110.4,145.4,109.04,141.45
S107.51,135.14,105.57,135.07 S96.57,135.86,95.36,134.16
S89.63,131.91,89.27,124.74 S97,113.58,97,113.58 S98.35,112.05,104.59,112.73
S111.33,116.57,117.73,116.12 C124.48,115.64,124.4,111.31,118.09,109.36
C112.95,107.78,104.59,109.03,101.66,109.54 A3.6,3.6,0,0,1,98.88,108.95
C98,108.29,97.56,107.21,99.94,105.65 C104.26,102.81,109.08,101.25,108.81,96.65
C108.81,96.65,108.16,95.76,111.03,95.38 S122.09,93.76,118.1,90.38
C118.13,90.34,119,84.86,120.62,85.7 Z" />
<path
android:strokeColor="#e1e5e8"
android:strokeWidth="3"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M129.7,104.74 S159.38,95.46,154.7,68.93" />
<path
android:strokeColor="#175cc4"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeMiterLimit="10"
android:strokeLineCap="round"
android:pathData="M79.45,184.39 C83.3933,184.39,86.59,187.587,86.59,191.53
C86.59,195.473,83.3933,198.67,79.45,198.67
C75.5067,198.67,72.31,195.473,72.31,191.53
C72.31,187.587,75.5067,184.39,79.45,184.39 Z" />
<path
android:fillColor="#2de0a5"
android:pathData="M76.86,186.08 C80.8033,186.08,84,189.277,84,193.22
C84,197.163,80.8033,200.36,76.86,200.36
C72.9167,200.36,69.72,197.163,69.72,193.22
C69.72,189.277,72.9167,186.08,76.86,186.08 Z" />
<path
android:fillColor="#175cc4"
android:pathData="M160.59,245.94 A4.74,4.74,0,0,0,155.85,241.2 L123.69,241.2
Q122.81,242.2,121.97,243.2 L155.85,243.2 A2.74,2.74,0,0,1,155.85,248.67
L118,248.67 C117.59,249.33,117.19,250,116.81,250.67 L155.81,250.67
A4.74,4.74,0,0,0,160.59,245.94 Z" />
<path
android:fillColor="#175cc4"
android:pathData="M198.59,258.21 A2.89,2.89,0,0,0,195.71,255.33 L114.47,255.33
C114.17,255.99,113.9,256.66,113.63,257.33 L195.7,257.33
A0.88,0.88,0,0,1,195.7,259.09 L113,259.09
C112.76,259.75,112.53,260.42,112.32,261.09 L195.7,261.09
A2.89,2.89,0,0,0,198.59,258.21 Z" />
<path
android:fillColor="#175cc4"
android:pathData="M192.59,273.45 A4.86,4.86,0,0,0,187.73,268.59 L128.25,268.59
A4.86,4.86,0,0,0,128.25,278.3 L187.73,278.3 A4.86,4.86,0,0,0,192.59,273.45 Z
M125.4,273.45 A2.86,2.86,0,0,1,128.26,270.59 L187.74,270.59
A2.86,2.86,0,1,1,187.74,276.3 L128.25,276.3 A2.86,2.86,0,0,1,125.4,273.45 Z" />
<path
android:fillColor="#175cc4"
android:pathData="M152.84,287.59 A1.75,1.75,0,0,1,152.84,284.09 L217.14,284.09
C217.22,283.43,217.29,282.76,217.35,282.09 L152.84,282.09
A3.75,3.75,0,0,0,152.84,289.59 L216.15,289.59
C216.31,288.93,216.44,288.26,216.57,287.59 Z" />
<path
android:fillColor="#175cc4"
android:pathData="M178.59,312.59 A5.51,5.51,0,0,0,173.09,307.09 L118.77,307.09
Q119.45,308.09,120.16,309.09 L173.09,309.09 A3.5,3.5,0,0,1,173.09,316.09
L126.29,316.09 Q127.36,317.09,128.48,318.09 L173.09,318.09
A5.51,5.51,0,0,0,178.59,312.59 Z" />
<path
android:fillColor="#175cc4"
android:pathData="M157.12,323.81 A4.81,4.81,0,0,0,152.32,328.61 A4.74,4.74,0,0,0,152.56,330.03
Q153.8,330.29,155.07,330.5 A2.79,2.79,0,0,1,157.13,325.81 L187.13,325.81
C188.35,325.22,189.55,324.59,190.71,323.92 A4.79,4.79,0,0,0,189.71,323.81 Z" />
<path
android:fillColor="#2de0a5"
android:pathData="M160.59,226.65 C190.353,226.65,214.48,250.777,214.48,280.54
C214.48,310.303,190.353,334.43,160.59,334.43
C130.827,334.43,106.7,310.303,106.7,280.54
C106.7,250.777,130.827,226.65,160.59,226.65 Z" />
</vector>
\ No newline at end of file
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportHeight="100.0"
android:viewportWidth="100.0">
<group
android:name="rotationGroup"
android:pivotX="50"
android:pivotY="50"
android:rotation="90">
<path
android:fillColor="#00000000"
android:pathData="M85,50A35,35 108.96,1 0,74.79 74.79"
android:strokeColor="#FFFFFF"
android:strokeWidth="12" />
<path
android:fillColor="#FFFFFF"
android:pathData="M105,49L70,49L85,65L100,49" />
</group>
</vector>
<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
tools:targetApi="lollipop" android:drawable="@drawable/ic_loading">
<target
android:name="rotationGroup"
android:animation="@anim/rotation" />
</animated-vector>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#030000">
<TextView
android:id="@+id/text_view_status"
android:layout_width="wrap_content"
android:layout_height="32dp"
android:background="@color/connection_crouton_background"
android:gravity="center"
android:text="@string/server_config_activity_authenticating"
android:textAlignment="center"
android:textColor="@android:color/white"
android:textSize="16sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Authenticating…" />
<ImageView
android:id="@+id/try_again_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
app:layout_constraintBottom_toBottomOf="@+id/text_view_status"
app:layout_constraintStart_toEndOf="@+id/text_view_status"
app:layout_constraintTop_toTopOf="@+id/text_view_status"
tools:src="@drawable/ic_loading" />
</android.support.constraint.ConstraintLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?attr/colorPrimaryDark" android:background="?attr/colorPrimaryDark"
android:padding="@dimen/margin_8" android:padding="@dimen/margin_8"
tools:context="chat.rocket.android.fragment.server_config.LoginFragment"> tools:context="chat.rocket.android.fragment.server_config.LoginFragment">
<ScrollView <ScrollView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent"
app:layout_constraintRight_toRightOf="parent"> app:layout_constraintTop_toTopOf="parent">
<android.support.constraint.ConstraintLayout <android.support.constraint.ConstraintLayout
android:id="@+id/container" android:id="@+id/container"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:minWidth="280dp" android:layout_height="wrap_content"
android:layout_height="wrap_content" android:background="@drawable/container_bg"
android:paddingStart="@dimen/margin_16" android:minWidth="280dp"
android:paddingLeft="@dimen/margin_16" android:paddingBottom="@dimen/margin_8"
android:paddingEnd="@dimen/margin_16" android:paddingEnd="@dimen/margin_16"
android:paddingRight="@dimen/margin_16" android:paddingLeft="@dimen/margin_16"
android:paddingTop="@dimen/margin_8" android:paddingRight="@dimen/margin_16"
android:paddingBottom="@dimen/margin_8" android:paddingStart="@dimen/margin_16"
android:background="@drawable/container_bg" android:paddingTop="@dimen/margin_8"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"> app:layout_constraintTop_toTopOf="parent">
<ImageButton <ImageButton
android:id="@+id/btn_login_with_twitter" android:id="@+id/btn_login_with_twitter"
android:layout_width="48dp" android:layout_width="48dp"
android:layout_height="48dp" android:layout_height="48dp"
android:scaleType="fitXY" android:background="?attr/selectableItemBackgroundBorderless"
app:srcCompat="@drawable/ic_button_twitter_24dp" android:scaleType="fitXY"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toTopOf="@+id/text_input_username"
app:layout_constraintBottom_toTopOf="@+id/text_input_username" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toLeftOf="@+id/btn_login_with_facebook"
app:layout_constraintRight_toLeftOf="@+id/btn_login_with_facebook" app:layout_constraintTop_toTopOf="parent"
android:background="?attr/selectableItemBackgroundBorderless" /> app:srcCompat="@drawable/ic_button_twitter_24dp" />
<ImageButton <ImageButton
android:id="@+id/btn_login_with_facebook" android:id="@+id/btn_login_with_facebook"
android:layout_width="48dp" android:layout_width="48dp"
android:layout_height="48dp" android:layout_height="48dp"
android:scaleType="fitXY" android:background="?attr/selectableItemBackgroundBorderless"
app:srcCompat="@drawable/ic_button_facebook_24dp" android:scaleType="fitXY"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toTopOf="@+id/text_input_username"
app:layout_constraintBottom_toTopOf="@+id/text_input_username" app:layout_constraintLeft_toRightOf="@+id/btn_login_with_twitter"
app:layout_constraintLeft_toRightOf="@+id/btn_login_with_twitter" app:layout_constraintRight_toLeftOf="@+id/btn_login_with_github"
app:layout_constraintRight_toLeftOf="@+id/btn_login_with_github" app:layout_constraintTop_toTopOf="parent"
android:background="?attr/selectableItemBackgroundBorderless" /> app:srcCompat="@drawable/ic_button_facebook_24dp" />
<ImageButton <ImageButton
android:id="@+id/btn_login_with_github" android:id="@+id/btn_login_with_github"
android:layout_width="48dp" android:layout_width="48dp"
android:layout_height="48dp" android:layout_height="48dp"
android:scaleType="fitXY" android:background="?attr/selectableItemBackgroundBorderless"
app:srcCompat="@drawable/ic_button_github_24dp" android:scaleType="fitXY"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toTopOf="@+id/text_input_username"
app:layout_constraintBottom_toTopOf="@+id/text_input_username" app:layout_constraintLeft_toRightOf="@+id/btn_login_with_facebook"
app:layout_constraintLeft_toRightOf="@+id/btn_login_with_facebook" app:layout_constraintRight_toLeftOf="@+id/btn_login_with_google"
app:layout_constraintRight_toLeftOf="@+id/btn_login_with_google" app:layout_constraintTop_toTopOf="parent"
android:background="?attr/selectableItemBackgroundBorderless" /> app:srcCompat="@drawable/ic_button_github_24dp" />
<ImageButton <ImageButton
android:id="@+id/btn_login_with_google" android:id="@+id/btn_login_with_google"
android:layout_width="48dp" android:layout_width="48dp"
android:layout_height="48dp" android:layout_height="48dp"
android:scaleType="fitXY" android:background="?attr/selectableItemBackgroundBorderless"
app:srcCompat="@drawable/ic_button_google_24dp" android:scaleType="fitXY"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toTopOf="@+id/text_input_username"
app:layout_constraintBottom_toTopOf="@+id/text_input_username" app:layout_constraintLeft_toRightOf="@+id/btn_login_with_github"
app:layout_constraintLeft_toRightOf="@+id/btn_login_with_github" app:layout_constraintRight_toRightOf="parent"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent"
android:background="?attr/selectableItemBackgroundBorderless" /> app:srcCompat="@drawable/ic_button_google_24dp" />
<android.support.design.widget.TextInputLayout <android.support.design.widget.TextInputLayout
android:id="@+id/text_input_username" android:id="@+id/text_input_username"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/btn_login_with_twitter" app:layout_constraintBottom_toTopOf="@+id/text_input_passwd"
app:layout_constraintBottom_toTopOf="@+id/text_input_passwd" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent"
app:layout_constraintRight_toRightOf="parent"> app:layout_constraintTop_toBottomOf="@+id/btn_login_with_twitter">
<android.support.design.widget.TextInputEditText <android.support.design.widget.TextInputEditText
android:id="@+id/editor_username" android:id="@+id/editor_username"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="@string/fragment_login_username_or_email" android:hint="@string/fragment_login_username_or_email"
android:imeOptions="actionNext" android:imeOptions="actionNext"
android:inputType="textWebEmailAddress" android:inputType="textWebEmailAddress"
android:maxLines="1" /> android:maxLines="1" />
</android.support.design.widget.TextInputLayout> </android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout <android.support.design.widget.TextInputLayout
android:id="@+id/text_input_passwd" android:id="@+id/text_input_passwd"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/text_input_username" app:passwordToggleEnabled="true"
app:layout_constraintBottom_toTopOf="@+id/btn_user_registration" app:layout_constraintBottom_toTopOf="@+id/btn_user_registration"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"> app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_input_username">
<android.support.design.widget.TextInputEditText <android.support.design.widget.TextInputEditText
android:id="@+id/editor_passwd" android:id="@+id/editor_passwd"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="@string/fragment_login_password" android:hint="@string/fragment_login_password"
android:imeOptions="actionNext" android:imeOptions="actionNext"
android:inputType="textWebPassword" android:inputType="textWebPassword"
android:maxLines="1" /> android:maxLines="1" />
</android.support.design.widget.TextInputLayout> </android.support.design.widget.TextInputLayout>
<Button <Button
android:id="@+id/btn_user_registration" android:id="@+id/btn_user_registration"
android:layout_width="0dp" style="@style/Widget.AppCompat.Button.Colored"
android:layout_height="wrap_content" android:layout_width="0dp"
android:layout_marginTop="@dimen/margin_8" android:layout_height="wrap_content"
android:text="@string/fragment_login_sign_up" android:layout_marginTop="@dimen/margin_8"
app:layout_constraintTop_toBottomOf="@+id/text_input_passwd" android:text="@string/fragment_login_sign_up"
app:layout_constraintRight_toLeftOf="@+id/btn_login_with_email" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintRight_toLeftOf="@+id/btn_login_with_email"
style="@style/Widget.AppCompat.Button.Colored" /> app:layout_constraintTop_toBottomOf="@+id/text_input_passwd" />
<Button <Button
android:id="@+id/btn_login_with_email" android:id="@+id/btn_login_with_email"
android:layout_width="0dp" style="@style/Widget.AppCompat.Button.Colored"
android:layout_height="wrap_content" android:layout_width="0dp"
android:layout_marginTop="@dimen/margin_8" android:layout_height="wrap_content"
android:text="@string/fragment_login_sign_in" android:layout_marginTop="@dimen/margin_8"
app:layout_constraintTop_toBottomOf="@+id/text_input_passwd" android:text="@string/fragment_login_sign_in"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="@+id/btn_user_registration" app:layout_constraintLeft_toRightOf="@+id/btn_user_registration"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintRight_toRightOf="parent"
style="@style/Widget.AppCompat.Button.Colored" /> app:layout_constraintTop_toBottomOf="@+id/text_input_passwd" />
</android.support.constraint.ConstraintLayout> </android.support.constraint.ConstraintLayout>
</ScrollView> </ScrollView>
<chat.rocket.android.widget.WaitingView <chat.rocket.android.widget.WaitingView
android:id="@+id/waiting" android:id="@+id/waiting"
......
<resources>
<string name="app_name">Rocket.Chat+</string>
<string name="fragment_sidebar_main_favorite_title">Избранное</string>
<string name="fragment_sidebar_main_channels_title">Каналы</string>
<string name="fragment_sidebar_main_direct_messages_title">Личные сообщения</string>
<string name="fragment_sidebar_main_livechat_title">Чат</string>
<string name="user_status_online">Онлайн</string>
<string name="user_status_away">Отошел</string>
<string name="user_status_busy">Занят</string>
<string name="user_status_invisible">Невидимый</string>
<string name="fragment_sidebar_main_logout_title">Выход</string>
<string name="dialog_add_channel_name">Название</string>
<string name="dialog_add_channel_private">Закрытый</string>
<string name="dialog_add_channel_read_only">Только для чтения</string>
<string name="fragment_room_list_pinned_message_title">Закрепленные сообщения (%s)</string>
<string name="fragment_room_list_favorite_message_title">Избранные сообщения (%s)</string>
<string name="fragment_room_list_file_list_title">Список файлов (%s)</string>
<string name="fragment_room_list_member_list_title">Список (%s)</string>
<string name="fragment_room_list_could_not_load_your_request">Не удалось обработать запрос.\n%s.</string>
<string name="fragment_room_list_no_pinned_message_to_show">Нет закрепленных сообщений</string>
<string name="fragment_room_list_no_favorite_message_to_show">Нет избранных сообщений</string>
<string name="fragment_room_list_no_file_list_to_show">Нет файлов для показа</string>
<string name="fragment_room_list_no_member_list_to_show">Нет участников для отображения</string>
<string name="start_of_conversation">Начало переписки</string>
<string name="sending">Отправить</string>
<string name="not_synced">Не синхронизировано</string>
<string name="failed_to_sync">Ошибка синхронизации</string>
<string name="failed_to_delete">Ошибка удаления</string>
<string name="failed_to_delete_message">Сообщение не может быть удалено в это время. Повторите попытку позже</string>
<string name="resend">Повторить</string>
<string name="discard">Удалить</string>
<string name="ok">ОК</string>
<plurals name="fmt_dialog_view_latest_message_title">
<item quantity="one">%d новое сообщение</item>
<item quantity="few">%d новых сообщения</item>
<item quantity="many">%d новых сообщений</item>
</plurals>
<string name="dialog_view_latest_message_action">посмотреть</string>
<string name="file_uploading_title">Загрузка...</string>
<string name="dialog_user_registration_email">Почта</string>
<string name="dialog_user_registration_username">Имя пользователя</string>
<string name="sub_username">\@%s</string>
<string name="username">\@%s</string>
<string name="dialog_user_registration_password">Пароль</string>
<string name="fragment_home_welcome_message">Добро пожаловать в Чат.\nВыберите канал из меню.</string>
<string name="fragment_input_hostname_hostname">Адрес сервера</string>
<string name="fragment_input_hostname_server_hint">open.rocket.chat</string>
<string name="fragment_input_hostname_connect">Подключиться</string>
<string name="fragment_login_username_or_email">Имя пользователя или email</string>
<string name="fragment_login_password">Пароль</string>
<string name="fragment_login_sign_up">Зарегистрироваться</string>
<string name="fragment_login_sign_in">Войти</string>
<string name="fragment_retry_login_retry_title">Повторить</string>
<string name="fragment_retry_login_error_title">Ошибка соединения</string>
<string name="server_config_activity_authenticating">Вход...</string>
<string name="home_fragment_title">Rocket.Chat - Главная</string>
<string name="fragment_sidebar_main_unread_rooms_title">Непрочитанные каналы</string>
<string name="doc_upload_message_spec_title">Прикрепить файл</string>
<string name="image_upload_message_spec_title">Прикрепить изображения</string>
<string name="audio_upload_message_spec_title">Вложить аудио</string>
<string name="video_upload_message_spec_title">Прикрепить видео</string>
<string name="input_hostname_invalid_server_message">Сервер не найден</string>
<string name="make_sure_your_server_version_is_up_to_date">Убедитесь, что ваш сервер Rocket.Chat обновлен до последней версии</string>
<string name="connection_error_try_later">Ошибка соединения. Попробуйте позже.</string>
<string name="version_info_text">Версия: %s</string>
<string name="two_factor_authentication_title">Двухфакторная аутентификация</string>
<string name="open_your_authentication_app_and_enter_the_code">Откройте приложение для проверки подлинности и введите код</string>
<string name="two_factor_code">Код авторизации</string>
<string name="spotlight_search">Поиск</string>
<string name="spotlight_load_more_results">Еще...</string>
<string name="edit_message">Текст сообщения</string>
<string name="message_options_no_message_info">Ooops. Что-то не работает!</string>
<string name="message_options_no_permissions_info">У Вас не достаточно прав</string>
<string name="menu_pinned_messages">Закрепленные сообщения</string>
<string name="menu_favorite_messages">Избранные сообщения</string>
<string name="menu_file_list">Список файлов</string>
<string name="menu_member_list">Список коллег</string>
<string name="add_new_team">Добавить новую команду</string>
</resources>
...@@ -13,4 +13,5 @@ ...@@ -13,4 +13,5 @@
<color name="colorSecondaryTextDark">#8A000000</color> <color name="colorSecondaryTextDark">#8A000000</color>
<color name="colorDivider">#1F000000</color> <color name="colorDivider">#1F000000</color>
<color name="connection_crouton_background">#030000</color>
</resources> </resources>
\ No newline at end of file
...@@ -18,8 +18,8 @@ ...@@ -18,8 +18,8 @@
<string name="fragment_room_list_file_list_title">File list (%s)</string> <string name="fragment_room_list_file_list_title">File list (%s)</string>
<string name="fragment_room_list_member_list_title">Member list (%s)</string> <string name="fragment_room_list_member_list_title">Member list (%s)</string>
<string name="fragment_room_list_could_not_load_your_request">Could not load your request.\n%s.</string> <string name="fragment_room_list_could_not_load_your_request">Could not load your request.\n%s.</string>
<string name="fragment_room_list_no_pinned_message_to_show">No pinned message to show</string> <string name="fragment_room_list_no_pinned_message_to_show">No pinned messages to show</string>
<string name="fragment_room_list_no_favorite_message_to_show">No favorite message to show</string> <string name="fragment_room_list_no_favorite_message_to_show">No favorite messages to show</string>
<string name="fragment_room_list_no_file_list_to_show">No file list to show</string> <string name="fragment_room_list_no_file_list_to_show">No file list to show</string>
<string name="fragment_room_list_no_member_list_to_show">No member list to show</string> <string name="fragment_room_list_no_member_list_to_show">No member list to show</string>
...@@ -47,7 +47,7 @@ ...@@ -47,7 +47,7 @@
<string name="username">\@%s</string> <string name="username">\@%s</string>
<string name="dialog_user_registration_password">Password</string> <string name="dialog_user_registration_password">Password</string>
<string name="fragment_home_welcome_message">Welcome to Rocket.Chat.Android\nSelect a channel from the drawer.</string> <string name="fragment_home_welcome_message">Welcome to Rocket.Chat.Android\nSelect a channel from the drawer.</string>
<string name="fragment_input_hostname_hostname">Hostname</string> <string name="fragment_input_hostname_hostname">Server</string>
<string name="fragment_input_hostname_server_hint">open.rocket.chat</string> <string name="fragment_input_hostname_server_hint">open.rocket.chat</string>
<string name="fragment_input_hostname_connect">CONNECT</string> <string name="fragment_input_hostname_connect">CONNECT</string>
<string name="fragment_login_username_or_email">Username or email</string> <string name="fragment_login_username_or_email">Username or email</string>
...@@ -55,7 +55,7 @@ ...@@ -55,7 +55,7 @@
<string name="fragment_login_sign_up">SIGN UP</string> <string name="fragment_login_sign_up">SIGN UP</string>
<string name="fragment_login_sign_in">SIGN IN</string> <string name="fragment_login_sign_in">SIGN IN</string>
<string name="fragment_retry_login_retry_title">RETRY</string> <string name="fragment_retry_login_retry_title">RETRY</string>
<string name="fragment_retry_login_error_title">Connection Error</string> <string name="fragment_retry_login_error_title">Your connection seems off…</string>
<string name="server_config_activity_authenticating">Authenticating…</string> <string name="server_config_activity_authenticating">Authenticating…</string>
<string name="home_fragment_title">Rocket.Chat - Home</string> <string name="home_fragment_title">Rocket.Chat - Home</string>
<string name="fragment_sidebar_main_unread_rooms_title">UNREAD ROOMS</string> <string name="fragment_sidebar_main_unread_rooms_title">UNREAD ROOMS</string>
......
...@@ -12,7 +12,6 @@ ...@@ -12,7 +12,6 @@
<item name="actionModeBackground">?attr/colorPrimaryDark</item> <item name="actionModeBackground">?attr/colorPrimaryDark</item>
<item name="android:statusBarColor" tools:targetApi="21">?attr/colorPrimaryDark</item> <item name="android:statusBarColor" tools:targetApi="21">?attr/colorPrimaryDark</item>
<item name="android:navigationBarColor" tools:targetApi="21">?attr/colorPrimaryDark</item>
<item name="android:windowBackground">@android:color/white</item> <item name="android:windowBackground">@android:color/white</item>
</style> </style>
...@@ -27,7 +26,6 @@ ...@@ -27,7 +26,6 @@
<item name="actionModeBackground">?attr/colorPrimaryDark</item> <item name="actionModeBackground">?attr/colorPrimaryDark</item>
<item name="android:statusBarColor" tools:targetApi="21">?attr/colorPrimaryDark</item> <item name="android:statusBarColor" tools:targetApi="21">?attr/colorPrimaryDark</item>
<item name="android:navigationBarColor" tools:targetApi="21">?attr/colorPrimaryDark</item>
</style> </style>
<style name="AppTheme.Dialog" parent="Theme.AppCompat.Light.Dialog"> <style name="AppTheme.Dialog" parent="Theme.AppCompat.Light.Dialog">
......
...@@ -23,12 +23,12 @@ object OkHttpHelper { ...@@ -23,12 +23,12 @@ object OkHttpHelper {
return httpClientForUploadFile ?: throw AssertionError("httpClientForUploadFile set to null by another thread") return httpClientForUploadFile ?: throw AssertionError("httpClientForUploadFile set to null by another thread")
} }
fun getClientForDownloadFile(context: Context): OkHttpClient { fun getClientForDownloadFile(): OkHttpClient {
if(httpClientForDownloadFile == null) { if(httpClientForDownloadFile == null) {
httpClientForDownloadFile = OkHttpClient.Builder() httpClientForDownloadFile = OkHttpClient.Builder()
.followRedirects(true) .followRedirects(true)
.followSslRedirects(true) .followSslRedirects(true)
.addInterceptor(CookieInterceptor(DefaultCookieProvider(RocketChatCache(context)))) .addInterceptor(CookieInterceptor(DefaultCookieProvider()))
.build() .build()
} }
return httpClientForDownloadFile ?: throw AssertionError("httpClientForDownloadFile set to null by another thread") return httpClientForDownloadFile ?: throw AssertionError("httpClientForDownloadFile set to null by another thread")
......
...@@ -16,10 +16,10 @@ buildscript { ...@@ -16,10 +16,10 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.0.1' classpath 'com.android.tools.build:gradle:3.0.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.1.60" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.2.0"
classpath 'io.realm:realm-gradle-plugin:4.2.0' classpath 'io.realm:realm-gradle-plugin:4.2.0'
classpath 'com.jakewharton.hugo:hugo-plugin:1.2.1' classpath 'com.jakewharton.hugo:hugo-plugin:1.2.1'
classpath 'com.google.gms:google-services:3.0.0' classpath 'com.google.gms:google-services:3.1.0'
classpath 'com.github.triplet.gradle:play-publisher:1.1.5' classpath 'com.github.triplet.gradle:play-publisher:1.1.5'
classpath 'io.fabric.tools:gradle:1.+' classpath 'io.fabric.tools:gradle:1.+'
} }
......
ext { ext {
preDexLibs = "true" != System.getenv("CI") preDexLibs = "true" != System.getenv("CI")
supportLibraryVersion = "27.0.1" supportLibraryVersion = "27.0.1"
playLibVersion = '11.6.2'
constraintLayoutVersion = "1.0.2" constraintLayoutVersion = "1.0.2"
kotlinVersion = "1.1.51" kotlinVersion = "1.2.0"
okHttpVersion = "3.9.0" okHttpVersion = "3.9.0"
rxbindingVersion = '2.0.0' rxbindingVersion = '2.0.0'
rxJavaVersion = "2.1.0"
supportDependencies = [ supportDependencies = [
designSupportLibrary: "com.android.support:design:${supportLibraryVersion}", designSupportLibrary: "com.android.support:design:${supportLibraryVersion}",
...@@ -12,16 +14,23 @@ ext { ...@@ -12,16 +14,23 @@ ext {
constraintLayout : "com.android.support.constraint:constraint-layout:${constraintLayoutVersion}", constraintLayout : "com.android.support.constraint:constraint-layout:${constraintLayoutVersion}",
cardView : "com.android.support:cardview-v7:${supportLibraryVersion}", cardView : "com.android.support:cardview-v7:${supportLibraryVersion}",
supportV13 : "com.android.support:support-v13:${supportLibraryVersion}", supportV13 : "com.android.support:support-v13:${supportLibraryVersion}",
multidex : "com.android.support:multidex:1.0.2" multidex : "com.android.support:multidex:1.0.2",
customTabs : "com.android.support:customtabs:${supportLibraryVersion}"
] ]
extraDependencies = [ extraDependencies = [
okHTTP : "com.squareup.okhttp3:okhttp:${okHttpVersion}", okHTTP : "com.squareup.okhttp3:okhttp:${okHttpVersion}",
rxJava : "io.reactivex.rxjava2:rxjava:2.1.0", rxJava : "io.reactivex.rxjava2:rxjava:${rxJavaVersion}",
rxKotlin : "io.reactivex.rxjava2:rxkotlin:${rxJavaVersion}",
boltTask : "com.parse.bolts:bolts-tasks:1.4.0", boltTask : "com.parse.bolts:bolts-tasks:1.4.0",
rxAndroid : "io.reactivex.rxjava2:rxandroid:2.0.1", rxAndroid : "io.reactivex.rxjava2:rxandroid:2.0.1",
textDrawable : "com.github.rocketchat:textdrawable:1.0.2", textDrawable : "com.github.rocketchat:textdrawable:1.0.2",
optional : "com.hadisatrio:Optional:v1.0.1" optional : "com.hadisatrio:Optional:v1.0.1",
androidJob : "com.evernote:android-job:1.2.1",
jstate : "com.unquietcode.tools.jstate:jstate:2.1",
crouton : "de.keyboardsurfer.android.widget:crouton:1.8.5@aar"
] ]
rxbindingDependencies = [ rxbindingDependencies = [
rxBinding : "com.jakewharton.rxbinding2:rxbinding:${rxbindingVersion}", rxBinding : "com.jakewharton.rxbinding2:rxbinding:${rxbindingVersion}",
rxBindingSupport : "com.jakewharton.rxbinding2:rxbinding-support-v4:${rxbindingVersion}", rxBindingSupport : "com.jakewharton.rxbinding2:rxbinding-support-v4:${rxbindingVersion}",
...@@ -37,5 +46,14 @@ subprojects { ...@@ -37,5 +46,14 @@ subprojects {
project.android.dexOptions.preDexLibraries = rootProject.ext.preDexLibs project.android.dexOptions.preDexLibraries = rootProject.ext.preDexLibs
} }
} }
project.configurations.all {
resolutionStrategy.eachDependency { details ->
if (details.requested.group == 'com.android.support'
&& !details.requested.name.contains('multidex') ) {
details.useVersion "${supportLibraryVersion}"
}
}
}
} }
...@@ -30,17 +30,18 @@ android { ...@@ -30,17 +30,18 @@ android {
} }
} }
dependencies { dependencies {
compile project(':log-wrapper') api project(':log-wrapper')
compile project(':rocket-chat-core') api project(':rocket-chat-core')
compile extraDependencies.rxJava implementation extraDependencies.rxJava
compile extraDependencies.boltTask implementation extraDependencies.rxKotlin
compile supportDependencies.annotation implementation extraDependencies.boltTask
compile supportDependencies.designSupportLibrary implementation supportDependencies.annotation
compile extraDependencies.rxAndroid implementation supportDependencies.designSupportLibrary
provided extraDependencies.optional implementation extraDependencies.rxAndroid
testCompile "org.jetbrains.kotlin:kotlin-test:$rootProject.ext.kotlinVersion" implementation extraDependencies.optional
testCompile "org.jetbrains.kotlin:kotlin-test-junit:$rootProject.ext.kotlinVersion" testImplementation "org.jetbrains.kotlin:kotlin-test:$rootProject.ext.kotlinVersion"
testCompile 'org.json:json:20170516' testImplementation "org.jetbrains.kotlin:kotlin-test-junit:$rootProject.ext.kotlinVersion"
testCompile 'org.skyscreamer:jsonassert:1.5.0' testImplementation 'org.json:json:20170516'
testCompile 'junit:junit:4.12' testImplementation 'org.skyscreamer:jsonassert:1.5.0'
testImplementation 'junit:junit:4.12'
} }
...@@ -15,32 +15,46 @@ import io.reactivex.android.schedulers.AndroidSchedulers; ...@@ -15,32 +15,46 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
import io.realm.RealmResults; import io.realm.RealmResults;
public class RealmPublicSettingRepository extends RealmRepository public class RealmPublicSettingRepository extends RealmRepository
implements PublicSettingRepository { implements PublicSettingRepository {
private final String hostname; private final String hostname;
public RealmPublicSettingRepository(String hostname) { public RealmPublicSettingRepository(String hostname) {
this.hostname = hostname; this.hostname = hostname;
} }
@Override @Override
public Single<Optional<PublicSetting>> getById(String id) { public Single<Optional<PublicSetting>> getById(String id) {
return Single.defer(() -> Flowable.using( return Single.defer(() -> Flowable.using(
() -> new Pair<>(RealmStore.getRealm(hostname), Looper.myLooper()), () -> new Pair<>(RealmStore.getRealm(hostname), Looper.myLooper()),
pair -> { pair -> {
if (pair.first == null) { if (pair.first == null) {
return Flowable.empty(); return Flowable.empty();
} }
return pair.first.where(RealmPublicSetting.class)
.equalTo(RealmPublicSetting.ID, id) return pair.first.where(RealmPublicSetting.class)
.findAll() .equalTo(RealmPublicSetting.ID, id)
.<RealmResults<RealmPublicSetting>>asFlowable(); .findAll()
}, .<RealmResults<RealmPublicSetting>>asFlowable();
pair -> close(pair.first, pair.second) },
) pair -> close(pair.first, pair.second)
.unsubscribeOn(AndroidSchedulers.from(Looper.myLooper())) )
.filter(it -> it.isLoaded() && it.isValid() && it.size() > 0) .unsubscribeOn(AndroidSchedulers.from(Looper.myLooper()))
.map(it -> Optional.of(it.get(0).asPublicSetting())) .filter(it -> it.isLoaded() && it.isValid())
.first(Optional.absent())); .map(it -> getPublicSettingOrDefault(id, it))
} .first(Optional.absent()));
}
private Optional<PublicSetting> getPublicSettingOrDefault(String id, RealmResults<RealmPublicSetting> results) {
if (results.size() > 0) {
return Optional.of(results.get(0).asPublicSetting());
}
PublicSetting defaultSetting = PublicSetting.builder()
.setId(id)
.setValue("")
.setUpdatedAt(0L)
.build();
return Optional.of(defaultSetting);
}
} }
...@@ -31,24 +31,25 @@ ext { ...@@ -31,24 +31,25 @@ ext {
frescoVersion = '1.4.0' frescoVersion = '1.4.0'
} }
dependencies { dependencies {
compile project(':rocket-chat-core') api project(':rocket-chat-core')
compile extraDependencies.okHTTP api extraDependencies.okHTTP
compile extraDependencies.textDrawable api extraDependencies.textDrawable
compile supportDependencies.annotation api supportDependencies.annotation
compile supportDependencies.cardView api supportDependencies.cardView
compile supportDependencies.designSupportLibrary api supportDependencies.designSupportLibrary
compile supportDependencies.constraintLayout api supportDependencies.constraintLayout
compile supportDependencies.supportV13 api supportDependencies.supportV13
compile rxbindingDependencies.rxBinding api supportDependencies.customTabs
compile rxbindingDependencies.rxBindingSupport api rxbindingDependencies.rxBinding
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$rootProject.ext.kotlinVersion" api rxbindingDependencies.rxBindingSupport
compile 'org.nibor.autolink:autolink:0.6.0' api "org.jetbrains.kotlin:kotlin-stdlib-jre8:$rootProject.ext.kotlinVersion"
compile 'com.github.yusukeiwaki.android-widget:widget-fontawesome:0.0.1' implementation 'org.nibor.autolink:autolink:0.6.0'
compile "com.facebook.fresco:fresco:$frescoVersion" implementation 'com.github.yusukeiwaki.android-widget:widget-fontawesome:0.0.1'
compile "com.facebook.fresco:imagepipeline-okhttp3:$frescoVersion" api "com.facebook.fresco:fresco:$frescoVersion"
compile 'com.caverock:androidsvg:1.2.1' api "com.facebook.fresco:imagepipeline-okhttp3:$frescoVersion"
testCompile "org.jetbrains.kotlin:kotlin-test-junit:$rootProject.ext.kotlinVersion" implementation 'com.caverock:androidsvg:1.2.1'
testCompile "org.jetbrains.kotlin:kotlin-test:$rootProject.ext.kotlinVersion" testImplementation "org.jetbrains.kotlin:kotlin-test-junit:$rootProject.ext.kotlinVersion"
testCompile 'junit:junit:4.12' testImplementation "org.jetbrains.kotlin:kotlin-test:$rootProject.ext.kotlinVersion"
testCompile "org.mockito:mockito-core:2.7.19" testImplementation 'junit:junit:4.12'
testImplementation "org.mockito:mockito-core:2.10.0"
} }
...@@ -10,7 +10,6 @@ import com.facebook.drawee.backends.pipeline.Fresco ...@@ -10,7 +10,6 @@ import com.facebook.drawee.backends.pipeline.Fresco
import com.facebook.drawee.drawable.ProgressBarDrawable import com.facebook.drawee.drawable.ProgressBarDrawable
import com.facebook.drawee.drawable.ScalingUtils import com.facebook.drawee.drawable.ScalingUtils
import com.facebook.drawee.generic.GenericDraweeHierarchy import com.facebook.drawee.generic.GenericDraweeHierarchy
import com.facebook.drawee.generic.RoundingParams
import com.facebook.drawee.view.SimpleDraweeView import com.facebook.drawee.view.SimpleDraweeView
object FrescoHelper { object FrescoHelper {
...@@ -39,7 +38,6 @@ object FrescoHelper { ...@@ -39,7 +38,6 @@ object FrescoHelper {
val hierarchy: GenericDraweeHierarchy = draweeView.hierarchy val hierarchy: GenericDraweeHierarchy = draweeView.hierarchy
hierarchy.setPlaceholderImage(VectorDrawableCompat.create(draweeView.resources, R.drawable.image_dummy, null)) hierarchy.setPlaceholderImage(VectorDrawableCompat.create(draweeView.resources, R.drawable.image_dummy, null))
hierarchy.setFailureImage(VectorDrawableCompat.create(draweeView.resources, R.drawable.image_error, null)) hierarchy.setFailureImage(VectorDrawableCompat.create(draweeView.resources, R.drawable.image_error, null))
hierarchy.roundingParams = RoundingParams().setCornersRadii(5F, 5F, 5F, 5F)
hierarchy.actualImageScaleType = ScalingUtils.ScaleType.FIT_CENTER hierarchy.actualImageScaleType = ScalingUtils.ScaleType.FIT_CENTER
hierarchy.setProgressBarImage(ProgressBarDrawable()) hierarchy.setProgressBarImage(ProgressBarDrawable())
......
...@@ -7,6 +7,7 @@ import android.graphics.Color; ...@@ -7,6 +7,7 @@ import android.graphics.Color;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.support.customtabs.CustomTabsIntent;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import android.support.v4.widget.TextViewCompat; import android.support.v4.widget.TextViewCompat;
import android.text.TextUtils; import android.text.TextUtils;
...@@ -17,8 +18,12 @@ import android.view.ViewGroup; ...@@ -17,8 +18,12 @@ import android.view.ViewGroup;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.TextView; import android.widget.TextView;
import com.facebook.cache.common.CacheKey;
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder; import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
import com.facebook.drawee.view.SimpleDraweeView; import com.facebook.drawee.view.SimpleDraweeView;
import com.facebook.imagepipeline.cache.DefaultCacheKeyFactory;
import com.facebook.imagepipeline.core.ImagePipelineFactory;
import com.facebook.imagepipeline.request.ImageRequest;
import java.util.List; import java.util.List;
...@@ -33,254 +38,264 @@ import chat.rocket.core.models.AttachmentTitle; ...@@ -33,254 +38,264 @@ import chat.rocket.core.models.AttachmentTitle;
/** /**
*/ */
public class RocketChatMessageAttachmentsLayout extends LinearLayout { public class RocketChatMessageAttachmentsLayout extends LinearLayout {
private LayoutInflater inflater; private LayoutInflater inflater;
private AbsoluteUrl absoluteUrl; private AbsoluteUrl absoluteUrl;
private List<Attachment> attachments; private List<Attachment> attachments;
public RocketChatMessageAttachmentsLayout(Context context) { public RocketChatMessageAttachmentsLayout(Context context) {
super(context); super(context);
initialize(context, null); initialize(context, null);
}
public RocketChatMessageAttachmentsLayout(Context context, AttributeSet attrs) {
super(context, attrs);
initialize(context, attrs);
}
public RocketChatMessageAttachmentsLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initialize(context, attrs);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public RocketChatMessageAttachmentsLayout(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initialize(context, attrs);
}
private void initialize(Context context, AttributeSet attrs) {
inflater = LayoutInflater.from(context);
setOrientation(VERTICAL);
}
public void setAbsoluteUrl(AbsoluteUrl absoluteUrl) {
this.absoluteUrl = absoluteUrl;
}
public void setAttachments(List<Attachment> attachments, boolean autoloadImages) {
if (this.attachments != null && this.attachments.equals(attachments)) {
return;
} }
this.attachments = attachments;
for (int i = 0, size = attachments.size(); i < size; i++) { public RocketChatMessageAttachmentsLayout(Context context, AttributeSet attrs) {
appendAttachmentView(attachments.get(i), autoloadImages, true); super(context, attrs);
initialize(context, attrs);
} }
}
public void appendAttachmentView(Attachment attachment, boolean autoloadImages, boolean showAttachmentStrip) { public RocketChatMessageAttachmentsLayout(Context context, AttributeSet attrs, int defStyleAttr) {
if (attachment == null) { super(context, attrs, defStyleAttr);
return; initialize(context, attrs);
} }
removeAllViews(); @TargetApi(Build.VERSION_CODES.LOLLIPOP)
View attachmentView = inflater.inflate(R.layout.message_inline_attachment, this, false); public RocketChatMessageAttachmentsLayout(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
colorizeAttachmentBar(attachment, attachmentView, showAttachmentStrip); super(context, attrs, defStyleAttr, defStyleRes);
showAuthorAttachment(attachment, attachmentView); initialize(context, attrs);
showTitleAttachment(attachment, attachmentView);
showReferenceAttachment(attachment, attachmentView);
showImageAttachment(attachment, attachmentView, autoloadImages);
// audio
// video
showFieldsAttachment(attachment, attachmentView);
addView(attachmentView);
}
private void colorizeAttachmentBar(Attachment attachment, View attachmentView, boolean showAttachmentStrip) {
final View attachmentStrip = attachmentView.findViewById(R.id.attachment_strip);
if (showAttachmentStrip) {
final String colorString = attachment.getColor();
if (TextUtils.isEmpty(colorString)) {
attachmentStrip.setBackgroundResource(R.color.inline_attachment_quote_line);
return;
}
try {
attachmentStrip.setBackgroundColor(Color.parseColor(colorString));
} catch (Exception e) {
attachmentStrip.setBackgroundResource(R.color.inline_attachment_quote_line);
}
} else {
attachmentStrip.setVisibility(GONE);
} }
}
private void initialize(Context context, AttributeSet attrs) {
private void showAuthorAttachment(Attachment attachment, View attachmentView) { inflater = LayoutInflater.from(context);
final View authorBox = attachmentView.findViewById(R.id.author_box); setOrientation(VERTICAL);
AttachmentAuthor author = attachment.getAttachmentAuthor();
if (author == null) {
authorBox.setVisibility(GONE);
return;
} }
authorBox.setVisibility(VISIBLE); public void setAbsoluteUrl(AbsoluteUrl absoluteUrl) {
this.absoluteUrl = absoluteUrl;
}
FrescoHelper.INSTANCE.loadImageWithCustomization((SimpleDraweeView) attachmentView.findViewById(R.id.author_icon), absolutize(author.getIconUrl())); public void setAttachments(List<Attachment> attachments, boolean autoloadImages) {
if (this.attachments != null && this.attachments.equals(attachments)) {
return;
}
this.attachments = attachments;
final TextView authorName = (TextView) attachmentView.findViewById(R.id.author_name); for (int i = 0, size = attachments.size(); i < size; i++) {
authorName.setText(author.getName()); appendAttachmentView(attachments.get(i), autoloadImages, true);
}
}
public void appendAttachmentView(Attachment attachment, boolean autoloadImages, boolean showAttachmentStrip) {
if (attachment == null) {
return;
}
final String link = absolutize(author.getLink()); removeAllViews();
authorName.setOnClickListener(new OnClickListener() { View attachmentView = inflater.inflate(R.layout.message_inline_attachment, this, false);
@Override
public void onClick(View view) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(link));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
view.getContext().startActivity(intent);
}
});
// timestamp and link - need to format time colorizeAttachmentBar(attachment, attachmentView, showAttachmentStrip);
} showAuthorAttachment(attachment, attachmentView);
showTitleAttachment(attachment, attachmentView);
showReferenceAttachment(attachment, attachmentView);
showImageAttachment(attachment, attachmentView, autoloadImages);
// audio
// video
showFieldsAttachment(attachment, attachmentView);
private void showTitleAttachment(Attachment attachment, View attachmentView) { addView(attachmentView);
TextView titleView = (TextView) attachmentView.findViewById(R.id.title);
AttachmentTitle title = attachment.getAttachmentTitle();
if (title == null || title.getTitle() == null) {
titleView.setVisibility(View.GONE);
return;
} }
titleView.setVisibility(View.VISIBLE); private void colorizeAttachmentBar(Attachment attachment, View attachmentView, boolean showAttachmentStrip) {
titleView.setText(title.getTitle()); final View attachmentStrip = attachmentView.findViewById(R.id.attachment_strip);
if (title.getLink() == null) { if (showAttachmentStrip) {
titleView.setOnClickListener(null); final String colorString = attachment.getColor();
titleView.setClickable(false); if (TextUtils.isEmpty(colorString)) {
} else { attachmentStrip.setBackgroundResource(R.color.inline_attachment_quote_line);
final String link = absolutize(title.getLink()); return;
titleView.setOnClickListener(new OnClickListener() { }
@Override
public void onClick(View view) { try {
final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(link)); attachmentStrip.setBackgroundColor(Color.parseColor(colorString));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } catch (Exception e) {
attachmentStrip.setBackgroundResource(R.color.inline_attachment_quote_line);
final Context context = view.getContext(); }
if (intent.resolveActivity(context.getPackageManager()) != null) { } else {
context.startActivity(intent); attachmentStrip.setVisibility(GONE);
}
} }
});
TextViewCompat.setTextAppearance(titleView,
R.style.TextAppearance_RocketChat_MessageAttachment_Title_Link);
} }
}
private void showReferenceAttachment(Attachment attachment, View attachmentView) { private void showAuthorAttachment(Attachment attachment, View attachmentView) {
final View refBox = attachmentView.findViewById(R.id.ref_box); final View authorBox = attachmentView.findViewById(R.id.author_box);
if (attachment.getThumbUrl() == null && attachment.getText() == null) { AttachmentAuthor author = attachment.getAttachmentAuthor();
refBox.setVisibility(GONE); if (author == null) {
return; authorBox.setVisibility(GONE);
} return;
}
authorBox.setVisibility(VISIBLE);
refBox.setVisibility(VISIBLE); FrescoHelper.INSTANCE.loadImageWithCustomization((SimpleDraweeView) attachmentView.findViewById(R.id.author_icon), absolutize(author.getIconUrl()));
final SimpleDraweeView thumbImage = (SimpleDraweeView) refBox.findViewById(R.id.thumb); final TextView authorName = attachmentView.findViewById(R.id.author_name);
authorName.setText(author.getName());
final String thumbUrl = attachment.getThumbUrl(); final String link = absolutize(author.getLink());
if (TextUtils.isEmpty(thumbUrl)) { authorName.setOnClickListener(new OnClickListener() {
thumbImage.setVisibility(GONE); @Override
} else { public void onClick(View view) {
thumbImage.setVisibility(VISIBLE); Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(link));
FrescoHelper.INSTANCE.loadImageWithCustomization(thumbImage, absolutize(thumbUrl)); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
view.getContext().startActivity(intent);
}
});
// timestamp and link - need to format time
} }
final TextView refText = (TextView) refBox.findViewById(R.id.text); private void showTitleAttachment(Attachment attachment, View attachmentView) {
TextView titleView = attachmentView.findViewById(R.id.title);
AttachmentTitle title = attachment.getAttachmentTitle();
if (title == null || title.getTitle() == null) {
titleView.setVisibility(View.GONE);
return;
}
final String refString = attachment.getText(); titleView.setVisibility(View.VISIBLE);
if (TextUtils.isEmpty(refString)) { titleView.setText(title.getTitle());
refText.setVisibility(GONE);
} else { if (title.getLink() == null) {
refText.setVisibility(VISIBLE); titleView.setOnClickListener(null);
refText.setText(refString); titleView.setClickable(false);
} else {
final String link = absolutize(title.getLink());
titleView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
new CustomTabsIntent.Builder()
.setToolbarColor(ContextCompat.getColor(getContext(), R.color.colorPrimary))
.setStartAnimations(getContext(), R.anim.slide_in_right, R.anim.slide_out_left)
.setExitAnimations(getContext(), R.anim.slide_in_left, R.anim.slide_out_right)
.build()
.launchUrl(getContext(), Uri.parse(link));
}
});
TextViewCompat.setTextAppearance(titleView,
R.style.TextAppearance_RocketChat_MessageAttachment_Title_Link);
}
} }
}
private void showImageAttachment(Attachment attachment, View attachmentView, boolean autoloadImages) { private void showReferenceAttachment(Attachment attachment, View attachmentView) {
final View imageContainer = attachmentView.findViewById(R.id.image_container); final View refBox = attachmentView.findViewById(R.id.ref_box);
if (attachment.getImageUrl() == null) { if (attachment.getThumbUrl() == null && attachment.getText() == null) {
imageContainer.setVisibility(GONE); refBox.setVisibility(GONE);
return; return;
}
refBox.setVisibility(VISIBLE);
final SimpleDraweeView thumbImage = refBox.findViewById(R.id.thumb);
final String thumbUrl = attachment.getThumbUrl();
if (TextUtils.isEmpty(thumbUrl)) {
thumbImage.setVisibility(GONE);
} else {
thumbImage.setVisibility(VISIBLE);
FrescoHelper.INSTANCE.loadImageWithCustomization(thumbImage, absolutize(thumbUrl));
}
final TextView refText = refBox.findViewById(R.id.text);
final String refString = attachment.getText();
if (TextUtils.isEmpty(refString)) {
refText.setVisibility(GONE);
} else {
refText.setVisibility(VISIBLE);
refText.setText(refString);
}
} }
imageContainer.setVisibility(VISIBLE); private void showImageAttachment(Attachment attachment, View attachmentView, boolean autoloadImages) {
final View imageContainer = attachmentView.findViewById(R.id.image_container);
if (attachment.getImageUrl() == null) {
imageContainer.setVisibility(GONE);
return;
}
final SimpleDraweeView attachedImage = attachmentView.findViewById(R.id.image); imageContainer.setVisibility(VISIBLE);
final View load = attachmentView.findViewById(R.id.image_load);
// Fix for https://fabric.io/rocketchat3/android/apps/chat.rocket.android/issues/59982403be077a4dcc4d7dc3/sessions/599F217000CF00015C771EEF2021AA0F_f9320e3f88fd11e7935256847afe9799_0_v2? final SimpleDraweeView attachedImage = attachmentView.findViewById(R.id.image);
// From: https://github.com/facebook/fresco/issues/1176#issuecomment-216830098 final View load = attachmentView.findViewById(R.id.image_load);
// android.support.v4.content.ContextCompat creates your vector drawable
Drawable placeholderDrawable = ContextCompat.getDrawable(getContext(), R.drawable.image_dummy);
// Set the placeholder image to the placeholder vector drawable // Fix for https://fabric.io/rocketchat3/android/apps/chat.rocket.android/issues/59982403be077a4dcc4d7dc3/sessions/599F217000CF00015C771EEF2021AA0F_f9320e3f88fd11e7935256847afe9799_0_v2?
attachedImage.setHierarchy( // From: https://github.com/facebook/fresco/issues/1176#issuecomment-216830098
GenericDraweeHierarchyBuilder.newInstance(getResources()) // android.support.v4.content.ContextCompat creates your vector drawable
.setPlaceholderImage(placeholderDrawable) Drawable placeholderDrawable = ContextCompat.getDrawable(getContext(), R.drawable.image_dummy);
.build());
loadImage(absolutize(attachment.getImageUrl()), attachedImage, load, autoloadImages); // Set the placeholder image to the placeholder vector drawable
} attachedImage.setHierarchy(
GenericDraweeHierarchyBuilder.newInstance(getResources())
.setPlaceholderImage(placeholderDrawable)
.build());
private void showFieldsAttachment(Attachment attachment, View attachmentView) { loadImage(absolutize(attachment.getImageUrl()), attachedImage, load, autoloadImages);
List<AttachmentField> fields = attachment.getAttachmentFields();
if (fields == null || fields.size() == 0) {
return;
} }
final ViewGroup attachmentContent = private void showFieldsAttachment(Attachment attachment, View attachmentView) {
(ViewGroup) attachmentView.findViewById(R.id.attachment_content); List<AttachmentField> fields = attachment.getAttachmentFields();
if (fields == null || fields.size() == 0) {
return;
}
final ViewGroup attachmentContent = attachmentView.findViewById(R.id.attachment_content);
for (int i = 0, size = fields.size(); i < size; i++) { for (int i = 0, size = fields.size(); i < size; i++) {
final AttachmentField attachmentField = fields.get(i); final AttachmentField attachmentField = fields.get(i);
if (attachmentField.getTitle() == null if (attachmentField.getTitle() == null
|| attachmentField.getText() == null) { || attachmentField.getText() == null) {
return; return;
} }
MessageAttachmentFieldLayout fieldLayout = new MessageAttachmentFieldLayout(getContext()); MessageAttachmentFieldLayout fieldLayout = new MessageAttachmentFieldLayout(getContext());
fieldLayout.setTitle(attachmentField.getTitle()); fieldLayout.setTitle(attachmentField.getTitle());
fieldLayout.setValue(attachmentField.getText()); fieldLayout.setValue(attachmentField.getText());
attachmentContent.addView(fieldLayout); attachmentContent.addView(fieldLayout);
}
}
private String absolutize(String url) {
if (absoluteUrl == null) {
return url;
}
return absoluteUrl.from(url);
} }
}
private String absolutize(String url) { private void loadImage(final String url, final SimpleDraweeView drawee, final View load,
if (absoluteUrl == null) { boolean autoloadImage) {
return url; if (autoloadImage || isCached(Uri.parse(url))) {
load.setVisibility(GONE);
FrescoHelper.INSTANCE.loadImageWithCustomization(drawee, url);
return;
}
load.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
load.setVisibility(GONE);
load.setOnClickListener(null);
FrescoHelper.INSTANCE.loadImageWithCustomization(drawee, url);
}
});
} }
return absoluteUrl.from(url);
} private boolean isCached(Uri loadUri) {
if (loadUri == null) {
private void loadImage(final String url, final SimpleDraweeView drawee, final View load, return false;
boolean autoloadImage) { }
if (autoloadImage) { ImageRequest imageRequest = ImageRequest.fromUri(loadUri);
load.setVisibility(GONE); CacheKey cacheKey = DefaultCacheKeyFactory.getInstance()
FrescoHelper.INSTANCE.loadImageWithCustomization(drawee, url); .getEncodedCacheKey(imageRequest, null);
return; return ImagePipelineFactory.getInstance()
.getMainFileCache().hasKey(cacheKey);
} }
load.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
load.setVisibility(GONE);
load.setOnClickListener(null);
FrescoHelper.INSTANCE.loadImageWithCustomization(drawee, url);
}
});
}
} }
<?xml version="1.0" encoding="utf-8"?>
<set
xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromXDelta="-100%p"
android:toXDelta="0"
android:duration="@android:integer/config_mediumAnimTime"/>
</set>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<set
xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromXDelta="100%p"
android:toXDelta="0"
android:duration="@android:integer/config_mediumAnimTime"/>
</set>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<set
xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromXDelta="0"
android:toXDelta="-100%p"
android:duration="@android:integer/config_mediumAnimTime"/>
</set>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<set
xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromXDelta="0"
android:toXDelta="100%p"
android:duration="@android:integer/config_mediumAnimTime"/>
</set>
\ No newline at end of file
...@@ -100,15 +100,15 @@ ...@@ -100,15 +100,15 @@
android:id="@+id/image_container" android:id="@+id/image_container"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="start"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:padding="5dp" android:background="@drawable/inline_attachment_background"
android:background="@drawable/inline_attachment_background"> android:padding="5dp">
<com.facebook.drawee.view.SimpleDraweeView <com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/image" android:id="@+id/image"
android:layout_width="200dp" android:layout_width="200dp"
android:layout_height="200dp"/> android:layout_height="200dp" />
<TextView <TextView
android:id="@+id/image_load" android:id="@+id/image_load"
......
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="message_composer_message_hint">Сообщение</string>
<string name="click_to_load">Нажмите для загрузки</string>
<string name="no_channel_found">Нет каналов</string>
<string name="no_user_found">Нет пользователей</string>
</resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<color name="color_accent">#FF2D91FA</color> <color name="color_accent">#FF2D91FA</color>
<color name="colorPrimary">#044b76</color>
<color name="colorPrimaryDark">#FFF</color>
<color name="color_shadow">#FFE6E6E7</color> <color name="color_shadow">#FFE6E6E7</color>
<color name="color_icon_composer">#FFA8A8A8</color> <color name="color_icon_composer">#FFA8A8A8</color>
......
...@@ -4,14 +4,15 @@ apply plugin: 'java' ...@@ -4,14 +4,15 @@ apply plugin: 'java'
dependencies { dependencies {
compile extraDependencies.rxJava compile extraDependencies.rxJava
compile extraDependencies.rxKotlin
compile extraDependencies.optional compile extraDependencies.optional
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$rootProject.ext.kotlinVersion" compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$rootProject.ext.kotlinVersion"
compile 'com.google.code.findbugs:jsr305:3.0.2' compile 'com.google.code.findbugs:jsr305:3.0.2'
compileOnly 'com.google.auto.value:auto-value:1.3' compileOnly 'com.google.auto.value:auto-value:1.3'
kapt 'com.google.auto.value:auto-value:1.3' kapt 'com.google.auto.value:auto-value:1.3'
kapt 'com.gabrielittner.auto.value:auto-value-with:1.0.0' kapt 'com.gabrielittner.auto.value:auto-value-with:1.0.0'
testCompile 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
testCompile "org.mockito:mockito-inline:2.8.9" testImplementation "org.mockito:mockito-inline:2.8.9"
} }
sourceCompatibility = "1.7" sourceCompatibility = "1.8"
targetCompatibility = "1.7" targetCompatibility = "1.8"
\ No newline at end of file \ No newline at end of file
...@@ -19,7 +19,7 @@ class MessageInteractor(private val messageRepository: MessageRepository, ...@@ -19,7 +19,7 @@ class MessageInteractor(private val messageRepository: MessageRepository,
val roomHistoryState = RoomHistoryState.builder() val roomHistoryState = RoomHistoryState.builder()
.setRoomId(room.roomId) .setRoomId(room.roomId)
.setSyncState(SyncState.NOT_SYNCED) .setSyncState(SyncState.NOT_SYNCED)
.setCount(100) .setCount(50)
.setReset(true) .setReset(true)
.setComplete(false) .setComplete(false)
.setTimestamp(0) .setTimestamp(0)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment