Commit cc1775c8 authored by Tiago Cunha's avatar Tiago Cunha

Merge branch 'develop' into feature/ci-build-prepare

parents e5fa58f4 4e4dd2f5
......@@ -40,9 +40,9 @@ android {
}
dependencies {
compile project(':log-wrapper-dev')
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
}
......@@ -7,7 +7,6 @@ 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
......@@ -16,7 +15,6 @@ public class DDPClient {
public DDPClient(OkHttpClient client) {
impl = new DDPClientImpl(this, client);
Timber.plant(new Timber.DebugTree());
}
public Task<DDPClientCallback.Connect> connect(String url) {
......
......@@ -5,6 +5,7 @@ import android.support.annotation.Nullable;
import android.text.TextUtils;
import bolts.Task;
import bolts.TaskCompletionSource;
import chat.rocket.android.log.RCLog;
import chat.rocket.android_ddp.rx.RxWebSocket;
import chat.rocket.android_ddp.rx.RxWebSocketCallback;
import java.util.concurrent.TimeUnit;
......@@ -16,7 +17,6 @@ import org.json.JSONObject;
import rx.Observable;
import rx.functions.Func1;
import rx.subscriptions.CompositeSubscription;
import timber.log.Timber;
public class DDPClientImpl {
private final DDPClient client;
......@@ -86,7 +86,7 @@ public class DDPClientImpl {
subscribeBaseListeners();
} catch (Exception e) {
Timber.e(e);
RCLog.e(e);
}
}
......@@ -304,9 +304,7 @@ public class DDPClientImpl {
observable.filter(callback -> callback instanceof RxWebSocketCallback.Close)
.cast(RxWebSocketCallback.Close.class)
.subscribe(close -> {
task.setResult(close);
}, err -> {
.subscribe(task::setResult, err -> {
if (err instanceof Exception) {
task.setError((Exception) err);
} else {
......@@ -326,7 +324,7 @@ public class DDPClientImpl {
String msg2 = (json == null ? origJson : json.create(origJson)).toString();
websocket.sendText(msg2);
} catch (Exception e) {
Timber.e(e);
RCLog.e(e);
}
}
......@@ -342,7 +340,7 @@ public class DDPClientImpl {
try {
websocket.close(code, reason);
} catch (Exception e) {
Timber.e(e);
RCLog.e(e);
}
}
......
package chat.rocket.android_ddp.rx;
import chat.rocket.android.log.RCLog;
import java.io.IOException;
import okhttp3.OkHttpClient;
import okhttp3.Request;
......@@ -14,7 +15,6 @@ import rx.Observable;
import rx.Subscriber;
import rx.exceptions.OnErrorNotImplementedException;
import rx.observables.ConnectableObservable;
import timber.log.Timber;
public class RxWebSocket {
private OkHttpClient httpClient;
......@@ -44,7 +44,7 @@ public class RxWebSocket {
isConnected = false;
subscriber.onError(new RxWebSocketCallback.Failure(webSocket, e, response));
} catch (OnErrorNotImplementedException ex) {
Timber.w(ex, "OnErrorNotImplementedException ignored");
RCLog.w(ex, "OnErrorNotImplementedException ignored");
}
}
......
package chat.rocket.android_ddp.rx;
import chat.rocket.android.log.RCLog;
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;
......@@ -60,7 +60,7 @@ public class RxWebSocketCallback {
try {
this.responseBodyString = responseBody.string();
} catch (Exception e) {
Timber.e(e, "error in reading response(Message)");
RCLog.e(e, "error in reading response(Message)");
}
}
......
......@@ -89,14 +89,14 @@ play {
repositories {
mavenCentral()
maven { url 'https://github.com/YusukeIwaki/realm-java-helpers/raw/master/repo' }
maven { url 'https://github.com/lijingle1/stetho-realm/raw/master/maven-repo' }
maven { url 'https://github.com/RocketChat/Android-DDP/raw/master/repository' }
maven { url 'http://dl.bintray.com/amulyakhare/maven' }
maven { url "https://clojars.org/repo/" } //for icepick.
}
dependencies {
debugCompile project(':log-wrapper-dev')
releaseCompile project(':log-wrapper-rel')
compile project(':android-ddp')
compile project(':rocket-chat-android-widgets')
compile project(':realm-helpers')
......@@ -111,14 +111,13 @@ dependencies {
compile 'com.google.firebase:firebase-core:10.0.0'
compile 'com.google.firebase:firebase-crash:10.0.0'
compile 'com.squareup.okhttp3:okhttp:3.4.1'
compile 'com.squareup.picasso:picasso:2.5.2'
compile rootProject.ext.okhttp3
compile rootProject.ext.picasso
compile 'com.facebook.stetho:stetho:1.4.1'
compile 'com.facebook.stetho:stetho-okhttp3:1.4.1'
compile 'com.uphyca:stetho_realm:2.0.1'
compile rootProject.ext.timber
compile 'com.jakewharton.rxbinding:rxbinding:0.4.0'
compile 'com.jakewharton.rxbinding:rxbinding-support-v4:0.4.0'
......
......@@ -9,7 +9,6 @@ import io.realm.Realm;
import io.realm.RealmConfiguration;
import java.util.List;
import chat.rocket.android.wrappers.InstabugWrapper;
import timber.log.Timber;
/**
* Customized Application-class for Rocket.Chat
......@@ -18,8 +17,6 @@ public class RocketChatApplication extends MultiDexApplication {
@Override public void onCreate() {
super.onCreate();
Timber.plant(new Timber.DebugTree());
Realm.init(this);
Realm.setDefaultConfiguration(
new RealmConfiguration.Builder().deleteRealmIfMigrationNeeded().build());
......
package chat.rocket.android.activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.support.annotation.Nullable;
import chat.rocket.android.LaunchUtil;
import chat.rocket.android.RocketChatCache;
import chat.rocket.android.model.ServerConfig;
......@@ -34,6 +36,25 @@ abstract class AbstractAuthedActivity extends AbstractFragmentActivity {
}
};
@Override protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
Intent intent = getIntent();
if (intent != null) {
if (intent.hasExtra("serverConfigId")) {
SharedPreferences.Editor editor = RocketChatCache.get(this).edit();
editor.putString(RocketChatCache.KEY_SELECTED_SERVER_CONFIG_ID,
intent.getStringExtra("serverConfigId"));
if (intent.hasExtra("roomId")) {
editor.putString(RocketChatCache.KEY_SELECTED_ROOM_ID, intent.getStringExtra("roomId"));
}
editor.apply();
}
}
}
}
private void updateServerConfigIdIfNeeded(SharedPreferences prefs) {
String newServerConfigId = prefs.getString(RocketChatCache.KEY_SELECTED_SERVER_CONFIG_ID, null);
if (serverConfigId == null) {
......
......@@ -6,30 +6,37 @@ import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.view.MotionEvent;
import chat.rocket.android.helper.OnBackPressListener;
import chat.rocket.android.log.RCLog;
import com.trello.rxlifecycle.components.support.RxAppCompatActivity;
import chat.rocket.android.wrappers.InstabugWrapper;
import icepick.Icepick;
import timber.log.Timber;
abstract class AbstractFragmentActivity extends RxAppCompatActivity {
@Override protected void onCreate(@Nullable Bundle savedInstanceState) {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Icepick.restoreInstanceState(this, savedInstanceState);
}
@Override protected void onSaveInstanceState(Bundle outState) {
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Icepick.saveInstanceState(this, outState);
}
protected abstract @IdRes int getLayoutContainerForFragment();
protected abstract
@IdRes
int getLayoutContainerForFragment();
@Override public final void onBackPressed() {
@Override
public final void onBackPressed() {
if (!onBackPress()) {
onBackPresseNotHandled();
onBackPressedNotHandled();
}
}
......@@ -50,7 +57,7 @@ abstract class AbstractFragmentActivity extends RxAppCompatActivity {
return false;
}
protected void onBackPresseNotHandled() {
protected void onBackPressedNotHandled() {
super.onBackPressed();
}
......@@ -67,11 +74,12 @@ abstract class AbstractFragmentActivity extends RxAppCompatActivity {
.commit();
}
@Override public boolean dispatchTouchEvent(MotionEvent event) {
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
try {
InstabugWrapper.wrap(event, this);
} catch (IllegalStateException exception) {
Timber.w(exception, "Instabug error (ignored)");
RCLog.w(exception, "Instabug error (ignored)");
}
return super.dispatchTouchEvent(event);
}
......
......@@ -101,7 +101,7 @@ public class AddServerActivity extends AbstractFragmentActivity {
fragment.setArguments(args);
}
@Override protected void onBackPresseNotHandled() {
@Override protected void onBackPressedNotHandled() {
moveTaskToBack(true);
}
}
......@@ -93,8 +93,7 @@ public class MainActivity extends AbstractAuthedActivity {
sessionObserver = realmHelper
.createObjectObserver(realm ->
realm.where(Session.class)
.equalTo("sessionId", Session.DEFAULT_ID)
Session.queryDefaultSession(realm)
.isNotNull("token")
.equalTo("tokenVerified", true)
.isNull("error"))
......
......@@ -42,8 +42,7 @@ public class ServerConfigActivity extends AbstractFragmentActivity {
}
sessionObserver = RealmStore.get(serverConfigId)
.createObjectObserver(realm ->
realm.where(Session.class).equalTo("sessionId", Session.DEFAULT_ID))
.createObjectObserver(Session::queryDefaultSession)
.setOnUpdateListener(this::onRenderServerConfigSession);
setContentView(R.layout.simple_screen);
......@@ -105,7 +104,7 @@ public class ServerConfigActivity extends AbstractFragmentActivity {
fragment.setArguments(args);
}
@Override protected void onBackPresseNotHandled() {
@Override protected void onBackPressedNotHandled() {
moveTaskToBack(true);
}
}
......@@ -4,6 +4,7 @@ import android.support.annotation.Nullable;
import bolts.Task;
import chat.rocket.android.helper.OkHttpHelper;
import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.log.RCLog;
import chat.rocket.android_ddp.DDPClient;
import chat.rocket.android_ddp.DDPClientCallback;
import chat.rocket.android_ddp.DDPSubscription;
......@@ -11,7 +12,6 @@ import java.util.UUID;
import org.json.JSONArray;
import org.json.JSONException;
import rx.Observable;
import timber.log.Timber;
/**
* DDP client wrapper.
......@@ -83,13 +83,13 @@ 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);
RCLog.d("rpc:[%s]> %s(%s) timeout=%d", methodCallId, methodName, params, timeoutMs);
if (TextUtils.isEmpty(params)) {
return ddpClient.rpc(methodName, null, methodCallId, timeoutMs).continueWithTask(task -> {
if (task.isFaulted()) {
Timber.d("rpc:[%s]< error = %s", methodCallId, task.getError());
RCLog.d("rpc:[%s]< error = %s", methodCallId, task.getError());
} else {
Timber.d("rpc:[%s]< result = %s", methodCallId, task.getResult().result);
RCLog.d("rpc:[%s]< result = %s", methodCallId, task.getResult().result);
}
return task;
});
......@@ -99,9 +99,9 @@ public class DDPClientWraper {
return ddpClient.rpc(methodName, new JSONArray(params), methodCallId, timeoutMs)
.continueWithTask(task -> {
if (task.isFaulted()) {
Timber.d("rpc:[%s]< error = %s", methodCallId, task.getError());
RCLog.d("rpc:[%s]< error = %s", methodCallId, task.getError());
} else {
Timber.d("rpc:[%s]< result = %s", methodCallId, task.getResult().result);
RCLog.d("rpc:[%s]< result = %s", methodCallId, task.getResult().result);
}
return task;
});
......
......@@ -19,11 +19,14 @@ 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.log.RCLog;
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.ddp.User;
import chat.rocket.android.model.internal.LoadMessageProcedure;
import chat.rocket.android.model.internal.Session;
import chat.rocket.android.realm_helper.RealmHelper;
import chat.rocket.android.realm_helper.RealmModelListAdapter;
import chat.rocket.android.realm_helper.RealmObjectObserver;
......@@ -35,7 +38,6 @@ import io.realm.Sort;
import java.lang.reflect.Field;
import java.util.UUID;
import org.json.JSONObject;
import timber.log.Timber;
/**
* Chat room screen.
......@@ -48,6 +50,8 @@ public class RoomFragment extends AbstractChatRoomFragment
private String roomId;
private RealmObjectObserver<RoomSubscription> roomObserver;
private String hostname;
private String userId;
private String token;
private LoadMoreScrollListener scrollListener;
private RealmObjectObserver<LoadMessageProcedure> procedureObserver;
private MessageComposerManager messageComposerManager;
......@@ -79,6 +83,10 @@ public class RoomFragment extends AbstractChatRoomFragment
.equalTo("serverConfigId", serverConfigId)
.isNotNull("hostname")
.findFirst()).getHostname();
userId = realmHelper.executeTransactionForRead(realm ->
User.queryCurrentUser(realm).findFirst()).get_id();
token = realmHelper.executeTransactionForRead(realm ->
Session.queryDefaultSession(realm).findFirst()).getToken();
roomObserver = realmHelper
.createObjectObserver(realm -> realm.where(RoomSubscription.class).equalTo("rid", roomId))
.setOnUpdateListener(this::onRenderRoom);
......@@ -102,7 +110,7 @@ public class RoomFragment extends AbstractChatRoomFragment
realm -> realm.where(Message.class)
.equalTo("rid", roomId)
.findAllSorted("ts", Sort.DESCENDING),
context -> new MessageListAdapter(context, hostname)
context -> new MessageListAdapter(context, hostname, userId, token)
);
listView.setAdapter(adapter);
adapter.setOnItemClickListener(this);
......@@ -167,7 +175,7 @@ public class RoomFragment extends AbstractChatRoomFragment
fieldSlidable.setAccessible(true);
fieldSlidable.setBoolean(pane, !opened);
} catch (Exception exception) {
Timber.w(exception, "failed to set CanSlide.");
RCLog.w(exception);
}
});
}
......@@ -225,7 +233,7 @@ public class RoomFragment extends AbstractChatRoomFragment
MessageListAdapter adapter = (MessageListAdapter) listView.getAdapter();
final int syncstate = procedure.getSyncstate();
final boolean hasNext = procedure.isHasNext();
Timber.d("hasNext: %s syncstate: %d", hasNext, syncstate);
RCLog.d("hasNext: %s syncstate: %d", hasNext, syncstate);
if (syncstate == SyncState.SYNCED || syncstate == SyncState.FAILED) {
scrollListener.setLoadingDone();
adapter.updateFooter(hasNext, true);
......
......@@ -10,6 +10,7 @@ import android.widget.TextView;
import chat.rocket.android.R;
import chat.rocket.android.helper.LogcatIfError;
import chat.rocket.android.layouthelper.chatroom.dialog.RoomUserAdapter;
import chat.rocket.android.log.RCLog;
import chat.rocket.android.model.SyncState;
import chat.rocket.android.model.internal.GetUsersOfRoomsProcedure;
import chat.rocket.android.realm_helper.RealmObjectObserver;
......@@ -19,7 +20,6 @@ import java.util.List;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import timber.log.Timber;
/**
* Dialog to show members in a room.
......@@ -122,7 +122,7 @@ public class UsersOfRoomDialogFragment extends AbstractChatroomDialogFragment {
}
onRenderUsers(users);
} catch (JSONException exception) {
Timber.e(exception);
RCLog.e(exception);
}
}
}
......
......@@ -9,13 +9,13 @@ 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.log.RCLog;
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 {
......@@ -91,7 +91,7 @@ public abstract class AbstractOAuthFragment extends AbstractWebViewFragment {
handleOAuthCallback(credentialToken, credentialSecret);
resultOK = true;
} catch (JSONException exception) {
Timber.e(exception, "failed to parse OAuth result.");
RCLog.e(exception, "failed to parse OAuth result.");
}
}
......
......@@ -10,12 +10,12 @@ import chat.rocket.android.R;
import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.layouthelper.oauth.OAuthProviderInfo;
import chat.rocket.android.log.RCLog;
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.
......@@ -87,7 +87,7 @@ public class LoginFragment extends AbstractServerConfigFragment {
try {
fragment = info.fragmentClass.newInstance();
} catch (Exception exception) {
Timber.w(exception, "failed to create new Fragment");
RCLog.w(exception, "failed to create new Fragment");
}
if (fragment != null) {
Bundle args = new Bundle();
......
......@@ -24,8 +24,7 @@ public class RetryLoginFragment extends AbstractServerConfigFragment {
@Override public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
sessionObserver = RealmStore.get(serverConfigId)
.createObjectObserver(realm ->
realm.where(Session.class).equalTo("sessionId", Session.DEFAULT_ID))
.createObjectObserver(Session::queryDefaultSession)
.setOnUpdateListener(this::onRenderServerConfigSession);
}
......
package chat.rocket.android.helper;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Typeface;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Looper;
import android.widget.ImageView;
import bolts.Task;
import bolts.TaskCompletionSource;
import chat.rocket.android.log.RCLog;
import com.amulyakhare.textdrawable.TextDrawable;
import com.squareup.picasso.Picasso;
import com.squareup.picasso.Target;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import timber.log.Timber;
/**
* Helper for rendering user avatar image.
......@@ -56,7 +64,7 @@ public class Avatar {
try {
return "https://" + hostname + "/avatar/" + URLEncoder.encode(username, "UTF-8") + ".jpg";
} catch (UnsupportedEncodingException exception) {
Timber.e(exception, "failed to get URL for user: %s", username);
RCLog.e(exception, "failed to get URL for user: %s", username);
return null;
}
}
......@@ -65,14 +73,9 @@ public class Avatar {
* render avatar into imageView.
*/
public void into(final ImageView imageView) {
Object tag = imageView.getTag();
if (tag != null && tag instanceof String) {
String username = (String) tag;
if (this.username.equals(username)) {
return;
}
if (ViewDataCache.isStored(username, imageView)) {
return;
}
imageView.setTag(this.username);
final Context context = imageView.getContext();
Picasso.with(context)
......@@ -90,4 +93,58 @@ public class Avatar {
.endConfig()
.buildRoundRect(getInitialsForUser(username), getColorForUser(username), round);
}
public Task<Bitmap> getBitmap(Context context, int size) {
TaskCompletionSource<Bitmap> task = new TaskCompletionSource<>();
// Picasso can be triggered only on Main Thread.
if (Looper.myLooper() != Looper.getMainLooper()) {
new Handler(Looper.getMainLooper()).post(() -> {
getBitmap(context, size).continueWith(_task -> {
if (_task.isFaulted()) {
task.setError(_task.getError());
} else {
task.setResult(_task.getResult());
}
return null;
});
});
return task.getTask();
}
Picasso.with(context)
.load(getImageUrl())
.error(getTextDrawable(context))
.into(new Target() {
@Override public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
if (bitmap != null) {
task.trySetResult(bitmap);
}
}
@Override public void onBitmapFailed(Drawable errorDrawable) {
task.trySetResult(drawableToBitmap(errorDrawable, size));
}
@Override public void onPrepareLoad(Drawable placeHolderDrawable) {
}
});
return task.getTask();
}
private static Bitmap drawableToBitmap (Drawable drawable, int size) {
if (drawable instanceof BitmapDrawable) {
BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
if(bitmapDrawable.getBitmap() != null) {
return bitmapDrawable.getBitmap();
}
}
Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
}
package chat.rocket.android.helper;
import chat.rocket.android.log.RCLog;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.TimeZone;
import timber.log.Timber;
/**
* Utility class for converting epoch ms and date-time string.
......@@ -64,7 +64,7 @@ public class DateTime {
cal.setTime(DATE_FORMAT.parse(dateString));
return cal.getTimeInMillis();
} catch (ParseException exception) {
Timber.w(exception, "failed to parse date: %s", dateString);
RCLog.w(exception, "failed to parse date: %s", dateString);
}
return 0;
}
......
......@@ -2,7 +2,7 @@ package chat.rocket.android.helper;
import bolts.Continuation;
import bolts.Task;
import timber.log.Timber;
import chat.rocket.android.log.RCLog;
/**
* Bolts-Task continuation for just logging if error occurred.
......@@ -10,7 +10,7 @@ import timber.log.Timber;
public class LogcatIfError implements Continuation {
@Override public Object then(Task task) throws Exception {
if (task.isFaulted()) {
Timber.w(task.getError());
RCLog.w(task.getError());
}
return task;
}
......
package chat.rocket.android.helper;
import android.view.View;
import hugo.weaving.DebugLog;
/**
* save String to setTag.
*/
public class ViewDataCache {
/**
* stores str if not stored. returns true if already stored.
*/
public static boolean isStored(String str, View view) {
if (view.getTag() != null && view.getTag() instanceof String
&& ((String) view.getTag()).equals(str)) {
return true;
}
view.setTag(str);
return false;
}
}
......@@ -15,12 +15,16 @@ import java.util.List;
public class MessageListAdapter
extends ExtRealmModelListAdapter<Message, PairedMessage, MessageViewHolder> {
private final String hostname;
private final String userId;
private final String token;
private boolean hasNext;
private boolean isLoaded;
public MessageListAdapter(Context context, String hostname) {
public MessageListAdapter(Context context, String hostname, String userId, String token) {
super(context);
this.hostname = hostname;
this.userId = userId;
this.token = token;
}
/**
......@@ -53,7 +57,7 @@ public class MessageListAdapter
}
@Override protected MessageViewHolder onCreateRealmModelViewHolder(int viewType, View itemView) {
return new MessageViewHolder(itemView, hostname);
return new MessageViewHolder(itemView, hostname, userId, token);
}
@Override protected List<PairedMessage> mapResultsToViewModel(List<Message> results) {
......
......@@ -10,7 +10,9 @@ 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.RocketChatMessageAttachmentsLayout;
import chat.rocket.android.widget.message.RocketChatMessageLayout;
import chat.rocket.android.widget.message.RocketChatMessageUrlsLayout;
/**
* View holder of NORMAL chat message.
......@@ -21,23 +23,32 @@ public class MessageViewHolder extends RealmModelViewHolder<PairedMessage> {
private final TextView timestamp;
private final View userAndTimeContainer;
private final String hostname;
private final String userId;
private final String token;
private final RocketChatMessageLayout body;
private final RocketChatMessageUrlsLayout urls;
private final RocketChatMessageAttachmentsLayout attachments;
private final View newDayContainer;
private final TextView newDayText;
/**
* constructor WITH hostname.
*/
public MessageViewHolder(View itemView, String hostname) {
public MessageViewHolder(View itemView, String hostname, String userId, String token) {
super(itemView);
avatar = (ImageView) itemView.findViewById(R.id.user_avatar);
username = (TextView) itemView.findViewById(R.id.username);
timestamp = (TextView) itemView.findViewById(R.id.timestamp);
userAndTimeContainer = itemView.findViewById(R.id.user_and_timestamp_container);
body = (RocketChatMessageLayout) itemView.findViewById(R.id.message_body);
urls = (RocketChatMessageUrlsLayout) itemView.findViewById(R.id.message_urls);
attachments =
(RocketChatMessageAttachmentsLayout) itemView.findViewById(R.id.message_attachments);
newDayContainer = itemView.findViewById(R.id.newday_container);
newDayText = (TextView) itemView.findViewById(R.id.newday_text);
this.hostname = hostname;
this.userId = userId;
this.token = token;
}
/**
......@@ -48,7 +59,9 @@ public class MessageViewHolder extends RealmModelViewHolder<PairedMessage> {
.avatarInto(avatar, hostname)
.usernameInto(username)
.timestampInto(timestamp)
.bodyInto(body);
.bodyInto(body)
.urlsInto(urls)
.attachmentsInto(attachments, hostname, userId, token);
if (pairedMessage.target != null) {
int syncstate = pairedMessage.target.getSyncstate();
......
package chat.rocket.android.model.ddp;
import chat.rocket.android.model.SyncState;
import io.realm.RealmList;
import io.realm.RealmObject;
import io.realm.annotations.PrimaryKey;
import org.json.JSONException;
......@@ -23,8 +22,8 @@ public class Message extends RealmObject {
private String msg;
private User u;
private boolean groupable;
private RealmList<MessageAttachment> attachments;
private RealmList<MessageUrl> urls;
private String attachments; //JSONArray.
private String urls; //JSONArray.
public String get_id() {
return _id;
......@@ -90,19 +89,19 @@ public class Message extends RealmObject {
this.groupable = groupable;
}
public RealmList<MessageAttachment> getAttachments() {
public String getAttachments() {
return attachments;
}
public void setAttachments(RealmList<MessageAttachment> attachments) {
public void setAttachments(String attachments) {
this.attachments = attachments;
}
public RealmList<MessageUrl> getUrls() {
public String getUrls() {
return urls;
}
public void setUrls(RealmList<MessageUrl> urls) {
public void setUrls(String urls) {
this.urls = urls;
}
......
package chat.rocket.android.model.ddp;
import io.realm.RealmObject;
/**
* Attachment.
*/
@SuppressWarnings({"PMD.ShortClassName", "PMD.ShortVariable",
"PMD.MethodNamingConventions", "PMD.VariableNamingConventions"})
public class MessageAttachment extends RealmObject {
private String title;
private String title_url;
private String image_url;
private String image_type;
private String image_size;
}
package chat.rocket.android.model.ddp;
import io.realm.RealmObject;
import io.realm.annotations.PrimaryKey;
/**
* Url.
*/
@SuppressWarnings({"PMD.ShortClassName", "PMD.ShortVariable",
"PMD.MethodNamingConventions", "PMD.VariableNamingConventions"})
public class MessageUrl extends RealmObject {
@PrimaryKey private String url;
private String parsedUrl;
}
......@@ -6,6 +6,7 @@ import bolts.Task;
import bolts.TaskCompletionSource;
import chat.rocket.android.helper.LogcatIfError;
import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.log.RCLog;
import chat.rocket.android.model.SyncState;
import chat.rocket.android.realm_helper.RealmHelper;
import chat.rocket.android.realm_helper.RealmObjectObserver;
......@@ -15,7 +16,6 @@ import io.realm.annotations.PrimaryKey;
import java.util.HashMap;
import java.util.UUID;
import org.json.JSONObject;
import timber.log.Timber;
public class MethodCall extends RealmObject {
......@@ -116,7 +116,7 @@ public class MethodCall extends RealmObject {
realm.where(MethodCall.class).equalTo("methodCallId", newId));
observer.setOnUpdateListener(methodCall -> {
int syncstate = methodCall.getSyncstate();
Timber.d("MethodCall[%s] syncstate=%d", methodCall.getMethodCallId(), syncstate);
RCLog.d("MethodCall[%s] syncstate=%d", methodCall.getMethodCallId(), syncstate);
if (syncstate == SyncState.SYNCED) {
String resultJson = methodCall.getResultJson();
if (TextUtils.isEmpty(resultJson)) {
......
......@@ -4,7 +4,9 @@ import chat.rocket.android.helper.LogcatIfError;
import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.realm_helper.RealmHelper;
import hugo.weaving.DebugLog;
import io.realm.Realm;
import io.realm.RealmObject;
import io.realm.RealmQuery;
import io.realm.annotations.PrimaryKey;
import org.json.JSONObject;
......@@ -50,6 +52,10 @@ public class Session extends RealmObject {
this.error = error;
}
public static RealmQuery<Session> queryDefaultSession(Realm realm) {
return realm.where(Session.class).equalTo("sessionId", Session.DEFAULT_ID);
}
/**
* Log the server connection is lost due to soem exception.
*/
......
package chat.rocket.android.notification;
/**
* notifier.
*/
public interface Notifier {
void publishNotificationIfNeeded();
}
package chat.rocket.android.notification;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationManagerCompat;
import android.support.v4.content.ContextCompat;
import bolts.Task;
import chat.rocket.android.R;
import chat.rocket.android.activity.MainActivity;
import chat.rocket.android.helper.Avatar;
import chat.rocket.android.model.ServerConfig;
import chat.rocket.android.realm_helper.RealmStore;
import org.json.JSONException;
import org.json.JSONObject;
/**
* utility class for notification.
*/
public class StreamNotifyUserNotifier implements Notifier {
private final Context context;
private final String hostname;
private final String title;
private final String text;
private final JSONObject payload;
public StreamNotifyUserNotifier(Context context, String hostname,
String title, String text, JSONObject payload) {
this.context = context;
this.hostname = hostname;
this.title = title;
this.text = text;
this.payload = payload;
}
@Override public void publishNotificationIfNeeded() {
if (!shouldNotify()) {
return;
}
generateNotificationAsync().onSuccess(task -> {
NotificationManagerCompat.from(context)
.notify(generateNotificationId(), task.getResult());
return null;
});
}
private boolean shouldNotify() {
// TODO: should check if target message is already read or not.
return true;
}
private int generateNotificationId() {
// TODO: should summary notification by user or room.
return (int) (System.currentTimeMillis() % Integer.MAX_VALUE);
}
private Task<Notification> generateNotificationAsync() {
int size = context.getResources().getDimensionPixelSize(R.dimen.notification_avatar_size);
return getUsername()
.onSuccessTask(task -> new Avatar(hostname, task.getResult()).getBitmap(context, size))
.continueWithTask(task -> {
Bitmap icon = task.isFaulted() ? null : task.getResult();
return Task.forResult(generateNotification(icon));
});
}
private Task<String> getUsername() {
try {
return Task.forResult(payload.getJSONObject("sender").getString("username"));
} catch (Exception exception) {
return Task.forError(exception);
}
}
private Notification generateNotification(Bitmap largeIcon) {
Intent intent = new Intent(context, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_CLEAR_TOP);
ServerConfig config = RealmStore.getDefault().executeTransactionForRead(realm ->
realm.where(ServerConfig.class).equalTo("hostname", hostname).findFirst());
if (config != null) {
intent.putExtra("serverConfigId", config.getServerConfigId());
try {
intent.putExtra("roomId", payload.getString("rid"));
} catch (JSONException exception) {
}
}
PendingIntent pendingIntent = PendingIntent.getActivity(context.getApplicationContext(),
(int) (System.currentTimeMillis() % Integer.MAX_VALUE),
intent, PendingIntent.FLAG_ONE_SHOT);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
.setContentTitle(title)
.setContentText(text)
.setAutoCancel(true)
.setColor(ContextCompat.getColor(context, R.color.colorPrimary))
.setSmallIcon(R.drawable.rocket_chat_notification_24dp)
.setContentIntent(pendingIntent);
if (largeIcon != null) {
builder.setLargeIcon(largeIcon);
}
if (text.length() > 20) {
return new NotificationCompat.BigTextStyle(builder)
.bigText(text)
.build();
} else {
return builder.build();
}
}
}
package chat.rocket.android.renderer;
import android.content.Context;
import android.view.View;
import android.widget.ImageView;
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.model.ddp.Message;
import chat.rocket.android.widget.message.RocketChatMessageAttachmentsLayout;
import chat.rocket.android.widget.message.RocketChatMessageLayout;
import chat.rocket.android.widget.message.RocketChatMessageUrlsLayout;
/**
* Renderer for Message model.
......@@ -77,4 +81,46 @@ public class MessageRenderer extends AbstractRenderer<Message> {
return this;
}
/**
* show urls in RocketChatMessageUrlsLayout.
*/
public MessageRenderer urlsInto(RocketChatMessageUrlsLayout urlsLayout) {
if (!shouldHandle(urlsLayout)) {
return this;
}
String urls = object.getUrls();
if (TextUtils.isEmpty(urls)) {
urlsLayout.setVisibility(View.GONE);
} else {
urlsLayout.setVisibility(View.VISIBLE);
urlsLayout.setUrls(urls);
}
return this;
}
/**
* show urls in RocketChatMessageUrlsLayout.
*/
public MessageRenderer attachmentsInto(RocketChatMessageAttachmentsLayout attachmentsLayout,
String hostname, String userId, String token) {
if (!shouldHandle(attachmentsLayout)) {
return this;
}
String attachments = object.getAttachments();
if (TextUtils.isEmpty(attachments)) {
attachmentsLayout.setVisibility(View.GONE);
} else {
attachmentsLayout.setVisibility(View.VISIBLE);
attachmentsLayout.setHostname(hostname);
attachmentsLayout.setCredential(userId, token);
attachmentsLayout.setAttachments(attachments);
}
return this;
}
}
......@@ -47,11 +47,11 @@ public class RocketChatService extends Service {
private void refreshServerConfigState() {
realmHelper.executeTransaction(realm -> {
RealmResults<ServerConfig> configs = realm.where(ServerConfig.class).findAll();
RealmResults<ServerConfig> configs = realm.where(ServerConfig.class)
.notEqualTo("state", ServerConfig.STATE_READY)
.findAll();
for (ServerConfig config: configs) {
if (config.getState() != ServerConfig.STATE_READY) {
config.setState(ServerConfig.STATE_READY);
}
config.setState(ServerConfig.STATE_READY);
}
return null;
}).continueWith(new LogcatIfError());;
......@@ -100,39 +100,28 @@ public class RocketChatService extends Service {
}
ServerConfig config = configList.get(0);
createWebSocketThread(config).onSuccess(task -> {
RocketChatWebSocketThread thread = task.getResult();
if (thread != null) {
thread.keepalive();
}
return null;
});
final String serverConfigId = config.getServerConfigId();
ServerConfig.updateState(serverConfigId, ServerConfig.STATE_CONNECTING)
.onSuccessTask(task -> createWebSocketThread(config))
.onSuccessTask(task -> {
RocketChatWebSocketThread thread = task.getResult();
if (thread != null) {
thread.keepalive();
}
return ServerConfig.updateState(serverConfigId, ServerConfig.STATE_CONNECTED);
}).continueWith(new LogcatIfError());
}
private Task<RocketChatWebSocketThread> createWebSocketThread(final ServerConfig config) {
final String serverConfigId = config.getServerConfigId();
webSocketThreads.put(serverConfigId, null);
return ServerConfig.updateState(serverConfigId, ServerConfig.STATE_CONNECTING)
.onSuccessTask(_task ->
RocketChatWebSocketThread.getStarted(getApplicationContext(), config))
.onSuccessTask(task ->
ServerConfig.updateState(serverConfigId, ServerConfig.STATE_CONNECTED)
.onSuccessTask(_task -> task))
return RocketChatWebSocketThread.getStarted(getApplicationContext(), config)
.onSuccessTask(task -> {
webSocketThreads.put(serverConfigId, task.getResult());
return task;
});
}
private Task<RocketChatWebSocketThread> findOrCreateWebSocketThread(final ServerConfig config) {
final String serverConfigId = config.getServerConfigId();
if (webSocketThreads.containsKey(serverConfigId)) {
return Task.forResult(webSocketThreads.get(serverConfigId));
} else {
return createWebSocketThread(config);
}
}
@Override public void onDestroy() {
if (connectionRequiredServerConfigObserver != null) {
connectionRequiredServerConfigObserver.unsub();
......
......@@ -9,6 +9,7 @@ import bolts.TaskCompletionSource;
import chat.rocket.android.api.DDPClientWraper;
import chat.rocket.android.helper.LogcatIfError;
import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.log.RCLog;
import chat.rocket.android.model.ServerConfig;
import chat.rocket.android.model.internal.Session;
import chat.rocket.android.realm_helper.RealmHelper;
......@@ -29,7 +30,6 @@ import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Iterator;
import org.json.JSONObject;
import timber.log.Timber;
/**
* Thread for handling WebSocket connection.
......@@ -91,8 +91,7 @@ public class RocketChatWebSocketThread extends HandlerThread {
private void forceInvalidateTokens() {
serverConfigRealm.executeTransaction(realm -> {
Session session = realm.where(Session.class)
.equalTo("sessionId", Session.DEFAULT_ID).findFirst();
Session session = Session.queryDefaultSession(realm).findFirst();
if (session != null
&& !TextUtils.isEmpty(session.getToken())
&& (session.isTokenVerified() || !TextUtils.isEmpty(session.getError()))) {
......@@ -113,7 +112,7 @@ public class RocketChatWebSocketThread extends HandlerThread {
@Override public boolean quit() {
if (isAlive()) {
new Handler(getLooper()).post(() -> {
Timber.d("thread %s: quit()", Thread.currentThread().getId());
RCLog.d("thread %s: quit()", Thread.currentThread().getId());
unregisterListeners();
RocketChatWebSocketThread.super.quit();
});
......@@ -159,9 +158,7 @@ public class RocketChatWebSocketThread extends HandlerThread {
.put("serverConfigId", serverConfigId)
.put("session", session))
).onSuccess(_task -> serverConfigRealm.executeTransaction(realm -> {
Session sessionObj = realm.where(Session.class)
.equalTo("sessionId", Session.DEFAULT_ID)
.findFirst();
Session sessionObj = Session.queryDefaultSession(realm).findFirst();
if (sessionObj == null) {
realm.createOrUpdateObjectFromJson(Session.class,
......@@ -212,11 +209,15 @@ public class RocketChatWebSocketThread extends HandlerThread {
}
listenersRegistered = true;
final ServerConfig config = defaultRealm.executeTransactionForRead(realm ->
realm.where(ServerConfig.class).equalTo("serverConfigId", serverConfigId).findFirst());
final String hostname = config.getHostname();
for (Class clazz : REGISTERABLE_CLASSES) {
try {
Constructor ctor = clazz.getConstructor(Context.class, RealmHelper.class,
Constructor ctor = clazz.getConstructor(Context.class, String.class, RealmHelper.class,
DDPClientWraper.class);
Object obj = ctor.newInstance(appContext, serverConfigRealm, ddpClient);
Object obj = ctor.newInstance(appContext, hostname, serverConfigRealm, ddpClient);
if (obj instanceof Registerable) {
Registerable registerable = (Registerable) obj;
......@@ -224,7 +225,7 @@ public class RocketChatWebSocketThread extends HandlerThread {
listeners.add(registerable);
}
} catch (Exception exception) {
Timber.w(exception, "Failed to register listeners!!");
RCLog.w(exception, "Failed to register listeners!!");
}
}
}
......
......@@ -4,6 +4,7 @@ import android.content.Context;
import android.text.TextUtils;
import chat.rocket.android.api.DDPClientWraper;
import chat.rocket.android.helper.LogcatIfError;
import chat.rocket.android.log.RCLog;
import chat.rocket.android.realm_helper.RealmHelper;
import chat.rocket.android.service.Registerable;
import chat.rocket.android_ddp.DDPSubscription;
......@@ -14,18 +15,19 @@ import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import rx.Subscription;
import timber.log.Timber;
public abstract class AbstractDDPDocEventSubscriber implements Registerable {
protected final Context context;
protected final String hostname;
protected final RealmHelper realmHelper;
protected final DDPClientWraper ddpClient;
private String subscriptionId;
private Subscription rxSubscription;
protected AbstractDDPDocEventSubscriber(Context context, RealmHelper realmHelper,
DDPClientWraper ddpClient) {
protected AbstractDDPDocEventSubscriber(Context context, String hostname,
RealmHelper realmHelper, DDPClientWraper ddpClient) {
this.context = context;
this.hostname = hostname;
this.realmHelper = realmHelper;
this.ddpClient = ddpClient;
}
......@@ -63,7 +65,7 @@ public abstract class AbstractDDPDocEventSubscriber implements Registerable {
return null;
}).continueWith(task -> {
if (task.isFaulted()) {
Timber.w(task.getError(), "DDP subscription failed.");
RCLog.w(task.getError(), "DDP subscription failed.");
}
return null;
});
......@@ -101,7 +103,7 @@ public abstract class AbstractDDPDocEventSubscriber implements Registerable {
//ignore movedBefore
}
} catch (Exception exception) {
Timber.w(exception, "failed to handle subscription callback");
RCLog.w(exception, "failed to handle subscription callback");
}
});
}
......
......@@ -7,9 +7,9 @@ import chat.rocket.android.service.ddp.AbstractDDPDocEventSubscriber;
import org.json.JSONArray;
abstract class AbstractBaseSubscriber extends AbstractDDPDocEventSubscriber {
protected AbstractBaseSubscriber(Context context, RealmHelper realmHelper,
protected AbstractBaseSubscriber(Context context, String hostname, RealmHelper realmHelper,
DDPClientWraper ddpClient) {
super(context, realmHelper, ddpClient);
super(context, hostname, realmHelper, ddpClient);
}
@Override protected final JSONArray getSubscriptionParams() {
......
......@@ -10,9 +10,9 @@ import io.realm.RealmObject;
* "activeUsers" subscriber.
*/
public class ActiveUsersSubscriber extends AbstractBaseSubscriber {
public ActiveUsersSubscriber(Context context, RealmHelper realmHelper,
public ActiveUsersSubscriber(Context context, String hostname, RealmHelper realmHelper,
DDPClientWraper ddpClient) {
super(context, realmHelper, ddpClient);
super(context, hostname, realmHelper, ddpClient);
}
@Override protected String getSubscriptionName() {
......
......@@ -10,9 +10,9 @@ import io.realm.RealmObject;
* meteor.loginServiceConfiguration subscriber
*/
public class LoginServiceConfigurationSubscriber extends AbstractBaseSubscriber {
public LoginServiceConfigurationSubscriber(Context context, RealmHelper realmHelper,
DDPClientWraper ddpClient) {
super(context, realmHelper, ddpClient);
public LoginServiceConfigurationSubscriber(Context context, String hostname,
RealmHelper realmHelper, DDPClientWraper ddpClient) {
super(context, hostname, realmHelper, ddpClient);
}
@Override protected String getSubscriptionName() {
......
......@@ -10,9 +10,9 @@ import io.realm.RealmObject;
* "userData" subscriber.
*/
public class UserDataSubscriber extends AbstractBaseSubscriber {
public UserDataSubscriber(Context context, RealmHelper realmHelper,
public UserDataSubscriber(Context context, String hostname, RealmHelper realmHelper,
DDPClientWraper ddpClient) {
super(context, realmHelper, ddpClient);
super(context, hostname, realmHelper, ddpClient);
}
@Override protected String getSubscriptionName() {
......
......@@ -3,17 +3,18 @@ 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.log.RCLog;
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.JSONException;
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);
protected AbstractStreamNotifyEventSubscriber(Context context, String hostname,
RealmHelper realmHelper, DDPClientWraper ddpClient) {
super(context, hostname, realmHelper, ddpClient);
}
@Override protected final boolean shouldTruncateTableOnInitialize() {
......@@ -24,26 +25,47 @@ abstract class AbstractStreamNotifyEventSubscriber extends AbstractDDPDocEventSu
return getSubscriptionName().equals(callbackName);
}
protected abstract String getSubscriptionParam();
@Override protected final JSONArray getSubscriptionParams() throws JSONException {
return new JSONArray().put(getSubscriptionParam()).put(false);
}
protected abstract String getPrimaryKeyForModel();
@Override protected void onDocumentChanged(DDPSubscription.Changed docEvent) {
@Override protected final void onDocumentAdded(DDPSubscription.Added docEvent) {
// do nothing.
}
@Override protected final void onDocumentRemoved(DDPSubscription.Removed docEvent) {
// do nothing.
}
@Override protected final 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());
if (!docEvent.fields.getString("eventName").equals(getSubscriptionParam())) {
return;
}
handleArgs(docEvent.fields.getJSONArray("args"));
} catch (Exception exception) {
Timber.w(exception, "failed to save stream-notify event.");
RCLog.w(exception, "failed to save stream-notify event.");
}
}
protected void handleArgs(JSONArray args) throws JSONException {
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());
}
}
}
package chat.rocket.android.service.ddp.stream;
import android.content.Context;
import chat.rocket.android.api.DDPClientWraper;
import chat.rocket.android.realm_helper.RealmHelper;
abstract class AbstractStreamNotifyUserEventSubscriber extends AbstractStreamNotifyEventSubscriber {
protected final String userId;
protected AbstractStreamNotifyUserEventSubscriber(Context context, String hostname,
RealmHelper realmHelper, DDPClientWraper ddpClient, String userId) {
super(context, hostname, realmHelper, ddpClient);
this.userId = userId;
}
@Override protected final String getSubscriptionName() {
return "stream-notify-user";
}
@Override protected final String getSubscriptionParam() {
return userId + "/" + getSubscriptionSubParam();
}
protected abstract String getSubscriptionSubParam();
}
package chat.rocket.android.service.ddp.stream;
import android.content.Context;
import chat.rocket.android.api.DDPClientWraper;
import chat.rocket.android.notification.Notifier;
import chat.rocket.android.notification.StreamNotifyUserNotifier;
import chat.rocket.android.realm_helper.RealmHelper;
import io.realm.RealmObject;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
public class StreamNotifyUserNotification extends AbstractStreamNotifyUserEventSubscriber {
public StreamNotifyUserNotification(Context context, String hostname, RealmHelper realmHelper,
DDPClientWraper ddpClient, String userId) {
super(context, hostname, realmHelper, ddpClient, userId);
}
@Override protected String getSubscriptionSubParam() {
return "notification";
}
@Override protected void handleArgs(JSONArray args) throws JSONException {
JSONObject target = args.getJSONObject(args.length() - 1);
Notifier notifier = new StreamNotifyUserNotifier(context, hostname,
target.getString("title"),
target.getString("text"),
target.getJSONObject("payload"));
notifier.publishNotificationIfNeeded();
}
@Override protected Class<? extends RealmObject> getModelClass() {
// not used because handleArgs is override.
return null;
}
@Override protected String getPrimaryKeyForModel() {
// not used because handleArgs is override.
return null;
}
}
......@@ -5,26 +5,15 @@ 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";
public class StreamNotifyUserSubscriptionsChanged extends AbstractStreamNotifyUserEventSubscriber {
public StreamNotifyUserSubscriptionsChanged(Context context, String hostname,
RealmHelper realmHelper, DDPClientWraper ddpClient, String userId) {
super(context, hostname, realmHelper, ddpClient, userId);
}
@Override protected JSONArray getSubscriptionParams() throws JSONException {
return new JSONArray()
.put(userId + "/subscriptions-changed")
.put(false);
@Override protected String getSubscriptionSubParam() {
return "subscriptions-changed";
}
@Override protected Class<? extends RealmObject> getModelClass() {
......
......@@ -5,7 +5,6 @@ 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;
......@@ -15,9 +14,9 @@ import org.json.JSONObject;
public class StreamRoomMessage extends AbstractStreamNotifyEventSubscriber {
private String roomId;
public StreamRoomMessage(Context context, RealmHelper realmHelper, DDPClientWraper ddpClient,
String roomId) {
super(context, realmHelper, ddpClient);
public StreamRoomMessage(Context context, String hostname,
RealmHelper realmHelper, DDPClientWraper ddpClient, String roomId) {
super(context, hostname, realmHelper, ddpClient);
this.roomId = roomId;
}
......@@ -25,10 +24,8 @@ public class StreamRoomMessage extends AbstractStreamNotifyEventSubscriber {
return "stream-room-messages";
}
@Override protected JSONArray getSubscriptionParams() throws JSONException {
return new JSONArray()
.put(roomId)
.put(false);
@Override protected String getSubscriptionParam() {
return roomId;
}
@Override protected Class<? extends RealmObject> getModelClass() {
......
......@@ -15,14 +15,16 @@ public class StreamRoomMessageManager implements Registerable {
private StreamRoomMessage streamRoomMessage;
private final Context context;
private final String hostname;
private final RealmHelper realmHelper;
private final DDPClientWraper ddpClient;
private final AbstractRocketChatCacheObserver cacheObserver;
private final Handler handler;
public StreamRoomMessageManager(Context context, RealmHelper realmHelper,
DDPClientWraper ddpClient) {
public StreamRoomMessageManager(Context context, String hostname,
RealmHelper realmHelper, DDPClientWraper ddpClient) {
this.context = context;
this.hostname = hostname;
this.realmHelper = realmHelper;
this.ddpClient = ddpClient;
......@@ -37,7 +39,7 @@ public class StreamRoomMessageManager implements Registerable {
private void registerStreamNotifyMessage(String roomId) {
handler.post(() -> {
streamRoomMessage = new StreamRoomMessage(context, realmHelper, ddpClient, roomId);
streamRoomMessage = new StreamRoomMessage(context, hostname, realmHelper, ddpClient, roomId);
streamRoomMessage.register();
});
}
......
......@@ -11,13 +11,15 @@ abstract class AbstractModelObserver<T extends RealmObject>
implements Registerable, RealmListObserver.Query<T>, RealmListObserver.OnUpdateListener<T> {
protected final Context context;
protected final String hostname;
protected final RealmHelper realmHelper;
protected final DDPClientWraper ddpClient;
private final RealmListObserver observer;
protected AbstractModelObserver(Context context, RealmHelper realmHelper,
DDPClientWraper ddpClient) {
protected AbstractModelObserver(Context context, String hostname,
RealmHelper realmHelper, DDPClientWraper ddpClient) {
this.context = context;
this.hostname = hostname;
this.realmHelper = realmHelper;
this.ddpClient = ddpClient;
observer = realmHelper.createListObserver(this).setOnUpdateListener(this);
......
......@@ -6,6 +6,7 @@ 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.StreamNotifyUserNotification;
import chat.rocket.android.service.ddp.stream.StreamNotifyUserSubscriptionsChanged;
import hugo.weaving.DebugLog;
import io.realm.Realm;
......@@ -23,9 +24,9 @@ public class CurrentUserObserver extends AbstractModelObserver<User> {
private ArrayList<Registerable> listeners;
public CurrentUserObserver(Context context, RealmHelper realmHelper,
DDPClientWraper ddpClient) {
super(context, realmHelper, ddpClient);
public CurrentUserObserver(Context context, String hostname,
RealmHelper realmHelper, DDPClientWraper ddpClient) {
super(context, hostname, realmHelper, ddpClient);
methodCall = new MethodCallHelper(realmHelper, ddpClient);
currentUserExists = false;
}
......@@ -59,11 +60,16 @@ public class CurrentUserObserver extends AbstractModelObserver<User> {
// get and observe Room subscriptions.
methodCall.getRoomSubscriptions().onSuccess(task -> {
Registerable listener = new StreamNotifyUserSubscriptionsChanged(
context, realmHelper, ddpClient, userId);
context, hostname, realmHelper, ddpClient, userId);
listener.register();
listeners.add(listener);
return null;
});
Registerable listener = new StreamNotifyUserNotification(
context, hostname, realmHelper, ddpClient, userId);
listener.register();
listeners.add(listener);
}
@DebugLog
......
......@@ -4,6 +4,7 @@ import android.content.Context;
import bolts.Task;
import chat.rocket.android.api.DDPClientWraper;
import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.android.log.RCLog;
import chat.rocket.android.model.SyncState;
import chat.rocket.android.model.internal.GetUsersOfRoomsProcedure;
import chat.rocket.android.realm_helper.RealmHelper;
......@@ -11,7 +12,6 @@ import io.realm.Realm;
import io.realm.RealmResults;
import java.util.List;
import org.json.JSONObject;
import timber.log.Timber;
/**
* Model observer for executing getUsersOfRooms.
......@@ -21,9 +21,9 @@ public class GetUsersOfRoomsProcedureObserver
private final MethodCallHelper methodCall;
public GetUsersOfRoomsProcedureObserver(Context context, RealmHelper realmHelper,
DDPClientWraper ddpClient) {
super(context, realmHelper, ddpClient);
public GetUsersOfRoomsProcedureObserver(Context context, String hostname,
RealmHelper realmHelper, DDPClientWraper ddpClient) {
super(context, hostname, realmHelper, ddpClient);
methodCall = new MethodCallHelper(realmHelper, ddpClient);
}
......@@ -58,7 +58,7 @@ public class GetUsersOfRoomsProcedureObserver
})
).continueWithTask(task -> {
if (task.isFaulted()) {
Timber.w(task.getError());
RCLog.w(task.getError());
return realmHelper.executeTransaction(realm ->
realm.createOrUpdateObjectFromJson(GetUsersOfRoomsProcedure.class, new JSONObject()
.put("roomId", roomId)
......
......@@ -2,18 +2,18 @@ package chat.rocket.android.service.observer;
import android.content.Context;
import bolts.Task;
import chat.rocket.android.api.DDPClientWraper;
import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.android.model.internal.LoadMessageProcedure;
import chat.rocket.android.log.RCLog;
import chat.rocket.android.model.SyncState;
import chat.rocket.android.model.ddp.Message;
import chat.rocket.android.model.internal.LoadMessageProcedure;
import chat.rocket.android.realm_helper.RealmHelper;
import chat.rocket.android.api.DDPClientWraper;
import io.realm.Realm;
import io.realm.RealmResults;
import io.realm.Sort;
import java.util.List;
import org.json.JSONObject;
import timber.log.Timber;
/**
* Background process for loading messages.
......@@ -22,9 +22,9 @@ public class LoadMessageProcedureObserver extends AbstractModelObserver<LoadMess
private final MethodCallHelper methodCall;
public LoadMessageProcedureObserver(Context context, RealmHelper realmHelper,
DDPClientWraper ddpClient) {
super(context, realmHelper, ddpClient);
public LoadMessageProcedureObserver(Context context, String hostname,
RealmHelper realmHelper, DDPClientWraper ddpClient) {
super(context, hostname, realmHelper, ddpClient);
methodCall = new MethodCallHelper(realmHelper, ddpClient);
}
......@@ -70,7 +70,7 @@ public class LoadMessageProcedureObserver extends AbstractModelObserver<LoadMess
})
).continueWithTask(task -> {
if (task.isFaulted()) {
Timber.w(task.getError());
RCLog.w(task.getError());
return realmHelper.executeTransaction(realm ->
realm.createOrUpdateObjectFromJson(LoadMessageProcedure.class, new JSONObject()
.put("roomId", roomId)
......
......@@ -18,13 +18,14 @@ import org.json.JSONObject;
*/
public class MethodCallObserver extends AbstractModelObserver<MethodCall> {
private String prevHash;
private String prevDigest;
/**
* constructor.
*/
public MethodCallObserver(Context context, RealmHelper realmHelper, DDPClientWraper ddpClient) {
super(context, realmHelper, ddpClient);
public MethodCallObserver(Context context, String hostname,
RealmHelper realmHelper, DDPClientWraper ddpClient) {
super(context, hostname, realmHelper, ddpClient);
realmHelper.executeTransaction(realm -> {
// resume pending operations.
RealmResults<MethodCall> pendingMethodCalls = realm.where(MethodCall.class)
......@@ -53,7 +54,7 @@ public class MethodCallObserver extends AbstractModelObserver<MethodCall> {
.findAll();
}
private String getHashFor(List<MethodCall> results) {
private String getDigestFor(List<MethodCall> results) {
if (results == null) {
return "-";
}
......@@ -68,17 +69,17 @@ public class MethodCallObserver extends AbstractModelObserver<MethodCall> {
}
@Override public void onUpdateResults(List<MethodCall> results) {
String hash = getHashFor(results);
if (prevHash == null) {
if (hash == null) {
String digest = getDigestFor(results);
if (prevDigest == null) {
if (digest == null) {
return;
}
} else {
if (prevHash.equals(hash)) {
if (prevDigest.equals(digest)) {
return;
}
}
prevHash = hash;
prevDigest = digest;
if (results == null || results.isEmpty()) {
return;
}
......
......@@ -4,6 +4,7 @@ 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.log.RCLog;
import chat.rocket.android.model.SyncState;
import chat.rocket.android.model.ddp.Message;
import chat.rocket.android.realm_helper.RealmHelper;
......@@ -11,7 +12,6 @@ import io.realm.Realm;
import io.realm.RealmResults;
import java.util.List;
import org.json.JSONObject;
import timber.log.Timber;
/**
* Observe messages for sending.
......@@ -20,9 +20,9 @@ public class NewMessageObserver extends AbstractModelObserver<Message> {
private final MethodCallHelper methodCall;
public NewMessageObserver(Context context, RealmHelper realmHelper,
DDPClientWraper ddpClient) {
super(context, realmHelper, ddpClient);
public NewMessageObserver(Context context, String hostname,
RealmHelper realmHelper, DDPClientWraper ddpClient) {
super(context, hostname, realmHelper, ddpClient);
methodCall = new MethodCallHelper(realmHelper, ddpClient);
realmHelper.executeTransaction(realm -> {
......@@ -69,7 +69,7 @@ public class NewMessageObserver extends AbstractModelObserver<Message> {
})
).continueWith(task -> {
if (task.isFaulted()) {
Timber.w(task.getError());
RCLog.w(task.getError());
realmHelper.executeTransaction(realm ->
realm.createOrUpdateObjectFromJson(Message.class, new JSONObject()
.put("_id", messageId)
......
......@@ -25,11 +25,12 @@ public class SessionObserver extends AbstractModelObserver<Session> {
/**
* constructor.
*/
public SessionObserver(Context context, RealmHelper realmHelper, DDPClientWraper ddpClient) {
super(context, realmHelper, ddpClient);
public SessionObserver(Context context, String hostname,
RealmHelper realmHelper, DDPClientWraper ddpClient) {
super(context, hostname, realmHelper, ddpClient);
count = 0;
streamNotifyMessage = new StreamRoomMessageManager(context, realmHelper, ddpClient);
streamNotifyMessage = new StreamRoomMessageManager(context, hostname, realmHelper, ddpClient);
}
@Override public RealmResults<Session> queryItems(Realm realm) {
......
......@@ -14,8 +14,9 @@ public class TokenLoginObserver extends AbstractModelObserver<Session> {
private final MethodCallHelper methodCall;
public TokenLoginObserver(Context context, RealmHelper realmHelper, DDPClientWraper ddpClient) {
super(context, realmHelper, ddpClient);
public TokenLoginObserver(Context context, String hostname,
RealmHelper realmHelper, DDPClientWraper ddpClient) {
super(context, hostname, realmHelper, ddpClient);
methodCall = new MethodCallHelper(realmHelper, ddpClient);
}
......
<vector android:height="24dp" android:viewportHeight="48.0"
android:viewportWidth="48.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#A3000000"
android:pathData="M44.99,23.47C44.99,21.42 44.38,19.45 43.16,17.62C42.07,15.97 40.54,14.52 38.62,13.29C34.91,10.92 30.03,9.62 24.88,9.62C23.16,9.62 21.47,9.77 19.82,10.05C18.8,9.1 17.61,8.24 16.35,7.57C9.6,4.3 4,7.49 4,7.49C4,7.49 9.21,11.76 8.36,15.49C6.03,17.8 4.77,20.58 4.77,23.47C4.77,23.48 4.77,23.49 4.77,23.5C4.77,23.51 4.77,23.51 4.77,23.52C4.77,26.42 6.03,29.2 8.36,31.5C9.21,35.24 4,39.5 4,39.5C4,39.5 9.6,42.69 16.35,39.43C17.61,38.75 18.8,37.89 19.82,36.94C21.47,37.23 23.16,37.37 24.88,37.37C30.03,37.37 34.91,36.07 38.62,33.7C40.54,32.48 42.07,31.02 43.16,29.38C44.38,27.55 44.99,25.58 44.99,23.53C44.99,23.52 44.99,23.51 44.99,23.5L44.99,23.47ZM24.88,12.53C34.41,12.53 42.14,17.45 42.14,23.52C42.14,29.6 34.41,34.52 24.88,34.52C22.76,34.52 20.73,34.28 18.85,33.83C16.94,36.12 12.74,39.31 8.67,38.28C9.99,36.86 11.96,34.45 11.54,30.5C9.09,28.6 7.63,26.17 7.63,23.52C7.63,17.45 15.35,12.53 24.88,12.53Z"
android:strokeColor="#00000000" android:strokeWidth="1"/>
<path android:fillColor="#A3000000"
android:pathData="M24.88,26.17C26.15,26.17 27.17,25.14 27.17,23.88C27.17,22.61 26.15,21.59 24.88,21.59C23.62,21.59 22.59,22.61 22.59,23.88C22.59,25.14 23.62,26.17 24.88,26.17ZM32.85,26.17C34.12,26.17 35.14,25.14 35.14,23.88C35.14,22.61 34.12,21.59 32.85,21.59C31.59,21.59 30.56,22.61 30.56,23.88C30.56,25.14 31.59,26.17 32.85,26.17ZM16.91,26.17C18.18,26.17 19.2,25.14 19.2,23.88C19.2,22.62 18.18,21.59 16.91,21.59C15.65,21.59 14.62,22.62 14.62,23.88C14.62,25.14 15.65,26.17 16.91,26.17L16.91,26.17Z"
android:strokeColor="#00000000" android:strokeWidth="1"/>
<path android:fillColor="#33000000"
android:pathData="M24.88,33.08C22.76,33.08 20.73,32.86 18.85,32.48C17.17,34.23 13.69,36.59 10.1,36.5C9.62,37.22 9.11,37.8 8.67,38.28C12.74,39.31 16.94,36.12 18.85,33.83C20.73,34.28 22.76,34.52 24.88,34.52C34.34,34.52 42.01,29.68 42.13,23.67C42.01,28.88 34.34,33.08 24.88,33.08L24.88,33.08Z"
android:strokeColor="#00000000" android:strokeWidth="1"/>
</vector>
......@@ -56,6 +56,14 @@
android:id="@+id/message_body"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<chat.rocket.android.widget.message.RocketChatMessageUrlsLayout
android:id="@+id/message_urls"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<chat.rocket.android.widget.message.RocketChatMessageAttachmentsLayout
android:id="@+id/message_attachments"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="avatar_image_size_normal">24dp</dimen>
<dimen name="avatar_image_size_large">48dp</dimen>
<dimen name="avatar_text_size_normal">11sp</dimen>
<dimen name="avatar_text_size_large">28sp</dimen>
<dimen name="notification_avatar_size">48dp</dimen>
</resources>
\ No newline at end of file
......@@ -16,7 +16,9 @@ ext {
rxJava = 'io.reactivex:rxjava:1.2.2'
boltsTask = 'com.parse.bolts:bolts-tasks:1.4.0'
timber = 'com.jakewharton.timber:timber:4.3.1'
okhttp3 = 'com.squareup.okhttp3:okhttp:3.4.1'
picasso = 'com.squareup.picasso:picasso:2.5.2'
picasso2Okhttp3Downloader = 'com.jakewharton.picasso:picasso2-okhttp3-downloader:1.1.0'
preDexLibs = !"true".equals(System.getenv("CI"))
}
......
apply plugin: 'com.android.library'
buildscript {
repositories {
jcenter()
}
dependencies {
classpath rootProject.ext.androidPlugin
}
}
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.compileSdkVersion
versionCode 1
versionName "1"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile rootProject.ext.supportAnnotations
}
\ No newline at end of file
<manifest package="chat.rocket.android.log"/>
\ No newline at end of file
package chat.rocket.android.log;
import android.util.Log;
public class RCLog {
public static void d(String log, Object... args) {
Log.d(getTag(), String.format(log, args));
}
public static void d(Throwable throwable) {
Log.d(getTag(), throwable.getMessage(), throwable);
}
public static void d(Throwable throwable, String log, Object... args) {
Log.d(getTag(), String.format(log, args), throwable);
}
public static void w(String log, Object... args) {
Log.w(getTag(), String.format(log, args));
}
public static void w(Throwable throwable) {
Log.w(getTag(), throwable.getMessage(), throwable);
}
public static void w(Throwable throwable, String log, Object... args) {
Log.w(getTag(), String.format(log, args), throwable);
}
public static void e(String log, Object... args) {
Log.e(getTag(), String.format(log, args));
}
public static void e(Throwable throwable) {
Log.e(getTag(), throwable.getMessage(), throwable);
}
public static void e(Throwable throwable, String log, Object... args) {
Log.e(getTag(), String.format(log, args), throwable);
}
private static String getTag() {
StackTraceElement[] elements = Thread.currentThread().getStackTrace();
if (elements.length >= 5) {
return getSimpleName(elements[4].getClassName());
} else {
return "Rocket.Chat";
}
}
private static String getSimpleName(String className) {
int idx = className.lastIndexOf(".");
return className.substring(idx + 1);
}
}
apply plugin: 'com.android.library'
buildscript {
repositories {
jcenter()
}
dependencies {
classpath rootProject.ext.androidPlugin
}
}
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.compileSdkVersion
versionCode 1
versionName "1"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile rootProject.ext.supportAnnotations
}
\ No newline at end of file
<manifest package="chat.rocket.android.log"/>
\ No newline at end of file
package chat.rocket.android.log;
public class RCLog {
public static void d(String log, Object... args) {
}
public static void d(Throwable throwable) {
}
public static void d(Throwable throwable, String log, Object... args) {
}
public static void w(String log, Object... args) {
}
public static void w(Throwable throwable) {
}
public static void w(Throwable throwable, String log, Object... args) {
}
public static void e(String log, Object... args) {
}
public static void e(Throwable throwable) {
}
public static void e(Throwable throwable, String log, Object... args) {
}
}
......@@ -43,8 +43,8 @@ android {
dependencies {
testCompile 'junit:junit:4.12'
compile project(':log-wrapper-rel')
compile rootProject.ext.boltsTask
compile rootProject.ext.timber
compile rootProject.ext.supportAnnotations
compile rootProject.ext.supportAppCompat
compile rootProject.ext.supportDesign
......
......@@ -6,6 +6,7 @@ import android.os.Looper;
import android.support.v7.widget.RecyclerView;
import bolts.Task;
import bolts.TaskCompletionSource;
import chat.rocket.android.log.RCLog;
import io.realm.Realm;
import io.realm.RealmConfiguration;
import io.realm.RealmObject;
......@@ -13,7 +14,6 @@ import io.realm.RealmResults;
import java.util.Collections;
import java.util.List;
import org.json.JSONException;
import timber.log.Timber;
@SuppressLint("NewApi")
public class RealmHelper {
......@@ -61,7 +61,7 @@ public class RealmHelper {
T source = transaction.execute(realm);
return source != null ? realm.copyFromRealm(source) : null;
} catch (Exception exception) {
Timber.w(exception, "failed to execute copyFromRealm");
RCLog.w(exception);
return null;
}
}
......@@ -71,7 +71,7 @@ public class RealmHelper {
try (Realm realm = instance()) {
return realm.copyFromRealm(transaction.execute(realm));
} catch (Exception exception) {
Timber.w(exception, "failed to execute copyFromRealm");
RCLog.w(exception);
return Collections.emptyList();
}
}
......@@ -85,13 +85,11 @@ public class RealmHelper {
final TaskCompletionSource<Void> task = new TaskCompletionSource<>();
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);
}
realm.executeTransaction(_realm -> {
try {
transaction.execute(_realm);
} catch (JSONException exception) {
throw new RuntimeException(exception);
}
});
task.setResult(null);
......
......@@ -31,10 +31,17 @@ public class RealmListObserver<T extends RealmObject> extends AbstractRealmResul
return query.queryItems(realm);
}
private String previousResultsString;
@Override public final RealmChangeListener<RealmResults<T>> getListener() {
return element -> {
return results -> {
String currentResultString = results != null ? results.toString() : null;
if (previousResultsString != null && previousResultsString.equals(currentResultString)) {
return;
}
previousResultsString = currentResultString;
if (onUpdateListener != null) {
onUpdateListener.onUpdateResults(element);
onUpdateListener.onUpdateResults(results);
}
};
}
......
......@@ -40,4 +40,7 @@ dependencies {
compile rootProject.ext.supportAppCompat
compile rootProject.ext.supportDesign
compile 'org.nibor.autolink:autolink:0.5.0'
compile rootProject.ext.okhttp3
compile rootProject.ext.picasso
compile rootProject.ext.picasso2Okhttp3Downloader
}
package chat.rocket.android.widget.helper;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
*/
public interface ImageFormat {
String PNG = "image/png";
String JPG = "image/jpg";
String JPEG = "image/jpeg";
String WEBP = "image/webp";
List<String> SUPPORTED_LIST = Collections.unmodifiableList(new ArrayList<String>() {
{
add(PNG);
add(JPG);
add(JPEG);
add(WEBP);
}
});
}
package chat.rocket.android.widget.message;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import chat.rocket.android.widget.R;
import chat.rocket.android.widget.helper.ImageFormat;
import com.jakewharton.picasso.OkHttp3Downloader;
import com.squareup.picasso.Picasso;
import java.io.IOException;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
/**
*/
public class RocketChatMessageAttachmentsLayout extends LinearLayout {
private LayoutInflater inflater;
private String hostname;
private String attachmentsString;
private String userId;
private String token;
private OkHttp3Downloader downloader;
public RocketChatMessageAttachmentsLayout(Context context) {
super(context);
initialize(context, null);
}
public RocketChatMessageAttachmentsLayout(Context context, AttributeSet attrs) {
super(context, attrs);
initialize(context, attrs);
}
public RocketChatMessageAttachmentsLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initialize(context, attrs);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public RocketChatMessageAttachmentsLayout(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initialize(context, attrs);
}
private void initialize(Context context, AttributeSet attrs) {
inflater = LayoutInflater.from(context);
setOrientation(VERTICAL);
}
public void setHostname(String hostname) {
this.hostname = hostname;
}
public void setCredential(String userId, String token) {
this.userId = userId;
this.token = token;
}
private OkHttp3Downloader getDownloader() {
if (downloader == null) {
Interceptor interceptor = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
// uid/token is required to download attachment files.
// see: RocketChat:lib/fileUpload.coffee
Request newRequest = chain.request().newBuilder()
.header("Cookie", "rc_uid=" + userId + ";rc_token=" + token)
.build();
return chain.proceed(newRequest);
}
};
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(interceptor)
.build();
downloader = new OkHttp3Downloader(okHttpClient);
}
return downloader;
}
public void setAttachments(String attachmentsString) {
if (this.attachmentsString != null && this.attachmentsString.equals(attachmentsString)) {
return;
}
this.attachmentsString = attachmentsString;
removeAllViews();
try {
JSONArray attachments = new JSONArray(attachmentsString);
for (int i = 0; i < attachments.length(); i++) {
JSONObject attachment = attachments.getJSONObject(i);
appendAttachmentView(attachment);
}
} catch (JSONException exception) {
return;
}
}
private void appendAttachmentView(JSONObject attachmentObj) throws JSONException {
if (attachmentObj.isNull("image_url")) {
return;
}
String imageURL = attachmentObj.getString("image_url");
String imageType = attachmentObj.getString("image_type");
if (TextUtils.isEmpty(imageURL)
|| !imageType.startsWith("image/")
|| !ImageFormat.SUPPORTED_LIST.contains(imageType)) {
return;
}
View attachmentView = inflater.inflate(R.layout.message_inline_attachment, this, false);
new Picasso.Builder(getContext())
.downloader(getDownloader())
.build()
.load(absolutize(imageURL))
.placeholder(R.drawable.image_dummy)
.error(R.drawable.image_error)
.into((ImageView) attachmentView.findViewById(R.id.image));
TextView titleView = (TextView) attachmentView.findViewById(R.id.title);
if (attachmentObj.isNull("title")) {
titleView.setVisibility(View.GONE);
} else {
titleView.setVisibility(View.VISIBLE);
titleView.setText(attachmentObj.getString("title"));
if (attachmentObj.isNull("title_link")) {
titleView.setOnClickListener(null);
titleView.setClickable(false);
} else {
final String link = absolutize(attachmentObj.getString("title_link"));
titleView.setOnClickListener(new OnClickListener() {
@Override public void onClick(View view) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(link));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
view.getContext().startActivity(intent);
}
});
}
}
addView(attachmentView);
}
private String absolutize(String url) {
return url.startsWith("/") ? "https://" + hostname + url : url;
}
}
......@@ -3,6 +3,7 @@ package chat.rocket.android.widget.message;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.LinearLayout;
......@@ -46,7 +47,29 @@ public class RocketChatMessageLayout extends LinearLayout {
public void setText(String messageBody) {
removeAllViews();
appendTextView(messageBody);
if (messageBody.contains("```")) {
boolean highlight = false;
for (final String chunk : TextUtils.split(messageBody.replace("\r\n", "\n"), "```")) {
final String trimmedChunk = chunk.replaceFirst("^\n", "").replaceFirst("\n$", "");
if (highlight) {
appendHighlightTextView(trimmedChunk);
} else if (trimmedChunk.length() > 0) {
appendTextView(trimmedChunk);
}
highlight = !highlight;
}
} else {
appendTextView(messageBody);
}
}
private void appendHighlightTextView(String text) {
TextView textView = (TextView) inflater.inflate(R.layout.message_body_highlight, this, false);
textView.setText(text);
Linkify.markup(textView);
addView(textView);
}
private void appendTextView(String text) {
......
package chat.rocket.android.widget.message;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import chat.rocket.android.widget.R;
import chat.rocket.android.widget.helper.ImageFormat;
import com.squareup.picasso.Picasso;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
/**
*/
public class RocketChatMessageUrlsLayout extends LinearLayout {
private LayoutInflater inflater;
private String urlsString;
public RocketChatMessageUrlsLayout(Context context) {
super(context);
initialize(context, null);
}
public RocketChatMessageUrlsLayout(Context context, AttributeSet attrs) {
super(context, attrs);
initialize(context, attrs);
}
public RocketChatMessageUrlsLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initialize(context, attrs);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public RocketChatMessageUrlsLayout(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initialize(context, attrs);
}
private void initialize(Context context, AttributeSet attrs) {
inflater = LayoutInflater.from(context);
setOrientation(VERTICAL);
}
public void setUrls(String urlsString) {
if (this.urlsString != null && this.urlsString.equals(urlsString)) {
return;
}
this.urlsString = urlsString;
removeAllViews();
try {
JSONArray urls = new JSONArray(urlsString);
for (int i = 0; i < urls.length(); i++) {
JSONObject url = urls.getJSONObject(i);
appendUrlView(url);
}
} catch (JSONException exception) {
return;
}
}
private void appendUrlView(JSONObject urlObj) throws JSONException {
final String url = urlObj.getString("url");
String contentType = urlObj.getJSONObject("headers").getString("contentType");
if (contentType.startsWith("image/") && ImageFormat.SUPPORTED_LIST.contains(contentType)) {
View inlineImage = inflater.inflate(R.layout.message_inline_image, this, false);
Picasso.with(getContext())
.load(url)
.placeholder(R.drawable.image_dummy)
.error(R.drawable.image_error)
.into((ImageView) inlineImage.findViewById(R.id.message_inline_image));
addView(inlineImage);
}
// see Rocket.Chat:packages/rocketchat-oembed/client/oembedUrlWidget.coffee
if (!urlObj.isNull("meta")) {
JSONObject meta = urlObj.getJSONObject("meta");
String title = null;
if (!meta.isNull("ogTitle")) {
title = meta.getString("ogTitle");
} else if (!meta.isNull("twitterTitle")) {
title = meta.getString("twitterTitle");
} else if (!meta.isNull("pageTitle")) {
title = meta.getString("pageTitle");
}
String description = null;
if (!meta.isNull("ogDescription")) {
description = meta.getString("ogDescription");
} else if (!meta.isNull("twitterDescription")) {
description = meta.getString("twitterDescription");
} else if (!meta.isNull("description")) {
description = meta.getString("description");
}
if (description != null) {
if (description.startsWith("\"")) {
description = description.substring(1);
}
if (description.endsWith("\"")) {
description = description.substring(0, description.length() - 1);
}
}
String imageURL = null;
if (!meta.isNull("ogImage")) {
imageURL = meta.getString("ogImage");
} else if (!meta.isNull("twitterImage")) {
imageURL = meta.getString("twitterImage");
}
String host = urlObj.getJSONObject("parsedUrl").getString("host");
View embedUrl = inflater.inflate(R.layout.message_inline_embed_url, this, false);
((TextView) embedUrl.findViewById(R.id.hostname)).setText(host);
((TextView) embedUrl.findViewById(R.id.title)).setText(title);
((TextView) embedUrl.findViewById(R.id.description)).setText(description);
ImageView image = (ImageView) embedUrl.findViewById(R.id.image);
if (TextUtils.isEmpty(imageURL)) {
image.setVisibility(View.GONE);
} else {
Picasso.with(getContext())
.load(imageURL)
.placeholder(R.drawable.image_dummy)
.error(R.drawable.image_error)
.into(image);
image.setVisibility(View.VISIBLE);
}
embedUrl.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
getContext().startActivity(intent);
}
});
addView(embedUrl);
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/highlight_text_background_color"/>
<stroke android:width="1dp" android:color="@color/highlight_text_border_color" />
<corners android:radius="4dp"/>
</shape>
\ No newline at end of file
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="80dp"
android:height="45dp"
android:viewportWidth="80.0"
android:viewportHeight="45.0">
<path
android:pathData="M0,0h80v45h-80z"
android:strokeColor="#00000000"
android:fillColor="#C3D1DA"
android:strokeWidth="1"/>
<path
android:pathData="M43.99,16.75C44.34,16.75 44.64,16.87 44.88,17.12C45.13,17.36 45.25,17.66 45.25,18.01L45.25,26.74C45.25,27.09 45.13,27.39 44.88,27.63C44.64,27.88 44.34,28 43.99,28L35.26,28C34.91,28 34.61,27.88 34.37,27.63C34.12,27.39 34,27.09 34,26.74L34,18.01C34,17.66 34.12,17.36 34.37,17.12C34.61,16.87 34.91,16.75 35.26,16.75L43.99,16.75ZM43.99,26.74L43.99,18.01L35.26,18.01L35.26,26.74L43.99,26.74ZM40.86,22.55L43.05,25.51L36.2,25.51L37.9,23.28L39.13,24.78L40.86,22.55Z"
android:strokeColor="#00000000"
android:fillColor="#5D8298"
android:strokeWidth="1"/>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="80dp"
android:height="45dp"
android:viewportWidth="80.0"
android:viewportHeight="45.0">
<path
android:pathData="M0,0h80v45h-80z"
android:strokeColor="#00000000"
android:fillColor="#C3D1DA"
android:strokeWidth="1"/>
<path
android:pathData="M33,28L39.88,16.13L46.74,28L33,28ZM40.5,26.13L40.5,24.87L39.24,24.87L39.24,26.13L40.5,26.13ZM40.5,23.63L40.5,21.12L39.24,21.12L39.24,23.63L40.5,23.63Z"
android:strokeColor="#00000000"
android:fillColor="#5D8298"
android:strokeWidth="1"/>
</vector>
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
<stroke android:color="@color/inline_attachment_box_outline" android:width="1dp"/>
<solid android:color="@color/inline_attachment_box_background"/>
<corners android:radius="2dp" />
</shape>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.RocketChat.MessageBody.Highlight"
android:background="@drawable/highlight_background_with_border"
android:padding="6dp"
android:layout_marginStart="0px"
android:layout_marginEnd="6dp"
android:layout_marginTop="3dp"
android:layout_marginBottom="3dp"
/>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_margin="4dp"
android:padding="4dp">
<View
android:layout_width="3dp"
android:layout_height="match_parent"
android:layout_marginRight="5dp"
android:background="@color/inline_attachment_quote_line"/>
<LinearLayout
android:layout_width="0px"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:layout_marginBottom="2dp"
android:textAppearance="@style/TextAppearance.RocketChat.MessageAttachment.Title"
android:background="?attr/selectableItemBackground"/>
<ImageView
android:id="@+id/image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxHeight="200dp"
android:layout_marginTop="4dp"
android:layout_marginRight="8dp"
android:adjustViewBounds="true"
android:scaleType="fitStart"/>
</LinearLayout>
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="?attr/selectableItemBackground"
android:layout_margin="4dp"
android:padding="4dp">
<View
android:layout_width="3dp"
android:layout_height="match_parent"
android:layout_marginEnd="5dp"
android:background="@color/inline_attachment_quote_line"/>
<ImageView
android:id="@+id/image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxHeight="60dp"
android:layout_marginTop="2dp"
android:layout_marginEnd="8dp"
android:adjustViewBounds="true"
android:scaleType="fitStart"/>
<LinearLayout
android:layout_width="0px"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/hostname"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:layout_marginBottom="2dp"
android:enabled="false"/>
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:layout_marginBottom="2dp"
android:textAppearance="@style/TextAppearance.RocketChat.MessageAttachment.Title"/>
<TextView
android:id="@+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:layout_marginBottom="2dp"
android:textAppearance="@style/TextAppearance.RocketChat.MessageAttachment.Description"/>
</LinearLayout>
</LinearLayout>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/inline_attachment_background"
android:layout_margin="4dp"
android:padding="4dp">
<ImageView
android:id="@+id/message_inline_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxHeight="200dp"
android:adjustViewBounds="true"
android:scaleType="fitStart"/>
</FrameLayout>
......@@ -4,8 +4,31 @@
<style name="TextAppearance.RocketChat.MessageBody" parent="TextAppearance.AppCompat.Body1">
</style>
<style name="TextAppearance.RocketChat.MessageBody.Highlight">
<item name="android:textColor">@color/highlight_text_color</item>
<item name="android:typeface">monospace</item>
<item name="android:textStyle">bold</item>
</style>
<style name="TextAppearance.RocketChat.MessageAttachment" parent="TextAppearance.AppCompat.Body1"/>
<style name="TextAppearance.RocketChat.MessageAttachment.Title" parent="TextAppearance.AppCompat.Title">
<item name="android:textSize">14sp</item>
<item name="android:textColor">?android:attr/textColorLink</item>
</style>
<style name="TextAppearance.RocketChat.MessageAttachment.Description">
<item name="android:textSize">12sp</item>
</style>
<style name="TextAppearance.RocketChat.MessageAttachment.Hostname"
parent="TextAppearance.AppCompat.Caption">
<item name="android:textSize">8sp</item>
</style>
<color name="highlight_text_color">#333</color>
<color name="highlight_text_background_color">#f8f8f8</color>
<color name="highlight_text_border_color">#ccc</color>
<color name="inline_attachment_quote_line">#FFCCCCCC</color>
<color name="inline_attachment_box_outline">#FFF0F0F0</color>
<color name="inline_attachment_box_background">@android:color/white</color>
</resources>
\ No newline at end of file
include ':app', ':android-ddp', ':rocket-chat-android-widgets', ':realm-helpers'
include ':app', ':android-ddp', ':rocket-chat-android-widgets', ':realm-helpers',
':log-wrapper-dev',
':log-wrapper-rel'
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