Commit d26e6210 authored by Tiago Cunha's avatar Tiago Cunha

Merge remote-tracking branch 'refs/remotes/origin/develop' into develop

parents 85d79171 b34ffdff
apply plugin: 'com.android.library'
apply plugin: 'me.tatarka.retrolambda'
buildscript {
repositories {
jcenter()
}
dependencies {
classpath rootProject.ext.androidPlugin
classpath rootProject.ext.retroLambdaPlugin
classpath rootProject.ext.retroLambdaPatch
}
}
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.compileSdkVersion
versionCode 1
versionName "0.0.8"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile rootProject.ext.supportAnnotations
compile 'com.squareup.okhttp3:okhttp-ws:3.4.1'
compile rootProject.ext.rxJava
compile rootProject.ext.boltsTask
compile rootProject.ext.timber
}
# -*- coding:utf-8 -*-
a='''
@Override
public void onOpen(WebSocket webSocket, Response response) {
}
@Override
public void onFailure(IOException e, Response response) {
}
@Override
public void onMessage(ResponseBody responseBody) throws IOException {
}
@Override
public void onPong(Buffer payload) {
}
@Override
public void onClose(int code, String reason) {
}
'''.strip().split('@Override')
for m in a[1:]:
m= " @Override\n "+m.strip()
mn = m.split("\n")[1].strip().split(" ")[2].split("(")[0]
if mn.startswith("on"):
d=dict()
d["classname"]=mn[2:]
params = [p for p in " ".join(m.split("\n")[1].strip()[:-1].split(" throws ")[0].split(" ")[2:]).strip()[len(mn)+1:-1].split(", ") if p.split(" ")[0]!="WebSocket"]
d["params"]="".join([", "+p for p in params])
paramnames = [p.split(" ")[-1] for p in params]
d["paramdefs"]="\n".join([" public "+p+";" for p in params])
d["thisis"]="\n".join([" this.{param} = {param};".format(param=p) for p in paramnames])
# print '''
# public static class {classname} extends Base {{
# {paramdefs}
# public {classname}(WebSocket websocket{params}) {{
# super("{classname}", websocket);
# {thisis}
# }}
# }}'''.format(**d)
######################
x=m.split("\n")
x[2]=''' mSubscriber.onNext(new RxWebSocketCallback.{classname}(mWebSocket, {params}));'''.format(classname=mn[2:],params=", ".join(paramnames))
print "\n".join(x)
<lint>
<issue id="InvalidPackage">
<ignore regexp="okio.*jar"/>
</issue>
</lint>
\ No newline at end of file
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/yi01/Library/Android/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="chat.rocket.android_ddp">
<application
android:allowBackup="true"
android:label="@string/app_name"
android:supportsRtl="true">
</application>
</manifest>
package chat.rocket.android_ddp;
import android.support.annotation.Nullable;
import bolts.Task;
import bolts.TaskCompletionSource;
import chat.rocket.android_ddp.rx.RxWebSocketCallback;
import okhttp3.OkHttpClient;
import org.json.JSONArray;
import rx.Observable;
import timber.log.Timber;
public class DDPClient {
// reference: https://github.com/eddflrs/meteor-ddp/blob/master/meteor-ddp.js
private final DDPClientImpl impl;
public DDPClient(OkHttpClient client) {
impl = new DDPClientImpl(this, client);
Timber.plant(new Timber.DebugTree());
}
public Task<DDPClientCallback.Connect> connect(String url) {
return connect(url, null);
}
public Task<DDPClientCallback.Connect> connect(String url, String session) {
TaskCompletionSource<DDPClientCallback.Connect> task = new TaskCompletionSource<>();
impl.connect(task, url, session);
return task.getTask();
}
public Task<DDPClientCallback.Ping> ping(@Nullable String id) {
TaskCompletionSource<DDPClientCallback.Ping> task = new TaskCompletionSource<>();
impl.ping(task, id);
return task.getTask();
}
public Task<DDPClientCallback.RPC> rpc(String method, JSONArray params, String id,
long timeoutMs) {
TaskCompletionSource<DDPClientCallback.RPC> task = new TaskCompletionSource<>();
impl.rpc(task, method, params, id, timeoutMs);
return task.getTask();
}
public Task<DDPSubscription.Ready> sub(String id, String name, JSONArray params) {
TaskCompletionSource<DDPSubscription.Ready> task = new TaskCompletionSource<>();
impl.sub(task, name, params, id);
return task.getTask();
}
public Task<DDPSubscription.NoSub> unsub(String id) {
TaskCompletionSource<DDPSubscription.NoSub> task = new TaskCompletionSource<>();
impl.unsub(task, id);
return task.getTask();
}
public Observable<DDPSubscription.Event> getSubscriptionCallback() {
return impl.getDDPSubscription();
}
public Task<RxWebSocketCallback.Close> getOnCloseCallback() {
return impl.getOnCloseCallback();
}
public boolean isConnected() {
return impl.isConnected();
}
public void close() {
impl.close(1000, "closed by DDPClient#close()");
}
}
package chat.rocket.android_ddp;
import android.support.annotation.Nullable;
import org.json.JSONObject;
public class DDPClientCallback {
public static abstract class Base {
public DDPClient client;
public Base(DDPClient client) {
this.client = client;
}
}
public static abstract class BaseException extends Exception {
public DDPClient client;
public BaseException(DDPClient client) {
this.client = client;
}
}
public static class Connect extends Base {
public String session;
public Connect(DDPClient client, String session) {
super(client);
this.session = session;
}
public static class Failed extends BaseException {
public String version;
public Failed(DDPClient client, String version) {
super(client);
this.version = version;
}
}
}
public static class Ping extends Base {
@Nullable public String id;
public Ping(DDPClient client, @Nullable String id) {
super(client);
this.id = id;
}
public static class Timeout extends BaseException {
public Timeout(DDPClient client) {
super(client);
}
}
}
public static class RPC extends Base {
public String id;
public String result;
public RPC(DDPClient client, String id, String result) {
super(client);
this.id = id;
this.result = result;
}
public static class Error extends BaseException {
public String id;
public JSONObject error;
public Error(DDPClient client, String id, JSONObject error) {
super(client);
this.id = id;
this.error = error;
}
}
public static class Timeout extends BaseException {
public Timeout(DDPClient client) {
super(client);
}
}
}
}
package chat.rocket.android_ddp;
import android.support.annotation.NonNull;
import org.json.JSONArray;
import org.json.JSONObject;
public class DDPSubscription {
public static abstract class Event {
public final DDPClient client;
public Event(DDPClient client) {
this.client = client;
}
}
public static abstract class BaseException extends Exception {
public final DDPClient client;
public BaseException(DDPClient client) {
this.client = client;
}
}
public static class NoSub extends Event {
public String id;
public NoSub(DDPClient client, String id) {
super(client);
this.id = id;
}
@Override public String toString() {
return "NoSub[id=" + id + "]";
}
public static class Error extends BaseException {
String id;
JSONObject error;
public Error(DDPClient client, String id, JSONObject error) {
super(client);
this.id = id;
this.error = error;
}
}
}
public static class Ready extends Event {
public String id;
public Ready(DDPClient client, String id) {
super(client);
this.id = id;
}
@Override public String toString() {
return "Ready[id=" + id + "]";
}
}
public static class DocEvent extends Event {
public String collection;
public String docID;
public DocEvent(DDPClient client, String collection, String docID) {
super(client);
this.collection = collection;
this.docID = docID;
}
@Override public String toString() {
return "DocEvent[id=" + docID + ", collection=" + collection + "]";
}
}
public static class Added extends DocEvent {
public JSONObject fields;
public Added(DDPClient client, String collection, String docID, JSONObject fields) {
super(client, collection, docID);
this.fields = fields;
}
public static class Before extends Added {
public String before;
public Before(DDPClient client, String collection, String docID, JSONObject fields,
String before) {
super(client, collection, docID, fields);
this.before = before;
}
}
}
public static class Changed extends DocEvent {
public JSONObject fields;
public JSONArray cleared;
public Changed(DDPClient client, String collection, String docID, JSONObject fields,
@NonNull JSONArray cleared) {
super(client, collection, docID);
this.fields = fields;
this.cleared = cleared;
}
}
public static class Removed extends DocEvent {
public Removed(DDPClient client, String collection, String docID) {
super(client, collection, docID);
}
}
public static class MovedBefore extends DocEvent {
public String before;
public MovedBefore(DDPClient client, String collection, String docID, String before) {
super(client, collection, docID);
this.before = before;
}
}
}
package chat.rocket.android_ddp.rx;
import java.io.IOException;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.ws.WebSocket;
import okhttp3.ws.WebSocketCall;
import okhttp3.ws.WebSocketListener;
import okio.Buffer;
import rx.Observable;
import rx.Subscriber;
import rx.exceptions.OnErrorNotImplementedException;
import rx.observables.ConnectableObservable;
import timber.log.Timber;
public class RxWebSocket {
private OkHttpClient httpClient;
private WebSocket webSocket;
private boolean isConnected;
public RxWebSocket(OkHttpClient client) {
httpClient = client;
isConnected = false;
}
public ConnectableObservable<RxWebSocketCallback.Base> connect(String url) {
final Request request = new Request.Builder().url(url).build();
WebSocketCall call = WebSocketCall.create(httpClient, request);
return Observable.create(new Observable.OnSubscribe<RxWebSocketCallback.Base>() {
@Override public void call(Subscriber<? super RxWebSocketCallback.Base> subscriber) {
call.enqueue(new WebSocketListener() {
@Override public void onOpen(WebSocket webSocket, Response response) {
isConnected = true;
RxWebSocket.this.webSocket = webSocket;
subscriber.onNext(new RxWebSocketCallback.Open(RxWebSocket.this.webSocket, response));
}
@Override public void onFailure(IOException e, Response response) {
try {
isConnected = false;
subscriber.onError(new RxWebSocketCallback.Failure(webSocket, e, response));
} catch (OnErrorNotImplementedException ex) {
Timber.w(ex, "OnErrorNotImplementedException ignored");
}
}
@Override public void onMessage(ResponseBody responseBody) throws IOException {
isConnected = true;
subscriber.onNext(new RxWebSocketCallback.Message(webSocket, responseBody));
}
@Override public void onPong(Buffer payload) {
isConnected = true;
subscriber.onNext(new RxWebSocketCallback.Pong(webSocket, payload));
}
@Override public void onClose(int code, String reason) {
isConnected = false;
subscriber.onNext(new RxWebSocketCallback.Close(webSocket, code, reason));
subscriber.onCompleted();
}
});
}
}).publish();
}
public void sendText(String message) throws IOException {
webSocket.sendMessage(RequestBody.create(WebSocket.TEXT, message));
}
public boolean isConnected() {
return isConnected;
}
public void close(int code, String reason) throws IOException {
webSocket.close(code, reason);
}
}
package chat.rocket.android_ddp.rx;
import java.io.IOException;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.ws.WebSocket;
import okio.Buffer;
import timber.log.Timber;
import static android.R.attr.type;
public class RxWebSocketCallback {
public static abstract class Base {
public String type;
public WebSocket ws;
public Base(String type, WebSocket ws) {
this.type = type;
this.ws = ws;
}
@Override public String toString() {
return "[" + type + "]";
}
}
public static class Open extends Base {
public Response response;
public Open(WebSocket websocket, Response response) {
super("Open", websocket);
this.response = response;
}
}
public static class Failure extends Exception {
public WebSocket ws;
public Response response;
public Failure(WebSocket websocket, IOException e, Response response) {
super(e);
this.ws = websocket;
this.response = response;
}
@Override public String toString() {
if (response != null) {
return "[" + type + "] " + response.message();
} else {
return super.toString();
}
}
}
public static class Message extends Base {
public String responseBodyString;
public Message(WebSocket websocket, ResponseBody responseBody) {
super("Message", websocket);
try {
this.responseBodyString = responseBody.string();
} catch (Exception e) {
Timber.e(e, "error in reading response(Message)");
}
}
@Override public String toString() {
return "[" + type + "] " + responseBodyString;
}
}
public static class Pong extends Base {
public Buffer payload;
public Pong(WebSocket websocket, Buffer payload) {
super("Pong", websocket);
this.payload = payload;
}
}
public static class Close extends Base {
public int code;
public String reason;
public Close(WebSocket websocket, int code, String reason) {
super("Close", websocket);
this.code = code;
this.reason = reason;
}
@Override public String toString() {
return "[" + type + "] code=" + code + ", reason=" + reason;
}
}
}
<resources>
<string name="app_name">android-ddp</string>
</resources>
......@@ -14,8 +14,8 @@ buildscript {
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
classpath 'me.tatarka:gradle-retrolambda:3.3.1'
classpath 'me.tatarka.retrolambda.projectlombok:lombok.ast:0.2.3.a2'
classpath rootProject.ext.retroLambdaPlugin
classpath rootProject.ext.retroLambdaPatch
classpath rootProject.ext.realmPlugin
classpath 'com.jakewharton.hugo:hugo-plugin:1.2.1'
classpath 'com.google.gms:google-services:3.0.0'
......@@ -78,6 +78,7 @@ repositories {
}
dependencies {
compile project(':android-ddp')
compile project(':rocket-chat-android-widgets')
compile project(':realm-helpers')
compile rootProject.ext.supportAppCompat
......@@ -98,8 +99,6 @@ dependencies {
compile 'com.facebook.stetho:stetho-okhttp3:1.4.1'
compile 'com.uphyca:stetho_realm:2.0.1'
compile 'chat.rocket:android-ddp:0.0.8'
compile rootProject.ext.timber
compile 'com.jakewharton.rxbinding:rxbinding:0.4.0'
compile 'com.jakewharton.rxbinding:rxbinding-support-v4:0.4.0'
......
......@@ -4,6 +4,7 @@ import android.support.multidex.MultiDexApplication;
import chat.rocket.android.model.ServerConfig;
import chat.rocket.android.realm_helper.RealmStore;
import com.facebook.stetho.Stetho;
import com.instabug.library.Feature;
import com.instabug.library.Instabug;
import com.instabug.library.invocation.InstabugInvocationEvent;
import com.uphyca.stetho_realm.RealmInspectorModulesProvider;
......@@ -38,6 +39,7 @@ public class RocketChatApplication extends MultiDexApplication {
new Instabug.Builder(this, getString(R.string.instabug_api_key))
.setInvocationEvent(InstabugInvocationEvent.FLOATING_BUTTON)
.setInAppMessagingState(Feature.State.DISABLED) //not available in Free plan...
.build();
//TODO: add periodic trigger for RocketChatService.keepalive(this) here!
......
......@@ -10,6 +10,7 @@ import chat.rocket.android.helper.OnBackPressListener;
import com.instabug.library.InstabugTrackingDelegate;
import com.trello.rxlifecycle.components.support.RxAppCompatActivity;
import icepick.Icepick;
import timber.log.Timber;
abstract class AbstractFragmentActivity extends RxAppCompatActivity {
......@@ -66,7 +67,11 @@ abstract class AbstractFragmentActivity extends RxAppCompatActivity {
}
@Override public boolean dispatchTouchEvent(MotionEvent event) {
InstabugTrackingDelegate.notifyActivityGotTouchEvent(event, this);
try {
InstabugTrackingDelegate.notifyActivityGotTouchEvent(event, this);
} catch (IllegalStateException exception) {
Timber.w(exception, "Instabug error (ignored)");
}
return super.dispatchTouchEvent(event);
}
}
......@@ -36,7 +36,7 @@ public class AddServerActivity extends AbstractFragmentActivity {
if (config == null || config.getState() == ServerConfig.STATE_CONNECTION_ERROR) {
showFragment(new InputHostnameFragment());
} else {
showFragment(WaitingFragment.create("Connecting to server..."));
showFragment(WaitingFragment.create(getString(R.string.add_server_activity_waiting_server)));
}
});
......
......@@ -123,13 +123,6 @@ public class MainActivity extends AbstractAuthedActivity {
}
}
@Override protected void onResume() {
super.onResume();
if (sessionObserver != null) {
sessionObserver.keepalive();
}
}
@Override protected void onDestroy() {
if (sessionObserver != null) {
sessionObserver.unsub();
......
......@@ -76,7 +76,7 @@ public class ServerConfigActivity extends AbstractFragmentActivity {
final String token = session.getToken();
if (!TextUtils.isEmpty(token)) {
if (TextUtils.isEmpty(session.getError())) {
showFragment(WaitingFragment.create("Authenticating..."));
showFragment(WaitingFragment.create(getString(R.string.server_config_activity_authenticating)));
} else {
showFragment(new RetryLoginFragment());
}
......
......@@ -11,6 +11,7 @@ import java.util.UUID;
import org.json.JSONArray;
import org.json.JSONException;
import rx.Observable;
import timber.log.Timber;
/**
* DDP client wrapper.
......@@ -82,12 +83,28 @@ public class DDPClientWraper {
*/
public Task<DDPClientCallback.RPC> rpc(String methodCallId, String methodName, String params,
long timeoutMs) {
Timber.d("rpc:[%s]> %s(%s) timeout=%d", methodCallId, methodName, params, timeoutMs);
if (TextUtils.isEmpty(params)) {
return ddpClient.rpc(methodName, null, methodCallId, timeoutMs);
return ddpClient.rpc(methodName, null, methodCallId, timeoutMs).continueWithTask(task -> {
if (task.isFaulted()) {
Timber.d("rpc:[%s]< error = %s", methodCallId, task.getError());
} else {
Timber.d("rpc:[%s]< result = %s", methodCallId, task.getResult().result);
}
return task;
});
}
try {
return ddpClient.rpc(methodName, new JSONArray(params), methodCallId, timeoutMs);
return ddpClient.rpc(methodName, new JSONArray(params), methodCallId, timeoutMs)
.continueWithTask(task -> {
if (task.isFaulted()) {
Timber.d("rpc:[%s]< error = %s", methodCallId, task.getError());
} else {
Timber.d("rpc:[%s]< result = %s", methodCallId, task.getResult().result);
}
return task;
});
} catch (JSONException exception) {
return Task.forError(exception);
}
......
......@@ -6,6 +6,7 @@ import bolts.Continuation;
import bolts.Task;
import chat.rocket.android.helper.CheckSum;
import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.model.SyncState;
import chat.rocket.android.model.ddp.Message;
import chat.rocket.android.model.ddp.RoomSubscription;
import chat.rocket.android.model.internal.MethodCall;
......@@ -170,9 +171,9 @@ public class MethodCallHelper {
}
/**
* Login with GitHub OAuth.
* Login with OAuth.
*/
public Task<Void> loginWithGitHub(final String credentialToken,
public Task<Void> loginWithOAuth(final String credentialToken,
final String credentialSecret) {
return call("login", TIMEOUT_MS, () -> new JSONArray().put(new JSONObject()
.put("oauth", new JSONObject()
......@@ -214,7 +215,7 @@ public class MethodCallHelper {
/**
* request "subscriptions/get".
*/
public Task<Void> getRooms() {
public Task<Void> getRoomSubscriptions() {
return call("subscriptions/get", TIMEOUT_MS).onSuccessTask(CONVERT_TO_JSON_ARRAY)
.onSuccessTask(task -> {
final JSONArray result = task.getResult();
......@@ -255,7 +256,10 @@ public class MethodCallHelper {
return realmHelper.executeTransaction(realm -> {
if (timestamp == 0) {
realm.where(Message.class).equalTo("rid", roomId).findAll().deleteAllFromRealm();
realm.where(Message.class)
.equalTo("rid", roomId)
.equalTo("syncstate", SyncState.SYNCED)
.findAll().deleteAllFromRealm();
}
if (messages.length() > 0) {
realm.createOrUpdateAllFromJson(Message.class, messages);
......@@ -278,4 +282,35 @@ public class MethodCallHelper {
return call("getUsersOfRoom", TIMEOUT_MS, () -> new JSONArray().put(roomId).put(showAll))
.onSuccessTask(CONVERT_TO_JSON_OBJECT);
}
/**
* send message.
*/
public Task<JSONObject> sendMessage(String messageId, String roomId, String msg) {
try {
return sendMessage(new JSONObject()
.put("_id", messageId)
.put("rid", roomId)
.put("msg", msg));
} catch (JSONException exception) {
return Task.forError(exception);
}
}
/**
* Send message object.
*/
public Task<JSONObject> sendMessage(final JSONObject messageJson) {
return call("sendMessage", TIMEOUT_MS, () -> new JSONArray().put(messageJson))
.onSuccessTask(CONVERT_TO_JSON_OBJECT)
.onSuccessTask(task -> Task.forResult(Message.customizeJson(task.getResult())));
}
/**
* 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));
}
}
......@@ -11,12 +11,12 @@ public class HomeFragment extends AbstractChatRoomFragment {
}
@Override protected void onSetupView() {
activityToolbar.setTitle("Rocket.Chat - Home");
activityToolbar.setTitle(R.string.home_fragment_title);
}
@Override public void onResume() {
super.onResume();
activityToolbar.setNavigationIcon(null);
activityToolbar.setTitle("Rocket.Chat - Home");
activityToolbar.setTitle(R.string.home_fragment_title);
}
}
......@@ -2,37 +2,46 @@ package chat.rocket.android.fragment.chatroom;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v4.widget.SlidingPaneLayout;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import chat.rocket.android.R;
import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.android.fragment.chatroom.dialog.UsersOfRoomDialogFragment;
import chat.rocket.android.helper.LoadMoreScrollListener;
import chat.rocket.android.helper.LogcatIfError;
import chat.rocket.android.helper.OnBackPressListener;
import chat.rocket.android.layouthelper.chatroom.MessageComposerManager;
import chat.rocket.android.layouthelper.chatroom.MessageListAdapter;
import chat.rocket.android.layouthelper.chatroom.PairedMessage;
import chat.rocket.android.model.ServerConfig;
import chat.rocket.android.model.SyncState;
import chat.rocket.android.model.ddp.Message;
import chat.rocket.android.model.ddp.RoomSubscription;
import chat.rocket.android.model.internal.LoadMessageProcedure;
import chat.rocket.android.realm_helper.RealmHelper;
import chat.rocket.android.realm_helper.RealmModelListAdapter;
import chat.rocket.android.realm_helper.RealmObjectObserver;
import chat.rocket.android.realm_helper.RealmStore;
import chat.rocket.android.service.RocketChatService;
import chat.rocket.android.widget.message.MessageComposer;
import com.jakewharton.rxbinding.support.v4.widget.RxDrawerLayout;
import io.realm.Sort;
import java.lang.reflect.Field;
import java.util.UUID;
import org.json.JSONObject;
import timber.log.Timber;
/**
* Chat room screen.
*/
public class RoomFragment extends AbstractChatRoomFragment implements OnBackPressListener {
public class RoomFragment extends AbstractChatRoomFragment
implements OnBackPressListener, RealmModelListAdapter.OnItemClickListener<PairedMessage> {
private String serverConfigId;
private RealmHelper realmHelper;
......@@ -41,6 +50,7 @@ public class RoomFragment extends AbstractChatRoomFragment implements OnBackPres
private String hostname;
private LoadMoreScrollListener scrollListener;
private RealmObjectObserver<LoadMessageProcedure> procedureObserver;
private MessageComposerManager messageComposerManager;
/**
* create fragment with roomId.
......@@ -88,12 +98,14 @@ public class RoomFragment extends AbstractChatRoomFragment implements OnBackPres
@Override protected void onSetupView() {
RecyclerView listView = (RecyclerView) rootView.findViewById(R.id.recyclerview);
listView.setAdapter(realmHelper.createListAdapter(getContext(),
MessageListAdapter adapter = (MessageListAdapter) realmHelper.createListAdapter(getContext(),
realm -> realm.where(Message.class)
.equalTo("rid", roomId)
.findAllSorted("ts", Sort.DESCENDING),
context -> new MessageListAdapter(context, hostname)
));
);
listView.setAdapter(adapter);
adapter.setOnItemClickListener(this);
LinearLayoutManager layoutManager = new LinearLayoutManager(getContext(),
LinearLayoutManager.VERTICAL, true);
......@@ -107,6 +119,33 @@ public class RoomFragment extends AbstractChatRoomFragment implements OnBackPres
listView.addOnScrollListener(scrollListener);
setupSideMenu();
setupMessageComposer();
}
@Override public void onItemClick(PairedMessage pairedMessage) {
if (pairedMessage.target != null) {
final int syncstate = pairedMessage.target.getSyncstate();
if (syncstate == SyncState.FAILED) {
final String messageId = pairedMessage.target.get_id();
new AlertDialog.Builder(getContext())
.setPositiveButton(R.string.resend, (dialog, which) -> {
realmHelper.executeTransaction(realm ->
realm.createOrUpdateObjectFromJson(Message.class, new JSONObject()
.put("_id", messageId)
.put("syncstate", SyncState.NOT_SYNCED))
).continueWith(new LogcatIfError());
})
.setNegativeButton(android.R.string.cancel, null)
.setNeutralButton(R.string.discard, (dialog, which) -> {
realmHelper.executeTransaction(realm ->
realm.where(Message.class)
.equalTo("_id", messageId).findAll().deleteAllFromRealm()
).continueWith(new LogcatIfError());;
})
.show();
}
}
}
private void setupSideMenu() {
......@@ -128,7 +167,7 @@ public class RoomFragment extends AbstractChatRoomFragment implements OnBackPres
fieldSlidable.setAccessible(true);
fieldSlidable.setBoolean(pane, !opened);
} catch (Exception exception) {
Timber.w(exception);
Timber.w(exception, "failed to set CanSlide.");
}
});
}
......@@ -143,7 +182,27 @@ public class RoomFragment extends AbstractChatRoomFragment implements OnBackPres
return false;
}
private void setupMessageComposer() {
final FloatingActionButton fabCompose =
(FloatingActionButton) rootView.findViewById(R.id.fab_compose);
final MessageComposer messageComposer =
(MessageComposer) rootView.findViewById(R.id.message_composer);
messageComposerManager = new MessageComposerManager(fabCompose, messageComposer);
messageComposerManager.setCallback(messageText ->
realmHelper.executeTransaction(realm ->
realm.createOrUpdateObjectFromJson(Message.class, new JSONObject()
.put("_id", UUID.randomUUID().toString())
.put("syncstate", SyncState.NOT_SYNCED)
.put("ts", System.currentTimeMillis())
.put("rid", roomId)
.put("msg", messageText))));
}
private void onRenderRoom(RoomSubscription roomSubscription) {
if (roomSubscription == null) {
return;
}
String type = roomSubscription.getT();
if (RoomSubscription.TYPE_CHANNEL.equals(type)) {
activityToolbar.setNavigationIcon(R.drawable.ic_hashtag_white_24dp);
......@@ -211,11 +270,21 @@ public class RoomFragment extends AbstractChatRoomFragment implements OnBackPres
}).continueWith(new LogcatIfError());
}
private void markAsReadIfNeeded() {
RoomSubscription room = realmHelper.executeTransactionForRead(realm ->
realm.where(RoomSubscription.class).equalTo("rid", roomId).findFirst());
if (room != null && room.isAlert()) {
new MethodCallHelper(getContext(), serverConfigId).readMessages(roomId)
.continueWith(new LogcatIfError());
}
}
@Override public void onResume() {
super.onResume();
roomObserver.sub();
procedureObserver.sub();
closeSideMenuIfNeeded();
markAsReadIfNeeded();
}
@Override public void onPause() {
......@@ -225,6 +294,6 @@ public class RoomFragment extends AbstractChatRoomFragment implements OnBackPres
}
@Override public boolean onBackPressed() {
return closeSideMenuIfNeeded();
return closeSideMenuIfNeeded() || messageComposerManager.hideMessageComposerIfNeeded();
}
}
package chat.rocket.android.fragment.oauth;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Base64;
import android.webkit.JavascriptInterface;
import android.webkit.WebView;
import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.android.fragment.AbstractWebViewFragment;
import chat.rocket.android.helper.LogcatIfError;
import chat.rocket.android.model.ServerConfig;
import chat.rocket.android.model.ddp.MeteorLoginServiceConfiguration;
import chat.rocket.android.realm_helper.RealmStore;
import java.nio.charset.Charset;
import org.json.JSONException;
import org.json.JSONObject;
import timber.log.Timber;
public abstract class AbstractOAuthFragment extends AbstractWebViewFragment {
protected String serverConfigId;
protected String hostname;
private String url;
private boolean resultOK;
protected abstract String getOAuthServiceName();
protected abstract String generateURL(MeteorLoginServiceConfiguration oauthConfig);
private boolean hasValidArgs(Bundle args) {
return args != null
&& args.containsKey("serverConfigId");
}
protected final String getStateString() {
try {
return Base64.encodeToString(new JSONObject().put("loginStyle", "popup")
.put("credentialToken", getOAuthServiceName() + System.currentTimeMillis())
.put("isCordova", true)
.toString()
.getBytes(Charset.forName("UTF-8")), Base64.NO_WRAP);
} catch (JSONException exception) {
throw new RuntimeException(exception);
}
}
@Override public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle args = getArguments();
if (!hasValidArgs(args)) {
throw new IllegalArgumentException(
"serverConfigId required");
}
serverConfigId = args.getString("serverConfigId");
ServerConfig serverConfig = RealmStore.getDefault().executeTransactionForRead(realm ->
realm.where(ServerConfig.class).equalTo("serverConfigId", serverConfigId).findFirst());
MeteorLoginServiceConfiguration oauthConfig =
RealmStore.get(serverConfigId).executeTransactionForRead(realm ->
realm.where(MeteorLoginServiceConfiguration.class)
.equalTo("service", getOAuthServiceName())
.findFirst());
if (serverConfig == null || oauthConfig == null) {
throw new IllegalArgumentException(
"Invalid serverConfigId given,");
}
hostname = serverConfig.getHostname();
url = generateURL(oauthConfig);
}
@Override protected void navigateToInitialPage(WebView webview) {
if (TextUtils.isEmpty(url)) {
finish();
return;
}
resultOK = false;
webview.loadUrl(url);
webview.addJavascriptInterface(new JSInterface(result -> {
// onPageFinish is called twice... Should ignore latter one.
if (resultOK) {
return;
}
if (result != null && result.optBoolean("setCredentialToken", false)) {
try {
final String credentialToken = result.getString("credentialToken");
final String credentialSecret = result.getString("credentialSecret");
handleOAuthCallback(credentialToken, credentialSecret);
resultOK = true;
} catch (JSONException exception) {
Timber.e(exception, "failed to parse OAuth result.");
}
}
onOAuthCompleted();
}), "_rocketchet_hook");
}
@Override protected void onPageLoaded(WebView webview, String url) {
super.onPageLoaded(webview, url);
if (url.contains(hostname) && url.contains("_oauth/" + getOAuthServiceName() + "?close")) {
final String jsHookUrl = "javascript:"
+ "window._rocketchet_hook.handleConfig(document.getElementById('config').innerText);";
webview.loadUrl(jsHookUrl);
}
}
private interface JSInterfaceCallback {
void hanldeResult(@Nullable JSONObject result);
}
private static final class JSInterface {
private final JSInterfaceCallback jsInterfaceCallback;
JSInterface(JSInterfaceCallback callback) {
jsInterfaceCallback = callback;
}
@JavascriptInterface public void handleConfig(String config) {
try {
jsInterfaceCallback.hanldeResult(new JSONObject(config));
} catch (Exception exception) {
jsInterfaceCallback.hanldeResult(null);
}
}
}
private void handleOAuthCallback(final String credentialToken, final String credentialSecret) {
new MethodCallHelper(getContext(), serverConfigId)
.loginWithOAuth(credentialToken, credentialSecret)
.continueWith(new LogcatIfError());
}
protected void onOAuthCompleted() {
}
}
package chat.rocket.android.fragment.oauth;
import chat.rocket.android.model.ddp.MeteorLoginServiceConfiguration;
import okhttp3.HttpUrl;
public class FacebookOAuthFragment extends AbstractOAuthFragment {
@Override protected String getOAuthServiceName() {
return "facebook";
}
@Override protected String generateURL(MeteorLoginServiceConfiguration oauthConfig) {
return new HttpUrl.Builder().scheme("https")
.host("www.facebook.com")
.addPathSegment("v2.2")
.addPathSegment("dialog")
.addPathSegment("oauth")
.addQueryParameter("client_id", oauthConfig.getAppId())
.addQueryParameter("redirect_uri", "https://" + hostname + "/_oauth/facebook?close")
.addQueryParameter("display", "popup")
.addQueryParameter("scope", "email")
.addQueryParameter("state", getStateString())
.build()
.toString();
}
}
package chat.rocket.android.fragment.oauth;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Base64;
import android.webkit.JavascriptInterface;
import android.webkit.WebView;
import chat.rocket.android.fragment.AbstractWebViewFragment;
import chat.rocket.android.helper.LogcatIfError;
import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.android.model.ServerConfig;
import chat.rocket.android.model.ddp.MeteorLoginServiceConfiguration;
import chat.rocket.android.realm_helper.RealmStore;
import java.nio.charset.Charset;
import okhttp3.HttpUrl;
import org.json.JSONException;
import org.json.JSONObject;
import timber.log.Timber;
public class GitHubOAuthFragment extends AbstractWebViewFragment {
public class GitHubOAuthFragment extends AbstractOAuthFragment {
private String serverConfigId;
private String hostname;
private String url;
private boolean resultOK;
/**
* create new Fragment with ServerConfig-ID.
*/
public static GitHubOAuthFragment create(final String serverConfigId) {
Bundle args = new Bundle();
args.putString("serverConfigId", serverConfigId);
GitHubOAuthFragment fragment = new GitHubOAuthFragment();
fragment.setArguments(args);
return fragment;
}
private boolean hasValidArgs(Bundle args) {
return args != null
&& args.containsKey("serverConfigId");
}
@Override public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle args = getArguments();
if (!hasValidArgs(args)) {
throw new IllegalArgumentException(
"serverConfigId required");
}
serverConfigId = args.getString("serverConfigId");
ServerConfig serverConfig = RealmStore.getDefault().executeTransactionForRead(realm ->
realm.where(ServerConfig.class).equalTo("serverConfigId", serverConfigId).findFirst());
MeteorLoginServiceConfiguration oauthConfig =
RealmStore.get(serverConfigId).executeTransactionForRead(realm ->
realm.where(MeteorLoginServiceConfiguration.class)
.equalTo("service", "github")
.findFirst());
if (serverConfig == null || oauthConfig == null) {
throw new IllegalArgumentException(
"Invalid serverConfigId given,");
}
hostname = serverConfig.getHostname();
url = generateURL(oauthConfig.getClientId());
}
private String generateURL(String clientId) {
try {
String state = Base64.encodeToString(new JSONObject().put("loginStyle", "popup")
.put("credentialToken", "github" + System.currentTimeMillis())
.put("isCordova", true)
.toString()
.getBytes(Charset.forName("UTF-8")), Base64.NO_WRAP);
return new HttpUrl.Builder().scheme("https")
.host("github.com")
.addPathSegment("login")
.addPathSegment("oauth")
.addPathSegment("authorize")
.addQueryParameter("client_id", clientId)
.addQueryParameter("scope", "user:email")
.addQueryParameter("state", state)
.build()
.toString();
} catch (Exception exception) {
Timber.e(exception, "failed to generate GitHub OAUth URL");
}
return null;
}
@Override protected void navigateToInitialPage(WebView webview) {
if (TextUtils.isEmpty(url)) {
finish();
return;
}
resultOK = false;
webview.loadUrl(url);
webview.addJavascriptInterface(new JSInterface(result -> {
// onPageFinish is called twice... Should ignore latter one.
if (resultOK) {
return;
}
if (result != null && result.optBoolean("setCredentialToken", false)) {
try {
final String credentialToken = result.getString("credentialToken");
final String credentialSecret = result.getString("credentialSecret");
handleOAuthCallback(credentialToken, credentialSecret);
resultOK = true;
} catch (JSONException exception) {
Timber.e(exception, "failed to parse OAuth result.");
}
}
onOAuthCompleted();
}), "_rocketchet_hook");
}
@Override protected void onPageLoaded(WebView webview, String url) {
super.onPageLoaded(webview, url);
if (url.contains(hostname) && url.contains("_oauth/github?close")) {
final String jsHookUrl = "javascript:"
+ "window._rocketchet_hook.handleConfig(document.getElementById('config').innerText);";
webview.loadUrl(jsHookUrl);
}
}
private interface JSInterfaceCallback {
void hanldeResult(@Nullable JSONObject result);
@Override protected String getOAuthServiceName() {
return "github";
}
private static final class JSInterface {
private final JSInterfaceCallback jsInterfaceCallback;
JSInterface(JSInterfaceCallback callback) {
jsInterfaceCallback = callback;
}
@JavascriptInterface public void handleConfig(String config) {
try {
jsInterfaceCallback.hanldeResult(new JSONObject(config));
} catch (Exception exception) {
jsInterfaceCallback.hanldeResult(null);
}
}
}
private void handleOAuthCallback(final String credentialToken, final String credentialSecret) {
new MethodCallHelper(getContext(), serverConfigId)
.loginWithGitHub(credentialToken, credentialSecret)
.continueWith(new LogcatIfError());
}
private void onOAuthCompleted() {
@Override protected String generateURL(MeteorLoginServiceConfiguration oauthConfig) {
return new HttpUrl.Builder().scheme("https")
.host("github.com")
.addPathSegment("login")
.addPathSegment("oauth")
.addPathSegment("authorize")
.addQueryParameter("client_id", oauthConfig.getClientId())
.addQueryParameter("scope", "user:email")
.addQueryParameter("state", getStateString())
.build()
.toString();
}
}
package chat.rocket.android.fragment.oauth;
import chat.rocket.android.model.ddp.MeteorLoginServiceConfiguration;
import okhttp3.HttpUrl;
public class GoogleOAuthFragment extends AbstractOAuthFragment {
@Override protected String getOAuthServiceName() {
return "google";
}
@Override protected String generateURL(MeteorLoginServiceConfiguration oauthConfig) {
return new HttpUrl.Builder().scheme("https")
.host("accounts.google.com")
.addPathSegment("o")
.addPathSegment("oauth2")
.addPathSegment("auth")
.addQueryParameter("response_type", "code")
.addQueryParameter("client_id", oauthConfig.getClientId())
.addQueryParameter("scope", "profile email")
.addQueryParameter("redirect_uri", "https://" + hostname + "/_oauth/google?close")
.addQueryParameter("state", getStateString())
.build()
.toString();
}
}
package chat.rocket.android.fragment.oauth;
import chat.rocket.android.model.ddp.MeteorLoginServiceConfiguration;
public class TwitterOAuthFragment extends AbstractOAuthFragment {
@Override protected String getOAuthServiceName() {
return "twitter";
}
@Override protected String generateURL(MeteorLoginServiceConfiguration oauthConfig) {
return "https://" + hostname + "/_oauth/twitter/"
+ "?requestTokenAndRedirect=true&state=" + getStateString();
}
}
......@@ -49,7 +49,6 @@ public class InputHostnameFragment extends AbstractServerConfigFragment {
@Override public void onResume() {
super.onResume();
serverConfigObserver.keepalive();
}
@Override public void onDestroyView() {
......
......@@ -3,16 +3,19 @@ package chat.rocket.android.fragment.server_config;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.support.v4.app.Fragment;
import android.view.View;
import android.widget.TextView;
import chat.rocket.android.R;
import chat.rocket.android.fragment.oauth.GitHubOAuthFragment;
import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.layouthelper.oauth.OAuthProviderInfo;
import chat.rocket.android.model.ddp.MeteorLoginServiceConfiguration;
import chat.rocket.android.realm_helper.RealmListObserver;
import chat.rocket.android.realm_helper.RealmStore;
import java.util.HashMap;
import java.util.List;
import timber.log.Timber;
/**
* Login screen.
......@@ -67,30 +70,42 @@ public class LoginFragment extends AbstractServerConfigFragment {
}
private void onRenderAuthProviders(List<MeteorLoginServiceConfiguration> authProviders) {
final View btnTwitter = rootView.findViewById(R.id.btn_login_with_twitter);
final View btnGitHub = rootView.findViewById(R.id.btn_login_with_github);
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);
}
boolean hasTwitter = false;
boolean hasGitHub = false;
for (MeteorLoginServiceConfiguration authProvider : authProviders) {
if (!hasTwitter
&& "twitter".equals(authProvider.getService())) {
hasTwitter = true;
btnTwitter.setOnClickListener(view -> {
});
}
if (!hasGitHub
&& "github".equals(authProvider.getService())) {
hasGitHub = true;
btnGitHub.setOnClickListener(view -> {
showFragmentWithBackStack(GitHubOAuthFragment.create(serverConfigId));
});
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 (java.lang.InstantiationException | IllegalAccessException exception) {
Timber.w(exception, "failed to create new Fragment");
}
if (fragment != null) {
Bundle args = new Bundle();
args.putString("serverConfigId", serverConfigId);
fragment.setArguments(args);
showFragmentWithBackStack(fragment);
}
});
viewMap.get(info.serviceName).setVisibility(View.VISIBLE);
}
}
}
btnTwitter.setVisibility(hasTwitter ? View.VISIBLE : View.GONE);
btnGitHub.setVisibility(hasGitHub ? View.VISIBLE : View.GONE);
for (OAuthProviderInfo info : OAuthProviderInfo.LIST) {
if (!supportedMap.get(info.serviceName)) {
viewMap.get(info.serviceName).setVisibility(View.GONE);
}
}
}
@Override public void onResume() {
......
......@@ -67,7 +67,7 @@ public class SidebarMainFragment extends AbstractFragment {
.setOnUpdateListener(list -> roomListManager.setRooms(list));
currentUserObserver = realmHelper
.createObjectObserver(realm -> realm.where(User.class).isNotEmpty("emails"))
.createObjectObserver(User::queryCurrentUser)
.setOnUpdateListener(this::onRenderCurrentUser);
methodCallHelper = new MethodCallHelper(getContext(), serverConfigId);
......
package chat.rocket.android.layouthelper.chatroom;
import android.support.design.widget.FloatingActionButton;
import bolts.Task;
import chat.rocket.android.widget.message.MessageComposer;
/**
* handling visibility of FAB-compose and MessageComposer.
*/
public class MessageComposerManager {
public interface Callback {
Task<Void> onSubmit(String messageText);
}
private final FloatingActionButton fabCompose;
private final MessageComposer messageComposer;
private Callback callback;
public MessageComposerManager(FloatingActionButton fabCompose, MessageComposer messageComposer) {
this.fabCompose = fabCompose;
this.messageComposer = messageComposer;
init();
}
private void init() {
fabCompose.setOnClickListener(view -> {
setMessageComposerVisibility(true);
});
messageComposer.setOnActionListener(new MessageComposer.ActionListener() {
@Override public void onSubmit(String message) {
if (callback != null) {
messageComposer.setEnabled(false);
callback.onSubmit(message).onSuccess(task -> {
clearComposingText();
return null;
}).continueWith(task -> {
messageComposer.setEnabled(true);
return null;
});
}
}
@Override public void onCancel() {
setMessageComposerVisibility(false);
}
});
setMessageComposerVisibility(false);
}
public void setCallback(Callback callback) {
this.callback = callback;
}
public void clearComposingText() {
messageComposer.setText("");
}
private void setMessageComposerVisibility(boolean show) {
if (show) {
fabCompose.hide();
messageComposer.show(null);
} else {
messageComposer.hide(fabCompose::show);
}
}
public boolean hideMessageComposerIfNeeded() {
if (messageComposer.isShown()) {
setMessageComposerVisibility(false);
return true;
}
return false;
}
}
......@@ -7,6 +7,7 @@ import android.widget.TextView;
import chat.rocket.android.R;
import chat.rocket.android.helper.DateTime;
import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.model.SyncState;
import chat.rocket.android.realm_helper.RealmModelViewHolder;
import chat.rocket.android.renderer.MessageRenderer;
import chat.rocket.android.widget.message.RocketChatMessageLayout;
......@@ -49,6 +50,15 @@ public class MessageViewHolder extends RealmModelViewHolder<PairedMessage> {
.timestampInto(timestamp)
.bodyInto(body);
if (pairedMessage.target != null) {
int syncstate = pairedMessage.target.getSyncstate();
if (syncstate == SyncState.NOT_SYNCED || syncstate == SyncState.SYNCING) {
itemView.setAlpha(0.6f);
} else {
itemView.setAlpha(1.0f);
}
}
renderNewDayAndSequential(pairedMessage);
}
......
......@@ -7,7 +7,7 @@ import chat.rocket.android.model.ddp.Message;
* View Model for messages in chatroom.
*/
public class PairedMessage {
final Message target;
public final Message target;
final Message nextSibling;
public PairedMessage(Message target, Message nextSibling) {
......
package chat.rocket.android.layouthelper.oauth;
import chat.rocket.android.R;
import chat.rocket.android.fragment.oauth.AbstractOAuthFragment;
import chat.rocket.android.fragment.oauth.FacebookOAuthFragment;
import chat.rocket.android.fragment.oauth.GitHubOAuthFragment;
import chat.rocket.android.fragment.oauth.GoogleOAuthFragment;
import chat.rocket.android.fragment.oauth.TwitterOAuthFragment;
import java.util.ArrayList;
public class OAuthProviderInfo {
public String serviceName;
public int buttonId;
public Class<? extends AbstractOAuthFragment> fragmentClass;
public OAuthProviderInfo(String serviceName, int buttonId,
Class<? extends AbstractOAuthFragment> fragmentClass) {
this.serviceName = serviceName;
this.buttonId = buttonId;
this.fragmentClass = fragmentClass;
}
public static ArrayList<OAuthProviderInfo> LIST = new ArrayList<OAuthProviderInfo>() {
{
add(new OAuthProviderInfo(
"twitter", R.id.btn_login_with_twitter, TwitterOAuthFragment.class));
add(new OAuthProviderInfo(
"github", R.id.btn_login_with_github, GitHubOAuthFragment.class));
add(new OAuthProviderInfo(
"google", R.id.btn_login_with_google, GoogleOAuthFragment.class));
add(new OAuthProviderInfo(
"facebook", R.id.btn_login_with_facebook, FacebookOAuthFragment.class));
}
};
}
package chat.rocket.android.model.ddp;
import io.realm.Realm;
import io.realm.RealmList;
import io.realm.RealmObject;
import io.realm.RealmQuery;
import io.realm.annotations.PrimaryKey;
/**
......@@ -60,4 +62,8 @@ public class User extends RealmObject {
public void setEmails(RealmList<Email> emails) {
this.emails = emails;
}
public static RealmQuery<User> queryCurrentUser(Realm realm) {
return realm.where(User.class).isNotEmpty("emails");
}
}
......@@ -12,6 +12,7 @@ import chat.rocket.android.realm_helper.RealmObjectObserver;
import chat.rocket.android.service.RocketChatService;
import io.realm.RealmObject;
import io.realm.annotations.PrimaryKey;
import java.util.HashMap;
import java.util.UUID;
import org.json.JSONObject;
import timber.log.Timber;
......@@ -89,6 +90,8 @@ public class MethodCall extends RealmObject {
}
}
private static final HashMap<String, RealmObjectObserver<MethodCall>> refMap = new HashMap<>();
/**
* insert a new record to request a method call.
*/
......@@ -122,14 +125,17 @@ public class MethodCall extends RealmObject {
task.setResult(resultJson);
}
observer.unsub();
refMap.remove(methodCall.getMethodCallId());
remove(realmHelper, methodCall.getMethodCallId()).continueWith(new LogcatIfError());
} else if (syncstate == SyncState.FAILED) {
task.setError(new Error(methodCall.getResultJson()));
observer.unsub();
refMap.remove(methodCall.getMethodCallId());
remove(realmHelper, methodCall.getMethodCallId()).continueWith(new LogcatIfError());
}
});
observer.sub();
refMap.put(newId, observer);
if (context != null) {
RocketChatService.keepalive(context);
......
......@@ -3,7 +3,9 @@ package chat.rocket.android.renderer;
import android.content.Context;
import android.widget.ImageView;
import android.widget.TextView;
import chat.rocket.android.R;
import chat.rocket.android.helper.DateTime;
import chat.rocket.android.model.SyncState;
import chat.rocket.android.model.ddp.Message;
import chat.rocket.android.widget.message.RocketChatMessageLayout;
......@@ -23,7 +25,14 @@ public class MessageRenderer extends AbstractRenderer<Message> {
* show Avatar image.
*/
public MessageRenderer avatarInto(ImageView imageView, String hostname) {
userRenderer.avatarInto(imageView, hostname);
switch (object.getSyncstate()) {
case SyncState.FAILED:
imageView.setImageResource(R.drawable.ic_error_outline_black_24dp);
break;
default:
userRenderer.avatarInto(imageView, hostname);
break;
}
return this;
}
......@@ -43,7 +52,15 @@ public class MessageRenderer extends AbstractRenderer<Message> {
return this;
}
textView.setText(DateTime.fromEpocMs(object.getTs(), DateTime.Format.TIME));
switch (object.getSyncstate()) {
case SyncState.NOT_SYNCED:
case SyncState.SYNCING:
textView.setText(R.string.sending);
break;
default:
textView.setText(DateTime.fromEpocMs(object.getTs(), DateTime.Format.TIME));
break;
}
return this;
}
......
......@@ -9,11 +9,6 @@ public interface Registerable {
*/
void register();
/**
* keepalive.
*/
void keepalive();
/**
* unregister.
*/
......
......@@ -88,7 +88,7 @@ public class RocketChatService extends Service {
}
return null;
}).onSuccessTask(task -> {
connectionRequiredServerConfigObserver.keepalive();
connectionRequiredServerConfigObserver.sub();
return null;
});
return START_STICKY;
......@@ -133,6 +133,13 @@ public class RocketChatService extends Service {
}
}
@Override public void onDestroy() {
if (connectionRequiredServerConfigObserver != null) {
connectionRequiredServerConfigObserver.unsub();
}
super.onDestroy();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
......
......@@ -13,11 +13,14 @@ import chat.rocket.android.model.ServerConfig;
import chat.rocket.android.model.internal.Session;
import chat.rocket.android.realm_helper.RealmHelper;
import chat.rocket.android.realm_helper.RealmStore;
import chat.rocket.android.service.ddp.ActiveUsersSubscriber;
import chat.rocket.android.service.ddp.LoginServiceConfigurationSubscriber;
import chat.rocket.android.service.ddp.base.ActiveUsersSubscriber;
import chat.rocket.android.service.ddp.base.LoginServiceConfigurationSubscriber;
import chat.rocket.android.service.ddp.base.UserDataSubscriber;
import chat.rocket.android.service.observer.CurrentUserObserver;
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.SessionObserver;
import chat.rocket.android.service.observer.TokenLoginObserver;
import chat.rocket.android_ddp.DDPClientCallback;
......@@ -35,11 +38,14 @@ public class RocketChatWebSocketThread extends HandlerThread {
private static final Class[] REGISTERABLE_CLASSES = {
LoginServiceConfigurationSubscriber.class,
ActiveUsersSubscriber.class,
UserDataSubscriber.class,
TokenLoginObserver.class,
MethodCallObserver.class,
SessionObserver.class,
LoadMessageProcedureObserver.class,
GetUsersOfRoomsProcedureObserver.class
GetUsersOfRoomsProcedureObserver.class,
NewMessageObserver.class,
CurrentUserObserver.class
};
private final Context appContext;
private final String serverConfigId;
......@@ -132,8 +138,6 @@ public class RocketChatWebSocketThread extends HandlerThread {
}
return null;
});
} else {
new Handler(getLooper()).post(this::keepaliveListeners);
}
}
......@@ -225,17 +229,6 @@ public class RocketChatWebSocketThread extends HandlerThread {
}
}
//@DebugLog
private void keepaliveListeners() {
if (!listenersRegistered) {
return;
}
for (Registerable registerable : listeners) {
registerable.keepalive();
}
}
@DebugLog
private void unregisterListeners() {
if (!listenersRegistered) {
......
......@@ -2,20 +2,21 @@ package chat.rocket.android.service.ddp;
import android.content.Context;
import android.text.TextUtils;
import chat.rocket.android.api.DDPClientWraper;
import chat.rocket.android.helper.LogcatIfError;
import chat.rocket.android.realm_helper.RealmHelper;
import chat.rocket.android.service.Registerable;
import chat.rocket.android.api.DDPClientWraper;
import chat.rocket.android_ddp.DDPSubscription;
import io.realm.Realm;
import io.realm.RealmObject;
import java.util.Iterator;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import rx.Subscription;
import timber.log.Timber;
abstract class AbstractDDPDocEventSubscriber implements Registerable {
public abstract class AbstractDDPDocEventSubscriber implements Registerable {
protected final Context context;
protected final RealmHelper realmHelper;
protected final DDPClientWraper ddpClient;
......@@ -31,16 +32,33 @@ abstract class AbstractDDPDocEventSubscriber implements Registerable {
protected abstract String getSubscriptionName();
protected abstract String getSubscriptionCallbackName();
protected abstract JSONArray getSubscriptionParams() throws JSONException;
protected boolean shouldTruncateTableOnInitialize() {
return false;
}
protected abstract boolean isTarget(String callbackName);
protected abstract Class<? extends RealmObject> getModelClass();
protected JSONObject customizeFieldJson(JSONObject json) {
protected JSONObject customizeFieldJson(JSONObject json) throws JSONException {
return json;
}
@Override public void register() {
ddpClient.subscribe(getSubscriptionName(), null).onSuccess(task -> {
protected void onRegister() {}
protected void onUnregister() {}
@Override public final void register() {
JSONArray params = null;
try {
params = getSubscriptionParams();
} catch (JSONException exception) {
// just ignore.
}
ddpClient.subscribe(getSubscriptionName(), params).onSuccess(task -> {
subscriptionId = task.getResult().id;
return null;
}).continueWith(task -> {
......@@ -50,20 +68,25 @@ abstract class AbstractDDPDocEventSubscriber implements Registerable {
return null;
});
realmHelper.executeTransaction(realm -> {
realm.delete(getModelClass());
return null;
}).onSuccess(task -> {
registerSubscriptionCallback();
return null;
}).continueWith(new LogcatIfError());
if (shouldTruncateTableOnInitialize()) {
realmHelper.executeTransaction(realm -> {
realm.delete(getModelClass());
return null;
}).onSuccess(task -> {
rxSubscription = subscribe();
return null;
}).continueWith(new LogcatIfError());
} else {
rxSubscription = subscribe();
}
onRegister();
}
private void registerSubscriptionCallback() {
rxSubscription = ddpClient.getSubscriptionCallback()
protected Subscription subscribe() {
return ddpClient.getSubscriptionCallback()
.filter(event -> event instanceof DDPSubscription.DocEvent)
.cast(DDPSubscription.DocEvent.class)
.filter(event -> getSubscriptionCallbackName().equals(event.collection))
.filter(event -> isTarget(event.collection))
.subscribe(docEvent -> {
try {
if (docEvent instanceof DDPSubscription.Added.Before) {
......@@ -108,9 +131,11 @@ abstract class AbstractDDPDocEventSubscriber implements Registerable {
throws JSONException {
//executed in RealmTransaction
JSONObject json = new JSONObject().put("_id", docEvent.docID);
for (int i = 0; i < docEvent.cleared.length(); i++) {
String fieldToDelete = docEvent.cleared.getString(i);
json.put(fieldToDelete, JSONObject.NULL);
if (docEvent.cleared != null) {
for (int i = 0; i < docEvent.cleared.length(); i++) {
String fieldToDelete = docEvent.cleared.getString(i);
json.put(fieldToDelete, JSONObject.NULL);
}
}
mergeJson(json, docEvent.fields);
realm.createOrUpdateObjectFromJson(getModelClass(), customizeFieldJson(json));
......@@ -137,11 +162,8 @@ abstract class AbstractDDPDocEventSubscriber implements Registerable {
}
}
@Override public void keepalive() {
}
@Override public void unregister() {
@Override public final void unregister() {
onUnregister();
if (rxSubscription != null) {
rxSubscription.unsubscribe();
}
......
package chat.rocket.android.service.ddp.base;
import android.content.Context;
import chat.rocket.android.api.DDPClientWraper;
import chat.rocket.android.realm_helper.RealmHelper;
import chat.rocket.android.service.ddp.AbstractDDPDocEventSubscriber;
import org.json.JSONArray;
abstract class AbstractBaseSubscriber extends AbstractDDPDocEventSubscriber {
protected AbstractBaseSubscriber(Context context, RealmHelper realmHelper,
DDPClientWraper ddpClient) {
super(context, realmHelper, ddpClient);
}
@Override protected final JSONArray getSubscriptionParams() {
return null;
}
@Override protected final boolean shouldTruncateTableOnInitialize() {
return true;
}
protected abstract String getSubscriptionCallbackName();
@Override protected final boolean isTarget(String callbackName) {
return getSubscriptionCallbackName().equals(callbackName);
}
}
package chat.rocket.android.service.ddp;
package chat.rocket.android.service.ddp.base;
import android.content.Context;
import chat.rocket.android.model.ddp.User;
......@@ -9,7 +9,7 @@ import io.realm.RealmObject;
/**
* "activeUsers" subscriber.
*/
public class ActiveUsersSubscriber extends AbstractDDPDocEventSubscriber {
public class ActiveUsersSubscriber extends AbstractBaseSubscriber {
public ActiveUsersSubscriber(Context context, RealmHelper realmHelper,
DDPClientWraper ddpClient) {
super(context, realmHelper, ddpClient);
......
package chat.rocket.android.service.ddp;
package chat.rocket.android.service.ddp.base;
import android.content.Context;
import chat.rocket.android.model.ddp.MeteorLoginServiceConfiguration;
......@@ -9,7 +9,7 @@ import io.realm.RealmObject;
/**
* meteor.loginServiceConfiguration subscriber
*/
public class LoginServiceConfigurationSubscriber extends AbstractDDPDocEventSubscriber {
public class LoginServiceConfigurationSubscriber extends AbstractBaseSubscriber {
public LoginServiceConfigurationSubscriber(Context context, RealmHelper realmHelper,
DDPClientWraper ddpClient) {
super(context, realmHelper, ddpClient);
......
package chat.rocket.android.service.ddp.base;
import android.content.Context;
import chat.rocket.android.api.DDPClientWraper;
import chat.rocket.android.model.ddp.User;
import chat.rocket.android.realm_helper.RealmHelper;
import io.realm.RealmObject;
/**
* "userData" subscriber.
*/
public class UserDataSubscriber extends AbstractBaseSubscriber {
public UserDataSubscriber(Context context, RealmHelper realmHelper,
DDPClientWraper ddpClient) {
super(context, realmHelper, ddpClient);
}
@Override protected String getSubscriptionName() {
return "userData";
}
@Override protected String getSubscriptionCallbackName() {
return "users";
}
@Override protected Class<? extends RealmObject> getModelClass() {
return User.class;
}
}
package chat.rocket.android.service.ddp.stream;
import android.content.Context;
import chat.rocket.android.api.DDPClientWraper;
import chat.rocket.android.helper.LogcatIfError;
import chat.rocket.android.realm_helper.RealmHelper;
import chat.rocket.android.service.ddp.AbstractDDPDocEventSubscriber;
import chat.rocket.android_ddp.DDPSubscription;
import org.json.JSONArray;
import org.json.JSONObject;
import timber.log.Timber;
abstract class AbstractStreamNotifyEventSubscriber extends AbstractDDPDocEventSubscriber {
protected AbstractStreamNotifyEventSubscriber(Context context, RealmHelper realmHelper,
DDPClientWraper ddpClient) {
super(context, realmHelper, ddpClient);
}
@Override protected final boolean shouldTruncateTableOnInitialize() {
return false;
}
@Override protected final boolean isTarget(String callbackName) {
return getSubscriptionName().equals(callbackName);
}
protected abstract String getPrimaryKeyForModel();
@Override protected void onDocumentChanged(DDPSubscription.Changed docEvent) {
try {
JSONArray args = docEvent.fields.getJSONArray("args");
String msg = args.length() > 0 ? args.getString(0) : null;
JSONObject target = args.getJSONObject(args.length() - 1);
if ("removed".equals(msg)) {
realmHelper.executeTransaction(realm ->
realm.where(getModelClass())
.equalTo(getPrimaryKeyForModel(), target.getString(getPrimaryKeyForModel()))
.findAll().deleteAllFromRealm()
).continueWith(new LogcatIfError());
} else { //inserted, updated
realmHelper.executeTransaction(realm ->
realm.createOrUpdateObjectFromJson(getModelClass(), customizeFieldJson(target))
).continueWith(new LogcatIfError());
}
} catch (Exception exception) {
Timber.w(exception, "failed to save stream-notify event.");
}
}
}
package chat.rocket.android.service.ddp.stream;
import android.content.Context;
import chat.rocket.android.api.DDPClientWraper;
import chat.rocket.android.model.ddp.RoomSubscription;
import chat.rocket.android.realm_helper.RealmHelper;
import io.realm.RealmObject;
import org.json.JSONArray;
import org.json.JSONException;
public class StreamNotifyUserSubscriptionsChanged extends AbstractStreamNotifyEventSubscriber {
private final String userId;
public StreamNotifyUserSubscriptionsChanged(Context context, RealmHelper realmHelper,
DDPClientWraper ddpClient, String userId) {
super(context, realmHelper, ddpClient);
this.userId = userId;
}
@Override protected String getSubscriptionName() {
return "stream-notify-user";
}
@Override protected JSONArray getSubscriptionParams() throws JSONException {
return new JSONArray()
.put(userId + "/subscriptions-changed")
.put(false);
}
@Override protected Class<? extends RealmObject> getModelClass() {
return RoomSubscription.class;
}
@Override protected String getPrimaryKeyForModel() {
return "rid";
}
}
package chat.rocket.android.service.ddp.stream;
import android.content.Context;
import chat.rocket.android.api.DDPClientWraper;
import chat.rocket.android.model.ddp.Message;
import chat.rocket.android.realm_helper.RealmHelper;
import io.realm.RealmObject;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
/**
* stream-room-message subscriber.
*/
public class StreamRoomMessage extends AbstractStreamNotifyEventSubscriber {
private String roomId;
public StreamRoomMessage(Context context, RealmHelper realmHelper, DDPClientWraper ddpClient,
String roomId) {
super(context, realmHelper, ddpClient);
this.roomId = roomId;
}
@Override protected String getSubscriptionName() {
return "stream-room-messages";
}
@Override protected JSONArray getSubscriptionParams() throws JSONException {
return new JSONArray()
.put(roomId)
.put(false);
}
@Override protected Class<? extends RealmObject> getModelClass() {
return Message.class;
}
@Override protected String getPrimaryKeyForModel() {
return "_id";
}
@Override protected JSONObject customizeFieldJson(JSONObject json) throws JSONException {
return Message.customizeJson(super.customizeFieldJson(json));
}
}
package chat.rocket.android.service.internal;
import android.content.Context;
import android.content.SharedPreferences;
import chat.rocket.android.RocketChatCache;
import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.model.ddp.RoomSubscription;
import chat.rocket.android.realm_helper.RealmHelper;
import chat.rocket.android.service.Registerable;
public abstract class AbstractRocketChatCacheObserver implements Registerable {
private final Context context;
private final RealmHelper realmHelper;
private String roomId;
protected AbstractRocketChatCacheObserver(Context context, RealmHelper realmHelper) {
this.context = context;
this.realmHelper = realmHelper;
}
private void updateRoomIdWith(SharedPreferences prefs) {
String roomId = prefs.getString(RocketChatCache.KEY_SELECTED_ROOM_ID, null);
if (!TextUtils.isEmpty(roomId)) {
RoomSubscription room = realmHelper.executeTransactionForRead(realm ->
realm.where(RoomSubscription.class).equalTo("rid", roomId).findFirst());
if (room != null) {
if (this.roomId == null || !this.roomId.equals(roomId)) {
this.roomId = roomId;
onRoomIdUpdated(roomId);
}
return;
}
}
if (this.roomId != null) {
this.roomId = null;
onRoomIdUpdated(null);
}
}
protected abstract void onRoomIdUpdated(String roomId);
private SharedPreferences.OnSharedPreferenceChangeListener listener =
(prefs, key) -> {
if (RocketChatCache.KEY_SELECTED_ROOM_ID.equals(key)) {
updateRoomIdWith(prefs);
}
};
@Override public final void register() {
SharedPreferences prefs = RocketChatCache.get(context);
prefs.registerOnSharedPreferenceChangeListener(listener);
updateRoomIdWith(prefs);
}
@Override public final void unregister() {
RocketChatCache.get(context).unregisterOnSharedPreferenceChangeListener(listener);
}
}
package chat.rocket.android.service.internal;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import chat.rocket.android.api.DDPClientWraper;
import chat.rocket.android.realm_helper.RealmHelper;
import chat.rocket.android.service.Registerable;
import chat.rocket.android.service.ddp.stream.StreamRoomMessage;
/**
* wrapper for managing stream-notify-message depending on RocketChatCache.
*/
public class StreamRoomMessageManager implements Registerable {
private StreamRoomMessage streamRoomMessage;
private final Context context;
private final RealmHelper realmHelper;
private final DDPClientWraper ddpClient;
private final AbstractRocketChatCacheObserver cacheObserver;
private final Handler handler;
public StreamRoomMessageManager(Context context, RealmHelper realmHelper,
DDPClientWraper ddpClient) {
this.context = context;
this.realmHelper = realmHelper;
this.ddpClient = ddpClient;
cacheObserver = new AbstractRocketChatCacheObserver(context, 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, realmHelper, ddpClient, roomId);
streamRoomMessage.register();
});
}
private void unregisterStreamNotifyMessageIfNeeded() {
handler.post(() -> {
if (streamRoomMessage != null) {
streamRoomMessage.unregister();
streamRoomMessage = null;
}
});
}
@Override public void register() {
cacheObserver.register();
}
@Override public void unregister() {
unregisterStreamNotifyMessageIfNeeded();
cacheObserver.unregister();
}
}
......@@ -27,10 +27,6 @@ abstract class AbstractModelObserver<T extends RealmObject>
observer.sub();
}
@Override public void keepalive() {
observer.keepalive();
}
@Override public void unregister() {
observer.unsub();
}
......
package chat.rocket.android.service.observer;
import android.content.Context;
import chat.rocket.android.api.DDPClientWraper;
import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.android.model.ddp.User;
import chat.rocket.android.realm_helper.RealmHelper;
import chat.rocket.android.service.Registerable;
import chat.rocket.android.service.ddp.stream.StreamNotifyUserSubscriptionsChanged;
import hugo.weaving.DebugLog;
import io.realm.Realm;
import io.realm.RealmResults;
import java.util.ArrayList;
import java.util.List;
/**
* observe the user with emails.
*/
public class CurrentUserObserver extends AbstractModelObserver<User> {
private boolean currentUserExists;
private final MethodCallHelper methodCall;
private ArrayList<Registerable> listeners;
public CurrentUserObserver(Context context, RealmHelper realmHelper,
DDPClientWraper ddpClient) {
super(context, realmHelper, ddpClient);
methodCall = new MethodCallHelper(realmHelper, ddpClient);
currentUserExists = false;
}
@Override public RealmResults<User> queryItems(Realm realm) {
return User.queryCurrentUser(realm).findAll();
}
@Override public void onUpdateResults(List<User> results) {
boolean exists = !results.isEmpty();
if (currentUserExists != exists) {
if (exists) {
onLogin(results.get(0));
} else {
onLogout();
}
currentUserExists = exists;
}
}
@DebugLog
private void onLogin(User user) {
if (listeners != null) {
onLogout();
}
listeners = new ArrayList<>();
final String userId = user.get_id();
// get and observe Room subscriptions.
methodCall.getRoomSubscriptions().onSuccess(task -> {
Registerable listener = new StreamNotifyUserSubscriptionsChanged(
context, realmHelper, ddpClient, userId);
listener.register();
listeners.add(listener);
return null;
});
}
@DebugLog
private void onLogout() {
if (listeners != null) {
for (Registerable listener : listeners) {
listener.unregister();
}
}
listeners = null;
}
}
package chat.rocket.android.service.observer;
import android.content.Context;
import chat.rocket.android.api.DDPClientWraper;
import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.android.helper.LogcatIfError;
import chat.rocket.android.model.SyncState;
import chat.rocket.android.model.ddp.Message;
import chat.rocket.android.realm_helper.RealmHelper;
import io.realm.Realm;
import io.realm.RealmResults;
import java.util.List;
import org.json.JSONObject;
import timber.log.Timber;
/**
* Observe messages for sending.
*/
public class NewMessageObserver extends AbstractModelObserver<Message> {
private final MethodCallHelper methodCall;
public NewMessageObserver(Context context, RealmHelper realmHelper,
DDPClientWraper ddpClient) {
super(context, realmHelper, ddpClient);
methodCall = new MethodCallHelper(realmHelper, ddpClient);
realmHelper.executeTransaction(realm -> {
// resume pending operations.
RealmResults<Message> pendingMethodCalls = realm.where(Message.class)
.equalTo("syncstate", SyncState.SYNCING)
.findAll();
for (Message message : pendingMethodCalls) {
message.setSyncstate(SyncState.NOT_SYNCED);
}
return null;
}).continueWith(new LogcatIfError());
}
@Override public RealmResults<Message> queryItems(Realm realm) {
return realm.where(Message.class)
.equalTo("syncstate", SyncState.NOT_SYNCED)
.isNotNull("rid")
.findAll();
}
@Override public void onUpdateResults(List<Message> results) {
if (results.isEmpty()) {
return;
}
Message message = results.get(0);
final String messageId = message.get_id();
final String roomId = message.getRid();
final String msg = message.getMsg();
realmHelper.executeTransaction(realm ->
realm.createOrUpdateObjectFromJson(Message.class, new JSONObject()
.put("_id", messageId)
.put("syncstate", SyncState.SYNCING)
)
).onSuccessTask(task ->
methodCall.sendMessage(messageId, roomId, msg).onSuccessTask(_task -> {
JSONObject messageJson = _task.getResult();
messageJson.put("syncstate", SyncState.SYNCED);
return realmHelper.executeTransaction(realm ->
realm.createOrUpdateObjectFromJson(Message.class, messageJson));
})
).continueWith(task -> {
if (task.isFaulted()) {
Timber.w(task.getError());
realmHelper.executeTransaction(realm ->
realm.createOrUpdateObjectFromJson(Message.class, new JSONObject()
.put("_id", messageId)
.put("syncstate", SyncState.FAILED)));
}
return null;
});
}
}
......@@ -2,12 +2,13 @@ package chat.rocket.android.service.observer;
import android.content.Context;
import chat.rocket.android.api.DDPClientWraper;
import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.android.helper.LogcatIfError;
import chat.rocket.android.model.internal.GetUsersOfRoomsProcedure;
import chat.rocket.android.model.internal.LoadMessageProcedure;
import chat.rocket.android.model.internal.MethodCall;
import chat.rocket.android.model.internal.Session;
import chat.rocket.android.realm_helper.RealmHelper;
import chat.rocket.android.service.internal.StreamRoomMessageManager;
import hugo.weaving.DebugLog;
import io.realm.Realm;
import io.realm.RealmResults;
......@@ -17,16 +18,18 @@ import java.util.List;
* Observes user is logged into server.
*/
public class SessionObserver extends AbstractModelObserver<Session> {
private final MethodCallHelper methodCall;
private int count;
private final StreamRoomMessageManager streamNotifyMessage;
/**
* constructor.
*/
public SessionObserver(Context context, RealmHelper realmHelper, DDPClientWraper ddpClient) {
super(context, realmHelper, ddpClient);
methodCall = new MethodCallHelper(realmHelper, ddpClient);
count = 0;
streamNotifyMessage = new StreamRoomMessageManager(context, realmHelper, ddpClient);
}
@Override public RealmResults<Session> queryItems(Realm realm) {
......@@ -57,15 +60,17 @@ public class SessionObserver extends AbstractModelObserver<Session> {
}
@DebugLog private void onLogin() {
methodCall.getRooms().continueWith(new LogcatIfError());
streamNotifyMessage.register();
}
@DebugLog private void onLogout() {
streamNotifyMessage.unregister();
realmHelper.executeTransaction(realm -> {
// remove all tables. ONLY INTERNAL TABLES!.
realm.delete(MethodCall.class);
realm.delete(LoadMessageProcedure.class);
realm.delete(GetUsersOfRoomsProcedure.class);
return null;
}).continueWith(new LogcatIfError());
}
......
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
</vector>
<vector android:alpha="0.78" android:height="24dp"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M11,15h2v2h-2zM11,7h2v6h-2zM11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"/>
</vector>
......@@ -2,37 +2,32 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
android:layout_height="match_parent">
<include layout="@layout/sidebar"/>
<include layout="@layout/sidebar" />
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<android.support.design.widget.AppBarLayout
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
android:layout_height="match_parent">
<android.support.v7.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:title="@string/app_name"
/>
</android.support.design.widget.AppBarLayout>
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<FrameLayout
android:id="@+id/activity_main_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
></FrameLayout>
<android.support.v7.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:title="@string/app_name" />
</android.support.design.widget.AppBarLayout>
<FrameLayout
android:id="@+id/activity_main_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</android.support.design.widget.CoordinatorLayout>
</android.support.design.widget.CoordinatorLayout>
</LinearLayout>
\ No newline at end of file
......@@ -4,7 +4,7 @@
android:layout_height="match_parent"
android:orientation="horizontal">
<include layout="@layout/fragment_room_main"/>
<include layout="@layout/fragment_room_main" />
<include layout="@layout/room_side_menu"/>
<include layout="@layout/room_side_menu" />
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.SlidingPaneLayout
android:id="@+id/sliding_pane"
xmlns:android="http://schemas.android.com/apk/res/android"
<android.support.v4.widget.SlidingPaneLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/sliding_pane"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<include layout="@layout/sidebar"/>
android:layout_height="match_parent">
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<include layout="@layout/sidebar" />
<android.support.design.widget.AppBarLayout
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
android:layout_height="match_parent">
<android.support.v7.widget.Toolbar
android:id="@+id/activity_main_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:title="@string/app_name"
/>
</android.support.design.widget.AppBarLayout>
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<FrameLayout
android:id="@+id/activity_main_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:clickable="true"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
></FrameLayout>
<android.support.v7.widget.Toolbar
android:id="@+id/activity_main_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:title="@string/app_name" />
</android.support.design.widget.AppBarLayout>
<FrameLayout
android:id="@+id/activity_main_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:clickable="true"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</android.support.design.widget.CoordinatorLayout>
</android.support.design.widget.CoordinatorLayout>
</android.support.v4.widget.SlidingPaneLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:minWidth="288dp"
android:padding="@dimen/margin_24"
>
<android.support.design.widget.TextInputLayout
android:id="@+id/text_input_email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
android:orientation="vertical"
android:padding="@dimen/margin_24">
<android.support.design.widget.TextInputEditText
android:id="@+id/editor_email"
<android.support.design.widget.TextInputLayout
android:id="@+id/text_input_email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="email address"
android:imeOptions="actionNext"
android:inputType="textWebEmailAddress"
android:singleLine="true"
/>
</android.support.design.widget.TextInputLayout>
android:layout_height="wrap_content">
<Space
android:layout_width="wrap_content"
android:layout_height="@dimen/margin_8"
/>
<android.support.design.widget.TextInputEditText
android:id="@+id/editor_email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/dialog_user_registration_email"
android:imeOptions="actionNext"
android:inputType="textWebEmailAddress"
android:singleLine="true" />
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:id="@+id/text_input_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<Space
android:layout_width="wrap_content"
android:layout_height="@dimen/margin_8" />
<android.support.design.widget.TextInputEditText
android:id="@+id/editor_username"
<android.support.design.widget.TextInputLayout
android:id="@+id/text_input_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="username"
android:imeOptions="actionNext"
android:inputType="textWebEmailAddress"
android:singleLine="true"
/>
</android.support.design.widget.TextInputLayout>
android:layout_height="wrap_content">
<Space
android:layout_width="wrap_content"
android:layout_height="@dimen/margin_8"
/>
<android.support.design.widget.TextInputEditText
android:id="@+id/editor_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/dialog_user_registration_username"
android:imeOptions="actionNext"
android:inputType="textWebEmailAddress"
android:singleLine="true" />
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:id="@+id/text_input_passwd"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:passwordToggleEnabled="true"
>
<Space
android:layout_width="wrap_content"
android:layout_height="@dimen/margin_8" />
<android.support.design.widget.TextInputEditText
android:id="@+id/editor_passwd"
<android.support.design.widget.TextInputLayout
android:id="@+id/text_input_passwd"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="password"
android:imeOptions="actionNext"
android:inputType="textWebPassword"
android:singleLine="true"
/>
</android.support.design.widget.TextInputLayout>
app:passwordToggleEnabled="true">
<android.support.design.widget.TextInputEditText
android:id="@+id/editor_passwd"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/dialog_user_registration_password"
android:imeOptions="actionNext"
android:inputType="textWebPassword"
android:singleLine="true" />
</android.support.design.widget.TextInputLayout>
<Space
android:layout_width="wrap_content"
android:layout_height="@dimen/margin_16"
/>
<Space
android:layout_width="wrap_content"
android:layout_height="@dimen/margin_16" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/btn_register_user"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
app:elevation="2dp"
app:fabSize="mini"
app:srcCompat="@drawable/ic_arrow_forward_white_24dp"
/>
<android.support.design.widget.FloatingActionButton
android:id="@+id/btn_register_user"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
app:elevation="2dp"
app:fabSize="mini"
app:srcCompat="@drawable/ic_arrow_forward_white_24dp" />
<chat.rocket.android.widget.WaitingView
android:id="@+id/waiting"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:dotCount="5"
app:dotSize="12dp"
android:layout_gravity="center"
/>
<chat.rocket.android.widget.WaitingView
android:id="@+id/waiting"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
app:dotCount="5"
app:dotSize="12dp" />
</LinearLayout>
\ No newline at end of file
......@@ -3,45 +3,45 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<FrameLayout
android:id="@+id/room_user_titlebar"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:paddingStart="@dimen/margin_16"
android:paddingRight="@dimen/margin_16"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/users_of_room_title"
android:textAppearance="@style/TextAppearance.AppCompat.Title"
android:layout_gravity="start|center_vertical"/>
<TextView
android:id="@+id/room_user_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:layout_gravity="end|center_vertical"/>
</FrameLayout>
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerview"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
<chat.rocket.android.widget.WaitingView
android:id="@+id/waiting"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
/>
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:id="@+id/room_user_titlebar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:paddingRight="@dimen/margin_16"
android:paddingStart="@dimen/margin_16">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:text="@string/users_of_room_title"
android:textAppearance="@style/TextAppearance.AppCompat.Title" />
<TextView
android:id="@+id/room_user_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
</FrameLayout>
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" />
</LinearLayout>
<chat.rocket.android.widget.WaitingView
android:id="@+id/waiting"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</FrameLayout>
\ No newline at end of file
......@@ -2,26 +2,26 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:minWidth="288dp"
android:padding="@dimen/margin_24"
android:gravity="center"
>
android:minWidth="288dp"
android:orientation="vertical"
android:padding="@dimen/margin_24">
<ImageView
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_marginBottom="@dimen/margin_24"
android:scaleType="fitCenter"
android:src="@mipmap/ic_launcher" />
<chat.rocket.android.widget.WaitingView
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ImageView
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_marginBottom="@dimen/margin_24"
android:scaleType="fitCenter"
android:src="@mipmap/ic_launcher"/>
<chat.rocket.android.widget.WaitingView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
<TextView
android:id="@+id/txt_caption"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Caption"
android:layout_marginTop="@dimen/margin_16"/>
<TextView
android:id="@+id/txt_caption"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_16"
android:textAppearance="@style/TextAppearance.AppCompat.Caption" />
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:theme="@style/Theme.AppCompat.Light"
android:gravity="center"
android:orientation="vertical"
android:padding="@dimen/margin_16"
android:gravity="center">
android:theme="@style/Theme.AppCompat.Light">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Welcome to Rocket.Chat.Android\nSelect a channel from the drawer."
android:textSize="14sp"
android:layout_marginBottom="@dimen/margin_16"
android:gravity="center"
android:layout_marginBottom="@dimen/margin_16"/>
android:text="@string/fragment_home_welcome_message"
android:textSize="14sp" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_launcher"/>
android:src="@mipmap/ic_launcher" />
</LinearLayout>
\ No newline at end of file
......@@ -3,57 +3,50 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorPrimaryDark"
>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@color/white"
android:minWidth="288dp"
android:orientation="horizontal"
android:padding="@dimen/margin_24"
>
android:background="?attr/colorPrimaryDark">
<LinearLayout
android:layout_width="0px"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
>
android:layout_gravity="center"
android:background="@color/white"
android:minWidth="288dp"
android:orientation="horizontal"
android:padding="@dimen/margin_24">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="hostname"
android:textAppearance="@style/TextAppearance.AppCompat.Caption"
/>
<LinearLayout
android:layout_width="0px"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<EditText
android:id="@+id/editor_hostname"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="demo.rocket.chat"
android:imeOptions="actionGo"
android:inputType="textWebEditText"
android:singleLine="true"
/>
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/fragment_input_hostname_hostname"
android:textAppearance="@style/TextAppearance.AppCompat.Caption" />
<Space
android:layout_width="@dimen/margin_8"
android:layout_height="wrap_content"
/>
<EditText
android:id="@+id/editor_hostname"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/fragment_input_hostname_server_hint"
android:imeOptions="actionGo"
android:inputType="textWebEditText"
android:singleLine="true" />
</LinearLayout>
<android.support.design.widget.FloatingActionButton
android:id="@+id/btn_connect"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
app:elevation="2dp"
app:fabSize="mini"
app:srcCompat="@drawable/ic_arrow_forward_white_24dp"
/>
</LinearLayout>
<Space
android:layout_width="@dimen/margin_8"
android:layout_height="wrap_content" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/btn_connect"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
app:elevation="2dp"
app:fabSize="mini"
app:srcCompat="@drawable/ic_arrow_forward_white_24dp" />
</LinearLayout>
</FrameLayout>
\ No newline at end of file
......@@ -3,114 +3,118 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorPrimaryDark"
>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@color/white"
android:minWidth="288dp"
android:orientation="vertical"
android:padding="@dimen/margin_24"
>
android:background="?attr/colorPrimaryDark">
<LinearLayout
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
<chat.rocket.android.widget.FontAwesomeButton
android:id="@+id/btn_login_with_twitter"
android:layout_width="48dp"
android:layout_height="48dp"
android:text="@string/fa_twitter"
android:textSize="16dp"
android:layout_marginEnd="@dimen/margin_8"
android:enabled="false"
/>
<chat.rocket.android.widget.FontAwesomeButton
android:id="@+id/btn_login_with_github"
android:layout_width="48dp"
android:layout_height="48dp"
android:text="@string/fa_github"
android:textSize="16dp"
android:layout_marginEnd="@dimen/margin_8"
/>
</LinearLayout>
android:layout_gravity="center"
android:background="@color/white"
android:minWidth="288dp"
android:orientation="vertical"
android:padding="@dimen/margin_24">
<android.support.design.widget.TextInputLayout
android:id="@+id/text_input_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<android.support.design.widget.TextInputEditText
android:id="@+id/editor_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="username or email"
android:imeOptions="actionNext"
android:inputType="textWebEmailAddress"
android:singleLine="true"
/>
</android.support.design.widget.TextInputLayout>
<Space
android:layout_width="wrap_content"
android:layout_height="@dimen/margin_8"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<android.support.design.widget.TextInputLayout
android:id="@+id/text_input_passwd"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:passwordToggleEnabled="true"
>
<android.support.design.widget.TextInputEditText
android:id="@+id/editor_passwd"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="password"
android:imeOptions="actionNext"
android:inputType="textWebPassword"
android:singleLine="true"
/>
</android.support.design.widget.TextInputLayout>
<Space
android:layout_width="wrap_content"
android:layout_height="@dimen/margin_16"
/>
<chat.rocket.android.widget.FontAwesomeButton
android:id="@+id/btn_login_with_twitter"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginEnd="@dimen/margin_8"
android:text="@string/fa_twitter"
android:textSize="16dp" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
<android.support.design.widget.FloatingActionButton
android:id="@+id/btn_user_registration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start|bottom"
app:elevation="2dp"
app:fabSize="mini"
app:backgroundTint="@color/white"
app:srcCompat="@drawable/ic_user_registration_blue_24dp"
/>
<android.support.design.widget.FloatingActionButton
android:id="@+id/btn_login_with_email"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
app:elevation="2dp"
app:fabSize="normal"
app:srcCompat="@drawable/ic_arrow_forward_white_24dp"
/>
</FrameLayout>
</LinearLayout>
<chat.rocket.android.widget.FontAwesomeButton
android:id="@+id/btn_login_with_facebook"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginEnd="@dimen/margin_8"
android:text="@string/fa_facebook_official"
android:textSize="16dp" />
<chat.rocket.android.widget.FontAwesomeButton
android:id="@+id/btn_login_with_github"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginEnd="@dimen/margin_8"
android:text="@string/fa_github"
android:textSize="16dp" />
<chat.rocket.android.widget.FontAwesomeButton
android:id="@+id/btn_login_with_google"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginEnd="@dimen/margin_8"
android:text="@string/fa_google"
android:textSize="16dp" />
</LinearLayout>
<android.support.design.widget.TextInputLayout
android:id="@+id/text_input_username"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<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:singleLine="true" />
</android.support.design.widget.TextInputLayout>
<Space
android:layout_width="wrap_content"
android:layout_height="@dimen/margin_8" />
<android.support.design.widget.TextInputLayout
android:id="@+id/text_input_passwd"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:passwordToggleEnabled="true">
<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:singleLine="true" />
</android.support.design.widget.TextInputLayout>
<Space
android:layout_width="wrap_content"
android:layout_height="@dimen/margin_16" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<android.support.design.widget.FloatingActionButton
android:id="@+id/btn_user_registration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start|bottom"
app:backgroundTint="@color/white"
app:elevation="2dp"
app:fabSize="mini"
app:srcCompat="@drawable/ic_user_registration_blue_24dp" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/btn_login_with_email"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
app:elevation="2dp"
app:fabSize="normal"
app:srcCompat="@drawable/ic_arrow_forward_white_24dp" />
</FrameLayout>
</LinearLayout>
</FrameLayout>
\ No newline at end of file
......@@ -3,70 +3,63 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorPrimaryDark"
>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@color/white"
android:minWidth="288dp"
android:orientation="vertical"
android:padding="@dimen/margin_24"
>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Title"
android:text="Oops..."
/>
<TextView
android:id="@+id/txt_error_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/Base.TextAppearance.AppCompat.Body1"
android:layout_marginTop="@dimen/margin_8"
android:layout_marginBottom="@dimen/margin_8"
/>
android:background="?attr/colorPrimaryDark">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:background="@color/white"
android:minWidth="288dp"
android:orientation="vertical"
>
<android.support.design.widget.FloatingActionButton
android:id="@+id/btn_retry_login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/margin_8"
app:elevation="2dp"
app:fabSize="normal"
app:srcCompat="@drawable/ic_arrow_forward_white_24dp"
/>
android:padding="@dimen/margin_24">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:text="RETRY"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/fragment_retry_login_error_title"
android:textAppearance="@style/TextAppearance.AppCompat.Title" />
</LinearLayout>
<TextView
android:id="@+id/txt_error_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/margin_8"
android:layout_marginTop="@dimen/margin_8"
android:textAppearance="@style/Base.TextAppearance.AppCompat.Body1" />
<chat.rocket.android.widget.WaitingView
android:id="@+id/waiting"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_16"
app:dotCount="5"
app:dotSize="12dp"
android:layout_gravity="center"
/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:orientation="vertical">
<android.support.design.widget.FloatingActionButton
android:id="@+id/btn_retry_login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/margin_8"
app:elevation="2dp"
app:fabSize="normal"
app:srcCompat="@drawable/ic_arrow_forward_white_24dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/fragment_retry_login_retry_title"
android:textAppearance="@style/TextAppearance.AppCompat.Body1" />
</LinearLayout>
</LinearLayout>
<chat.rocket.android.widget.WaitingView
android:id="@+id/waiting"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="@dimen/margin_16"
app:dotCount="5"
app:dotSize="12dp" />
</LinearLayout>
</FrameLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
android:layout_height="match_parent">
<include layout="@layout/fragment_room_main"/>
<include layout="@layout/fragment_room_main" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="end"
android:clickable="true"
android:theme="@style/AppTheme.Dark"
>
<include layout="@layout/room_side_menu"/>
</FrameLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="end"
android:clickable="true"
android:theme="@style/AppTheme.Dark">
<include layout="@layout/room_side_menu" />
</FrameLayout>
</android.support.v4.widget.DrawerLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
......@@ -11,11 +12,20 @@
android:layout_height="match_parent"
/>
<chat.rocket.android.widget.message.MessageComposer
android:id="@+id/message_composer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/Theme.AppCompat.Light"
android:layout_gravity="bottom"
android:background="@android:color/white"/>
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab_compose"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_margin="@dimen/margin_16"
app:srcCompat="@drawable/ic_compose_white_24dp"
/>
</FrameLayout>
\ No newline at end of file
......@@ -3,26 +3,25 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorPrimaryDark"
android:theme="@style/AppTheme.Dark"
>
android:theme="@style/AppTheme.Dark">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="center"
android:gravity="center"
>
<chat.rocket.android.widget.WaitingView
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
<TextView
android:id="@+id/txt_caption"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Caption"
android:layout_marginTop="@dimen/margin_32"/>
</LinearLayout>
android:layout_gravity="center"
android:gravity="center"
android:orientation="vertical">
<chat.rocket.android.widget.WaitingView
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/txt_caption"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_32"
android:textAppearance="@style/TextAppearance.AppCompat.Caption" />
</LinearLayout>
</FrameLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme"
>
<include layout="@layout/list_item_message_newday"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
<ImageView
android:id="@+id/user_avatar"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_margin="8dp"
tools:src="@drawable/ic_default_avatar"
/>
android:orientation="vertical"
android:theme="@style/AppTheme">
<include layout="@layout/list_item_message_newday" />
<LinearLayout
android:layout_width="0px"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:layout_marginEnd="8dp">
<LinearLayout
android:id="@+id/user_and_timestamp_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textStyle="bold"
tools:text="John Doe"/>
<Space
android:layout_width="@dimen/margin_8"
android:layout_height="wrap_content"
/>
<TextView
android:id="@+id/timestamp"
android:layout_width="wrap_content"
android:orientation="horizontal">
<ImageView
android:id="@+id/user_avatar"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_margin="8dp"
tools:src="@drawable/ic_default_avatar" />
<LinearLayout
android:layout_width="0px"
android:layout_height="wrap_content"
android:enabled="false"
tools:text="12:34"/>
</LinearLayout>
<chat.rocket.android.widget.message.RocketChatMessageLayout
android:id="@+id/message_body"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
android:layout_marginEnd="8dp"
android:layout_weight="1"
android:orientation="vertical">
<LinearLayout
android:id="@+id/user_and_timestamp_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textStyle="bold"
tools:text="John Doe" />
<Space
android:layout_width="@dimen/margin_8"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/timestamp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:enabled="false"
tools:text="12:34" />
</LinearLayout>
<chat.rocket.android.widget.message.RocketChatMessageLayout
android:id="@+id/message_body"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<Space
xmlns:android="http://schemas.android.com/apk/res/android"
<Space xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/space"
android:layout_width="match_parent"
android:layout_height="88dp"
/>
\ No newline at end of file
android:layout_height="88dp" />
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme"
>
android:orientation="vertical"
android:theme="@style/AppTheme">
<chat.rocket.android.widget.WaitingView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="@dimen/margin_8"
/>
<chat.rocket.android.widget.WaitingView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="@dimen/margin_8" />
</FrameLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/newday_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:layout_margin="16dp"
>
android:gravity="center_vertical"
android:orientation="horizontal">
<View
android:layout_width="0px"
android:layout_height="1dp"
android:layout_weight="1"
android:background="@color/newday_color"/>
<View
android:layout_width="0px"
android:layout_height="1dp"
android:layout_weight="1"
android:background="@color/newday_color" />
<TextView
android:id="@+id/newday_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/newday_color"
android:textSize="8sp"
android:textStyle="bold"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
tools:text="2016/01/23"/>
<TextView
android:id="@+id/newday_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:textColor="@color/newday_color"
android:textSize="8sp"
android:textStyle="bold"
tools:text="2016/01/23" />
<View
android:layout_width="0px"
android:layout_height="1dp"
android:layout_weight="1"
android:background="@color/newday_color"/>
<View
android:layout_width="0px"
android:layout_height="1dp"
android:layout_weight="1"
android:background="@color/newday_color" />
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme"
>
android:orientation="vertical"
android:theme="@style/AppTheme">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/margin_16"
android:text="@string/start_of_conversation"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:layout_gravity="center"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="@dimen/margin_16"
android:text="@string/start_of_conversation"
android:textAppearance="@style/TextAppearance.AppCompat.Body1" />
</FrameLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="?attr/listPreferredItemHeightSmall"
android:gravity="center_vertical">
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:id="@+id/room_user_status"
android:layout_width="8dp"
android:layout_height="8dp"
android:layout_margin="@dimen/margin_8"/>
<ImageView
android:id="@+id/room_user_status"
android:layout_width="8dp"
android:layout_height="8dp"
android:layout_margin="@dimen/margin_8" />
<ImageView
android:id="@+id/room_user_avatar"
android:layout_width="24dp"
android:layout_height="24dp" />
<ImageView
android:id="@+id/room_user_avatar"
android:layout_width="24dp"
android:layout_height="24dp" />
<TextView
android:id="@+id/room_user_name"
android:layout_width="0px"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="@dimen/margin_8"
android:layout_marginRight="@dimen/margin_8"
android:layout_weight="1"
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
<TextView
android:id="@+id/room_user_name"
android:layout_width="0px"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="@dimen/margin_8"
android:layout_marginRight="@dimen/margin_8"
android:layout_weight="1"
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/room_side_menu"
android:layout_width="48dp"
android:layout_height="match_parent"
android:orientation="vertical"
android:layout_gravity="end"
>
android:orientation="vertical">
<chat.rocket.android.widget.FontAwesomeButton
android:layout_width="48dp"
android:layout_height="48dp"
android:textSize="24dp"
android:text="@string/fa_search"
android:enabled="false"
/>
<chat.rocket.android.widget.FontAwesomeButton
android:id="@+id/btn_users"
android:layout_width="48dp"
android:layout_height="48dp"
android:textSize="24dp"
android:text="@string/fa_users"
/>
<chat.rocket.android.widget.FontAwesomeButton
android:layout_width="48dp"
android:layout_height="48dp"
android:textSize="24dp"
android:text="@string/fa_at"
android:enabled="false"
/>
<chat.rocket.android.widget.FontAwesomeButton
android:layout_width="48dp"
android:layout_height="48dp"
android:enabled="false"
android:text="@string/fa_search"
android:textSize="24dp" />
<chat.rocket.android.widget.FontAwesomeButton
android:id="@+id/btn_users"
android:layout_width="48dp"
android:layout_height="48dp"
android:text="@string/fa_users"
android:textSize="24dp" />
<chat.rocket.android.widget.FontAwesomeButton
android:layout_width="48dp"
android:layout_height="48dp"
android:enabled="false"
android:text="@string/fa_at"
android:textSize="24dp" />
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.SlidingPaneLayout
<android.support.v4.widget.SlidingPaneLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/sub_sliding_pane"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_gravity="start"
android:layout_width="280dp"
android:layout_height="match_parent"
android:theme="@style/AppTheme.Dark"
>
<android.support.v4.widget.NestedScrollView
android:layout_width="96dp"
android:layout_height="match_parent"
android:layout_gravity="start"
android:background="?attr/colorPrimaryDark"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>
android:layout_gravity="start"
android:theme="@style/AppTheme.Dark">
<android.support.v4.widget.NestedScrollView
android:layout_width="96dp"
android:layout_height="match_parent"
android:layout_gravity="start"
android:background="?attr/colorPrimaryDark">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageButton
style="@style/Base.Widget.AppCompat.Button.Borderless"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_margin="@dimen/margin_8"
android:src="@mipmap/ic_launcher" />
<ImageButton
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_margin="@dimen/margin_8"
android:src="@mipmap/ic_launcher"
style="@style/Base.Widget.AppCompat.Button.Borderless"
/>
<chat.rocket.android.widget.FontAwesomeButton
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_margin="@dimen/margin_8"
android:text="@string/fa_plus"
style="@style/Base.Widget.AppCompat.Button.Borderless"
/>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
<chat.rocket.android.widget.FontAwesomeButton
style="@style/Base.Widget.AppCompat.Button.Borderless"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_margin="@dimen/margin_8"
android:text="@string/fa_plus" />
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
<FrameLayout
android:id="@+id/sidebar_fragment_container"
android:layout_width="280dp"
android:layout_height="match_parent"
/>
<FrameLayout
android:id="@+id/sidebar_fragment_container"
android:layout_width="280dp"
android:layout_height="match_parent" />
</android.support.v4.widget.SlidingPaneLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<WebView
xmlns:android="http://schemas.android.com/apk/res/android"
<WebView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/webview"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
\ No newline at end of file
android:layout_height="match_parent" />
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="instabug_api_key">e111b2f9ac16d4eed4a3568cf6270835</string>
<string name="instabug_api_key">ac4314823dbb87263c76b22db0135727</string>
</resources>
\ No newline at end of file
......@@ -3,6 +3,8 @@
<string name="fa_chevron_down" translatable="false">&#xf078;</string>
<string name="fa_twitter" translatable="false">&#xf099;</string>
<string name="fa_github" translatable="false">&#xf09b;</string>
<string name="fa_google" translatable="false">&#xf1a0;</string>
<string name="fa_facebook_official" translatable="false">&#xf230;</string>
<string name="fa_plus" translatable="false">&#xf067;</string>
<string name="fa_sign_out" translatable="false">&#xf08b;</string>
......
<resources>
<string name="app_name">Rocket.Chat.Android</string>
<string name="fragment_sidebar_main_channels_title">CHANNELS</string>
<string name="fragment_sidebar_main_direct_messages_title">DIRECT MESSAGES</string>
<string name="user_status_online">Online</string>
<string name="user_status_away">Away</string>
<string name="user_status_busy">Busy</string>
<string name="user_status_invisible">Invisible</string>
<string name="fragment_sidebar_main_logout_title">Logout</string>
<string name="app_name">Rocket.Chat.Android</string>
<string name="fragment_sidebar_main_channels_title">CHANNELS</string>
<string name="fragment_sidebar_main_direct_messages_title">DIRECT MESSAGES</string>
<string name="user_status_online">Online</string>
<string name="user_status_away">Away</string>
<string name="user_status_busy">Busy</string>
<string name="user_status_invisible">Invisible</string>
<string name="fragment_sidebar_main_logout_title">Logout</string>
<string name="start_of_conversation">Start of conversation</string>
<string name="users_of_room_title">Members List</string>
<string name="fmt_room_user_count">Total: %,d users</string>
<string name="start_of_conversation">Start of conversation</string>
<string name="users_of_room_title">Members List</string>
<string name="fmt_room_user_count">Total: %,d users</string>
<string name="sending">Sending…</string>
<string name="resend">Resend</string>
<string name="discard">Discard</string>
<string name="dialog_user_registration_email">Email</string>
<string name="dialog_user_registration_username">Username</string>
<string name="dialog_user_registration_password">Password</string>
<string name="fragment_home_welcome_message">Welcome to Rocket.Chat.Android\nSelect a channel from the drawer.</string>
<string name="fragment_input_hostname_hostname">Hostname</string>
<string name="fragment_input_hostname_server_hint">demo.rocket.chat</string>
<string name="fragment_login_username_or_email">Username or email</string>
<string name="fragment_login_password">Password</string>
<string name="fragment_retry_login_retry_title">RETRY</string>
<string name="fragment_retry_login_error_title">Oops…</string>
<string name="add_server_activity_waiting_server">Connecting to server…</string>
<string name="server_config_activity_authenticating">Authenticating…</string>
<string name="home_fragment_title">Rocket.Chat - Home</string>
</resources>
ext {
androidPlugin = 'com.android.tools.build:gradle:2.2.3'
realmPlugin = 'io.realm:realm-gradle-plugin:2.2.1'
retroLambdaPlugin = 'me.tatarka:gradle-retrolambda:3.3.1'
retroLambdaPatch = 'me.tatarka.retrolambda.projectlombok:lombok.ast:0.2.3.a2'
compileSdkVersion = 25
buildToolsVersion = '25.0.1'
minSdkVersion = 21 //for accelerating multi-dex build. OVERRIDEN BY Circle CI to 17
......
......@@ -4,7 +4,6 @@ import io.realm.Realm;
import io.realm.RealmChangeListener;
import io.realm.RealmObject;
import io.realm.RealmResults;
import timber.log.Timber;
abstract class AbstractRealmResultsObserver<T extends RealmObject> {
protected Realm realm;
......@@ -31,26 +30,16 @@ abstract class AbstractRealmResultsObserver<T extends RealmObject> {
results.addChangeListener(listener);
}
public void keepalive() {
if (realm == null || realm.isClosed() || !results.isValid()) {
unsub();
sub();
}
}
public void unsub() {
try {
if (realm != null) {
if (results != null) {
if (results.isValid()) {
results.removeChangeListener(listener);
}
results = null;
}
if (realm != null && !realm.isClosed()) {
realm.close();
}
} catch (IllegalStateException exception) {
Timber.w(exception);
realm.close();
realm = null;
}
}
......
......@@ -11,6 +11,7 @@ import io.realm.RealmObject;
import io.realm.RealmResults;
import java.util.Collections;
import java.util.List;
import org.json.JSONException;
import timber.log.Timber;
public class RealmHelper {
......@@ -34,12 +35,9 @@ public class RealmHelper {
return Collections.emptyList();
}
Realm realm = instance();
List<E> list = realm.copyFromRealm(objects);
if (!realm.isClosed()) {
realm.close();
try (Realm realm = instance()) {
return realm.copyFromRealm(objects);
}
return list;
}
public <E extends RealmObject> E copyFromRealm(E object) {
......@@ -47,56 +45,33 @@ public class RealmHelper {
return null;
}
Realm realm = instance();
E element = realm.copyFromRealm(object);
if (!realm.isClosed()) {
realm.close();
try (Realm realm = instance()) {
return realm.copyFromRealm(object);
}
return element;
}
public interface Transaction<T> {
T execute(Realm realm) throws Exception;
T execute(Realm realm) throws JSONException;
}
public <T extends RealmObject> T executeTransactionForRead(Transaction<T> transaction) {
Realm realm = instance();
T object;
try {
try (Realm realm = instance()) {
T source = transaction.execute(realm);
object = source != null ? realm.copyFromRealm(source) : null;
return source != null ? realm.copyFromRealm(source) : null;
} catch (Exception exception) {
Timber.w(exception);
object = null;
} finally {
if (!realm.isClosed()) {
realm.close();
}
Timber.w(exception, "failed to execute copyFromRealm");
return null;
}
return object;
}
public <T extends RealmObject> List<T> executeTransactionForReadResults(
Transaction<RealmResults<T>> transaction) {
Realm realm = instance();
List<T> object;
try {
object = realm.copyFromRealm(transaction.execute(realm));
try (Realm realm = instance()) {
return realm.copyFromRealm(transaction.execute(realm));
} catch (Exception exception) {
Timber.w(exception);
object = null;
} finally {
if (!realm.isClosed()) {
realm.close();
}
Timber.w(exception, "failed to execute copyFromRealm");
return Collections.emptyList();
}
return object;
}
public Task<Void> executeTransaction(final RealmHelper.Transaction transaction) {
......@@ -107,19 +82,19 @@ public class RealmHelper {
private Task<Void> executeTransactionSync(final RealmHelper.Transaction transaction) {
final TaskCompletionSource<Void> task = new TaskCompletionSource<>();
final Realm realm = instance();
realm.executeTransaction(new Realm.Transaction() {
@Override public void execute(Realm realm) {
try {
transaction.execute(realm);
task.setResult(null);
} catch (Exception exception) {
task.setError(exception);
try (Realm realm = instance()) {
realm.executeTransaction(new Realm.Transaction() {
@Override public void execute(Realm realm) {
try {
transaction.execute(realm);
} catch (JSONException exception) {
throw new RuntimeException(exception);
}
}
}
});
if (!realm.isClosed()) {
realm.close();
});
task.setResult(null);
} catch (Exception exception) {
task.setError(exception);
}
return task.getTask();
......@@ -133,27 +108,22 @@ public class RealmHelper {
@Override public void execute(Realm realm) {
try {
transaction.execute(realm);
} catch (Exception exception) {
task.setError(exception);
if (!realm.isClosed()) {
realm.close();
}
} catch (JSONException exception) {
throw new RuntimeException(exception);
}
}
}, new Realm.Transaction.OnSuccess() {
@Override public void onSuccess() {
if (task.trySetResult(null)) {
if (realm != null && !realm.isClosed()) {
realm.close();
}
}
realm.close();
task.setResult(null);
}
}, new Realm.Transaction.OnError() {
@Override public void onError(Throwable error) {
if (task.trySetError(new Exception(error))) {
if (!realm.isClosed()) {
realm.close();
}
realm.close();
if (error instanceof Exception) {
task.setError((Exception) error);
} else {
task.setError(new Exception(error));
}
}
});
......
......@@ -16,9 +16,14 @@ public abstract class RealmModelListAdapter<T extends RealmObject, VM,
RealmModelListAdapter<T, VM, VH> getNewInstance(Context context);
}
public interface OnItemClickListener<VM> {
void onItemClick(VM model);
}
protected final LayoutInflater inflater;
private RealmListObserver<T> realmListObserver;
private List<VM> adapterData;
private OnItemClickListener<VM> onItemClickListener;
protected RealmModelListAdapter(Context context) {
this.inflater = LayoutInflater.from(context);
......@@ -64,7 +69,17 @@ public abstract class RealmModelListAdapter<T extends RealmObject, VM,
}
@Override public void onBindViewHolder(VH holder, int position) {
holder.bind(getItem(position));
VM model = getItem(position);
holder.itemView.setTag(model);
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View view) {
VM model2 = (VM) (view.getTag());
if (model2 != null && onItemClickListener != null) {
onItemClickListener.onItemClick(model2);
}
}
});
holder.bind(model);
}
@Override public int getItemCount() {
......@@ -85,4 +100,8 @@ public abstract class RealmModelListAdapter<T extends RealmObject, VM,
notifyDataSetChanged();
}
}
public void setOnItemClickListener(OnItemClickListener<VM> onItemClickListener) {
this.onItemClickListener = onItemClickListener;
}
}
......@@ -18,6 +18,8 @@ android {
targetSdkVersion rootProject.ext.compileSdkVersion
versionCode 1
versionName "1"
vectorDrawables.useSupportLibrary = true
}
buildTypes {
release {
......
package chat.rocket.android.widget.message;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.LinearLayout;
import android.widget.TextView;
import chat.rocket.android.widget.R;
public class MessageComposer extends LinearLayout {
protected ActionListener actionListener;
protected ViewGroup composer;
public MessageComposer(Context context) {
super(context);
init();
}
public MessageComposer(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MessageComposer(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public MessageComposer(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
public final void setOnActionListener(@Nullable ActionListener listener) {
actionListener = listener;
}
private void init() {
composer = (ViewGroup) LayoutInflater.from(getContext())
.inflate(R.layout.message_composer, this, false);
composer.findViewById(R.id.btn_submit).setOnClickListener(new OnClickListener() {
@Override public void onClick(View view) {
String messageText = getText();
if (messageText.length() > 0) {
if (actionListener != null) {
actionListener.onSubmit(messageText);
}
}
}
});
addView(composer);
}
private TextView getEditor() {
return (TextView) composer.findViewById(R.id.editor);
}
public final String getText() {
return getEditor().getText().toString().trim();
}
public final void setText(CharSequence text) {
getEditor().setText(text);
}
public void setEnabled(boolean enabled) {
getEditor().setEnabled(enabled);
composer.findViewById(R.id.btn_submit).setEnabled(enabled);
}
protected final void focusToEditor() {
final TextView editor = getEditor();
editor.requestFocus();
InputMethodManager imm =
(InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(editor, InputMethodManager.SHOW_IMPLICIT);
}
protected final void unFocusEditor() {
InputMethodManager imm =
(InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
}
public void show(@Nullable Runnable callback) {
focusToEditor();
setVisibility(View.VISIBLE);
if (callback != null) {
callback.run();
}
}
public void hide(@Nullable Runnable callback) {
unFocusEditor();
setVisibility(View.GONE);
if (callback != null) {
callback.run();
}
}
public boolean isShown() {
return getVisibility() == View.VISIBLE;
}
public interface ActionListener {
void onSubmit(String message);
void onCancel();
}
}
......@@ -9,6 +9,7 @@ import android.widget.LinearLayout;
import android.widget.TextView;
import chat.rocket.android.widget.R;
import chat.rocket.android.widget.helper.Linkify;
import com.emojione.Emojione;
/**
*/
......@@ -49,7 +50,7 @@ public class RocketChatMessageLayout extends LinearLayout {
private void appendTextView(String text) {
TextView textView = (TextView) inflater.inflate(R.layout.message_body, this, false);
textView.setText(text);
textView.setText(Emojione.shortnameToUnicode(text, false));
Linkify.markup(textView);
InlineHightlighter.highlight(textView);
......
<vector android:alpha="0.87" android:height="24dp"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M2.01,21L23,12 2.01,3 2,10l15,2 -15,2z"/>
</vector>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<chat.rocket.android.widget.DividerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
<EditText
android:id="@+id/editor"
android:layout_width="0px"
android:layout_height="wrap_content"
android:layout_weight="1"
android:maxLines="3"
/>
<ImageButton
android:id="@+id/btn_submit"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
app:srcCompat="@drawable/ic_send_black_24dp"
/>
</LinearLayout>
</LinearLayout>
include ':app', ':rocket-chat-android-widgets', ':realm-helpers'
include ':app', ':android-ddp', ':rocket-chat-android-widgets', ':realm-helpers'
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