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

Merge pull request #635 from RocketChat/pack-of-fixes

[FIX] Pack of fixes
parents 8407c568 f9bf712c
......@@ -21,9 +21,10 @@ android {
}
}
dependencies {
compile project(':log-wrapper')
compile extraDependencies.okHTTP
compile extraDependencies.rxJava
compile extraDependencies.boltTask
compile supportDependencies.annotation
api project(':log-wrapper')
implementation extraDependencies.okHTTP
implementation extraDependencies.rxJava
implementation extraDependencies.rxKotlin
implementation extraDependencies.boltTask
implementation supportDependencies.annotation
}
\ No newline at end of file
......@@ -84,7 +84,11 @@ public class DDPClient {
}
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;
import okhttp3.OkHttpClient;
public class DDPClientImpl {
private final DDPClient client;
private RxWebSocket websocket;
private Flowable<RxWebSocketCallback.Base> flowable;
private CompositeDisposable disposables;
private String currentSession;
public DDPClientImpl(DDPClient self, OkHttpClient client) {
websocket = new RxWebSocket(client);
this.client = self;
}
private static JSONObject toJson(String s) {
if (TextUtils.isEmpty(s)) {
return null;
private final DDPClient client;
private RxWebSocket websocket;
private Flowable<RxWebSocketCallback.Base> flowable;
private CompositeDisposable disposables;
private String currentSession;
/* package */ DDPClientImpl(DDPClient self, OkHttpClient client) {
websocket = new RxWebSocket(client);
this.client = self;
}
try {
return new JSONObject(s);
} catch (JSONException e) {
return null;
private static JSONObject toJson(String s) {
if (TextUtils.isEmpty(s)) {
return null;
}
try {
return new JSONObject(s);
} catch (JSONException e) {
return null;
}
}
}
private static String extractMsg(JSONObject response) {
if (response == null || response.isNull("msg")) {
return null;
} else {
return response.optString("msg");
private static String extractMsg(JSONObject response) {
if (response == null || response.isNull("msg")) {
return null;
} else {
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();
} catch (Exception e) {
RCLog.e(e);
subscribeBaseListeners();
} catch (Exception e) {
RCLog.e(e);
}
}
}
public Maybe<DDPClientCallback.Base> ping(@Nullable final String id) {
final boolean requested = (TextUtils.isEmpty(id)) ?
sendMessage("ping", null) :
sendMessage("ping", json -> json.put("id", id));
if (requested) {
return flowable.filter(callback -> callback instanceof RxWebSocketCallback.Message)
.timeout(8, TimeUnit.SECONDS)
.map(callback -> ((RxWebSocketCallback.Message) callback).responseBodyString)
.map(DDPClientImpl::toJson)
.filter(response -> "pong".equalsIgnoreCase(extractMsg(response)))
.doOnError(error -> {
RCLog.e(error, "Heartbeat ping[%s] xxx failed xxx", id);
})
.map(response -> {
String msg = extractMsg(response);
if ("pong".equals(msg)) {
RCLog.d("pong[%s] <", id);
if (response.isNull("id")) {
return new DDPClientCallback.Ping(client, null);
} else {
String _id = response.optString("id");
if (id.equals(_id)) {
return new DDPClientCallback.Ping(client, _id);
} else {
return new DDPClientCallback.Ping.UnMatched(client, _id);
}
}
}
// if we receive anything other than a pong throw an exception
throw new DDPClientCallback.RPC.Error(client, id, response);
}).firstElement();
} else {
return Maybe.error(new DDPClientCallback.Closed(client));
/* package */ Maybe<DDPClientCallback.Base> ping(@Nullable final String id) {
final boolean requested = (TextUtils.isEmpty(id)) ?
sendMessage("ping", null) :
sendMessage("ping", json -> json.put("id", id));
if (requested) {
return flowable.filter(callback -> callback instanceof RxWebSocketCallback.Message)
.timeout(8, TimeUnit.SECONDS)
.map(callback -> ((RxWebSocketCallback.Message) callback).responseBodyString)
.map(DDPClientImpl::toJson)
.filter(response -> "pong".equalsIgnoreCase(extractMsg(response)))
.doOnError(error -> {
RCLog.e(error, "Heartbeat ping[%s] xxx failed xxx", id);
})
.map(response -> {
String msg = extractMsg(response);
if ("pong".equals(msg)) {
RCLog.d("pong[%s] <", id);
if (response.isNull("id")) {
return new DDPClientCallback.Ping(client, null);
} else {
String _id = response.optString("id");
if (id.equals(_id)) {
return new DDPClientCallback.Ping(client, _id);
} else {
return new DDPClientCallback.Ping.UnMatched(client, _id);
}
}
}
// if we receive anything other than a pong throw an exception
throw new DDPClientCallback.RPC.Error(client, id, response);
}).firstElement();
} else {
return Maybe.error(new DDPClientCallback.Closed(client));
}
}
}
public void ping(final TaskCompletionSource<DDPClientCallback.Ping> task,
@Nullable final String id) {
final boolean requested = (TextUtils.isEmpty(id)) ?
sendMessage("ping", null) :
sendMessage("ping", json -> json.put("id", id));
if (requested) {
CompositeDisposable disposables = new CompositeDisposable();
disposables.add(
flowable.filter(callback -> callback instanceof RxWebSocketCallback.Message)
.timeout(8, TimeUnit.SECONDS)
.map(callback -> ((RxWebSocketCallback.Message) callback).responseBodyString)
.map(DDPClientImpl::toJson)
.subscribe(
response -> {
String msg = extractMsg(response);
if ("pong".equals(msg)) {
if (response.isNull("id")) {
task.setResult(new DDPClientCallback.Ping(client, null));
} else {
String _id = response.optString("id");
if (id.equals(_id)) {
task.setResult(new DDPClientCallback.Ping(client, id));
/* package */void ping(final TaskCompletionSource<DDPClientCallback.Ping> task,
@Nullable final String id) {
final boolean requested = (TextUtils.isEmpty(id)) ?
sendMessage("ping", null) :
sendMessage("ping", json -> json.put("id", id));
if (requested) {
CompositeDisposable disposables = new CompositeDisposable();
disposables.add(
flowable.filter(callback -> callback instanceof RxWebSocketCallback.Message)
.timeout(8, TimeUnit.SECONDS)
.map(callback -> ((RxWebSocketCallback.Message) callback).responseBodyString)
.map(DDPClientImpl::toJson)
.subscribe(
response -> {
String msg = extractMsg(response);
if ("pong".equals(msg)) {
if (response.isNull("id")) {
task.setResult(new DDPClientCallback.Ping(client, null));
} else {
String _id = response.optString("id");
if (id.equals(_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();
}
},
err -> task.trySetError(new DDPClientCallback.Ping.Timeout(client))
)
);
addErrorCallback(disposables, task);
} else {
task.trySetError(new DDPClientCallback.Closed(client));
)
);
addErrorCallback(disposables, task);
} else {
task.trySetError(new DDPClientCallback.Closed(client));
}
}
}
public void sub(final TaskCompletionSource<DDPSubscription.Ready> task, String name,
JSONArray params, String id) {
final boolean requested =
sendMessage("sub", json -> json.put("id", id).put("name", name).put("params", params));
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 ("ready".equals(msg) && !response.isNull("subs")) {
JSONArray ids = response.optJSONArray("subs");
for (int i = 0; i < ids.length(); i++) {
String _id = ids.optString(i);
if (id.equals(_id)) {
task.setResult(new DDPSubscription.Ready(client, id));
disposables.clear();
break;
}
}
} else if ("nosub".equals(msg) && !response.isNull("id") && !response.isNull(
"error")) {
String _id = response.optString("id");
if (id.equals(_id)) {
task.trySetError(new DDPSubscription.NoSub.Error(client, id,
response.optJSONObject("error")));
disposables.clear();
}
}
},
RCLog::e
)
);
addErrorCallback(disposables, task);
} else {
task.trySetError(new DDPClientCallback.Closed(client));
/* package */
void sub(final TaskCompletionSource<DDPSubscription.Ready> task, String name,
JSONArray params, String id) {
final boolean requested =
sendMessage("sub", json -> json.put("id", id).put("name", name).put("params", params));
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 ("ready".equals(msg) && !response.isNull("subs")) {
JSONArray ids = response.optJSONArray("subs");
for (int i = 0; i < ids.length(); i++) {
String _id = ids.optString(i);
if (id.equals(_id)) {
task.setResult(new DDPSubscription.Ready(client, id));
disposables.clear();
break;
}
}
} else if ("nosub".equals(msg) && !response.isNull("id") && !response.isNull(
"error")) {
String _id = response.optString("id");
if (id.equals(_id)) {
task.trySetError(new DDPSubscription.NoSub.Error(client, id,
response.optJSONObject("error")));
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);
} else {
task.trySetError(new DDPClientCallback.Closed(client));
}
}
}
public void unsub(final TaskCompletionSource<DDPSubscription.NoSub> task,
@Nullable final String id) {
/* package */ void unsub(final TaskCompletionSource<DDPSubscription.NoSub> task,
@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) {
CompositeDisposable disposables = new CompositeDisposable();
/* package */ void rpc(final TaskCompletionSource<DDPClientCallback.RPC> task, String method,
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(
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 -> {
}
)
);
addErrorCallback(disposables, task);
} else {
task.trySetError(new DDPClientCallback.Closed(client));
addErrorCallback(disposables, task);
} else {
task.trySetError(new DDPClientCallback.Closed(client));
}
}
private void subscribeBaseListeners() {
if (disposables != null &&
disposables.size() > 0 && !disposables.isDisposed()) {
return;
}
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 ("ping".equals(msg)) {
if (response.isNull("id")) {
sendMessage("pong", null);
} else {
sendMessage("pong", json -> json.put("id", response.getString("id")));
}
}
},
RCLog::e
)
);
}
}
public void rpc(final TaskCompletionSource<DDPClientCallback.RPC> task, String method,
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 -> {
/* package */ 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);
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));
for (String m : targetMsgs) {
if (m.equals(msg)) {
return true;
}
disposables.clear();
}
}
},
err -> {
if (err instanceof TimeoutException) {
task.trySetError(new DDPClientCallback.RPC.Timeout(client));
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"));
}
}
)
);
addErrorCallback(disposables, task);
} else {
task.trySetError(new DDPClientCallback.Closed(client));
return null;
});
}
}
private void subscribeBaseListeners() {
if (disposables != null &&
disposables.size() > 0 && !disposables.isDisposed()) {
return;
/* package */ void unsubscribeBaseListeners() {
if (disposables.size() > 0 || !disposables.isDisposed()) {
disposables.clear();
}
}
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 ("ping".equals(msg)) {
if (response.isNull("id")) {
sendMessage("pong", null);
} else {
sendMessage("pong", json -> json.put("id", response.getString("id")));
}
}
},
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;
/* package */ Task<RxWebSocketCallback.Close> getOnCloseCallback() {
TaskCompletionSource<RxWebSocketCallback.Close> task = new TaskCompletionSource<>();
flowable.filter(callback -> callback instanceof RxWebSocketCallback.Close)
.cast(RxWebSocketCallback.Close.class)
.subscribe(
task::setResult,
err -> setTaskError(task, err)
);
return task.getTask().onSuccessTask(_task -> {
unsubscribeBaseListeners();
return _task;
});
}
}
public void unsubscribeBaseListeners() {
if (disposables.size() > 0 || !disposables.isDisposed()) {
disposables.clear();
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;
}
}
}
public Task<RxWebSocketCallback.Close> getOnCloseCallback() {
TaskCompletionSource<RxWebSocketCallback.Close> task = new TaskCompletionSource<>();
private void sendMessage(String msg, @Nullable JSONBuilder json,
TaskCompletionSource<?> taskForSetError) {
if (!sendMessage(msg, json) && taskForSetError != null) {
taskForSetError.trySetError(new DDPClientCallback.Closed(client));
}
}
flowable.filter(callback -> callback instanceof RxWebSocketCallback.Close)
.cast(RxWebSocketCallback.Close.class)
.subscribe(
task::setResult,
err -> setTaskError(task, err)
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();
}
)
);
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,
TaskCompletionSource<?> taskForSetError) {
if (!sendMessage(msg, json) && taskForSetError != null) {
taskForSetError.trySetError(new DDPClientCallback.Closed(client));
}
}
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);
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) {
if (task.getTask().isCompleted()) {
return;
private void setTaskError(TaskCompletionSource task, Throwable throwable) {
if (task.getTask().isCompleted()) {
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 {
@NonNull
JSONObject create(JSONObject root) throws JSONException;
}
private interface JSONBuilder {
@NonNull
JSONObject create(JSONObject root) throws JSONException;
}
}
......@@ -98,7 +98,6 @@ play {
track = "${track}"
}
ext {
playLibVersion = '11.6.0'
stethoVersion = '1.5.0'
stethoOkhttp3Version = '1.5.0'
stethoRealmVersion = '2.2.2'
......@@ -109,43 +108,49 @@ ext {
}
dependencies {
compile project(':android-ddp')
compile project(':rocket-chat-android-widgets')
compile project(':persistence-realm')
compile extraDependencies.okHTTP
compile extraDependencies.rxJava
compile extraDependencies.boltTask
compile supportDependencies.multidex
compile supportDependencies.designSupportLibrary
compile supportDependencies.annotation
compile rxbindingDependencies.rxBinding
compile rxbindingDependencies.rxBindingSupport
compile rxbindingDependencies.rxBindingAppcompact
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$rootProject.ext.kotlinVersion"
compile "com.google.firebase:firebase-core:$playLibVersion"
compile "com.google.firebase:firebase-crash:$playLibVersion"
compile "com.google.android.gms:play-services-gcm:$playLibVersion"
compile "com.trello.rxlifecycle2:rxlifecycle:$rxlifecycleVersion"
compile "com.trello.rxlifecycle2:rxlifecycle-android:$rxlifecycleVersion"
compile "com.trello.rxlifecycle2:rxlifecycle-components:$rxlifecycleVersion"
compile 'nl.littlerobots.rxlint:rxlint:1.2'
compile "frankiesardo:icepick:$icepickVersion"
api project(':android-ddp')
api project(':rocket-chat-android-widgets')
api project(':persistence-realm')
implementation extraDependencies.okHTTP
implementation extraDependencies.rxJava
implementation extraDependencies.rxKotlin
implementation extraDependencies.boltTask
implementation supportDependencies.multidex
implementation supportDependencies.designSupportLibrary
implementation supportDependencies.annotation
implementation rxbindingDependencies.rxBinding
implementation rxbindingDependencies.rxBindingSupport
implementation rxbindingDependencies.rxBindingAppcompact
api "org.jetbrains.kotlin:kotlin-stdlib-jre8:$rootProject.ext.kotlinVersion"
implementation "com.google.firebase:firebase-core:$rootProject.ext.playLibVersion"
implementation "com.google.firebase:firebase-crash:$rootProject.ext.playLibVersion"
implementation "com.google.android.gms:play-services-gcm:$rootProject.ext.playLibVersion"
implementation "com.trello.rxlifecycle2:rxlifecycle:$rxlifecycleVersion"
implementation "com.trello.rxlifecycle2:rxlifecycle-android:$rxlifecycleVersion"
implementation "com.trello.rxlifecycle2:rxlifecycle-components:$rxlifecycleVersion"
implementation 'nl.littlerobots.rxlint:rxlint:1.2'
implementation "frankiesardo:icepick:$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"
compile('com.crashlytics.sdk.android:crashlytics:2.6.8@aar') {
implementation('com.crashlytics.sdk.android:crashlytics:2.6.8@aar') {
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.uphyca:stetho_realm:$stethoRealmVersion"
debugCompile "com.tspoon.traceur:traceur:1.0.1"
testCompile 'junit:junit:4.12'
testCompile 'org.robolectric:robolectric:3.3'
testCompile "org.jetbrains.kotlin:kotlin-test:$rootProject.ext.kotlinVersion"
testCompile "org.jetbrains.kotlin:kotlin-test-junit:$rootProject.ext.kotlinVersion"
testCompile "org.jetbrains.kotlin:kotlin-reflect:$rootProject.ext.kotlinVersion"
testCompile "com.nhaarman:mockito-kotlin:1.5.0"
testCompile 'org.amshove.kluent:kluent:1.14'
testImplementation 'junit:junit:4.12'
testImplementation 'org.robolectric:robolectric:3.3'
testImplementation "org.jetbrains.kotlin:kotlin-test:$rootProject.ext.kotlinVersion"
testImplementation "org.jetbrains.kotlin:kotlin-test-junit:$rootProject.ext.kotlinVersion"
testImplementation "org.jetbrains.kotlin:kotlin-reflect:$rootProject.ext.kotlinVersion"
testImplementation "com.nhaarman:mockito-kotlin:1.5.0"
testImplementation 'org.amshove.kluent:kluent:1.14'
}
apply plugin: 'com.google.gms.google-services'
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.DefaultCookieProvider
import com.facebook.stetho.okhttp3.StethoInterceptor
......@@ -24,17 +22,18 @@ object OkHttpHelper {
return httpClientForUploadFile ?: throw AssertionError("httpClientForUploadFile set to null by another thread")
}
fun getClientForDownloadFile(context: Context): OkHttpClient {
if(httpClientForDownloadFile == null) {
fun getClientForDownloadFile(): OkHttpClient {
if (httpClientForDownloadFile == null) {
httpClientForDownloadFile = OkHttpClient.Builder()
.addNetworkInterceptor(StethoInterceptor())
.followRedirects(true)
.followSslRedirects(true)
.addInterceptor(CookieInterceptor(DefaultCookieProvider(RocketChatCache(context))))
.addInterceptor(CookieInterceptor(DefaultCookieProvider()))
.build()
}
return httpClientForDownloadFile ?: throw AssertionError("httpClientForDownloadFile set to null by another thread")
}
/**
* Returns the OkHttpClient instance for WebSocket connection.
* @return The OkHttpClient WebSocket connection instance.
......
<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.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.VIBRATE" />
<permission
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
android:name=".RocketChatApplication"
......@@ -26,39 +27,40 @@
android:configChanges="orientation|screenSize"
android:windowSoftInputMode="adjustResize">
<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.LAUNCHER"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".activity.room.RoomActivity"
android:configChanges="orientation|screenSize"
android:windowSoftInputMode="adjustResize"/>
android:windowSoftInputMode="adjustResize" />
<activity
android:name=".activity.AddServerActivity"
android:configChanges="orientation|screenSize"
android:windowSoftInputMode="adjustResize"
android:launchMode="singleTop"/>
android:launchMode="singleTop"
android:windowSoftInputMode="adjustResize" />
<activity
android:name=".activity.LoginActivity"
android:configChanges="orientation|screenSize"
android:windowSoftInputMode="adjustResize"/>
android:windowSoftInputMode="adjustResize" />
<service android:name=".service.RocketChatService"/>
<service android:name=".service.RocketChatService" />
<receiver
android:name="com.google.android.gms.gcm.GcmReceiver"
android:exported="true"
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE"/>
<action android:name="com.google.android.c2dm.intent.REGISTRATION"/>
<category android:name="chat.rocket.android"/>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<action android:name="com.google.android.c2dm.intent.REGISTRATION" />
<category android:name="chat.rocket.android" />
</intent-filter>
</receiver>
......@@ -67,8 +69,8 @@
android:exported="true"
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE"/>
<category android:name="chat.rocket.android"/>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<category android:name="chat.rocket.android" />
</intent-filter>
</receiver>
......@@ -76,7 +78,8 @@
android:name="com.google.firebase.iid.FirebaseInstanceIdInternalReceiver"
android:exported="false" />
<service android:name="com.google.firebase.iid.FirebaseInstanceIdService"
<service
android:name="com.google.firebase.iid.FirebaseInstanceIdService"
android:exported="true">
<intent-filter android:priority="-500">
<action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
......@@ -87,7 +90,7 @@
android:name=".push.gcm.GCMIntentService"
android:exported="false">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE"/>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
</intent-filter>
</service>
......@@ -95,14 +98,16 @@
android:name=".push.gcm.GcmInstanceIDListenerService"
android:exported="false">
<intent-filter>
<action android:name="com.google.android.gms.iid.InstanceID"/>
<action android:name="com.google.android.gms.iid.InstanceID" />
</intent-filter>
</service>
<receiver android:name=".push.PushManager$DeleteReceiver"
<receiver
android:name=".push.PushManager$DeleteReceiver"
android:exported="false" />
<receiver android:name=".push.PushManager$ReplyReceiver"
<receiver
android:name=".push.PushManager$ReplyReceiver"
android:exported="false" />
<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
......@@ -5,6 +5,7 @@ import android.support.multidex.MultiDexApplication;
import android.support.v7.app.AppCompatDelegate;
import com.crashlytics.android.Crashlytics;
import com.evernote.android.job.JobManager;
import java.util.List;
......@@ -34,6 +35,8 @@ public class RocketChatApplication extends MultiDexApplication {
@Override
public void onCreate() {
super.onCreate();
RocketChatCache.INSTANCE.initialize(this);
JobManager.create(this).addJobCreator(new RocketChatJobCreator());
DDPClient.initialize(OkHttpHelper.INSTANCE.getClientForWebSocket());
Fabric.with(this, new Crashlytics());
......@@ -44,7 +47,7 @@ public class RocketChatApplication extends MultiDexApplication {
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) {
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
......@@ -57,7 +60,7 @@ public class RocketChatApplication extends MultiDexApplication {
if (BuildConfig.DEBUG) {
e.printStackTrace();
}
Logger.report(e);
Logger.INSTANCE.report(e);
});
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 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)
}
}
\ 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;
import okhttp3.HttpUrl;
abstract class AbstractAuthedActivity extends AbstractFragmentActivity {
@State protected String hostname;
@State protected String roomId;
@State
protected String hostname;
@State
protected String roomId;
private RocketChatCache rocketChatCache;
private CompositeDisposable compositeDisposable = new CompositeDisposable();
private boolean isNotification;
private CompositeDisposable compositeDisposable = new CompositeDisposable();
private boolean isNotification;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@Override
protected void onCreate(@Nullable Bundle 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) {
handleIntent(getIntent());
RocketChatCache.INSTANCE.setSelectedServerHostname(serverInfo.getHostname());
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
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
handleIntent(intent);
}
private boolean assertRoomSubscriptionExists(String roomId) {
if (!assertServerRealmStoreExists(hostname)) {
return false;
}
private void handleIntent(Intent intent) {
if (intent == null) {
return;
RealmRoom room = RealmStore.get(hostname).executeTransactionForRead(realm ->
realm.where(RealmRoom.class).equalTo(RealmRoom.ROOM_ID, roomId).findFirst());
if (room == null) {
RocketChatCache.INSTANCE.setSelectedRoomId(null);
return false;
}
return true;
}
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.getSiteUrlFor(hostnameFromPush);
rocketChatCache.setSelectedServerHostname(loginHostname);
private void updateRoomId(String roomId) {
this.roomId = roomId;
onRoomIdUpdated();
}
protected void onHostnameUpdated() {
}
protected void onRoomIdUpdated() {
}
if (intent.hasExtra(PushManager.EXTRA_ROOM_ID)) {
rocketChatCache.setSelectedRoomId(intent.getStringExtra(PushManager.EXTRA_ROOM_ID));
@Override
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 {
updateHostnameIfNeeded(rocketChatCache.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.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.getSelectedRoomId());
} else {
recoverFromHostnameError();
}
} else {
if (hostname.equals(newHostname)) {
updateHostname(newHostname);
updateRoomIdIfNeeded(rocketChatCache.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);
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
)
);
}
}
@Override
protected void onPause() {
compositeDisposable.clear();
super.onPause();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
}
private void subscribeToConfigChanges() {
compositeDisposable.add(
RocketChatCache.INSTANCE.getSelectedServerHostnamePublisher()
.map(Optional::get)
.distinctUntilChanged()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
this::updateHostnameIfNeeded,
Logger.INSTANCE::report
)
);
compositeDisposable.add(
RocketChatCache.INSTANCE.getSelectedRoomIdPublisher()
.filter(Optional::isPresent)
.map(Optional::get)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
this::updateRoomIdIfNeeded,
Logger.INSTANCE::report
)
);
}
}
......@@ -16,77 +16,85 @@ import chat.rocket.persistence.realm.repositories.RealmSessionRepository;
* Activity for Login, Sign-up, and Retry connecting...
*/
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
protected int getLayoutContainerForFragment() {
return R.id.content;
}
@Override
protected int getLayoutContainerForFragment() {
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
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@Override
public void showRetryLogin(String hostname) {
showFragment(new RetryLoginFragment(), hostname);
}
String hostname = null;
Intent intent = getIntent();
if (intent != null && intent.getExtras() != null) {
hostname = intent.getStringExtra(KEY_HOSTNAME);
@Override
public void closeView() {
finish();
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
}
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();
@Override
protected boolean onBackPress() {
LoginFragment loginFragment = (LoginFragment) getSupportFragmentManager()
.findFragmentById(getLayoutContainerForFragment());
loginFragment.goBack();
return true;
}
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>
view.closeView();
}
},
Logger::report
Logger.INSTANCE::report
);
addSubscription(subscription);
......
......@@ -5,7 +5,7 @@ import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.annotation.NonNull;
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.content.ContextCompat;
import android.support.v4.widget.SlidingPaneLayout;
......@@ -18,7 +18,9 @@ import android.widget.TextView;
import com.facebook.drawee.view.SimpleDraweeView;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import chat.rocket.android.ConnectionStatusManager;
import chat.rocket.android.LaunchUtil;
import chat.rocket.android.R;
import chat.rocket.android.RocketChatCache;
......@@ -30,6 +32,7 @@ import chat.rocket.android.helper.KeyboardHelper;
import chat.rocket.android.service.ConnectivityManager;
import chat.rocket.android.service.ConnectivityManagerApi;
import chat.rocket.android.widget.RoomToolbar;
import chat.rocket.android.widget.helper.DebouncingOnClickListener;
import chat.rocket.android.widget.helper.FrescoHelper;
import chat.rocket.core.interactors.CanCreateRoomInteractor;
import chat.rocket.core.interactors.RoomInteractor;
......@@ -40,6 +43,8 @@ import chat.rocket.persistence.realm.repositories.RealmPublicSettingRepository;
import chat.rocket.persistence.realm.repositories.RealmRoomRepository;
import chat.rocket.persistence.realm.repositories.RealmSessionRepository;
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;
/**
......@@ -49,7 +54,11 @@ public class MainActivity extends AbstractAuthedActivity implements MainContract
private RoomToolbar toolbar;
private SlidingPaneLayout pane;
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
public int getLayoutContainerForFragment() {
......@@ -62,6 +71,7 @@ public class MainActivity extends AbstractAuthedActivity implements MainContract
setContentView(R.layout.activity_main);
toolbar = findViewById(R.id.activity_main_toolbar);
pane = findViewById(R.id.sliding_pane);
loadCroutonViewIfNeeded();
setupToolbar();
}
......@@ -71,7 +81,7 @@ public class MainActivity extends AbstractAuthedActivity implements MainContract
ConnectivityManagerApi connectivityManager = ConnectivityManager.getInstance(getApplicationContext());
if (hostname == null || presenter == null) {
String previousHostname = hostname;
hostname = new RocketChatCache(getApplicationContext()).getSelectedServerHostname();
hostname = RocketChatCache.INSTANCE.getSelectedServerHostname();
if (hostname == null) {
showAddServerScreen();
} else {
......@@ -85,7 +95,7 @@ public class MainActivity extends AbstractAuthedActivity implements MainContract
connectivityManager.keepAliveServer();
presenter.bindView(this);
presenter.loadSignedInServers(hostname);
roomId = new RocketChatCache(getApplicationContext()).getSelectedRoomId();
roomId = RocketChatCache.INSTANCE.getSelectedRoomId();
}
}
......@@ -94,8 +104,7 @@ public class MainActivity extends AbstractAuthedActivity implements MainContract
if (presenter != null) {
presenter.release();
}
// Dismiss any status ticker
if (statusTicker != null) statusTicker.dismiss();
Crouton.cancelAllCroutons();
super.onPause();
}
......@@ -177,15 +186,12 @@ public class MainActivity extends AbstractAuthedActivity implements MainContract
PublicSettingRepository publicSettingRepository = new RealmPublicSettingRepository(hostname);
RocketChatCache rocketChatCache = new RocketChatCache(this);
presenter = new MainPresenter(
roomInteractor,
createRoomInteractor,
sessionInteractor,
new MethodCallHelper(this, hostname),
ConnectivityManager.getInstance(getApplicationContext()),
rocketChatCache,
publicSettingRepository
);
......@@ -194,12 +200,12 @@ public class MainActivity extends AbstractAuthedActivity implements MainContract
presenter.bindView(this);
presenter.loadSignedInServers(hostname);
roomId = rocketChatCache.getSelectedRoomId();
roomId = RocketChatCache.INSTANCE.getSelectedRoomId();
}
private void updateSidebarMainFragment() {
closeSidebarIfNeeded();
String selectedServerHostname = new RocketChatCache(this).getSelectedServerHostname();
String selectedServerHostname = RocketChatCache.INSTANCE.getSelectedServerHostname();
Fragment sidebarFragment = findFragmentByTag(selectedServerHostname);
if (sidebarFragment == null) {
sidebarFragment = SidebarMainFragment.create(selectedServerHostname);
......@@ -250,34 +256,84 @@ public class MainActivity extends AbstractAuthedActivity implements MainContract
}
@Override
public synchronized void showConnectionError() {
dismissStatusTickerIfShowing();
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();
public void showConnectionError() {
ConnectionStatusManager.INSTANCE.setConnectionError(this::showConnectionErrorCrouton);
}
@Override
public synchronized void showConnecting() {
dismissStatusTickerIfShowing();
statusTicker = Snackbar.make(findViewById(getLayoutContainerForFragment()),
R.string.server_config_activity_authenticating, Snackbar.LENGTH_INDEFINITE);
statusTicker.show();
public void showConnecting() {
ConnectionStatusManager.INSTANCE.setConnecting(this::showConnectingCrouton);
}
@Override
public synchronized void showConnectionOk() {
dismissStatusTickerIfShowing();
public void showConnectionOk() {
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 showConnectionErrorCrouton(boolean success) {
if (success) {
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 dismissStatusTickerIfShowing() {
if (statusTicker != null) {
statusTicker.dismiss();
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
public void showSignedInServers(List<Pair<String, Pair<String, String>>> serverList) {
final SlidingPaneLayout subPane = findViewById(R.id.sub_sliding_pane);
......@@ -334,21 +390,20 @@ public class MainActivity extends AbstractAuthedActivity implements MainContract
Fragment fragment = getSupportFragmentManager().findFragmentById(getLayoutContainerForFragment());
if (fragment != null && fragment instanceof RoomFragment) {
RoomFragment roomFragment = (RoomFragment) fragment;
roomFragment.loadMessages();
roomFragment.loadMissedMessages();
}
}
private void changeServerIfNeeded(String serverHostname) {
if (!hostname.equalsIgnoreCase(serverHostname)) {
RocketChatCache rocketChatCache = new RocketChatCache(getApplicationContext());
rocketChatCache.setSelectedServerHostname(serverHostname);
RocketChatCache.INSTANCE.setSelectedServerHostname(serverHostname);
}
}
@DebugLog
public void onLogout() {
presenter.prepareToLogout();
if (new RocketChatCache(getApplicationContext()).getSelectedServerHostname() == null) {
if (RocketChatCache.INSTANCE.getSelectedServerHostname() == null) {
finish();
LaunchUtil.showMainActivity(this);
} else {
......
......@@ -41,7 +41,6 @@ public class MainPresenter extends BasePresenter<MainContract.View>
private final SessionInteractor sessionInteractor;
private final MethodCallHelper methodCallHelper;
private final ConnectivityManagerApi connectivityManagerApi;
private final RocketChatCache rocketChatCache;
private final PublicSettingRepository publicSettingRepository;
public MainPresenter(RoomInteractor roomInteractor,
......@@ -49,13 +48,12 @@ public class MainPresenter extends BasePresenter<MainContract.View>
SessionInteractor sessionInteractor,
MethodCallHelper methodCallHelper,
ConnectivityManagerApi connectivityManagerApi,
RocketChatCache rocketChatCache, PublicSettingRepository publicSettingRepository) {
PublicSettingRepository publicSettingRepository) {
this.roomInteractor = roomInteractor;
this.canCreateRoomInteractor = canCreateRoomInteractor;
this.sessionInteractor = sessionInteractor;
this.methodCallHelper = methodCallHelper;
this.connectivityManagerApi = connectivityManagerApi;
this.rocketChatCache = rocketChatCache;
this.publicSettingRepository = publicSettingRepository;
}
......@@ -97,12 +95,13 @@ public class MainPresenter extends BasePresenter<MainContract.View>
subscribeToNetworkChanges();
subscribeToUnreadCount();
subscribeToSession();
setUserOnline();
}
@Override
public void release() {
setUserAway();
if (RocketChatCache.INSTANCE.getSessionToken() != null) {
setUserAway();
}
super.release();
}
......@@ -120,7 +119,7 @@ public class MainPresenter extends BasePresenter<MainContract.View>
view.showHome();
}
},
Logger::report
Logger.INSTANCE::report
);
addSubscription(subscription);
......@@ -157,13 +156,13 @@ public class MainPresenter extends BasePresenter<MainContract.View>
String logoUrl = (jsonObject.has("url")) ?
jsonObject.optString("url") : jsonObject.optString("defaultUrl");
String siteName = serverInfoPair.second;
rocketChatCache.addHostname(hostname.toLowerCase(), logoUrl, siteName);
return rocketChatCache.getServerList();
RocketChatCache.INSTANCE.addHostname(hostname.toLowerCase(), logoUrl, siteName);
return RocketChatCache.INSTANCE.getServerList();
}
private void openRoom() {
String hostname = rocketChatCache.getSelectedServerHostname();
String roomId = rocketChatCache.getSelectedRoomId();
String hostname = RocketChatCache.INSTANCE.getSelectedServerHostname();
String roomId = RocketChatCache.INSTANCE.getSelectedRoomId();
if (roomId == null || roomId.length() == 0) {
view.showHome();
......@@ -183,7 +182,7 @@ public class MainPresenter extends BasePresenter<MainContract.View>
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
pair -> view.showUnreadCount(pair.first, pair.second),
Logger::report
Logger.INSTANCE::report
);
addSubscription(subscription);
......@@ -211,10 +210,11 @@ public class MainPresenter extends BasePresenter<MainContract.View>
view.showConnecting();
return;
}
view.showConnectionOk();
// TODO: Should we remove below and above calls to view?
// view.showConnectionOk();
RocketChatCache.INSTANCE.setSessionToken(session.getToken());
},
Logger::report
Logger.INSTANCE::report
);
addSubscription(subscription);
......@@ -227,17 +227,21 @@ public class MainPresenter extends BasePresenter<MainContract.View>
.subscribe(
connectivity -> {
if (connectivity.state == ServerConnectivity.STATE_CONNECTED) {
view.showConnectionOk();
view.refreshRoom();
//TODO: notify almost connected or something like that.
// view.showConnectionOk();
} else if (connectivity.state == ServerConnectivity.STATE_DISCONNECTED) {
if (connectivity.code == DDPClient.REASON_NETWORK_ERROR) {
view.showConnectionError();
}
} else if (connectivity.state == ServerConnectivity.STATE_SESSION_ESTABLISHED) {
setUserOnline();
view.refreshRoom();
view.showConnectionOk();
} else {
view.showConnecting();
}
},
Logger::report
RCLog::e
);
addSubscription(disposable);
......
......@@ -7,6 +7,7 @@ import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.Iterator;
import java.util.UUID;
import bolts.Continuation;
......@@ -14,6 +15,7 @@ import bolts.Task;
import chat.rocket.android.RocketChatCache;
import chat.rocket.android.helper.CheckSum;
import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.log.RCLog;
import chat.rocket.android.service.ConnectivityManager;
import chat.rocket.android_ddp.DDPClient;
import chat.rocket.android_ddp.DDPClientCallback;
......@@ -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.RealmSession;
import hugo.weaving.DebugLog;
import io.realm.RealmQuery;
import okhttp3.HttpUrl;
/**
* Utility class for creating/handling MethodCall or RPC.
*
* <p>
* TODO: separate method into several manager classes (SubscriptionManager, MessageManager, ...).
*/
public class MethodCallHelper {
protected static final long TIMEOUT_MS = 20000;
protected static final Continuation<String, Task<JSONObject>> CONVERT_TO_JSON_OBJECT =
task -> Task.forResult(new JSONObject(task.getResult()));
protected static final Continuation<String, Task<JSONArray>> CONVERT_TO_JSON_ARRAY =
task -> Task.forResult(new JSONArray(task.getResult()));
protected final Context context;
protected final RealmHelper realmHelper;
/**
* initialize with Context and hostname.
*/
public MethodCallHelper(Context context, String hostname) {
this.context = context.getApplicationContext();
this.realmHelper = RealmStore.getOrCreate(hostname);
}
/**
* initialize with RealmHelper and DDPClient.
*/
public MethodCallHelper(RealmHelper realmHelper) {
this.context = null;
this.realmHelper = realmHelper;
}
public MethodCallHelper(Context context, RealmHelper realmHelper) {
this.context = context.getApplicationContext();
this.realmHelper = realmHelper;
}
@DebugLog
private Task<String> executeMethodCall(String methodName, String param, long timeout) {
if (DDPClient.get() != null) {
return DDPClient.get().rpc(UUID.randomUUID().toString(), methodName, param, timeout)
.onSuccessTask(task -> Task.forResult(task.getResult().result))
.continueWithTask(task_ -> {
if (task_.isFaulted()) {
return Task.forError(task_.getError());
}
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..."));
protected static final long TIMEOUT_MS = 20000;
protected static final Continuation<String, Task<JSONObject>> CONVERT_TO_JSON_OBJECT =
task -> Task.forResult(new JSONObject(task.getResult()));
protected static final Continuation<String, Task<JSONArray>> CONVERT_TO_JSON_ARRAY =
task -> Task.forResult(new JSONArray(task.getResult()));
protected final Context context;
protected final RealmHelper realmHelper;
/**
* initialize with Context and hostname.
*/
public MethodCallHelper(Context context, String hostname) {
this.context = context.getApplicationContext();
this.realmHelper = RealmStore.getOrCreate(hostname);
}
/**
* initialize with RealmHelper and DDPClient.
*/
public MethodCallHelper(RealmHelper realmHelper) {
this.context = null;
this.realmHelper = realmHelper;
}
public MethodCallHelper(Context context, RealmHelper realmHelper) {
this.context = context.getApplicationContext();
this.realmHelper = realmHelper;
}
@DebugLog
private Task<String> executeMethodCall(String methodName, String param, long timeout) {
if (DDPClient.get() != null) {
return DDPClient.get().rpc(UUID.randomUUID().toString(), methodName, param, timeout)
.onSuccessTask(task -> Task.forResult(task.getResult().result))
.continueWithTask(task_ -> {
if (task_.isFaulted()) {
return Task.forError(task_.getError());
}
return Task.forResult(task_.getResult());
});
} 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 -> {
realm.delete(RealmRoom.class);
realm.createOrUpdateAllFromJson(
RealmRoom.class, result);
return null;
});
} catch (JSONException exception) {
return Task.forError(exception);
}
});
}
/**
* 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);
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(exception.getMessage()));
} else {
return Task.forError(exception);
}
} else {
return _task;
}
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);
}
public Task<Void> createChannel(final String name, final boolean readOnly) {
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 {
return updateMessage(messageJson);
}
} catch (JSONException exception) {
return Task.forError(exception);
}
}
public Task<Void> deleteMessage(String messageID) {
try {
JSONObject messageJson = new JSONObject()
.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);
}
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));
}
}
if (siteName != null && siteUrl != null) {
HttpUrl httpSiteUrl = HttpUrl.parse(siteUrl);
if (httpSiteUrl != null) {
String host = httpSiteUrl.host();
RocketChatCache rocketChatCache = new RocketChatCache(context);
rocketChatCache.addHostnameSiteUrl(host, currentHostname);
rocketChatCache.addHostSiteName(currentHostname, siteName);
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"));
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);
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;
});
});
}
public Task<Void> searchSpotlightUsers(String term) {
return searchSpotlight(RealmSpotlightUser.class, "users", term);
}
public Task<Void> searchSpotlightRooms(String term) {
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));
}
}
/**
* 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 -> {
realm.delete(RealmRoom.class);
realm.createOrUpdateAllFromJson(
RealmRoom.class, result);
JSONObject openedRooms = RocketChatCache.INSTANCE.getOpenedRooms();
RealmQuery<RealmRoom> query = realm.where(RealmRoom.class);
Iterator<String> keys = openedRooms.keys();
while (keys.hasNext()) {
String rid = keys.next();
RealmRoom realmRoom = query.equalTo(RealmRoom.ID, rid).findFirst();
if (realmRoom == null) {
RocketChatCache.INSTANCE.removeOpenedRoom(rid);
} else {
loadMissedMessages(rid, realmRoom.getLastSeen())
.continueWithTask(task1 -> {
if (task1.isFaulted()) {
Exception error = task1.getError();
RCLog.e(error);
}
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) {
jsonString = userJsonArray.toString();
public Task<Void> createChannel(final String name, final boolean readOnly) {
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 {
jsonString = jsonString.replace("]", "") + "," + userJsonArray.toString().replace("[", "");
return updateMessage(messageJson);
}
}
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;
}
} catch (JSONException exception) {
return Task.forError(exception);
}
}
Object items = result.get(key);
if (!(items instanceof JSONArray)) {
return null;
}
public Task<Void> deleteMessage(String messageID) {
try {
JSONObject messageJson = new JSONObject()
.put("_id", messageID);
return realmHelper.executeTransaction(realm -> {
realm.delete(clazz);
realm.createOrUpdateAllFromJson(clazz, (JSONArray) items);
return null;
});
});
}
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);
}
}
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 {
JSONArray buildParam() throws JSONException;
}
public Task<Void> searchSpotlightRooms(String term) {
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;
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());
if (realmHelper == null) {
return "";
@Override
public String getHostname() {
return getHostnameFromCache();
}
final RealmUser user = realmHelper.executeTransactionForRead(realm ->
RealmUser.queryCurrentUser(realm).findFirst());
final RealmSession session = realmHelper.executeTransactionForRead(realm ->
RealmSession.queryDefaultSession(realm).findFirst());
@Override
public String getCookie() {
final String hostname = getHostnameFromCache();
if (hostname == null) {
return "";
}
if (user == null || session == null) {
return "";
}
final RealmHelper realmHelper = RealmStore.get(getHostnameFromCache());
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 rocketChatCache.getSelectedServerHostname();
}
return "rc_uid=" + user.getId() + ";rc_token=" + session.getToken();
}
private String getHostnameFromCache() {
return RocketChatCache.INSTANCE.getSelectedServerHostname();
}
}
package chat.rocket.android.extensions
import chat.rocket.android.BuildConfig
fun Throwable.printStackTraceOnDebug() {
if (BuildConfig.DEBUG) {
this.printStackTrace()
}
}
\ No newline at end of file
......@@ -12,7 +12,6 @@ import android.widget.TextView;
import chat.rocket.android.BuildConfig;
import chat.rocket.android.LaunchUtil;
import chat.rocket.android.R;
import chat.rocket.android.RocketChatCache;
import chat.rocket.android.fragment.AbstractFragment;
import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.service.ConnectivityManager;
......@@ -34,7 +33,7 @@ public class InputHostnameFragment extends AbstractFragment implements InputHost
super.onCreate(savedInstanceState);
Context appContext = getContext().getApplicationContext();
presenter = new InputHostnamePresenter(new RocketChatCache(appContext), ConnectivityManager.getInstance(appContext));
presenter = new InputHostnamePresenter(ConnectivityManager.getInstance(appContext));
}
@Override
......@@ -52,7 +51,7 @@ public class InputHostnameFragment extends AbstractFragment implements InputHost
}
private void setupVersionInfo() {
TextView versionInfoView = (TextView) rootView.findViewById(R.id.version_info);
TextView versionInfoView = rootView.findViewById(R.id.version_info);
versionInfoView.setText(getString(R.string.version_info_text, BuildConfig.VERSION_NAME));
}
......
......@@ -14,11 +14,9 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
public class InputHostnamePresenter extends BasePresenter<InputHostnameContract.View> implements InputHostnameContract.Presenter {
private final RocketChatCache rocketChatCache;
private final ConnectivityManagerApi connectivityManager;
public InputHostnamePresenter(RocketChatCache rocketChatCache, ConnectivityManagerApi connectivityManager) {
this.rocketChatCache = rocketChatCache;
public InputHostnamePresenter(ConnectivityManagerApi connectivityManager) {
this.connectivityManager = connectivityManager;
}
......@@ -47,14 +45,14 @@ public class InputHostnamePresenter extends BasePresenter<InputHostnameContract.
}
},
throwable -> {
Logger.report(throwable);
Logger.INSTANCE.report(throwable);
view.showConnectionError();
});
addSubscription(subscription);
}
private void onServerValid(String hostname, boolean usesSecureConnection) {
rocketChatCache.setSelectedServerHostname(hostname);
RocketChatCache.INSTANCE.setSelectedServerHostname(hostname);
String server = hostname.replace("/", ".");
connectivityManager.addOrUpdateServer(server, server, !usesSecureConnection);
......
......@@ -11,7 +11,7 @@ import chat.rocket.android.fragment.AbstractFragment;
import chat.rocket.android.widget.RoomToolbar;
import chat.rocket.core.models.User;
abstract class AbstractChatRoomFragment extends AbstractFragment {
public abstract class AbstractChatRoomFragment extends AbstractFragment {
private RoomToolbar roomToolbar;
@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
private MethodCallHelper methodCallHelper;
private AbsoluteUrlHelper absoluteUrlHelper;
private Message edittingMessage = null;
private Message editingMessage = null;
private RoomToolbar toolbar;
......@@ -344,7 +344,7 @@ public class RoomFragment extends AbstractChatRoomFragment implements
optionalPane.ifPresent(pane -> pane.setPanelSlideListener(new SlidingPaneLayout.PanelSlideListener() {
@Override
public void onPanelSlide(View view, float v) {
public void onPanelSlide(@NonNull View view, float v) {
messageFormManager.enableComposingText(false);
sidebarFragment.clearSearchViewFocus();
//Ref: ActionBarDrawerToggle#setProgress
......@@ -352,12 +352,12 @@ public class RoomFragment extends AbstractChatRoomFragment implements
}
@Override
public void onPanelOpened(View view) {
public void onPanelOpened(@NonNull View view) {
toolbar.setNavigationIconVerticalMirror(true);
}
@Override
public void onPanelClosed(View view) {
public void onPanelClosed(@NonNull View view) {
messageFormManager.enableComposingText(true);
toolbar.setNavigationIconVerticalMirror(false);
subPane.closePane();
......@@ -487,8 +487,8 @@ public class RoomFragment extends AbstractChatRoomFragment implements
@Override
public boolean onBackPressed() {
if (edittingMessage != null) {
edittingMessage = null;
if (editingMessage != null) {
editingMessage = null;
messageFormManager.clearComposingText();
}
return false;
......@@ -540,22 +540,22 @@ public class RoomFragment extends AbstractChatRoomFragment implements
inputContentInfo.releasePermission();
} catch (Exception e) {
RCLog.e(e);
Logger.report(e);
Logger.INSTANCE.report(e);
}
return true;
}
private void sendMessage(String messageText) {
if (edittingMessage == null) {
if (editingMessage == null) {
presenter.sendMessage(messageText);
} else {
presenter.updateMessage(edittingMessage, messageText);
presenter.updateMessage(editingMessage, messageText);
}
}
@Override
public void setupWith(RocketChatAbsoluteUrl rocketChatAbsoluteUrl) {
public void setupWith(@NonNull RocketChatAbsoluteUrl rocketChatAbsoluteUrl) {
if (rocketChatAbsoluteUrl != null) {
token = rocketChatAbsoluteUrl.getToken();
userId = rocketChatAbsoluteUrl.getUserId();
......@@ -564,7 +564,7 @@ public class RoomFragment extends AbstractChatRoomFragment implements
}
@Override
public void render(Room room) {
public void render(@NonNull Room room) {
roomType = room.getType();
setToolbarTitle(room.getName());
......@@ -589,7 +589,7 @@ public class RoomFragment extends AbstractChatRoomFragment implements
}
@Override
public void showUserStatus(User user) {
public void showUserStatus(@NonNull User user) {
showToolbarUserStatuslIcon(user.getStatus());
}
......@@ -610,7 +610,7 @@ public class RoomFragment extends AbstractChatRoomFragment implements
public void onMessageSendSuccessfully() {
scrollToLatestMessage();
messageFormManager.onMessageSend();
edittingMessage = null;
editingMessage = null;
}
@Override
......@@ -629,15 +629,16 @@ public class RoomFragment extends AbstractChatRoomFragment implements
}
@Override
public void showMessages(List<Message> messages) {
public void showMessages(@NonNull List<? extends Message> messages) {
if (messageListAdapter == null) {
return;
}
messageListAdapter.updateData(messages);
messageListAdapter.updateData((List<Message>) messages);
}
@Override
public void showMessageSendFailure(Message message) {
public void showMessageSendFailure(@NonNull Message message) {
new AlertDialog.Builder(getContext())
.setPositiveButton(R.string.resend,
(dialog, which) -> presenter.resendMessage(message))
......@@ -648,7 +649,7 @@ public class RoomFragment extends AbstractChatRoomFragment implements
}
@Override
public void showMessageDeleteFailure(Message message) {
public void showMessageDeleteFailure(@NonNull Message message) {
new AlertDialog.Builder(getContext())
.setTitle(getContext().getString(R.string.failed_to_delete))
.setMessage(getContext().getString(R.string.failed_to_delete_message))
......@@ -667,12 +668,12 @@ public class RoomFragment extends AbstractChatRoomFragment implements
}
@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);
}
@Override
public void onCopy(String message) {
public void onCopy(@NonNull String message) {
RocketChatApplication context = RocketChatApplication.getInstance();
ClipboardManager clipboardManager =
(ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
......@@ -680,7 +681,7 @@ public class RoomFragment extends AbstractChatRoomFragment implements
}
@Override
public void showMessageActions(Message message) {
public void showMessageActions(@NonNull Message message) {
Activity context = getActivity();
if (context != null && context instanceof MainActivity) {
MessagePopup.take(message)
......@@ -694,7 +695,7 @@ public class RoomFragment extends AbstractChatRoomFragment implements
}
private void onEditMessage(Message message) {
edittingMessage = message;
editingMessage = message;
messageFormManager.setEditMessage(message.getMessage());
}
......@@ -716,7 +717,7 @@ public class RoomFragment extends AbstractChatRoomFragment implements
}
}
public void loadMessages() {
presenter.loadMessages();
public void loadMissedMessages() {
presenter.loadMissedMessages();
}
}
\ No newline at end of file
......@@ -6,11 +6,17 @@ import android.support.v4.util.Pair;
import com.hadisatrio.optional.Optional;
import org.json.JSONException;
import org.json.JSONObject;
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.helper.AbsoluteUrlHelper;
import chat.rocket.android.helper.LogIfError;
import chat.rocket.android.helper.Logger;
import chat.rocket.android.log.RCLog;
import chat.rocket.android.service.ConnectivityManagerApi;
import chat.rocket.android.shared.BasePresenter;
import chat.rocket.core.SyncState;
......@@ -27,391 +33,414 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
public class RoomPresenter extends BasePresenter<RoomContract.View>
implements RoomContract.Presenter {
private final String roomId;
private final MessageInteractor messageInteractor;
private final UserRepository userRepository;
private final RoomRepository roomRepository;
private final AbsoluteUrlHelper absoluteUrlHelper;
private final MethodCallHelper methodCallHelper;
private final ConnectivityManagerApi connectivityManagerApi;
private Room currentRoom;
public RoomPresenter(String roomId,
UserRepository userRepository,
MessageInteractor messageInteractor,
RoomRepository roomRepository,
AbsoluteUrlHelper absoluteUrlHelper,
MethodCallHelper methodCallHelper,
ConnectivityManagerApi connectivityManagerApi) {
this.roomId = roomId;
this.userRepository = userRepository;
this.messageInteractor = messageInteractor;
this.roomRepository = roomRepository;
this.absoluteUrlHelper = absoluteUrlHelper;
this.methodCallHelper = methodCallHelper;
this.connectivityManagerApi = connectivityManagerApi;
}
@Override
public void bindView(@NonNull RoomContract.View view) {
super.bindView(view);
refreshRoom();
}
@Override
public void refreshRoom() {
getRoomRoles();
getRoomInfo();
getRoomHistoryStateInfo();
getMessages();
getUserPreferences();
}
@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
);
implements RoomContract.Presenter {
private final String roomId;
private final MessageInteractor messageInteractor;
private final UserRepository userRepository;
private final RoomRepository roomRepository;
private final AbsoluteUrlHelper absoluteUrlHelper;
private final MethodCallHelper methodCallHelper;
private final ConnectivityManagerApi connectivityManagerApi;
private Room currentRoom;
/* package */RoomPresenter(String roomId,
UserRepository userRepository,
MessageInteractor messageInteractor,
RoomRepository roomRepository,
AbsoluteUrlHelper absoluteUrlHelper,
MethodCallHelper methodCallHelper,
ConnectivityManagerApi connectivityManagerApi) {
this.roomId = roomId;
this.userRepository = userRepository;
this.messageInteractor = messageInteractor;
this.roomRepository = roomRepository;
this.absoluteUrlHelper = absoluteUrlHelper;
this.methodCallHelper = methodCallHelper;
this.connectivityManagerApi = connectivityManagerApi;
}
addSubscription(subscription);
}
@Override
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
);
@Override
public void bindView(@NonNull RoomContract.View view) {
super.bindView(view);
refreshRoom();
}
addSubscription(subscription);
}
@Override
public void refreshRoom() {
getRoomRoles();
getRoomInfo();
getRoomHistoryStateInfo();
getMessages();
getUserPreferences();
}
@Override
public void onMessageSelected(@Nullable Message message) {
if (message == null) {
return;
@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.INSTANCE::report
);
addSubscription(subscription);
}
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);
@Override
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.INSTANCE::report
);
addSubscription(subscription);
}
}
@Override
public void onMessageTap(@Nullable Message message) {
if (message == null) {
return;
@Override
public void onMessageSelected(@Nullable Message message) {
if (message == null) {
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) {
view.showMessageSendFailure(message);
@Override
public void onMessageTap(@Nullable Message message) {
if (message == null) {
return;
}
if (message.getSyncState() == SyncState.FAILED) {
view.showMessageSendFailure(message);
}
}
}
@Override
public void replyMessage(@NonNull Message message, boolean justQuote) {
final Disposable subscription = this.absoluteUrlHelper.getRocketChatAbsoluteUrl()
.cache()
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
serverUrl -> {
if (serverUrl.isPresent()) {
RocketChatAbsoluteUrl absoluteUrl = serverUrl.get();
String baseUrl = absoluteUrl.getBaseUrl();
view.onReply(absoluteUrl, buildReplyOrQuoteMarkdown(baseUrl, message, justQuote), message);
}
},
Logger::report
);
}
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 "";
@Override
public void replyMessage(@NonNull Message message, boolean justQuote) {
final Disposable subscription = this.absoluteUrlHelper.getRocketChatAbsoluteUrl()
.cache()
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
serverUrl -> {
if (serverUrl.isPresent()) {
RocketChatAbsoluteUrl absoluteUrl = serverUrl.get();
String baseUrl = absoluteUrl.getBaseUrl();
view.onReply(absoluteUrl, buildReplyOrQuoteMarkdown(baseUrl, message, justQuote), message);
}
},
Logger.INSTANCE::report
);
addSubscription(subscription);
}
if (currentRoom.isDirectMessage()) {
return String.format("[ ](%s/direct/%s?msg=%s) ", baseUrl,
message.getUser().getUsername(),
message.getId());
} else {
return String.format("[ ](%s/channel/%s?msg=%s) %s", baseUrl,
currentRoom.getName(),
message.getId(),
justQuote ? "" : "@" + message.getUser().getUsername() + " ");
public void acceptMessageDeleteFailure(Message message) {
final Disposable subscription = messageInteractor.acceptDeleteFailure(message)
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe();
addSubscription(subscription);
}
}
@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 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);
}
@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);
@Override
public void loadMissedMessages() {
RocketChatApplication appContext = RocketChatApplication.getInstance();
JSONObject openedRooms = RocketChatCache.INSTANCE.getOpenedRooms();
if (openedRooms.has(roomId)) {
try {
JSONObject room = openedRooms.getJSONObject(roomId);
String rid = room.optString("rid");
long ls = room.optLong("ls");
methodCallHelper.loadMissedMessages(rid, ls)
.continueWith(new LogIfError());
} catch (JSONException e) {
RCLog.e(e);
}
);
}
}
addSubscription(subscription);
}
@Override
public void deleteMessage(@NonNull Message message) {
final Disposable subscription = messageInteractor.delete(message)
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe();
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::report
);
private String buildReplyOrQuoteMarkdown(String baseUrl, Message message, boolean justQuote) {
if (currentRoom == null || message.getUser() == null) {
return "";
}
if (currentRoom.isDirectMessage()) {
return String.format("[ ](%s/direct/%s?msg=%s) ", baseUrl,
message.getUser().getUsername(),
message.getId());
} else {
return String.format("[ ](%s/channel/%s?msg=%s) %s", baseUrl,
currentRoom.getName(),
message.getId(),
justQuote ? "" : "@" + message.getUser().getUsername() + " ");
}
}
addSubscription(subscription);
}
@Override
public void onMarkAsRead() {
final Disposable subscription = roomRepository.getById(roomId)
.filter(Optional::isPresent)
.map(Optional::get)
.firstElement()
.filter(Room::isAlert)
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
room -> methodCallHelper.readMessages(room.getRoomId())
.continueWith(new LogIfError()),
Logger::report
);
@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.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);
}
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());
addSubscription(subscription);
}
}
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);
}
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)
.flatMap(messageInteractor::getAllFrom)
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
view::showMessages,
Logger::report
);
@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.INSTANCE.report(throwable);
}
);
addSubscription(subscription);
}
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::report
);
@Override
public void deleteMessage(@NonNull Message message) {
final Disposable subscription = messageInteractor.delete(message)
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe();
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() {
final Disposable subscription = absoluteUrlHelper.getRocketChatAbsoluteUrl()
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
it -> view.setupWith(it.orNull()),
Logger::report
@Override
public void onMarkAsRead() {
final Disposable subscription = roomRepository.getById(roomId)
.filter(Optional::isPresent)
.map(Optional::get)
.firstElement()
.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<Pair<Room, User>> getRoomUserPair() {
return Single.zip(
getSingleRoom(),
getCurrentUser(),
Pair::new
);
}
private Single<Room> getSingleRoom() {
return roomRepository.getById(roomId)
.filter(Optional::isPresent)
.map(Optional::get)
.firstElement()
.toSingle();
}
private Single<User> getCurrentUser() {
return userRepository.getCurrent()
.filter(Optional::isPresent)
.map(Optional::get)
.firstElement()
.toSingle();
}
private Single<Room> getSingleRoom() {
return roomRepository.getById(roomId)
.filter(Optional::isPresent)
.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;
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 internalListener = new OnMessageOptionSelectedListener() {
@Override
public void onEdit(Message message) {
if (externalListener != null) {
externalListener.onEdit(message);
}
}
};
private OnMessageOptionSelectedListener externalListener = null;
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) {
Bundle bundle = new Bundle();
bundle.putString(ARG_MESSAGE_ID, message.getId());
MessageOptionsDialogFragment messageOptionsDialogFragment = new MessageOptionsDialogFragment();
messageOptionsDialogFragment.setArguments(bundle);
MessageOptionsDialogFragment messageOptionsDialogFragment = new MessageOptionsDialogFragment();
messageOptionsDialogFragment.setArguments(bundle);
return messageOptionsDialogFragment;
}
return messageOptionsDialogFragment;
}
public void setOnMessageOptionSelectedListener(
OnMessageOptionSelectedListener onMessageOptionSelectedListener) {
externalListener = onMessageOptionSelectedListener;
}
public void setOnMessageOptionSelectedListener(
OnMessageOptionSelectedListener onMessageOptionSelectedListener) {
externalListener = onMessageOptionSelectedListener;
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
BottomSheetDialog bottomSheetDialog = new BottomSheetDialog(getContext());
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
BottomSheetDialog bottomSheetDialog = new BottomSheetDialog(getContext());
bottomSheetDialog.setContentView(R.layout.dialog_message_options);
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();
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;
}
return bottomSheetDialog;
}
@Override
public void onDismiss(DialogInterface dialog) {
compositeDisposable.clear();
super.onDismiss(dialog);
}
private void setUpDialog(final BottomSheetDialog bottomSheetDialog, String messageId) {
RocketChatCache cache = new RocketChatCache(bottomSheetDialog.getContext());
String hostname = cache.getSelectedServerHostname();
EditMessageInteractor editMessageInteractor = getEditMessageInteractor(hostname);
MessageRepository messageRepository = new RealmMessageRepository(hostname);
Disposable disposable = messageRepository.getById(messageId)
.flatMap(it -> {
if (!it.isPresent()) {
return Single.just(Pair.<Message, Boolean>create(null, false));
}
Message message = it.get();
return Single.zip(
Single.just(message),
editMessageInteractor.isAllowed(message),
Pair::create
);
})
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
pair -> {
if (pair.second) {
bottomSheetDialog.findViewById(R.id.message_options_info)
.setVisibility(View.GONE);
View editView = bottomSheetDialog.findViewById(R.id.message_options_edit_action);
editView.setVisibility(View.VISIBLE);
editView.setOnClickListener(view -> internalListener.onEdit(pair.first));
} else {
((TextView) bottomSheetDialog.findViewById(R.id.message_options_info))
.setText(R.string.message_options_no_permissions_info);
}
},
throwable -> {
((TextView) bottomSheetDialog.findViewById(R.id.message_options_info))
.setText(R.string.message_options_no_message_info);
Logger.report(throwable);
}
@Override
public void onDismiss(DialogInterface dialog) {
compositeDisposable.clear();
super.onDismiss(dialog);
}
private void setUpDialog(final BottomSheetDialog bottomSheetDialog, String messageId) {
String hostname = RocketChatCache.INSTANCE.getSelectedServerHostname();
EditMessageInteractor editMessageInteractor = getEditMessageInteractor(hostname);
MessageRepository messageRepository = new RealmMessageRepository(hostname);
Disposable disposable = messageRepository.getById(messageId)
.flatMap(it -> {
if (!it.isPresent()) {
return Single.just(Pair.<Message, Boolean>create(null, false));
}
Message message = it.get();
return Single.zip(
Single.just(message),
editMessageInteractor.isAllowed(message),
Pair::create
);
})
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
pair -> {
if (pair.second) {
bottomSheetDialog.findViewById(R.id.message_options_info)
.setVisibility(View.GONE);
View editView = bottomSheetDialog.findViewById(R.id.message_options_edit_action);
editView.setVisibility(View.VISIBLE);
editView.setOnClickListener(view -> internalListener.onEdit(pair.first));
} else {
((TextView) bottomSheetDialog.findViewById(R.id.message_options_info))
.setText(R.string.message_options_no_permissions_info);
}
},
throwable -> {
((TextView) bottomSheetDialog.findViewById(R.id.message_options_info))
.setText(R.string.message_options_no_message_info);
Logger.INSTANCE.report(throwable);
}
);
compositeDisposable.add(disposable);
}
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);
}
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
);
MessageRepository messageRepository = new RealmMessageRepository(hostname);
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);
}
MessageRepository messageRepository = new RealmMessageRepository(hostname);
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);
}
}
......@@ -37,7 +37,7 @@ public class OAuthPresenter extends BasePresenter<OAuthContract.View>
view.close();
}
},
Logger::report
Logger.INSTANCE::report
)
);
}
......
......@@ -8,7 +8,7 @@ import chat.rocket.android.R;
import chat.rocket.android.fragment.AbstractFragment;
import chat.rocket.android.helper.TextUtils;
abstract class AbstractServerConfigFragment extends AbstractFragment {
public abstract class AbstractServerConfigFragment extends AbstractFragment {
public static final String KEY_HOSTNAME = "hostname";
protected String hostname;
......
......@@ -7,21 +7,25 @@ import chat.rocket.core.models.LoginServiceConfiguration;
public interface LoginContract {
interface View extends BaseContract.View {
interface View extends BaseContract.View {
void showLoader();
void showLoader();
void hideLoader();
void hideLoader();
void showError(String message);
void showError(String message);
void showLoginServices(List<LoginServiceConfiguration> loginServiceList);
void showLoginServices(List<LoginServiceConfiguration> loginServiceList);
void showTwoStepAuth();
}
void showTwoStepAuth();
interface Presenter extends BaseContract.Presenter<View> {
void goBack();
}
void login(String username, String password);
}
interface Presenter extends BaseContract.Presenter<View> {
void login(String username, String password);
void goBack();
}
}
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.v4.app.Fragment
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 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
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)
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")
}
}
override fun showLoader() {
container.visibility = View.GONE
waitingView.visibility = View.VISIBLE
}
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()
}
}
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 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() {
val context = RocketChatApplication.getInstance()
val hostname = RocketChatCache.getSelectedServerHostname()
hostname?.let {
ConnectivityManager.getInstance(context).removeServer(hostname)
RocketChatCache.clearSelectedHostnameReferences()
}
LaunchUtil.showMainActivity(context)
}
override fun login(username: String, password: String) {
if (TextUtils.isEmpty(username) || TextUtils.isEmpty(password)) {
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>
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
this::onSession,
Logger::report
Logger.INSTANCE::report
)
);
}
......
......@@ -24,7 +24,6 @@ import java.util.List;
import chat.rocket.android.BuildConfig;
import chat.rocket.android.R;
import chat.rocket.android.RocketChatCache;
import chat.rocket.android.activity.MainActivity;
import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.android.fragment.AbstractFragment;
......@@ -94,13 +93,11 @@ public class SidebarMainFragment extends AbstractFragment implements SidebarMain
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)
......@@ -239,7 +236,7 @@ public class SidebarMainFragment extends AbstractFragment implements SidebarMain
.compose(bindToLifecycle())
.subscribe(
this::showUserActionContainer,
Logger::report
Logger.INSTANCE::report
);
}
......
......@@ -38,7 +38,6 @@ public class SidebarMainPresenter extends BasePresenter<SidebarMainContract.View
private final String hostname;
private final RoomInteractor roomInteractor;
private final UserRepository userRepository;
private final RocketChatCache rocketChatCache;
private final AbsoluteUrlHelper absoluteUrlHelper;
private final MethodCallHelper methodCallHelper;
private SpotlightRepository realmSpotlightRepository;
......@@ -47,14 +46,12 @@ public class SidebarMainPresenter extends BasePresenter<SidebarMainContract.View
public SidebarMainPresenter(String hostname,
RoomInteractor roomInteractor,
UserRepository userRepository,
RocketChatCache rocketChatCache,
AbsoluteUrlHelper absoluteUrlHelper,
MethodCallHelper methodCallHelper,
RealmSpotlightRepository realmSpotlightRepository) {
this.hostname = hostname;
this.roomInteractor = roomInteractor;
this.userRepository = userRepository;
this.rocketChatCache = rocketChatCache;
this.absoluteUrlHelper = absoluteUrlHelper;
this.methodCallHelper = methodCallHelper;
this.realmSpotlightRepository = realmSpotlightRepository;
......@@ -80,14 +77,14 @@ public class SidebarMainPresenter extends BasePresenter<SidebarMainContract.View
)
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(pair -> view.show(pair.first.orNull()), Logger::report);
.subscribe(pair -> view.show(pair.first.orNull()), Logger.INSTANCE::report);
addSubscription(subscription);
}
@Override
public void onRoomSelected(RoomSidebar roomSidebar) {
rocketChatCache.setSelectedRoomId(roomSidebar.getRoomId());
RocketChatCache.INSTANCE.setSelectedRoomId(roomSidebar.getRoomId());
}
@Override
......@@ -103,7 +100,7 @@ public class SidebarMainPresenter extends BasePresenter<SidebarMainContract.View
methodCallHelper.createDirectMessage(username)
.continueWithTask(task -> {
if (task.isCompleted()) {
rocketChatCache.setSelectedRoomId(task.getResult());
RocketChatCache.INSTANCE.setSelectedRoomId(task.getResult());
}
return null;
});
......@@ -111,7 +108,7 @@ public class SidebarMainPresenter extends BasePresenter<SidebarMainContract.View
methodCallHelper.joinRoom(spotlight.getId())
.continueWithTask(task -> {
if (task.isCompleted()) {
rocketChatCache.setSelectedRoomId(spotlight.getId());
RocketChatCache.INSTANCE.setSelectedRoomId(spotlight.getId());
}
return null;
});
......@@ -142,7 +139,7 @@ public class SidebarMainPresenter extends BasePresenter<SidebarMainContract.View
public void onLogout(Continuation<Void, Object> continuation) {
methodCallHelper.logout().continueWith(task -> {
if (task.isFaulted()) {
Logger.report(task.getError());
Logger.INSTANCE.report(task.getError());
return Task.forError(task.getError());
}
return task.onSuccess(continuation);
......@@ -157,12 +154,12 @@ public class SidebarMainPresenter extends BasePresenter<SidebarMainContract.View
}
clearSubscriptions();
String currentHostname = rocketChatCache.getSelectedServerHostname();
String currentHostname = RocketChatCache.INSTANCE.getSelectedServerHostname();
RealmHelper realmHelper = RealmStore.getOrCreate(currentHostname);
return realmHelper.executeTransaction(realm -> {
rocketChatCache.removeHostname(currentHostname);
rocketChatCache.removeSelectedRoomId(currentHostname);
rocketChatCache.setSelectedServerHostname(rocketChatCache.getFirstLoggedHostnameIfAny());
RocketChatCache.INSTANCE.removeHostname(currentHostname);
RocketChatCache.INSTANCE.removeSelectedRoomId(currentHostname);
RocketChatCache.INSTANCE.setSelectedServerHostname(RocketChatCache.INSTANCE.getFirstLoggedHostnameIfAny());
realm.executeTransactionAsync(Realm::deleteAll);
view.onPreparedToLogOut();
ConnectivityManager.getInstance(RocketChatApplication.getInstance())
......@@ -183,7 +180,7 @@ public class SidebarMainPresenter extends BasePresenter<SidebarMainContract.View
.distinctUntilChanged()
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::processRooms, Logger::report);
.subscribe(this::processRooms, Logger.INSTANCE::report);
addSubscription(subscription);
}
......@@ -227,7 +224,7 @@ public class SidebarMainPresenter extends BasePresenter<SidebarMainContract.View
.distinctUntilChanged()
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::processUsers, Logger::report);
.subscribe(this::processUsers, Logger.INSTANCE::report);
addSubscription(subscription);
}
......
......@@ -47,7 +47,7 @@ public class AddChannelDialogFragment extends AbstractAddRoomDialogFragment {
.compose(bindToLifecycle())
.subscribe(
buttonAddChannel::setEnabled,
Logger::report
Logger.INSTANCE::report
);
buttonAddChannel.setOnClickListener(view -> createRoom());
......
......@@ -68,7 +68,7 @@ public class AddDirectMessageDialogFragment extends AbstractAddRoomDialogFragmen
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
this::setupView,
Logger::report
Logger.INSTANCE::report
)
);
......@@ -77,7 +77,7 @@ public class AddDirectMessageDialogFragment extends AbstractAddRoomDialogFragmen
.compose(bindToLifecycle())
.subscribe(
buttonAddDirectMessage::setEnabled,
Logger::report
Logger.INSTANCE::report
);
buttonAddDirectMessage.setOnClickListener(view -> createRoom());
......
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 {
}
private void showAvailableActionsOnly(Context context) {
RocketChatCache cache = new RocketChatCache(context.getApplicationContext());
String hostname = cache.getSelectedServerHostname();
String hostname = RocketChatCache.INSTANCE.getSelectedServerHostname();
EditMessageInteractor editMessageInteractor = getEditMessageInteractor(hostname);
......@@ -102,7 +100,7 @@ public class MessagePopup {
.create()
.show();
},
Logger::report
Logger.INSTANCE::report
);
compositeDisposable.add(disposable);
}
......@@ -167,7 +165,7 @@ public class MessagePopup {
}
public MessagePopup setDeleteAction(ActionListener actionListener) {
DELETE_ACTION_INFO.actionListener= actionListener;
DELETE_ACTION_INFO.actionListener = actionListener;
return singleton;
}
......
......@@ -101,7 +101,7 @@ public class RoomListItemViewHolder extends RecyclerView.ViewHolder {
break;
default: {
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
import android.annotation.SuppressLint
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
......@@ -137,6 +138,7 @@ object PushManager {
return group
}
@SuppressLint("NewApi")
internal fun showNotification(context: Context, lastPushMessage: PushMessage) {
if (lastPushMessage.host == null || lastPushMessage.message == null || lastPushMessage.title == null) {
return
......@@ -202,7 +204,7 @@ object PushManager {
.setDeleteIntent(deleteIntent)
.setMessageNotification()
val subText = RocketChatCache(context).getHostSiteName(host)
val subText = RocketChatCache.getHostSiteName(host)
if (subText.isNotEmpty()) {
builder.setSubText(subText)
}
......@@ -257,6 +259,7 @@ object PushManager {
}
}
@SuppressLint("NewApi")
@RequiresApi(Build.VERSION_CODES.N)
internal fun createGroupNotificationForNougatAndAbove(context: Context, lastPushMessage: PushMessage): Notification? {
with(lastPushMessage) {
......@@ -289,7 +292,7 @@ object PushManager {
manager.createNotificationChannel(groupChannel)
}
val subText = RocketChatCache(context).getHostSiteName(host)
val subText = RocketChatCache.getHostSiteName(host)
if (subText.isNotEmpty()) {
builder.setSubText(subText)
}
......@@ -344,7 +347,7 @@ object PushManager {
.setContentIntent(contentIntent)
.setMessageNotification()
val subText = RocketChatCache(context).getHostSiteName(host)
val subText = RocketChatCache.getHostSiteName(host)
if (subText.isNotEmpty()) {
builder.setSubText(subText)
}
......@@ -370,6 +373,7 @@ object PushManager {
}
}
@SuppressLint("NewApi")
@RequiresApi(Build.VERSION_CODES.N)
internal fun createSingleNotificationForNougatAndAbove(context: Context, lastPushMessage: PushMessage): Notification? {
val manager: NotificationManager =
......@@ -404,7 +408,7 @@ object PushManager {
manager.createNotificationChannel(channel)
}
val subText = RocketChatCache(context).getHostSiteName(host)
val subText = RocketChatCache.getHostSiteName(host)
if (subText.isNotEmpty()) {
builder.setSubText(subText)
}
......@@ -503,6 +507,7 @@ object PushManager {
setAutoCancel(true)
setShowWhen(true)
setColor(res.getColor(R.color.colorRed400, ctx.theme))
setDefaults(Notification.DEFAULT_ALL)
setSmallIcon(smallIcon)
})
return this
......@@ -646,7 +651,7 @@ object PushManager {
}
val httpUrl = HttpUrl.parse(pushMessage.host)
httpUrl?.let {
val siteUrl = RocketChatCache(context).getSiteUrlFor(httpUrl.host())
val siteUrl = RocketChatCache.getSiteUrlFor(httpUrl.host())
if (siteUrl != null) {
sendMessage(siteUrl, message, pushMessage.rid)
}
......@@ -693,7 +698,6 @@ object PushManager {
.subscribe({ _ ->
// Empty
}, { throwable ->
throwable.printStackTrace()
Logger.report(throwable)
})
}
......
......@@ -28,4 +28,8 @@ public interface ConnectivityManagerApi {
int getConnectivityState(@NonNull String hostname);
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;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import chat.rocket.android.ConnectionStatusManager;
import chat.rocket.android.RocketChatCache;
import chat.rocket.android.helper.RxHelper;
import chat.rocket.android.log.RCLog;
......@@ -74,7 +75,7 @@ import io.reactivex.subjects.BehaviorSubject;
@DebugLog
@Override
public void ensureConnections() {
String hostname = new RocketChatCache(appContext).getSelectedServerHostname();
String hostname = RocketChatCache.INSTANCE.getSelectedServerHostname();
if (hostname == null) {
return;
}
......@@ -107,7 +108,7 @@ import io.reactivex.subjects.BehaviorSubject;
public void removeServer(String hostname) {
RealmBasedServerInfo.remove(hostname);
if (serverConnectivityList.containsKey(hostname)) {
disconnectFromServerIfNeeded(hostname)
disconnectFromServerIfNeeded(hostname, DDPClient.REASON_CLOSED_BY_USER)
.subscribe(_val -> {
}, RCLog::e);
}
......@@ -136,6 +137,13 @@ import io.reactivex.subjects.BehaviorSubject;
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
@Override
public void notifyConnectionEstablished(String hostname, String session) {
......@@ -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(() -> {
final int connectivity = serverConnectivityList.get(hostname);
if (connectivity == ServerConnectivity.STATE_DISCONNECTED) {
......@@ -209,8 +217,8 @@ import io.reactivex.subjects.BehaviorSubject;
if (connectivity == ServerConnectivity.STATE_CONNECTING) {
return waitForConnected(hostname)
.doOnError(err -> notifyConnectionLost(hostname, DDPClient.REASON_NETWORK_ERROR))
.flatMap(_val -> disconnectFromServerIfNeeded(hostname));
// .doOnError(err -> notifyConnectionLost(hostname, DDPClient.REASON_CLOSED_BY_USER))
.flatMap(_val -> disconnectFromServerIfNeeded(hostname, DDPClient.REASON_CLOSED_BY_USER));
}
if (connectivity == ServerConnectivity.STATE_DISCONNECTING) {
......@@ -253,6 +261,7 @@ import io.reactivex.subjects.BehaviorSubject;
private Single<Boolean> connectToServer(String hostname) {
return Single.defer(() -> {
if (!serverConnectivityList.containsKey(hostname)) {
ConnectionStatusManager.INSTANCE.setConnectionError();
return Single.error(new IllegalArgumentException("hostname not found"));
}
......@@ -263,8 +272,10 @@ import io.reactivex.subjects.BehaviorSubject;
}
if (serviceInterface != null) {
ConnectionStatusManager.INSTANCE.setConnecting();
return serviceInterface.ensureConnectionToServer(hostname);
} else {
ConnectionStatusManager.INSTANCE.setConnectionError();
return Single.error(new ThreadLooperNotPreparedException("not prepared"));
}
});
......@@ -279,7 +290,7 @@ import io.reactivex.subjects.BehaviorSubject;
if (serviceInterface != null) {
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);
serverConnectivityList.put(hostname, ServerConnectivity.STATE_DISCONNECTED);
......
......@@ -38,7 +38,8 @@ public class RocketChatService extends Service implements ConnectivityServiceInt
/**
* ensure RocketChatService alive.
*/
/*package*/static void keepAlive(Context context) {
/*package*/
static void keepAlive(Context context) {
context.startService(new Intent(context, RocketChatService.class));
}
......@@ -80,7 +81,7 @@ public class RocketChatService extends Service implements ConnectivityServiceInt
}
if (currentWebSocketThread != null) {
return currentWebSocketThread.terminate()
return currentWebSocketThread.terminate(false)
// after disconnection from server
.doAfterTerminate(() -> {
currentWebSocketThread = null;
......@@ -99,14 +100,15 @@ public class RocketChatService extends Service implements ConnectivityServiceInt
return Single.defer(() -> {
webSocketThreadLock.acquire();
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) {
webSocketThreadLock.release();
return Single.just(currentWebSocketThread);
}
if (currentWebSocketThread != null) {
return currentWebSocketThread.terminate()
boolean hasFailed = existsThreadForHostname(hostname);
return currentWebSocketThread.terminate(hasFailed)
.doAfterTerminate(() -> currentWebSocketThread = null)
.flatMap(terminated ->
RocketChatWebSocketThread.getStarted(getApplicationContext(), hostname)
......@@ -117,7 +119,7 @@ public class RocketChatService extends Service implements ConnectivityServiceInt
.doOnError(throwable -> {
currentWebSocketThread = null;
RCLog.e(throwable);
Logger.report(throwable);
Logger.INSTANCE.report(throwable);
webSocketThreadLock.release();
})
);
......@@ -131,7 +133,7 @@ public class RocketChatService extends Service implements ConnectivityServiceInt
.doOnError(throwable -> {
currentWebSocketThread = null;
RCLog.e(throwable);
Logger.report(throwable);
Logger.INSTANCE.report(throwable);
webSocketThreadLock.release();
});
});
......
......@@ -14,6 +14,7 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import bolts.Task;
import chat.rocket.android.ConnectionStatusManager;
import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.android.helper.LogIfError;
import chat.rocket.android.helper.RxHelper;
......@@ -29,7 +30,6 @@ import chat.rocket.android.service.observer.FileUploadingWithUfsObserver;
import chat.rocket.android.service.observer.GcmPushRegistrationObserver;
import chat.rocket.android.service.observer.GetUsersOfRoomsProcedureObserver;
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.PushSettingsObserver;
import chat.rocket.android.service.observer.SessionObserver;
......@@ -54,8 +54,7 @@ public class RocketChatWebSocketThread extends HandlerThread {
LoginServiceConfigurationSubscriber.class,
ActiveUsersSubscriber.class,
UserDataSubscriber.class,
MethodCallObserver.class,
SessionObserver.class,
// MethodCallObserver.class,
LoadMessageProcedureObserver.class,
GetUsersOfRoomsProcedureObserver.class,
NewMessageObserver.class,
......@@ -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
/* package */ Single<Boolean> terminate() {
/* package */ Single<Boolean> terminate(boolean hasFailed) {
if (isAlive()) {
return Single.create(emitter -> {
new Handler(getLooper()).post(() -> {
RCLog.d("thread %s: terminated()", Thread.currentThread().getId());
unregisterListenersAndClose();
connectivityManager.notifyConnectionLost(hostname,
DDPClient.REASON_CLOSED_BY_USER);
RocketChatWebSocketThread.super.quit();
emitter.onSuccess(true);
});
});
return Single.create(emitter -> new Handler(getLooper()).post(() -> {
RCLog.d("thread %s: terminated()", Thread.currentThread().getId());
int reason = (hasFailed) ?
DDPClient.REASON_NETWORK_ERROR : DDPClient.REASON_CLOSED_BY_USER;
unregisterListenersAndClose(reason);
connectivityManager.notifyConnectionLost(hostname, reason);
RocketChatWebSocketThread.super.quit();
ConnectionStatusManager.INSTANCE.setOffline();
emitter.onSuccess(true);
}));
} else {
connectivityManager.notifyConnectionLost(hostname,
DDPClient.REASON_NETWORK_ERROR);
......@@ -163,7 +167,7 @@ public class RocketChatWebSocketThread extends HandlerThread {
@DebugLog
/* package */ Single<Boolean> keepAlive() {
return checkIfConnectionAlive()
.flatMap(alive -> alive ? Single.just(true) : connectWithExponentialBackoff());
.flatMap(alive -> connectWithExponentialBackoff());
}
@DebugLog
......@@ -224,13 +228,11 @@ public class RocketChatWebSocketThread extends HandlerThread {
.onSuccessTask(task -> {
final String newSession = task.getResult().session;
connectivityManager.notifyConnectionEstablished(hostname, newSession);
// handling WebSocket#onClose() callback.
task.getResult().client.getOnCloseCallback().onSuccess(_task -> {
RxWebSocketCallback.Close result = _task.getResult();
if (result.code == DDPClient.REASON_NETWORK_ERROR) {
reconnect();
} else {
unregisterListenersAndClose();
}
return null;
});
......@@ -382,6 +384,10 @@ public class RocketChatWebSocketThread extends HandlerThread {
RCLog.w(exception, "Failed to register listeners!!");
}
}
// Register SessionObserver late.
SessionObserver sessionObserver = new SessionObserver(appContext, hostname, realmHelper);
sessionObserver.register();
listeners.add(sessionObserver);
listenersRegistered = true;
startHeartBeat();
}
......@@ -410,9 +416,9 @@ public class RocketChatWebSocketThread extends HandlerThread {
}
@DebugLog
private void unregisterListenersAndClose() {
private void unregisterListenersAndClose(int reason) {
unregisterListeners();
DDPClient.get().close();
DDPClient.get().close(reason);
}
@DebugLog
......
......@@ -5,50 +5,50 @@ package chat.rocket.android.service;
*/
public class ServerConnectivity {
public static final int STATE_CONNECTED = 1;
public static final int STATE_DISCONNECTED = 2;
public static final int STATE_CONNECTING = 3;
/*package*/ static final int STATE_DISCONNECTING = 4;
public static final ServerConnectivity CONNECTED = new ServerConnectivity(null, STATE_CONNECTED);
public final String hostname;
public final int state;
public final int code;
ServerConnectivity(String hostname, int state) {
this.hostname = hostname;
this.state = state;
this.code = -1;
}
ServerConnectivity(String hostname, int state, int code) {
this.hostname = hostname;
this.state = state;
this.code = code;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ServerConnectivity that = (ServerConnectivity) o;
return state == that.state;
}
@Override
public int hashCode() {
return state;
}
/**
* This exception should be thrown when connection is lost during waiting for CONNECTED.
*/
public static class DisconnectedException extends Exception {
public DisconnectedException() {
super("Disconnected");
public static final int STATE_DISCONNECTED = 0;
/* package */ static final int STATE_DISCONNECTING = 1;
/* package */ static final int STATE_CONNECTING = 2;
public static final int STATE_CONNECTED = 3;
public static final int STATE_SESSION_ESTABLISHED = 4;
/* package */ static final ServerConnectivity CONNECTED = new ServerConnectivity(null, STATE_CONNECTED);
public final String hostname;
public final int state;
public final int code;
ServerConnectivity(String hostname, int state) {
this.hostname = hostname;
this.state = state;
this.code = -1;
}
ServerConnectivity(String hostname, int state, int code) {
this.hostname = hostname;
this.state = state;
this.code = code;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ServerConnectivity that = (ServerConnectivity) o;
return state == that.state;
}
@Override
public int hashCode() {
return state;
}
/**
* This exception should be thrown when connection is lost during waiting for CONNECTED.
*/
/* package */static class DisconnectedException extends Exception {
/* package */DisconnectedException() {
super("Disconnected");
}
}
}
}
package chat.rocket.android.service.internal;
import android.content.Context;
import com.hadisatrio.optional.Optional;
import chat.rocket.android.RocketChatCache;
......@@ -13,13 +11,11 @@ import chat.rocket.persistence.realm.models.ddp.RealmRoom;
import io.reactivex.disposables.CompositeDisposable;
public abstract class AbstractRocketChatCacheObserver implements Registrable {
private final Context context;
private final RealmHelper realmHelper;
private String roomId;
private CompositeDisposable compositeDisposable = new CompositeDisposable();
protected AbstractRocketChatCacheObserver(Context context, RealmHelper realmHelper) {
this.context = context;
protected AbstractRocketChatCacheObserver(RealmHelper realmHelper) {
this.realmHelper = realmHelper;
}
......@@ -47,7 +43,7 @@ public abstract class AbstractRocketChatCacheObserver implements Registrable {
@Override
public final void register() {
compositeDisposable.add(
new RocketChatCache(context)
RocketChatCache.INSTANCE
.getSelectedRoomIdPublisher()
.filter(Optional::isPresent)
.map(Optional::get)
......
......@@ -13,60 +13,58 @@ import chat.rocket.persistence.realm.RealmHelper;
* wrapper for managing stream-notify-message depending on RocketChatCache.
*/
public class StreamRoomMessageManager implements Registrable {
private final Context context;
private final String hostname;
private final RealmHelper realmHelper;
private final AbstractRocketChatCacheObserver cacheObserver;
private final Handler handler;
private final RocketChatCache rocketChatCache;
private StreamRoomMessage streamRoomMessage;
private final Context context;
private final String hostname;
private final RealmHelper realmHelper;
private final AbstractRocketChatCacheObserver cacheObserver;
private final Handler handler;
private StreamRoomMessage streamRoomMessage;
public StreamRoomMessageManager(Context context, String hostname,
RealmHelper realmHelper) {
this.context = context;
this.hostname = hostname;
this.realmHelper = realmHelper;
this.rocketChatCache = new RocketChatCache(context);
public StreamRoomMessageManager(Context context, String hostname,
RealmHelper realmHelper) {
this.context = context;
this.hostname = hostname;
this.realmHelper = realmHelper;
cacheObserver = new AbstractRocketChatCacheObserver(context, realmHelper) {
@Override
protected void onRoomIdUpdated(String roomId) {
unregisterStreamNotifyMessageIfNeeded();
registerStreamNotifyMessage(roomId);
}
};
handler = new Handler(Looper.myLooper());
}
cacheObserver = new AbstractRocketChatCacheObserver(realmHelper) {
@Override
protected void onRoomIdUpdated(String roomId) {
unregisterStreamNotifyMessageIfNeeded();
registerStreamNotifyMessage(roomId);
}
};
handler = new Handler(Looper.myLooper());
}
private void registerStreamNotifyMessage(String roomId) {
handler.post(() -> {
streamRoomMessage = new StreamRoomMessage(context, hostname, realmHelper, roomId);
streamRoomMessage.register();
});
}
private void registerStreamNotifyMessage(String roomId) {
handler.post(() -> {
streamRoomMessage = new StreamRoomMessage(context, hostname, realmHelper, roomId);
streamRoomMessage.register();
});
}
private void unregisterStreamNotifyMessageIfNeeded() {
handler.post(() -> {
if (streamRoomMessage != null) {
streamRoomMessage.unregister();
streamRoomMessage = null;
}
});
}
private void unregisterStreamNotifyMessageIfNeeded() {
handler.post(() -> {
if (streamRoomMessage != null) {
streamRoomMessage.unregister();
streamRoomMessage = null;
}
});
}
@Override
public void register() {
cacheObserver.register();
String selectedRoomId = rocketChatCache.getSelectedRoomId();
if (selectedRoomId == null) {
return;
@Override
public void register() {
cacheObserver.register();
String selectedRoomId = RocketChatCache.INSTANCE.getSelectedRoomId();
if (selectedRoomId == null) {
return;
}
registerStreamNotifyMessage(selectedRoomId);
}
registerStreamNotifyMessage(selectedRoomId);
}
@Override
public void unregister() {
unregisterStreamNotifyMessageIfNeeded();
cacheObserver.unregister();
}
@Override
public void unregister() {
unregisterStreamNotifyMessageIfNeeded();
cacheObserver.unregister();
}
}
......@@ -68,7 +68,7 @@ public class GcmPushRegistrationObserver extends AbstractModelObserver<GcmPushRe
final RealmUser currentUser = realmHelper.executeTransactionForRead(realm ->
RealmUser.queryCurrentUser(realm).findFirst());
final String userId = currentUser != null ? currentUser.getId() : null;
final String pushId = new RocketChatCache(context).getOrCreatePushId();
final String pushId = RocketChatCache.INSTANCE.getOrCreatePushId();
return new RaixPushHelper(realmHelper)
.pushUpdate(pushId, gcmToken, userId);
......
......@@ -7,6 +7,7 @@ import java.util.List;
import chat.rocket.android.RocketChatCache;
import chat.rocket.android.api.RaixPushHelper;
import chat.rocket.android.helper.LogIfError;
import chat.rocket.android.service.ConnectivityManager;
import chat.rocket.android.service.internal.StreamRoomMessageManager;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.persistence.realm.models.internal.GetUsersOfRoomsProcedure;
......@@ -73,8 +74,10 @@ public class SessionObserver extends AbstractModelObserver<RealmSession> {
// update push info
pushHelper
.pushSetUser(new RocketChatCache(context).getOrCreatePushId())
.pushSetUser(RocketChatCache.INSTANCE.getOrCreatePushId())
.continueWith(new LogIfError());
ConnectivityManager.getInstance(context).notifySessionEstablished(hostname);
}
@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"?>
<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="?attr/colorPrimaryDark"
android:padding="@dimen/margin_8"
tools:context="chat.rocket.android.fragment.server_config.LoginFragment">
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="?attr/colorPrimaryDark"
android:padding="@dimen/margin_8"
tools:context="chat.rocket.android.fragment.server_config.LoginFragment">
<ScrollView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent">
<ScrollView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
<android.support.constraint.ConstraintLayout
android:id="@+id/container"
android:layout_width="wrap_content"
android:minWidth="280dp"
android:layout_height="wrap_content"
android:paddingStart="@dimen/margin_16"
android:paddingLeft="@dimen/margin_16"
android:paddingEnd="@dimen/margin_16"
android:paddingRight="@dimen/margin_16"
android:paddingTop="@dimen/margin_8"
android:paddingBottom="@dimen/margin_8"
android:background="@drawable/container_bg"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent">
<android.support.constraint.ConstraintLayout
android:id="@+id/container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/container_bg"
android:minWidth="280dp"
android:paddingBottom="@dimen/margin_8"
android:paddingEnd="@dimen/margin_16"
android:paddingLeft="@dimen/margin_16"
android:paddingRight="@dimen/margin_16"
android:paddingStart="@dimen/margin_16"
android:paddingTop="@dimen/margin_8"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageButton
android:id="@+id/btn_login_with_twitter"
android:layout_width="48dp"
android:layout_height="48dp"
android:scaleType="fitXY"
app:srcCompat="@drawable/ic_button_twitter_24dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@+id/text_input_username"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/btn_login_with_facebook"
android:background="?attr/selectableItemBackgroundBorderless" />
<ImageButton
android:id="@+id/btn_login_with_twitter"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:scaleType="fitXY"
app:layout_constraintBottom_toTopOf="@+id/text_input_username"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/btn_login_with_facebook"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_button_twitter_24dp" />
<ImageButton
android:id="@+id/btn_login_with_facebook"
android:layout_width="48dp"
android:layout_height="48dp"
android:scaleType="fitXY"
app:srcCompat="@drawable/ic_button_facebook_24dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@+id/text_input_username"
app:layout_constraintLeft_toRightOf="@+id/btn_login_with_twitter"
app:layout_constraintRight_toLeftOf="@+id/btn_login_with_github"
android:background="?attr/selectableItemBackgroundBorderless" />
<ImageButton
android:id="@+id/btn_login_with_facebook"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:scaleType="fitXY"
app:layout_constraintBottom_toTopOf="@+id/text_input_username"
app:layout_constraintLeft_toRightOf="@+id/btn_login_with_twitter"
app:layout_constraintRight_toLeftOf="@+id/btn_login_with_github"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_button_facebook_24dp" />
<ImageButton
android:id="@+id/btn_login_with_github"
android:layout_width="48dp"
android:layout_height="48dp"
android:scaleType="fitXY"
app:srcCompat="@drawable/ic_button_github_24dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@+id/text_input_username"
app:layout_constraintLeft_toRightOf="@+id/btn_login_with_facebook"
app:layout_constraintRight_toLeftOf="@+id/btn_login_with_google"
android:background="?attr/selectableItemBackgroundBorderless" />
<ImageButton
android:id="@+id/btn_login_with_github"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:scaleType="fitXY"
app:layout_constraintBottom_toTopOf="@+id/text_input_username"
app:layout_constraintLeft_toRightOf="@+id/btn_login_with_facebook"
app:layout_constraintRight_toLeftOf="@+id/btn_login_with_google"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_button_github_24dp" />
<ImageButton
android:id="@+id/btn_login_with_google"
android:layout_width="48dp"
android:layout_height="48dp"
android:scaleType="fitXY"
app:srcCompat="@drawable/ic_button_google_24dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@+id/text_input_username"
app:layout_constraintLeft_toRightOf="@+id/btn_login_with_github"
app:layout_constraintRight_toRightOf="parent"
android:background="?attr/selectableItemBackgroundBorderless" />
<ImageButton
android:id="@+id/btn_login_with_google"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:scaleType="fitXY"
app:layout_constraintBottom_toTopOf="@+id/text_input_username"
app:layout_constraintLeft_toRightOf="@+id/btn_login_with_github"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_button_google_24dp" />
<android.support.design.widget.TextInputLayout
android:id="@+id/text_input_username"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/btn_login_with_twitter"
app:layout_constraintBottom_toTopOf="@+id/text_input_passwd"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent">
<android.support.design.widget.TextInputLayout
android:id="@+id/text_input_username"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@+id/text_input_passwd"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btn_login_with_twitter">
<android.support.design.widget.TextInputEditText
android:id="@+id/editor_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/fragment_login_username_or_email"
android:imeOptions="actionNext"
android:inputType="textWebEmailAddress"
android:maxLines="1" />
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputEditText
android:id="@+id/editor_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/fragment_login_username_or_email"
android:imeOptions="actionNext"
android:inputType="textWebEmailAddress"
android:maxLines="1" />
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:id="@+id/text_input_passwd"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/text_input_username"
app:layout_constraintBottom_toTopOf="@+id/btn_user_registration"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent">
<android.support.design.widget.TextInputLayout
android:id="@+id/text_input_passwd"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@+id/btn_user_registration"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_input_username">
<android.support.design.widget.TextInputEditText
android:id="@+id/editor_passwd"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/fragment_login_password"
android:imeOptions="actionNext"
android:inputType="textWebPassword"
android:maxLines="1" />
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputEditText
android:id="@+id/editor_passwd"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/fragment_login_password"
android:imeOptions="actionNext"
android:inputType="textWebPassword"
android:maxLines="1" />
</android.support.design.widget.TextInputLayout>
<Button
android:id="@+id/btn_user_registration"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_8"
android:text="@string/fragment_login_sign_up"
app:layout_constraintTop_toBottomOf="@+id/text_input_passwd"
app:layout_constraintRight_toLeftOf="@+id/btn_login_with_email"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
style="@style/Widget.AppCompat.Button.Colored" />
<Button
android:id="@+id/btn_user_registration"
style="@style/Widget.AppCompat.Button.Colored"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_8"
android:text="@string/fragment_login_sign_up"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/btn_login_with_email"
app:layout_constraintTop_toBottomOf="@+id/text_input_passwd" />
<Button
android:id="@+id/btn_login_with_email"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_8"
android:text="@string/fragment_login_sign_in"
app:layout_constraintTop_toBottomOf="@+id/text_input_passwd"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toRightOf="@+id/btn_user_registration"
app:layout_constraintBottom_toBottomOf="parent"
style="@style/Widget.AppCompat.Button.Colored" />
</android.support.constraint.ConstraintLayout>
</ScrollView>
<Button
android:id="@+id/btn_login_with_email"
style="@style/Widget.AppCompat.Button.Colored"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_8"
android:text="@string/fragment_login_sign_in"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="@+id/btn_user_registration"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_input_passwd" />
</android.support.constraint.ConstraintLayout>
</ScrollView>
<chat.rocket.android.widget.WaitingView
android:id="@+id/waiting"
......
......@@ -13,4 +13,5 @@
<color name="colorSecondaryTextDark">#8A000000</color>
<color name="colorDivider">#1F000000</color>
<color name="connection_crouton_background">#030000</color>
</resources>
\ No newline at end of file
......@@ -55,7 +55,7 @@
<string name="fragment_login_sign_up">SIGN UP</string>
<string name="fragment_login_sign_in">SIGN IN</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="home_fragment_title">Rocket.Chat - Home</string>
<string name="fragment_sidebar_main_unread_rooms_title">UNREAD ROOMS</string>
......
......@@ -12,7 +12,6 @@
<item name="actionModeBackground">?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>
</style>
......@@ -27,7 +26,6 @@
<item name="actionModeBackground">?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 name="AppTheme.Dialog" parent="Theme.AppCompat.Light.Dialog">
......
......@@ -23,12 +23,12 @@ object OkHttpHelper {
return httpClientForUploadFile ?: throw AssertionError("httpClientForUploadFile set to null by another thread")
}
fun getClientForDownloadFile(context: Context): OkHttpClient {
fun getClientForDownloadFile(): OkHttpClient {
if(httpClientForDownloadFile == null) {
httpClientForDownloadFile = OkHttpClient.Builder()
.followRedirects(true)
.followSslRedirects(true)
.addInterceptor(CookieInterceptor(DefaultCookieProvider(RocketChatCache(context))))
.addInterceptor(CookieInterceptor(DefaultCookieProvider()))
.build()
}
return httpClientForDownloadFile ?: throw AssertionError("httpClientForDownloadFile set to null by another thread")
......
......@@ -16,10 +16,10 @@ buildscript {
}
dependencies {
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 '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 'io.fabric.tools:gradle:1.+'
}
......
ext {
preDexLibs = "true" != System.getenv("CI")
supportLibraryVersion = "27.0.1"
playLibVersion = '11.6.2'
constraintLayoutVersion = "1.0.2"
kotlinVersion = "1.1.51"
kotlinVersion = "1.2.0"
okHttpVersion = "3.9.0"
rxbindingVersion = '2.0.0'
rxJavaVersion = "2.1.0"
supportDependencies = [
designSupportLibrary: "com.android.support:design:${supportLibraryVersion}",
......@@ -15,14 +17,20 @@ ext {
multidex : "com.android.support:multidex:1.0.2",
customTabs : "com.android.support:customtabs:${supportLibraryVersion}"
]
extraDependencies = [
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",
rxAndroid : "io.reactivex.rxjava2:rxandroid:2.0.1",
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 = [
rxBinding : "com.jakewharton.rxbinding2:rxbinding:${rxbindingVersion}",
rxBindingSupport : "com.jakewharton.rxbinding2:rxbinding-support-v4:${rxbindingVersion}",
......@@ -38,5 +46,14 @@ subprojects {
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 {
}
}
dependencies {
compile project(':log-wrapper')
compile project(':rocket-chat-core')
compile extraDependencies.rxJava
compile extraDependencies.boltTask
compile supportDependencies.annotation
compile supportDependencies.designSupportLibrary
compile extraDependencies.rxAndroid
provided extraDependencies.optional
testCompile "org.jetbrains.kotlin:kotlin-test:$rootProject.ext.kotlinVersion"
testCompile "org.jetbrains.kotlin:kotlin-test-junit:$rootProject.ext.kotlinVersion"
testCompile 'org.json:json:20170516'
testCompile 'org.skyscreamer:jsonassert:1.5.0'
testCompile 'junit:junit:4.12'
api project(':log-wrapper')
api project(':rocket-chat-core')
implementation extraDependencies.rxJava
implementation extraDependencies.rxKotlin
implementation extraDependencies.boltTask
implementation supportDependencies.annotation
implementation supportDependencies.designSupportLibrary
implementation extraDependencies.rxAndroid
implementation extraDependencies.optional
testImplementation "org.jetbrains.kotlin:kotlin-test:$rootProject.ext.kotlinVersion"
testImplementation "org.jetbrains.kotlin:kotlin-test-junit:$rootProject.ext.kotlinVersion"
testImplementation 'org.json:json:20170516'
testImplementation 'org.skyscreamer:jsonassert:1.5.0'
testImplementation 'junit:junit:4.12'
}
......@@ -31,25 +31,25 @@ ext {
frescoVersion = '1.4.0'
}
dependencies {
compile project(':rocket-chat-core')
compile extraDependencies.okHTTP
compile extraDependencies.textDrawable
compile supportDependencies.annotation
compile supportDependencies.cardView
compile supportDependencies.designSupportLibrary
compile supportDependencies.constraintLayout
compile supportDependencies.supportV13
compile supportDependencies.customTabs
compile rxbindingDependencies.rxBinding
compile rxbindingDependencies.rxBindingSupport
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$rootProject.ext.kotlinVersion"
compile 'org.nibor.autolink:autolink:0.6.0'
compile 'com.github.yusukeiwaki.android-widget:widget-fontawesome:0.0.1'
compile "com.facebook.fresco:fresco:$frescoVersion"
compile "com.facebook.fresco:imagepipeline-okhttp3:$frescoVersion"
compile 'com.caverock:androidsvg:1.2.1'
testCompile "org.jetbrains.kotlin:kotlin-test-junit:$rootProject.ext.kotlinVersion"
testCompile "org.jetbrains.kotlin:kotlin-test:$rootProject.ext.kotlinVersion"
testCompile 'junit:junit:4.12'
testCompile "org.mockito:mockito-core:2.7.19"
api project(':rocket-chat-core')
api extraDependencies.okHTTP
api extraDependencies.textDrawable
api supportDependencies.annotation
api supportDependencies.cardView
api supportDependencies.designSupportLibrary
api supportDependencies.constraintLayout
api supportDependencies.supportV13
api supportDependencies.customTabs
api rxbindingDependencies.rxBinding
api rxbindingDependencies.rxBindingSupport
api "org.jetbrains.kotlin:kotlin-stdlib-jre8:$rootProject.ext.kotlinVersion"
implementation 'org.nibor.autolink:autolink:0.6.0'
implementation 'com.github.yusukeiwaki.android-widget:widget-fontawesome:0.0.1'
api "com.facebook.fresco:fresco:$frescoVersion"
api "com.facebook.fresco:imagepipeline-okhttp3:$frescoVersion"
implementation 'com.caverock:androidsvg:1.2.1'
testImplementation "org.jetbrains.kotlin:kotlin-test-junit:$rootProject.ext.kotlinVersion"
testImplementation "org.jetbrains.kotlin:kotlin-test:$rootProject.ext.kotlinVersion"
testImplementation 'junit:junit:4.12'
testImplementation "org.mockito:mockito-core:2.10.0"
}
......@@ -4,14 +4,15 @@ apply plugin: 'java'
dependencies {
compile extraDependencies.rxJava
compile extraDependencies.rxKotlin
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'
compileOnly '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'
testCompile 'junit:junit:4.12'
testCompile "org.mockito:mockito-inline:2.8.9"
testImplementation 'junit:junit:4.12'
testImplementation "org.mockito:mockito-inline:2.8.9"
}
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
\ No newline at end of file
sourceCompatibility = "1.8"
targetCompatibility = "1.8"
\ No newline at end of file
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