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

Merge pull request #560 from RocketChat/develop

[RELEASE] Merge develop into master
parents 1f3713b3 92ee59e8
......@@ -19,4 +19,4 @@ git clone https://github.com/RocketChat/Rocket.Chat.Android
### Code style guide
Before submitting a PR you should follow our [Coding Style](https://github.com/RocketChat/Rocket.Chat.Android/blob/develop/CODING_STYLE.md).
Before submitting a PR you should follow our [Coding Style](https://github.com/RocketChat/java-code-styles/blob/master/CODING_STYLE.md).
apply plugin: 'com.android.library'
apply plugin: 'me.tatarka.retrolambda'
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'
classpath 'me.tatarka:gradle-retrolambda:3.6.1'
classpath 'me.tatarka.retrolambda.projectlombok:lombok.ast:0.2.3.a2'
}
}
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
......
# -*- 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)
package chat.rocket.android_ddp;
import io.reactivex.Flowable;
import io.reactivex.Maybe;
import io.reactivex.annotations.Nullable;
import android.text.TextUtils;
import org.json.JSONArray;
import org.json.JSONException;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference;
import bolts.Task;
import bolts.TaskCompletionSource;
import chat.rocket.android.log.RCLog;
import chat.rocket.android_ddp.rx.RxWebSocketCallback;
import io.reactivex.Flowable;
import io.reactivex.Maybe;
import io.reactivex.annotations.NonNull;
import io.reactivex.annotations.Nullable;
import okhttp3.OkHttpClient;
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);
}
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 Maybe<DDPClientCallback.Base> doPing(@Nullable String id) {
return impl.ping(id);
}
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 Flowable<DDPSubscription.Event> getSubscriptionCallback() {
return impl.getDDPSubscription();
}
public Task<RxWebSocketCallback.Close> getOnCloseCallback() {
return impl.getOnCloseCallback();
}
public void close() {
impl.close(1000, "closed by DDPClient#close()");
}
// reference: https://github.com/eddflrs/meteor-ddp/blob/master/meteor-ddp.js
public static final int REASON_CLOSED_BY_USER = 1000;
public static final int REASON_NETWORK_ERROR = 1001;
private static volatile DDPClient singleton;
private static volatile OkHttpClient client;
private final DDPClientImpl impl;
private final AtomicReference<String> hostname = new AtomicReference<>();
public static void initialize(OkHttpClient okHttpClient) {
client = okHttpClient;
}
public static DDPClient get() {
DDPClient result = singleton;
if (result == null) {
synchronized (DDPClient.class) {
result = singleton;
if (result == null) {
singleton = result = new DDPClient(client);
}
}
}
return result;
}
private DDPClient(OkHttpClient client) {
impl = new DDPClientImpl(this, client);
}
private Task<DDPClientCallback.Connect> connect(String url, String session) {
hostname.set(url);
TaskCompletionSource<DDPClientCallback.Connect> task = new TaskCompletionSource<>();
impl.connect(task, url, session);
return task.getTask();
}
private Task<DDPClientCallback.Ping> ping(@Nullable String id) {
TaskCompletionSource<DDPClientCallback.Ping> task = new TaskCompletionSource<>();
impl.ping(task, id);
return task.getTask();
}
private Maybe<DDPClientCallback.Base> doPing(@Nullable String id) {
return impl.ping(id);
}
private 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();
}
private Task<DDPSubscription.NoSub> unsub(String id) {
TaskCompletionSource<DDPSubscription.NoSub> task = new TaskCompletionSource<>();
impl.unsub(task, id);
return task.getTask();
}
public Task<RxWebSocketCallback.Close> getOnCloseCallback() {
return impl.getOnCloseCallback();
}
public void close() {
impl.close(REASON_CLOSED_BY_USER, "closed by DDPClient#close()");
}
/**
* check WebSocket connectivity with ping.
*/
public Task<Void> ping() {
final String pingId = UUID.randomUUID().toString();
RCLog.d("ping[%s] >", pingId);
return ping(pingId)
.continueWithTask(task -> {
if (task.isFaulted()) {
RCLog.d(task.getError(), "ping[%s] xxx failed xxx", pingId);
return Task.forError(task.getError());
} else {
RCLog.d("pong[%s] <", pingId);
return Task.forResult(null);
}
});
}
/**
* check WebSocket connectivity with ping.
*/
public Maybe<DDPClientCallback.Base> doPing() {
final String pingId = UUID.randomUUID().toString();
RCLog.d("ping[%s] >", pingId);
return doPing(pingId);
}
/**
* Connect to WebSocket server with DDP client.
*/
public Task<DDPClientCallback.Connect> connect(@NonNull String hostname, @Nullable String session,
boolean usesSecureConnection) {
final String protocol = usesSecureConnection ? "wss://" : "ws://";
return connect(protocol + hostname + "/websocket", session);
}
/**
* Subscribe with DDP client.
*/
public Task<DDPSubscription.Ready> subscribe(final String name, JSONArray param) {
final String subscriptionId = UUID.randomUUID().toString();
RCLog.d("sub:[%s]> %s(%s)", subscriptionId, name, param);
return sub(subscriptionId, name, param);
}
/**
* Unsubscribe with DDP client.
*/
public Task<DDPSubscription.NoSub> unsubscribe(final String subscriptionId) {
RCLog.d("unsub:[%s]>", subscriptionId);
return unsub(subscriptionId);
}
/**
* Returns Observable for handling DDP subscription.
*/
public Flowable<DDPSubscription.Event> getSubscriptionCallback() {
return impl.getDDPSubscription();
}
/**
* Execute raw RPC.
*/
public Task<DDPClientCallback.RPC> rpc(String methodCallId, String methodName, String params,
long timeoutMs) {
TaskCompletionSource<DDPClientCallback.RPC> task = new TaskCompletionSource<>();
RCLog.d("rpc:[%s]> %s(%s) timeout=%d", methodCallId, methodName, params, timeoutMs);
if (TextUtils.isEmpty(params)) {
impl.rpc(task, methodName, null, methodCallId, timeoutMs);
return task.getTask().continueWithTask(task_ -> {
if (task_.isFaulted()) {
RCLog.d("rpc:[%s]< error = %s", methodCallId, task_.getError());
} else {
RCLog.d("rpc:[%s]< result = %s", methodCallId, task_.getResult().result);
}
return task_;
});
}
try {
impl.rpc(task, methodName, new JSONArray(params), methodCallId, timeoutMs);
return task.getTask().continueWithTask(task_ -> {
if (task_.isFaulted()) {
RCLog.d("rpc:[%s]< error = %s", methodCallId, task_.getError());
} else {
RCLog.d("rpc:[%s]< result = %s", methodCallId, task_.getResult().result);
}
return task_;
});
} catch (JSONException exception) {
return Task.forError(exception);
}
}
}
......@@ -2,6 +2,7 @@ package chat.rocket.android_ddp;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.json.JSONObject;
public class DDPClientCallback {
......
......@@ -52,14 +52,14 @@ public class DDPClientImpl {
}
}
public void connect(final TaskCompletionSource<DDPClientCallback.Connect> task, final String url,
/* package */ void connect(final TaskCompletionSource<DDPClientCallback.Connect> task, final String url,
String session) {
try {
flowable = websocket.connect(url).autoConnect(2);
CompositeDisposable disposables = new CompositeDisposable();
disposables.add(
flowable.retry().filter(callback -> callback instanceof RxWebSocketCallback.Open)
flowable.filter(callback -> callback instanceof RxWebSocketCallback.Open)
.subscribe(
callback ->
sendMessage("connect",
......@@ -115,6 +115,7 @@ public class DDPClientImpl {
if (requested) {
return flowable.filter(callback -> callback instanceof RxWebSocketCallback.Message)
.timeout(8, TimeUnit.SECONDS)
.map(callback -> ((RxWebSocketCallback.Message) callback).responseBodyString)
.map(DDPClientImpl::toJson)
.filter(response -> "pong".equalsIgnoreCase(extractMsg(response)))
......
package chat.rocket.android_ddp;
import android.support.annotation.NonNull;
import org.json.JSONArray;
import org.json.JSONObject;
......
package chat.rocket.android_ddp.rx;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import chat.rocket.android.log.RCLog;
import io.reactivex.BackpressureStrategy;
......
......@@ -8,35 +8,10 @@ repositories {
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'me.tatarka.retrolambda'
apply plugin: 'com.jakewharton.hugo'
apply plugin: 'com.github.triplet.play'
apply from: '../config/quality/quality.gradle'
buildscript {
repositories {
jcenter()
mavenCentral()
maven { url 'https://maven.fabric.io/public' }
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$rootProject.ext.kotlinVersion"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
classpath 'me.tatarka:gradle-retrolambda:3.5.0'
classpath 'me.tatarka.retrolambda.projectlombok:lombok.ast:0.2.3.a2'
classpath 'io.realm:realm-gradle-plugin:2.3.1'
classpath 'com.jakewharton.hugo:hugo-plugin:1.2.1'
classpath 'com.google.gms:google-services:3.0.0'
classpath 'com.github.triplet.gradle:play-publisher:1.1.5'
classpath 'io.fabric.tools:gradle:1.+'
}
// Exclude the version that the android plugin depends on.
configurations.classpath.exclude group: 'com.android.tools.external.lombok'
}
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
......@@ -45,8 +20,8 @@ android {
applicationId "chat.rocket.android"
minSdkVersion 16
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 44
versionName "1.0.22"
versionCode 54
versionName "1.0.31"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
multiDexEnabled true
......@@ -108,6 +83,13 @@ android {
androidTest.java.srcDirs += 'src/androidTest/kotlin'
}
}
dexOptions {
if(System.getenv()["CIRCLECI"] as boolean) {
javaMaxHeapSize "1536M"
preDexLibraries false
}
}
}
play {
......@@ -115,7 +97,7 @@ play {
track = "${track}"
}
ext {
playLibVersion = '11.0.4'
playLibVersion = '11.6.0'
stethoVersion = '1.5.0'
stethoOkhttp3Version = '1.5.0'
stethoRealmVersion = '2.1.0'
......@@ -132,47 +114,37 @@ dependencies {
compile extraDependencies.okHTTP
compile extraDependencies.rxJava
compile extraDependencies.boltTask
compile supportDependencies.multidex
compile supportDependencies.designSupportLibrary
compile supportDependencies.annotation
compile supportDependencies.kotlin;
compile rxbindingDependencies.rxBinding
compile rxbindingDependencies.rxBindingSupport
compile rxbindingDependencies.rxBindingAppcompact
compile 'com.android.support:multidex:1.0.1'
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$rootProject.ext.kotlinVersion"
compile "com.google.firebase:firebase-core:$playLibVersion"
compile "com.google.firebase:firebase-crash:$playLibVersion"
compile "com.google.android.gms:play-services-gcm:$playLibVersion"
debugCompile "com.facebook.stetho:stetho:$stethoVersion"
debugCompile "com.facebook.stetho:stetho-okhttp3:$stethoOkhttp3Version"
debugCompile "com.uphyca:stetho_realm:$stethoRealmVersion"
compile "com.trello.rxlifecycle2:rxlifecycle:$rxlifecycleVersion"
compile "com.trello.rxlifecycle2:rxlifecycle-android:$rxlifecycleVersion"
compile "com.trello.rxlifecycle2:rxlifecycle-components:$rxlifecycleVersion"
compile 'nl.littlerobots.rxlint:rxlint:1.2'
compile "frankiesardo:icepick:$icepickVersion"
provided "frankiesardo:icepick-processor:$icepickVersion"
annotationProcessor "frankiesardo:icepick-processor:$icepickVersion"
compile "com.github.hotchemi:permissionsdispatcher:$permissionsdispatcherVersion"
annotationProcessor "com.github.hotchemi:permissionsdispatcher-processor:$permissionsdispatcherVersion"
compile('com.crashlytics.sdk.android:crashlytics:2.6.8@aar') {
transitive = true;
}
debugCompile "com.facebook.stetho:stetho:$stethoVersion"
debugCompile "com.facebook.stetho:stetho-okhttp3:$stethoOkhttp3Version"
debugCompile "com.uphyca:stetho_realm:$stethoRealmVersion"
debugCompile "com.tspoon.traceur:traceur:1.0.1"
compile 'com.aurelhubert:ahbottomnavigation:2.0.6'
compile('com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:0.10.6@aar') {
transitive = true
}
compile 'com.github.JakeWharton:ViewPagerIndicator:2.4.1@aar'
compile 'com.jakewharton:butterknife:8.5.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1'
compile 'com.jakewharton.timber:timber:4.5.1'
compile 'com.github.matrixxun:MaterialBadgeTextView:c5a27e8243'
compile 'com.github.chrisbanes:PhotoView:2.0.0'
provided 'io.reactivex:rxjava:1.3.0'
provided "com.github.akarnokd:rxjava2-interop:0.10.2"
provided 'com.hadisatrio:Optional:v1.0.1'
testCompile 'junit:junit:4.12'
testCompile "org.mockito:mockito-core:2.7.19"
testCompile 'org.robolectric:robolectric:3.3'
testCompile "org.jetbrains.kotlin:kotlin-test:$rootProject.ext.kotlinVersion"
testCompile "org.jetbrains.kotlin:kotlin-test-junit:$rootProject.ext.kotlinVersion"
testCompile "org.jetbrains.kotlin:kotlin-reflect:$rootProject.ext.kotlinVersion"
testCompile "com.nhaarman:mockito-kotlin:1.5.0"
testCompile 'org.amshove.kluent:kluent:1.14'
}
apply plugin: 'com.google.gms.google-services'
package chat.rocket.android;
import android.os.StrictMode;
import com.facebook.stetho.Stetho;
import com.tspoon.traceur.Traceur;
import com.uphyca.stetho_realm.RealmInspectorModulesProvider;
......
......@@ -5,8 +5,8 @@ import chat.rocket.android.RocketChatCache
import chat.rocket.android.api.rest.CookieInterceptor
import chat.rocket.android.api.rest.DefaultCookieProvider
import com.facebook.stetho.okhttp3.StethoInterceptor
import java.util.concurrent.TimeUnit
import okhttp3.OkHttpClient
import java.util.concurrent.TimeUnit
object OkHttpHelper {
......
......@@ -5,6 +5,7 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<permission
android:name="chat.rocket.android.permission.C2D_MESSAGE"
......@@ -56,10 +57,32 @@
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE"/>
<category android:name="com.example.gcm"/>
<action android:name="com.google.android.c2dm.intent.REGISTRATION"/>
<category android:name="chat.rocket.android"/>
</intent-filter>
</receiver>
<receiver
android:name="com.google.firebase.iid.FirebaseInstanceIdReceiver"
android:exported="true"
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE"/>
<category android:name="chat.rocket.android"/>
</intent-filter>
</receiver>
<receiver
android:name="com.google.firebase.iid.FirebaseInstanceIdInternalReceiver"
android:exported="false" />
<service android:name="com.google.firebase.iid.FirebaseInstanceIdService"
android:exported="true">
<intent-filter android:priority="-500">
<action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
</intent-filter>
</service>
<service
android:name=".push.gcm.GCMIntentService"
android:exported="false">
......@@ -76,6 +99,12 @@
</intent-filter>
</service>
<receiver android:name=".push.PushManager$DeleteReceiver"
android:exported="false" />
<receiver android:name=".push.PushManager$ReplyReceiver"
android:exported="false" />
<meta-data
android:name="io.fabric.ApiKey"
android:value="12ac6e94f850aaffcdff52001af77ca415d06a43" />
......
......@@ -4,46 +4,62 @@ import android.os.Build;
import android.support.multidex.MultiDexApplication;
import android.support.v7.app.AppCompatDelegate;
import chat.rocket.android.helper.OkHttpHelper;
import com.crashlytics.android.Crashlytics;
import io.fabric.sdk.android.Fabric;
import java.util.List;
import chat.rocket.persistence.realm.RealmStore;
import chat.rocket.android.helper.Logger;
import chat.rocket.android.helper.OkHttpHelper;
import chat.rocket.android.service.ConnectivityManager;
import chat.rocket.core.models.ServerInfo;
import chat.rocket.android.widget.RocketChatWidgets;
import chat.rocket.android_ddp.DDPClient;
import chat.rocket.core.models.ServerInfo;
import chat.rocket.persistence.realm.RealmStore;
import chat.rocket.persistence.realm.RocketChatPersistenceRealm;
import io.fabric.sdk.android.Fabric;
import io.reactivex.exceptions.UndeliverableException;
import io.reactivex.plugins.RxJavaPlugins;
/**
* Customized Application-class for Rocket.Chat
*/
public class RocketChatApplication extends MultiDexApplication {
private static RocketChatApplication instance;
private static RocketChatApplication instance;
public static RocketChatApplication getInstance() {
return instance;
}
public static RocketChatApplication getInstance() {
return instance;
}
@Override
public void onCreate() {
super.onCreate();
Fabric.with(this, new Crashlytics());
@Override
public void onCreate() {
super.onCreate();
DDPClient.initialize(OkHttpHelper.INSTANCE.getClientForWebSocket());
Fabric.with(this, new Crashlytics());
RocketChatPersistenceRealm.init(this);
RocketChatPersistenceRealm.init(this);
List<ServerInfo> serverInfoList = ConnectivityManager.getInstance(this).getServerList();
for (ServerInfo serverInfo : serverInfoList) {
RealmStore.put(serverInfo.getHostname());
}
List<ServerInfo> serverInfoList = ConnectivityManager.getInstance(this).getServerList();
for (ServerInfo serverInfo : serverInfoList) {
RealmStore.put(serverInfo.getHostname());
}
RocketChatWidgets.initialize(this, OkHttpHelper.INSTANCE.getClientForDownloadFile(this));
RocketChatWidgets.initialize(this, OkHttpHelper.INSTANCE.getClientForDownloadFile(this));
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
}
RxJavaPlugins.setErrorHandler(e -> {
if (e instanceof UndeliverableException) {
e = e.getCause();
}
if (BuildConfig.DEBUG) {
e.printStackTrace();
}
Logger.report(e);
});
instance = this;
}
instance = this;
}
}
\ No newline at end of file
......@@ -22,12 +22,15 @@ import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable;
import io.reactivex.annotations.NonNull;
import io.reactivex.annotations.Nullable;
import okhttp3.HttpUrl;
/**
* sharedpreference-based cache.
*/
public class RocketChatCache {
private static final String KEY_SELECTED_SERVER_HOSTNAME = "KEY_SELECTED_SERVER_HOSTNAME";
private static final String KEY_SELECTED_SITE_URL = "KEY_SELECTED_SITE_URL";
private static final String KEY_SELECTED_SITE_NAME = "KEY_SELECTED_SITE_NAME";
private static final String KEY_SELECTED_ROOM_ID = "KEY_SELECTED_ROOM_ID";
private static final String KEY_PUSH_ID = "KEY_PUSH_ID";
private static final String KEY_HOSTNAME_LIST = "KEY_HOSTNAME_LIST";
......@@ -50,6 +53,75 @@ public class RocketChatCache {
setString(KEY_SELECTED_SERVER_HOSTNAME, newHostname);
}
public void addHostSiteName(@NonNull String currentHostname, @NonNull String siteName) {
try {
String hostSiteNamesJson = getHostSiteNamesJson();
JSONObject jsonObject = (hostSiteNamesJson == null) ?
new JSONObject() : new JSONObject(hostSiteNamesJson);
jsonObject.put(currentHostname, siteName);
setString(KEY_SELECTED_SITE_NAME, jsonObject.toString());
} catch (JSONException e) {
RCLog.e(e);
}
}
public @NonNull String getHostSiteName(@NonNull String host) {
if (host.startsWith("http")) {
HttpUrl url = HttpUrl.parse(host);
if (url != null) {
host = url.host();
}
}
try {
String hostSiteNamesJson = getHostSiteNamesJson();
JSONObject jsonObject = (hostSiteNamesJson == null) ?
new JSONObject() : new JSONObject(hostSiteNamesJson);
host = getSiteUrlFor(host);
return jsonObject.optString(host);
} catch (JSONException e) {
RCLog.e(e);
}
return "";
}
private @Nullable String getHostSiteNamesJson() {
return getString(KEY_SELECTED_SITE_NAME, null);
}
public void addHostnameSiteUrl(@Nullable String hostnameAlias, @NonNull String currentHostname) {
String alias = null;
if (hostnameAlias != null) {
alias = hostnameAlias.toLowerCase();
}
try {
String selectedHostnameAliasJson = getLoginHostnamesJson();
JSONObject jsonObject = selectedHostnameAliasJson == null ?
new JSONObject() : new JSONObject(selectedHostnameAliasJson);
jsonObject.put(alias, currentHostname);
setString(KEY_SELECTED_SITE_URL, jsonObject.toString());
} catch (JSONException e) {
RCLog.e(e);
}
}
public @Nullable String getSiteUrlFor(String hostname) {
try {
String selectedServerHostname = getSelectedServerHostname();
if (getLoginHostnamesJson() == null || getLoginHostnamesJson().isEmpty()) {
return null;
}
return new JSONObject(getLoginHostnamesJson())
.optString(hostname, selectedServerHostname);
} catch (JSONException e) {
RCLog.e(e);
}
return null;
}
private @Nullable String getLoginHostnamesJson() {
return getString(KEY_SELECTED_SITE_URL, null);
}
public void addHostname(@NonNull String hostname, @Nullable String hostnameAvatarUri, String siteName) {
String hostnameList = getString(KEY_HOSTNAME_LIST, null);
try {
......
......@@ -3,21 +3,25 @@ package chat.rocket.android.activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import com.hadisatrio.optional.Optional;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
import java.util.List;
import chat.rocket.android.LaunchUtil;
import chat.rocket.android.R;
import chat.rocket.android.RocketChatCache;
import chat.rocket.android.helper.Logger;
import chat.rocket.android.push.PushConstants;
import chat.rocket.android.push.PushNotificationHandler;
import chat.rocket.android.push.PushManager;
import chat.rocket.android.service.ConnectivityManager;
import chat.rocket.core.models.ServerInfo;
import chat.rocket.persistence.realm.RealmStore;
import chat.rocket.persistence.realm.models.ddp.RealmRoom;
import icepick.State;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
import okhttp3.HttpUrl;
abstract class AbstractAuthedActivity extends AbstractFragmentActivity {
@State protected String hostname;
......@@ -39,7 +43,6 @@ abstract class AbstractAuthedActivity extends AbstractFragmentActivity {
}
updateHostnameIfNeeded(rocketChatCache.getSelectedServerHostname());
updateRoomIdIfNeeded(rocketChatCache.getSelectedRoomId());
}
@Override
......@@ -53,20 +56,36 @@ abstract class AbstractAuthedActivity extends AbstractFragmentActivity {
return;
}
if (intent.hasExtra(PushConstants.HOSTNAME)) {
rocketChatCache.setSelectedServerHostname(intent.getStringExtra(PushConstants.HOSTNAME));
if (intent.hasExtra(PushConstants.ROOM_ID)) {
rocketChatCache.setSelectedRoomId(intent.getStringExtra(PushConstants.ROOM_ID));
if (intent.hasExtra(PushManager.EXTRA_HOSTNAME)) {
String hostname = intent.getStringExtra(PushManager.EXTRA_HOSTNAME);
HttpUrl url = HttpUrl.parse(hostname);
if (url != null) {
String hostnameFromPush = url.host();
String loginHostname = rocketChatCache.getSiteUrlFor(hostnameFromPush);
rocketChatCache.setSelectedServerHostname(loginHostname);
if (intent.hasExtra(PushManager.EXTRA_ROOM_ID)) {
rocketChatCache.setSelectedRoomId(intent.getStringExtra(PushManager.EXTRA_ROOM_ID));
}
}
PushManager.INSTANCE.clearNotificationsByHost(hostname);
} else {
updateHostnameIfNeeded(rocketChatCache.getSelectedServerHostname());
}
if (intent.hasExtra(PushConstants.NOT_ID)) {
if (intent.hasExtra(PushManager.EXTRA_NOT_ID) && intent.hasExtra(PushManager.EXTRA_HOSTNAME)) {
isNotification = true;
PushNotificationHandler
.cleanUpNotificationStack(intent.getIntExtra(PushConstants.NOT_ID, 0));
int notificationId = intent.getIntExtra(PushManager.EXTRA_NOT_ID, 0);
String hostname = intent.getStringExtra(PushManager.EXTRA_HOSTNAME);
HttpUrl url = HttpUrl.parse(hostname);
if (url != null) {
String hostnameFromPush = url.host();
String loginHostname = rocketChatCache.getSiteUrlFor(hostnameFromPush);
PushManager.INSTANCE.clearNotificationsByHostAndNotificationId(loginHostname, notificationId);
} else {
PushManager.INSTANCE.clearNotificationsByNotificationId(notificationId);
}
}
}
......@@ -74,17 +93,22 @@ abstract class AbstractAuthedActivity extends AbstractFragmentActivity {
if (hostname == null) {
if (newHostname != null && assertServerRealmStoreExists(newHostname)) {
updateHostname(newHostname);
updateRoomIdIfNeeded(rocketChatCache.getSelectedRoomId());
} else {
recoverFromHostnameError();
}
} else {
if (hostname.equals(newHostname)) {
updateHostname(newHostname);
updateRoomIdIfNeeded(rocketChatCache.getSelectedRoomId());
return;
}
if (assertServerRealmStoreExists(newHostname)) {
updateHostname(newHostname);
Intent intent = new Intent(this, MainActivity.class);
startActivity(intent);
finish();
overridePendingTransition(R.anim.slide_in, R.anim.slide_out);
} else {
recoverFromHostnameError();
}
......
......@@ -8,8 +8,8 @@ import android.support.v4.app.Fragment;
import chat.rocket.android.R;
import chat.rocket.android.fragment.server_config.LoginFragment;
import chat.rocket.android.fragment.server_config.RetryLoginFragment;
import chat.rocket.core.interactors.SessionInteractor;
import chat.rocket.android.service.ConnectivityManager;
import chat.rocket.core.interactors.SessionInteractor;
import chat.rocket.persistence.realm.repositories.RealmSessionRepository;
/**
......
......@@ -2,14 +2,13 @@ package chat.rocket.android.activity;
import android.support.annotation.NonNull;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import chat.rocket.android.BackgroundLooper;
import chat.rocket.android.helper.Logger;
import chat.rocket.android.service.ConnectivityManagerApi;
import chat.rocket.android.shared.BasePresenter;
import chat.rocket.core.interactors.SessionInteractor;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
public class LoginPresenter extends BasePresenter<LoginContract.View>
implements LoginContract.Presenter {
......
package chat.rocket.android.activity;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.support.v4.app.Fragment;
......@@ -26,6 +28,7 @@ import chat.rocket.android.fragment.chatroom.RoomFragment;
import chat.rocket.android.fragment.sidebar.SidebarMainFragment;
import chat.rocket.android.helper.KeyboardHelper;
import chat.rocket.android.service.ConnectivityManager;
import chat.rocket.android.service.ConnectivityManagerApi;
import chat.rocket.android.widget.RoomToolbar;
import chat.rocket.android.widget.helper.FrescoHelper;
import chat.rocket.core.interactors.CanCreateRoomInteractor;
......@@ -43,314 +46,316 @@ import hugo.weaving.DebugLog;
* Entry-point for Rocket.Chat.Android application.
*/
public class MainActivity extends AbstractAuthedActivity implements MainContract.View {
private RoomToolbar toolbar;
private StatusTicker statusTicker;
private SlidingPaneLayout pane;
private MainContract.Presenter presenter;
@Override
public int getLayoutContainerForFragment() {
return R.id.activity_main_container;
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
toolbar = (RoomToolbar) findViewById(R.id.activity_main_toolbar);
statusTicker = new StatusTicker();
pane = (SlidingPaneLayout) findViewById(R.id.sliding_pane);
setupToolbar();
}
@Override
protected void onResume() {
super.onResume();
if (hostname == null || presenter == null) {
hostname = new RocketChatCache(getApplicationContext()).getSelectedServerHostname();
if (hostname == null) {
showAddServerScreen();
} else {
onHostnameUpdated();
}
} else {
presenter.bindViewOnly(this);
presenter.loadSignedInServers(hostname);
private RoomToolbar toolbar;
private SlidingPaneLayout pane;
private MainContract.Presenter presenter;
private volatile Snackbar statusTicker;
@Override
public int getLayoutContainerForFragment() {
return R.id.activity_main_container;
}
}
@Override
protected void onPause() {
if (presenter != null) {
presenter.release();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
toolbar = findViewById(R.id.activity_main_toolbar);
pane = findViewById(R.id.sliding_pane);
setupToolbar();
}
super.onPause();
}
private void showAddServerActivity() {
closeSidebarIfNeeded();
Intent intent = new Intent(this, AddServerActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent.putExtra(AddServerActivity.EXTRA_FINISH_ON_BACK_PRESS, true);
startActivity(intent);
}
private void setupToolbar() {
if (pane != null) {
pane.setPanelSlideListener(new SlidingPaneLayout.PanelSlideListener() {
@Override
public void onPanelSlide(View view, float v) {
//Ref: ActionBarDrawerToggle#setProgress
toolbar.setNavigationIconProgress(v);
@Override
protected void onResume() {
super.onResume();
ConnectivityManagerApi connectivityManager = ConnectivityManager.getInstance(getApplicationContext());
if (hostname == null || presenter == null) {
String previousHostname = hostname;
hostname = new RocketChatCache(getApplicationContext()).getSelectedServerHostname();
if (hostname == null) {
showAddServerScreen();
} else {
onHostnameUpdated();
if (!hostname.equalsIgnoreCase(previousHostname)) {
connectivityManager.resetConnectivityStateList();
connectivityManager.keepAliveServer();
}
}
} else {
connectivityManager.keepAliveServer();
presenter.bindView(this);
presenter.loadSignedInServers(hostname);
roomId = new RocketChatCache(getApplicationContext()).getSelectedRoomId();
}
}
@Override
public void onPanelOpened(View view) {
toolbar.setNavigationIconVerticalMirror(true);
@Override
protected void onPause() {
if (presenter != null) {
presenter.release();
}
// Dismiss any status ticker
if (statusTicker != null) statusTicker.dismiss();
@Override
public void onPanelClosed(View view) {
toolbar.setNavigationIconVerticalMirror(false);
Fragment fragment = getSupportFragmentManager()
.findFragmentById(R.id.sidebar_fragment_container);
if (fragment != null && fragment instanceof SidebarMainFragment) {
SidebarMainFragment sidebarMainFragment = (SidebarMainFragment) fragment;
sidebarMainFragment.toggleUserActionContainer(false);
sidebarMainFragment.showUserActionContainer(false);
}
}
});
if (toolbar != null) {
toolbar.setNavigationOnClickListener(view -> {
if (pane.isSlideable() && !pane.isOpen()) {
pane.openPane();
}
});
}
super.onPause();
}
}
private boolean closeSidebarIfNeeded() {
// REMARK: Tablet UI doesn't have SlidingPane!
if (pane != null && pane.isSlideable() && pane.isOpen()) {
pane.closePane();
return true;
private void showAddServerActivity() {
closeSidebarIfNeeded();
Intent intent = new Intent(this, AddServerActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent.putExtra(AddServerActivity.EXTRA_FINISH_ON_BACK_PRESS, true);
startActivity(intent);
}
return false;
}
@DebugLog
@Override
protected void onHostnameUpdated() {
super.onHostnameUpdated();
private void setupToolbar() {
if (pane != null) {
pane.setPanelSlideListener(new SlidingPaneLayout.PanelSlideListener() {
@Override
public void onPanelSlide(@NonNull View view, float v) {
//Ref: ActionBarDrawerToggle#setProgress
toolbar.setNavigationIconProgress(v);
}
@Override
public void onPanelOpened(@NonNull View view) {
toolbar.setNavigationIconVerticalMirror(true);
}
@Override
public void onPanelClosed(@NonNull View view) {
toolbar.setNavigationIconVerticalMirror(false);
Fragment fragment = getSupportFragmentManager()
.findFragmentById(R.id.sidebar_fragment_container);
if (fragment != null && fragment instanceof SidebarMainFragment) {
SidebarMainFragment sidebarMainFragment = (SidebarMainFragment) fragment;
sidebarMainFragment.toggleUserActionContainer(false);
sidebarMainFragment.showUserActionContainer(false);
}
}
});
if (toolbar != null) {
toolbar.setNavigationOnClickListener(view -> {
if (pane.isSlideable() && !pane.isOpen()) {
pane.openPane();
}
});
}
}
closeSidebarIfNeeded();
}
if (presenter != null) {
presenter.release();
private boolean closeSidebarIfNeeded() {
// REMARK: Tablet UI doesn't have SlidingPane!
if (pane != null && pane.isSlideable() && pane.isOpen()) {
pane.closePane();
return true;
}
return false;
}
RoomInteractor roomInteractor = new RoomInteractor(new RealmRoomRepository(hostname));
@DebugLog
@Override
protected void onHostnameUpdated() {
super.onHostnameUpdated();
CanCreateRoomInteractor createRoomInteractor = new CanCreateRoomInteractor(
new RealmUserRepository(hostname),
new SessionInteractor(new RealmSessionRepository(hostname))
);
if (presenter != null) {
presenter.release();
}
RoomInteractor roomInteractor = new RoomInteractor(new RealmRoomRepository(hostname));
SessionInteractor sessionInteractor = new SessionInteractor(
new RealmSessionRepository(hostname)
);
CanCreateRoomInteractor createRoomInteractor = new CanCreateRoomInteractor(
new RealmUserRepository(hostname),
new SessionInteractor(new RealmSessionRepository(hostname))
);
PublicSettingRepository publicSettingRepository = new RealmPublicSettingRepository(hostname);
SessionInteractor sessionInteractor = new SessionInteractor(
new RealmSessionRepository(hostname)
);
RocketChatCache rocketChatCache = new RocketChatCache(this);
PublicSettingRepository publicSettingRepository = new RealmPublicSettingRepository(hostname);
presenter = new MainPresenter(
roomInteractor,
createRoomInteractor,
sessionInteractor,
new MethodCallHelper(this, hostname),
ConnectivityManager.getInstance(getApplicationContext()),
rocketChatCache,
publicSettingRepository
);
RocketChatCache rocketChatCache = new RocketChatCache(this);
updateSidebarMainFragment();
presenter = new MainPresenter(
roomInteractor,
createRoomInteractor,
sessionInteractor,
new MethodCallHelper(this, hostname),
ConnectivityManager.getInstance(getApplicationContext()),
rocketChatCache,
publicSettingRepository
);
presenter.bindView(this);
presenter.loadSignedInServers(hostname);
updateSidebarMainFragment();
roomId = rocketChatCache.getSelectedRoomId();
}
presenter.bindView(this);
presenter.loadSignedInServers(hostname);
private void updateSidebarMainFragment() {
closeSidebarIfNeeded();
String selectedServerHostname = new RocketChatCache(this).getSelectedServerHostname();
Fragment sidebarFragment = findFragmentByTag(selectedServerHostname);
if (sidebarFragment == null) {
sidebarFragment = SidebarMainFragment.create(selectedServerHostname);
roomId = rocketChatCache.getSelectedRoomId();
}
getSupportFragmentManager().beginTransaction()
.replace(R.id.sidebar_fragment_container, sidebarFragment, selectedServerHostname)
.commit();
getSupportFragmentManager().executePendingTransactions();
}
@Override
protected void onRoomIdUpdated() {
super.onRoomIdUpdated();
presenter.onOpenRoom(hostname, roomId);
}
@Override
protected boolean onBackPress() {
return closeSidebarIfNeeded() || super.onBackPress();
}
@Override
public void showHome() {
showFragment(new HomeFragment());
}
@Override
public void showRoom(String hostname, String roomId) {
showFragment(RoomFragment.create(hostname, roomId));
closeSidebarIfNeeded();
KeyboardHelper.hideSoftKeyboard(this);
}
@Override
public void showUnreadCount(long roomsCount, int mentionsCount) {
toolbar.setUnreadBadge((int) roomsCount, mentionsCount);
}
@Override
public void showAddServerScreen() {
LaunchUtil.showAddServerActivity(this);
}
@Override
public void showLoginScreen() {
LaunchUtil.showLoginActivity(this, hostname);
statusTicker.updateStatus(StatusTicker.STATUS_DISMISS, null);
}
@Override
public void showConnectionError() {
statusTicker.updateStatus(StatusTicker.STATUS_CONNECTION_ERROR,
Snackbar.make(findViewById(getLayoutContainerForFragment()),
R.string.fragment_retry_login_error_title, Snackbar.LENGTH_INDEFINITE)
.setAction(R.string.fragment_retry_login_retry_title, view ->
presenter.onRetryLogin()));
}
@Override
public void showConnecting() {
statusTicker.updateStatus(StatusTicker.STATUS_TOKEN_LOGIN,
Snackbar.make(findViewById(getLayoutContainerForFragment()),
R.string.server_config_activity_authenticating, Snackbar.LENGTH_INDEFINITE));
}
@Override
public void showConnectionOk() {
statusTicker.updateStatus(StatusTicker.STATUS_DISMISS, null);
}
@Override
public void showSignedInServers(List<Pair<String, Pair<String, String>>> serverList) {
final SlidingPaneLayout subPane = (SlidingPaneLayout) findViewById(R.id.sub_sliding_pane);
if (subPane != null) {
LinearLayout serverListContainer = subPane.findViewById(R.id.server_list_bar);
View addServerButton = subPane.findViewById(R.id.btn_add_server);
addServerButton.setOnClickListener(view -> showAddServerActivity());
serverListContainer.removeAllViews();
for (Pair<String, Pair<String, String>> server : serverList) {
String serverHostname = server.first;
Pair<String, String> serverInfoPair = server.second;
String logoUrl = serverInfoPair.first;
String siteName = serverInfoPair.second;
View serverView = serverListContainer.findViewWithTag(serverHostname);
if (serverView == null) {
View newServerView = LayoutInflater.from(this).inflate(R.layout.server_row, serverListContainer, false);
SimpleDraweeView serverButton = newServerView.findViewById(R.id.drawee_server_button);
TextView hostnameLabel = newServerView.findViewById(R.id.text_view_server_label);
TextView siteNameLabel = newServerView.findViewById(R.id.text_view_site_name_label);
ImageView dotView = newServerView.findViewById(R.id.selected_server_dot);
newServerView.setTag(serverHostname);
hostnameLabel.setText(serverHostname);
siteNameLabel.setText(siteName);
// Currently selected server
if (hostname.equalsIgnoreCase(serverHostname)) {
newServerView.setSelected(true);
dotView.setVisibility(View.VISIBLE);
} else {
newServerView.setSelected(false);
dotView.setVisibility(View.GONE);
}
newServerView.setOnClickListener(view -> changeServerIfNeeded(serverHostname));
FrescoHelper.INSTANCE.loadImage(serverButton, logoUrl, ContextCompat.getDrawable(this, R.mipmap.ic_launcher));
serverListContainer.addView(newServerView);
private void updateSidebarMainFragment() {
closeSidebarIfNeeded();
String selectedServerHostname = new RocketChatCache(this).getSelectedServerHostname();
Fragment sidebarFragment = findFragmentByTag(selectedServerHostname);
if (sidebarFragment == null) {
sidebarFragment = SidebarMainFragment.create(selectedServerHostname);
}
}
serverListContainer.addView(addServerButton);
getSupportFragmentManager().beginTransaction()
.replace(R.id.sidebar_fragment_container, sidebarFragment, selectedServerHostname)
.commit();
getSupportFragmentManager().executePendingTransactions();
}
@Override
protected void onRoomIdUpdated() {
super.onRoomIdUpdated();
presenter.onOpenRoom(hostname, roomId);
}
@Override
protected boolean onBackPress() {
return closeSidebarIfNeeded() || super.onBackPress();
}
@Override
public void showHome() {
showFragment(new HomeFragment());
}
@Override
public void showRoom(String hostname, String roomId) {
showFragment(RoomFragment.create(hostname, roomId));
closeSidebarIfNeeded();
KeyboardHelper.hideSoftKeyboard(this);
}
@Override
public void showUnreadCount(long roomsCount, int mentionsCount) {
toolbar.setUnreadBadge((int) roomsCount, mentionsCount);
}
}
private void changeServerIfNeeded(String serverHostname) {
if (!hostname.equalsIgnoreCase(serverHostname)) {
RocketChatCache rocketChatCache = new RocketChatCache(getApplicationContext());
rocketChatCache.setSelectedServerHostname(serverHostname);
@Override
public void showAddServerScreen() {
LaunchUtil.showAddServerActivity(this);
}
}
@DebugLog
public void onLogout() {
if (new RocketChatCache(getApplicationContext()).getSelectedServerHostname() == null) {
LaunchUtil.showMainActivity(this);
} else {
onHostnameUpdated();
@Override
public void showLoginScreen() {
LaunchUtil.showLoginActivity(this, hostname);
showConnectionOk();
}
}
@DebugLog
public void beforeLogoutCleanUp() {
presenter.beforeLogoutCleanUp();
}
@Override
public synchronized void showConnectionError() {
dismissStatusTickerIfShowing();
statusTicker = Snackbar.make(findViewById(getLayoutContainerForFragment()),
R.string.fragment_retry_login_error_title, Snackbar.LENGTH_INDEFINITE)
.setAction(R.string.fragment_retry_login_retry_title, view ->
ConnectivityManager.getInstance(getApplicationContext()).keepAliveServer());
statusTicker.show();
}
//TODO: consider this class to define in layouthelper for more complicated operation.
private static class StatusTicker {
public static final int STATUS_DISMISS = 0;
public static final int STATUS_CONNECTION_ERROR = 1;
public static final int STATUS_TOKEN_LOGIN = 2;
public static final int STATUS_LOGGING_OUT = 3;
@Override
public synchronized void showConnecting() {
dismissStatusTickerIfShowing();
statusTicker = Snackbar.make(findViewById(getLayoutContainerForFragment()),
R.string.server_config_activity_authenticating, Snackbar.LENGTH_INDEFINITE);
statusTicker.show();
}
private int status;
private Snackbar snackbar;
@Override
public synchronized void showConnectionOk() {
dismissStatusTickerIfShowing();
}
public StatusTicker() {
status = STATUS_DISMISS;
private void dismissStatusTickerIfShowing() {
if (statusTicker != null) {
statusTicker.dismiss();
}
}
@Override
public void showSignedInServers(List<Pair<String, Pair<String, String>>> serverList) {
final SlidingPaneLayout subPane = findViewById(R.id.sub_sliding_pane);
if (subPane != null) {
LinearLayout serverListContainer = subPane.findViewById(R.id.server_list_bar);
View addServerButton = subPane.findViewById(R.id.btn_add_server);
addServerButton.setOnClickListener(view -> showAddServerActivity());
serverListContainer.removeAllViews();
for (Pair<String, Pair<String, String>> server : serverList) {
String serverHostname = server.first;
Pair<String, String> serverInfoPair = server.second;
String logoUrl = serverInfoPair.first;
String siteName = serverInfoPair.second;
View serverView = serverListContainer.findViewWithTag(serverHostname);
if (serverView == null) {
View newServerView = LayoutInflater.from(this).inflate(R.layout.server_row, serverListContainer, false);
SimpleDraweeView serverButton = newServerView.findViewById(R.id.drawee_server_button);
TextView hostnameLabel = newServerView.findViewById(R.id.text_view_server_label);
TextView siteNameLabel = newServerView.findViewById(R.id.text_view_site_name_label);
ImageView dotView = newServerView.findViewById(R.id.selected_server_dot);
newServerView.setTag(serverHostname);
hostnameLabel.setText(serverHostname);
siteNameLabel.setText(siteName);
// Currently selected server
if (hostname.equalsIgnoreCase(serverHostname)) {
newServerView.setSelected(true);
dotView.setVisibility(View.VISIBLE);
} else {
newServerView.setSelected(false);
dotView.setVisibility(View.GONE);
}
newServerView.setOnClickListener(view -> changeServerIfNeeded(serverHostname));
Drawable drawable = ContextCompat.getDrawable(this, R.mipmap.ic_launcher);
if (drawable == null) {
int id = getResources().getIdentifier(
"rocket_chat_notification", "drawable", getPackageName());
drawable = ContextCompat.getDrawable(this, id);
}
FrescoHelper.INSTANCE.loadImage(serverButton, logoUrl, drawable);
serverListContainer.addView(newServerView);
}
}
serverListContainer.addView(addServerButton);
}
}
public void updateStatus(int status, Snackbar snackbar) {
if (status == this.status) {
return;
}
this.status = status;
if (this.snackbar != null) {
this.snackbar.dismiss();
}
if (status != STATUS_DISMISS) {
this.snackbar = snackbar;
if (this.snackbar != null) {
this.snackbar.show();
@Override
public void refreshRoom() {
Fragment fragment = getSupportFragmentManager().findFragmentById(getLayoutContainerForFragment());
if (fragment != null && fragment instanceof RoomFragment) {
RoomFragment roomFragment = (RoomFragment) fragment;
roomFragment.loadMessages();
}
}
}
}
private void changeServerIfNeeded(String serverHostname) {
if (!hostname.equalsIgnoreCase(serverHostname)) {
RocketChatCache rocketChatCache = new RocketChatCache(getApplicationContext());
rocketChatCache.setSelectedServerHostname(serverHostname);
}
}
@DebugLog
public void onLogout() {
if (new RocketChatCache(getApplicationContext()).getSelectedServerHostname() == null) {
LaunchUtil.showMainActivity(this);
} else {
onHostnameUpdated();
}
}
@DebugLog
public void beforeLogoutCleanUp() {
presenter.beforeLogoutCleanUp();
}
}
......@@ -26,6 +26,8 @@ public interface MainContract {
void showConnectionOk();
void showSignedInServers(List<Pair<String, Pair<String, String>>> serverList);
void refreshRoom();
}
interface Presenter extends BaseContract.Presenter<View> {
......
......@@ -18,6 +18,7 @@ import chat.rocket.android.log.RCLog;
import chat.rocket.android.service.ConnectivityManagerApi;
import chat.rocket.android.service.ServerConnectivity;
import chat.rocket.android.shared.BasePresenter;
import chat.rocket.android_ddp.DDPClient;
import chat.rocket.core.PublicSettingsConstants;
import chat.rocket.core.interactors.CanCreateRoomInteractor;
import chat.rocket.core.interactors.RoomInteractor;
......@@ -27,225 +28,230 @@ import chat.rocket.core.models.Session;
import chat.rocket.core.models.User;
import chat.rocket.core.repositories.PublicSettingRepository;
import chat.rocket.core.utils.Pair;
import hu.akarnokd.rxjava.interop.RxJavaInterop;
import io.reactivex.Flowable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
public class MainPresenter extends BasePresenter<MainContract.View>
implements MainContract.Presenter {
private final CanCreateRoomInteractor canCreateRoomInteractor;
private final RoomInteractor roomInteractor;
private final SessionInteractor sessionInteractor;
private final MethodCallHelper methodCallHelper;
private final ConnectivityManagerApi connectivityManagerApi;
private final RocketChatCache rocketChatCache;
private final PublicSettingRepository publicSettingRepository;
public MainPresenter(RoomInteractor roomInteractor,
CanCreateRoomInteractor canCreateRoomInteractor,
SessionInteractor sessionInteractor,
MethodCallHelper methodCallHelper,
ConnectivityManagerApi connectivityManagerApi,
RocketChatCache rocketChatCache, PublicSettingRepository publicSettingRepository) {
this.roomInteractor = roomInteractor;
this.canCreateRoomInteractor = canCreateRoomInteractor;
this.sessionInteractor = sessionInteractor;
this.methodCallHelper = methodCallHelper;
this.connectivityManagerApi = connectivityManagerApi;
this.rocketChatCache = rocketChatCache;
this.publicSettingRepository = publicSettingRepository;
}
@Override
public void bindViewOnly(@NonNull MainContract.View view) {
super.bindView(view);
subscribeToUnreadCount();
subscribeToSession();
setUserOnline();
}
@Override
public void loadSignedInServers(@NonNull String hostname) {
final Disposable disposable = publicSettingRepository.getById(PublicSettingsConstants.Assets.LOGO)
.zipWith(publicSettingRepository.getById(PublicSettingsConstants.General.SITE_NAME), Pair::new)
.map(this::getLogoAndSiteNamePair)
.map(settings -> getServerList(hostname, settings))
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
view::showSignedInServers,
RCLog::e
);
addSubscription(disposable);
}
@Override
public void bindView(@NonNull MainContract.View view) {
super.bindView(view);
if (shouldLaunchAddServerActivity()) {
view.showAddServerScreen();
return;
}
openRoom();
subscribeToNetworkChanges();
subscribeToUnreadCount();
subscribeToSession();
setUserOnline();
}
@Override
public void release() {
setUserAway();
super.release();
}
@Override
public void onOpenRoom(String hostname, String roomId) {
final Disposable subscription = canCreateRoomInteractor.canCreate(roomId)
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
allowed -> {
if (allowed) {
view.showRoom(hostname, roomId);
} else {
view.showHome();
}
},
Logger::report
);
addSubscription(subscription);
}
@Override
public void onRetryLogin() {
final Disposable subscription = sessionInteractor.retryLogin()
.subscribe();
addSubscription(subscription);
}
@Override
public void beforeLogoutCleanUp() {
clearSubscriptions();
}
private Pair<String, String> getLogoAndSiteNamePair(Pair<Optional<PublicSetting>, Optional<PublicSetting>> settingsPair) {
String logoUrl = "";
String siteName = "";
if (settingsPair.first.isPresent()) {
logoUrl = settingsPair.first.get().getValue();
}
if (settingsPair.second.isPresent()) {
siteName = settingsPair.second.get().getValue();
}
return new Pair<>(logoUrl, siteName);
}
private List<Pair<String, Pair<String, String>>> getServerList(String hostname, Pair<String, String> serverInfoPair) throws JSONException {
JSONObject jsonObject = new JSONObject(serverInfoPair.first);
String logoUrl = (jsonObject.has("url")) ?
jsonObject.optString("url") : jsonObject.optString("defaultUrl");
String siteName = serverInfoPair.second;
rocketChatCache.addHostname(hostname.toLowerCase(), logoUrl, siteName);
return rocketChatCache.getServerList();
}
private void openRoom() {
String hostname = rocketChatCache.getSelectedServerHostname();
String roomId = rocketChatCache.getSelectedRoomId();
if (roomId == null || roomId.length() == 0) {
view.showHome();
return;
}
onOpenRoom(hostname, roomId);
}
private void subscribeToUnreadCount() {
final Disposable subscription = Flowable.combineLatest(
roomInteractor.getTotalUnreadRoomsCount(),
roomInteractor.getTotalUnreadMentionsCount(),
(Pair::new)
)
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
pair -> view.showUnreadCount(pair.first, pair.second),
Logger::report
);
addSubscription(subscription);
}
private void subscribeToSession() {
final Disposable subscription = sessionInteractor.getDefault()
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
sessionOptional -> {
Session session = sessionOptional.orNull();
if (session == null || session.getToken() == null) {
view.showLoginScreen();
return;
}
String error = session.getError();
if (error != null && error.length() != 0) {
view.showConnectionError();
return;
}
if (!session.isTokenVerified()) {
view.showConnecting();
return;
}
view.showConnectionOk();
},
Logger::report
);
addSubscription(subscription);
}
private void subscribeToNetworkChanges() {
Disposable disposable = RxJavaInterop.toV2Flowable(connectivityManagerApi.getServerConnectivityAsObservable())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
connectivity -> {
if (connectivity.state == ServerConnectivity.STATE_CONNECTED) {
view.showConnectionOk();
return;
}
view.showConnecting();
},
Logger::report
);
addSubscription(disposable);
}
private void setUserOnline() {
methodCallHelper.setUserPresence(User.STATUS_ONLINE)
.continueWith(new LogIfError());
}
private void setUserAway() {
methodCallHelper.setUserPresence(User.STATUS_AWAY)
.continueWith(new LogIfError());
}
private boolean shouldLaunchAddServerActivity() {
return connectivityManagerApi.getServerList().isEmpty();
}
implements MainContract.Presenter {
private final CanCreateRoomInteractor canCreateRoomInteractor;
private final RoomInteractor roomInteractor;
private final SessionInteractor sessionInteractor;
private final MethodCallHelper methodCallHelper;
private final ConnectivityManagerApi connectivityManagerApi;
private final RocketChatCache rocketChatCache;
private final PublicSettingRepository publicSettingRepository;
public MainPresenter(RoomInteractor roomInteractor,
CanCreateRoomInteractor canCreateRoomInteractor,
SessionInteractor sessionInteractor,
MethodCallHelper methodCallHelper,
ConnectivityManagerApi connectivityManagerApi,
RocketChatCache rocketChatCache, PublicSettingRepository publicSettingRepository) {
this.roomInteractor = roomInteractor;
this.canCreateRoomInteractor = canCreateRoomInteractor;
this.sessionInteractor = sessionInteractor;
this.methodCallHelper = methodCallHelper;
this.connectivityManagerApi = connectivityManagerApi;
this.rocketChatCache = rocketChatCache;
this.publicSettingRepository = publicSettingRepository;
}
@Override
public void bindViewOnly(@NonNull MainContract.View view) {
super.bindView(view);
subscribeToUnreadCount();
subscribeToSession();
setUserOnline();
}
@Override
public void loadSignedInServers(@NonNull String hostname) {
final Disposable disposable = publicSettingRepository.getById(PublicSettingsConstants.Assets.LOGO)
.zipWith(publicSettingRepository.getById(PublicSettingsConstants.General.SITE_NAME), Pair::new)
.map(this::getLogoAndSiteNamePair)
.map(settings -> getServerList(hostname, settings))
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
view::showSignedInServers,
RCLog::e
);
addSubscription(disposable);
}
@Override
public void bindView(@NonNull MainContract.View view) {
super.bindView(view);
if (shouldLaunchAddServerActivity()) {
view.showAddServerScreen();
return;
}
openRoom();
subscribeToNetworkChanges();
subscribeToUnreadCount();
subscribeToSession();
setUserOnline();
}
@Override
public void release() {
setUserAway();
super.release();
}
@Override
public void onOpenRoom(String hostname, String roomId) {
final Disposable subscription = canCreateRoomInteractor.canCreate(roomId)
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
allowed -> {
if (allowed) {
view.showRoom(hostname, roomId);
} else {
view.showHome();
}
},
Logger::report
);
addSubscription(subscription);
}
@Override
public void onRetryLogin() {
final Disposable subscription = sessionInteractor.retryLogin()
.subscribe();
addSubscription(subscription);
}
@Override
public void beforeLogoutCleanUp() {
clearSubscriptions();
}
private Pair<String, String> getLogoAndSiteNamePair(Pair<Optional<PublicSetting>, Optional<PublicSetting>> settingsPair) {
String logoUrl = "";
String siteName = "";
if (settingsPair.first.isPresent()) {
logoUrl = settingsPair.first.get().getValue();
}
if (settingsPair.second.isPresent()) {
siteName = settingsPair.second.get().getValue();
}
return new Pair<>(logoUrl, siteName);
}
private List<Pair<String, Pair<String, String>>> getServerList(String hostname, Pair<String, String> serverInfoPair) throws JSONException {
JSONObject jsonObject = new JSONObject(serverInfoPair.first);
String logoUrl = (jsonObject.has("url")) ?
jsonObject.optString("url") : jsonObject.optString("defaultUrl");
String siteName = serverInfoPair.second;
rocketChatCache.addHostname(hostname.toLowerCase(), logoUrl, siteName);
return rocketChatCache.getServerList();
}
private void openRoom() {
String hostname = rocketChatCache.getSelectedServerHostname();
String roomId = rocketChatCache.getSelectedRoomId();
if (roomId == null || roomId.length() == 0) {
view.showHome();
return;
}
onOpenRoom(hostname, roomId);
}
private void subscribeToUnreadCount() {
final Disposable subscription = Flowable.combineLatest(
roomInteractor.getTotalUnreadRoomsCount(),
roomInteractor.getTotalUnreadMentionsCount(),
(Pair::new)
)
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
pair -> view.showUnreadCount(pair.first, pair.second),
Logger::report
);
addSubscription(subscription);
}
private void subscribeToSession() {
final Disposable subscription = sessionInteractor.getDefault()
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
sessionOptional -> {
Session session = sessionOptional.orNull();
if (session == null || session.getToken() == null) {
view.showLoginScreen();
return;
}
String error = session.getError();
if (error != null && error.length() != 0) {
view.showConnectionError();
return;
}
if (!session.isTokenVerified()) {
view.showConnecting();
return;
}
view.showConnectionOk();
},
Logger::report
);
addSubscription(subscription);
}
private void subscribeToNetworkChanges() {
Disposable disposable = connectivityManagerApi.getServerConnectivityAsObservable()
.distinctUntilChanged()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
connectivity -> {
if (connectivity.state == ServerConnectivity.STATE_CONNECTED) {
view.showConnectionOk();
view.refreshRoom();
} else if (connectivity.state == ServerConnectivity.STATE_DISCONNECTED) {
if (connectivity.code == DDPClient.REASON_NETWORK_ERROR) {
view.showConnectionError();
}
} else {
view.showConnecting();
}
},
Logger::report
);
addSubscription(disposable);
}
private void setUserOnline() {
methodCallHelper.setUserPresence(User.STATUS_ONLINE)
.continueWith(new LogIfError());
}
private void setUserAway() {
methodCallHelper.setUserPresence(User.STATUS_AWAY)
.continueWith(new LogIfError());
}
private boolean shouldLaunchAddServerActivity() {
return connectivityManagerApi.getServerList().isEmpty();
}
}
......@@ -5,6 +5,7 @@ import android.support.v4.app.Fragment
import android.support.v7.app.AppCompatActivity
import chat.rocket.android.R
import chat.rocket.android.fragment.chatroom.list.RoomListFragment
import chat.rocket.core.models.Room
import kotlinx.android.synthetic.main.activity_room.*
class RoomActivity : AppCompatActivity() {
......@@ -20,7 +21,7 @@ class RoomActivity : AppCompatActivity() {
val extras = intent.extras
val roomListFragment = RoomListFragment.newInstance(extras.getInt("actionId"),
extras.getString("roomId"),
extras.getString("roomType"),
extras.getString("roomType", Room.TYPE_GROUP),
extras.getString("hostname"),
extras.getString("token"),
extras.getString("userId"))
......
package chat.rocket.android.api;
import android.support.annotation.Nullable;
import chat.rocket.android.helper.OkHttpHelper;
import org.json.JSONArray;
import org.json.JSONException;
import java.util.UUID;
import bolts.Task;
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;
import io.reactivex.Flowable;
import io.reactivex.Maybe;
/**
* DDP client wrapper.
*/
public class DDPClientWrapper {
private final DDPClient ddpClient;
private final String hostname;
private DDPClientWrapper(String hostname) {
ddpClient = new DDPClient(OkHttpHelper.INSTANCE.getClientForWebSocket());
this.hostname = hostname;
}
/**
* build new API client instance.
*/
public static DDPClientWrapper create(String hostname) {
return new DDPClientWrapper(hostname);
}
/**
* Connect to WebSocket server with DDP client.
*/
public Task<DDPClientCallback.Connect> connect(@Nullable String session,
boolean usesSecureConnection) {
final String protocol = usesSecureConnection ? "wss://" : "ws://";
return ddpClient.connect(protocol + hostname + "/websocket", session);
}
/**
* close connection.
*/
public void close() {
ddpClient.close();
}
/**
* Subscribe with DDP client.
*/
public Task<DDPSubscription.Ready> subscribe(final String name, JSONArray param) {
final String subscriptionId = UUID.randomUUID().toString();
RCLog.d("sub:[%s]> %s(%s)", subscriptionId, name, param);
return ddpClient.sub(subscriptionId, name, param);
}
/**
* Unsubscribe with DDP client.
*/
public Task<DDPSubscription.NoSub> unsubscribe(final String subscriptionId) {
RCLog.d("unsub:[%s]>", subscriptionId);
return ddpClient.unsub(subscriptionId);
}
/**
* Returns Observable for handling DDP subscription.
*/
public Flowable<DDPSubscription.Event> getSubscriptionCallback() {
return ddpClient.getSubscriptionCallback();
}
/**
* Execute raw RPC.
*/
public Task<DDPClientCallback.RPC> rpc(String methodCallId, String methodName, String params,
long 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()) {
RCLog.d("rpc:[%s]< error = %s", methodCallId, task.getError());
} else {
RCLog.d("rpc:[%s]< result = %s", methodCallId, task.getResult().result);
}
return task;
});
}
try {
return ddpClient.rpc(methodName, new JSONArray(params), methodCallId, timeoutMs)
.continueWithTask(task -> {
if (task.isFaulted()) {
RCLog.d("rpc:[%s]< error = %s", methodCallId, task.getError());
} else {
RCLog.d("rpc:[%s]< result = %s", methodCallId, task.getResult().result);
}
return task;
});
} catch (JSONException exception) {
return Task.forError(exception);
}
}
/**
* check WebSocket connectivity with ping.
*/
public Task<Void> ping() {
final String pingId = UUID.randomUUID().toString();
RCLog.d("ping[%s] >", pingId);
return ddpClient.ping(pingId)
.continueWithTask(task -> {
if (task.isFaulted()) {
RCLog.d(task.getError(), "ping[%s] xxx failed xxx", pingId);
return Task.forError(task.getError());
} else {
RCLog.d("pong[%s] <", pingId);
return Task.forResult(null);
}
});
}
/**
* check WebSocket connectivity with ping.
*/
public Maybe<DDPClientCallback.Base> doPing() {
final String pingId = UUID.randomUUID().toString();
RCLog.d("ping[%s] >", pingId);
return ddpClient.doPing(pingId);
}
}
package chat.rocket.android.api;
import android.content.Context;
import org.json.JSONArray;
import org.json.JSONObject;
import bolts.Task;
import chat.rocket.android.helper.TextUtils;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.android.service.DDPClientRef;
/**
* MethodCall for uploading file.
......@@ -17,8 +17,8 @@ public class FileUploadingHelper extends MethodCallHelper {
super(context, hostname);
}
public FileUploadingHelper(RealmHelper realmHelper, DDPClientRef ddpClientRef) {
super(realmHelper, ddpClientRef);
public FileUploadingHelper(RealmHelper realmHelper) {
super(realmHelper);
}
public Task<JSONObject> uploadS3Request(String filename, long filesize, String mimeType,
......
......@@ -11,11 +11,13 @@ import java.util.UUID;
import bolts.Continuation;
import bolts.Task;
import chat.rocket.android.RocketChatCache;
import chat.rocket.android.helper.CheckSum;
import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.service.ConnectivityManager;
import chat.rocket.android.service.DDPClientRef;
import chat.rocket.android_ddp.DDPClient;
import chat.rocket.android_ddp.DDPClientCallback;
import chat.rocket.core.PublicSettingsConstants;
import chat.rocket.core.SyncState;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.persistence.realm.RealmStore;
......@@ -30,6 +32,7 @@ import chat.rocket.persistence.realm.models.ddp.RealmSpotlightUser;
import chat.rocket.persistence.realm.models.internal.MethodCall;
import chat.rocket.persistence.realm.models.internal.RealmSession;
import hugo.weaving.DebugLog;
import okhttp3.HttpUrl;
/**
* Utility class for creating/handling MethodCall or RPC.
......@@ -45,7 +48,6 @@ public class MethodCallHelper {
task -> Task.forResult(new JSONArray(task.getResult()));
protected final Context context;
protected final RealmHelper realmHelper;
protected final DDPClientRef ddpClientRef;
/**
* initialize with Context and hostname.
......@@ -53,23 +55,32 @@ public class MethodCallHelper {
public MethodCallHelper(Context context, String hostname) {
this.context = context.getApplicationContext();
this.realmHelper = RealmStore.getOrCreate(hostname);
ddpClientRef = null;
}
/**
* initialize with RealmHelper and DDPClient.
*/
public MethodCallHelper(RealmHelper realmHelper, DDPClientRef ddpClientRef) {
public MethodCallHelper(RealmHelper realmHelper) {
this.context = null;
this.realmHelper = realmHelper;
this.ddpClientRef = ddpClientRef;
}
public MethodCallHelper(Context context, RealmHelper realmHelper) {
this.context = context.getApplicationContext();
this.realmHelper = realmHelper;
}
@DebugLog
private Task<String> executeMethodCall(String methodName, String param, long timeout) {
if (ddpClientRef != null) {
return ddpClientRef.get().rpc(UUID.randomUUID().toString(), methodName, param, timeout)
.onSuccessTask(task -> Task.forResult(task.getResult().result));
if (DDPClient.get() != null) {
return DDPClient.get().rpc(UUID.randomUUID().toString(), methodName, param, timeout)
.onSuccessTask(task -> Task.forResult(task.getResult().result))
.continueWithTask(task_ -> {
if (task_.isFaulted()) {
return Task.forError(task_.getError());
}
return Task.forResult(task_.getResult());
});
} else {
return MethodCall.execute(realmHelper, methodName, param, timeout)
.onSuccessTask(task -> {
......@@ -84,8 +95,13 @@ public class MethodCallHelper {
return task.continueWithTask(_task -> {
if (_task.isFaulted()) {
Exception exception = _task.getError();
if (exception instanceof MethodCall.Error) {
String errMessageJson = exception.getMessage();
if (exception instanceof MethodCall.Error || exception instanceof DDPClientCallback.RPC.Error) {
String errMessageJson;
if (exception instanceof DDPClientCallback.RPC.Error) {
errMessageJson = ((DDPClientCallback.RPC.Error) exception).error.toString();
} else {
errMessageJson = exception.getMessage();
}
if (TextUtils.isEmpty(errMessageJson)) {
return Task.forError(exception);
}
......@@ -96,11 +112,10 @@ public class MethodCallHelper {
return Task.forError(new TwoStepAuthException(errMessage));
}
return Task.forError(new Exception(errMessage));
} else if (exception instanceof DDPClientCallback.RPC.Error) {
String errMessage = ((DDPClientCallback.RPC.Error) exception).error.getString("message");
return Task.forError(new Exception(errMessage));
} else if (exception instanceof DDPClientCallback.RPC.Timeout) {
return Task.forError(new MethodCall.Timeout());
} else if (exception instanceof DDPClientCallback.Closed) {
return Task.forError(new Exception("Oops, your connection seems off..."));
} else {
return Task.forError(exception);
}
......@@ -179,8 +194,8 @@ public class MethodCallHelper {
.put("algorithm", "sha-256"));
return new JSONArray().put(param);
}).onSuccessTask(CONVERT_TO_JSON_OBJECT)
.onSuccessTask(task -> Task.forResult(task.getResult().getString("token")))
.onSuccessTask(this::saveToken);
.onSuccessTask(task -> Task.forResult(task.getResult().getString("token")))
.onSuccessTask(this::saveToken);
}
public Task<Void> loginWithLdap(final String username, final String password) {
......@@ -385,6 +400,17 @@ public class MethodCallHelper {
}
}
public Task<Void> deleteMessage(String messageID) {
try {
JSONObject messageJson = new JSONObject()
.put("_id", messageID);
return deleteMessage(messageJson);
} catch(JSONException exception) {
return Task.forError(exception);
}
}
/**
* Send message object.
*/
......@@ -398,6 +424,11 @@ public class MethodCallHelper {
.onSuccessTask(task -> Task.forResult(null));
}
private Task<Void> deleteMessage(final JSONObject messageJson) {
return call("deleteMessage", TIMEOUT_MS, () -> new JSONArray().put(messageJson))
.onSuccessTask(task -> Task.forResult(null));
}
/**
* mark all messages are read in the room.
*/
......@@ -406,13 +437,31 @@ public class MethodCallHelper {
.onSuccessTask(task -> Task.forResult(null));
}
public Task<Void> getPublicSettings() {
public Task<Void> getPublicSettings(String currentHostname) {
return call("public-settings/get", TIMEOUT_MS)
.onSuccessTask(CONVERT_TO_JSON_ARRAY)
.onSuccessTask(task -> {
final JSONArray settings = task.getResult();
String siteUrl = null;
String siteName = null;
for (int i = 0; i < settings.length(); i++) {
RealmPublicSetting.customizeJson(settings.getJSONObject(i));
JSONObject jsonObject = settings.getJSONObject(i);
RealmPublicSetting.customizeJson(jsonObject);
if (isPublicSetting(jsonObject, PublicSettingsConstants.General.SITE_URL)) {
siteUrl = jsonObject.getString(RealmPublicSetting.VALUE);
} else if (isPublicSetting(jsonObject, PublicSettingsConstants.General.SITE_NAME)) {
siteName = jsonObject.getString(RealmPublicSetting.VALUE);
}
}
if (siteName != null && siteUrl != null) {
HttpUrl httpSiteUrl = HttpUrl.parse(siteUrl);
if (httpSiteUrl != null) {
String host = httpSiteUrl.host();
RocketChatCache rocketChatCache = new RocketChatCache(context);
rocketChatCache.addHostnameSiteUrl(host, currentHostname);
rocketChatCache.addHostSiteName(currentHostname, siteName);
}
}
return realmHelper.executeTransaction(realm -> {
......@@ -423,6 +472,10 @@ public class MethodCallHelper {
});
}
private boolean isPublicSetting(JSONObject jsonObject, String id) {
return jsonObject.optString(RealmPublicSetting.ID).equalsIgnoreCase(id);
}
public Task<Void> getPermissions() {
return call("permissions/get", TIMEOUT_MS)
.onSuccessTask(CONVERT_TO_JSON_ARRAY)
......
......@@ -3,21 +3,20 @@ package chat.rocket.android.api;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.json.JSONArray;
import org.json.JSONObject;
import bolts.Task;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.android.service.DDPClientRef;
public class RaixPushHelper extends MethodCallHelper {
public RaixPushHelper(Context context, String hostname) {
super(context, hostname);
}
public RaixPushHelper(RealmHelper realmHelper,
DDPClientRef ddpClientRef) {
super(realmHelper, ddpClientRef);
public RaixPushHelper(RealmHelper realmHelper) {
super(realmHelper);
}
public Task<Void> pushUpdate(@NonNull String pushId, @NonNull String gcmToken,
......
package chat.rocket.android.api.rest;
import java.io.IOException;
import chat.rocket.android.helper.TextUtils;
import okhttp3.Interceptor;
import okhttp3.Request;
......
package chat.rocket.android.api.rest;
import chat.rocket.android.RocketChatCache;
import chat.rocket.persistence.realm.models.ddp.RealmUser;
import chat.rocket.persistence.realm.models.internal.RealmSession;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.persistence.realm.RealmStore;
import chat.rocket.persistence.realm.models.ddp.RealmUser;
import chat.rocket.persistence.realm.models.internal.RealmSession;
public class DefaultCookieProvider implements CookieProvider {
......
......@@ -2,12 +2,13 @@ package chat.rocket.android.api.rest;
import android.support.annotation.NonNull;
import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable;
import io.reactivex.FlowableEmitter;
import org.json.JSONObject;
import java.io.IOException;
import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable;
import io.reactivex.FlowableEmitter;
import okhttp3.Call;
import okhttp3.OkHttpClient;
import okhttp3.Request;
......
package chat.rocket.android.api.rest;
import io.reactivex.Flowable;
import org.json.JSONObject;
import io.reactivex.Flowable;
public interface ServerPolicyApi {
String SECURE_PROTOCOL = "https://";
......
......@@ -6,6 +6,7 @@ import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.trello.rxlifecycle2.components.support.RxFragment;
/**
......
......@@ -7,6 +7,7 @@ import android.support.constraint.ConstraintLayout;
import android.support.design.widget.Snackbar;
import android.view.View;
import android.widget.TextView;
import chat.rocket.android.BuildConfig;
import chat.rocket.android.LaunchUtil;
import chat.rocket.android.R;
......
package chat.rocket.android.fragment.add_server;
import chat.rocket.android.BackgroundLooper;
import chat.rocket.android.helper.Logger;
import chat.rocket.android.helper.OkHttpHelper;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import chat.rocket.android.RocketChatCache;
import chat.rocket.android.api.rest.DefaultServerPolicyApi;
import chat.rocket.android.api.rest.ServerPolicyApi;
import chat.rocket.android.helper.Logger;
import chat.rocket.android.helper.OkHttpHelper;
import chat.rocket.android.helper.ServerPolicyApiValidationHelper;
import chat.rocket.android.helper.ServerPolicyHelper;
import chat.rocket.android.service.ConnectivityManagerApi;
import chat.rocket.android.shared.BasePresenter;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
public class InputHostnamePresenter extends BasePresenter<InputHostnameContract.View> implements InputHostnameContract.Presenter {
private final RocketChatCache rocketChatCache;
......
......@@ -35,6 +35,8 @@ public interface RoomContract {
void showMessageSendFailure(Message message);
void showMessageDeleteFailure(Message message);
void autoloadImages();
void manualLoadImages();
......@@ -71,5 +73,7 @@ public interface RoomContract {
void refreshRoom();
void replyMessage(@NonNull Message message, boolean justQuote);
void acceptMessageDeleteFailure(Message message);
}
}
......@@ -647,6 +647,15 @@ public class RoomFragment extends AbstractChatRoomFragment implements
.show();
}
@Override
public void showMessageDeleteFailure(Message message) {
new AlertDialog.Builder(getContext())
.setTitle(getContext().getString(R.string.failed_to_delete))
.setMessage(getContext().getString(R.string.failed_to_delete_message))
.setPositiveButton(R.string.ok, (dialog, which) -> presenter.acceptMessageDeleteFailure(message))
.show();
}
@Override
public void autoloadImages() {
messageListAdapter.setAutoloadImages(true);
......@@ -679,6 +688,7 @@ public class RoomFragment extends AbstractChatRoomFragment implements
.setEditAction(this::onEditMessage)
.setCopyAction(msg -> onCopy(message.getMessage()))
.setQuoteAction(msg -> presenter.replyMessage(message, true))
.setDeleteAction(this::onDeleteMessage)
.showWith(context);
}
}
......@@ -688,6 +698,10 @@ public class RoomFragment extends AbstractChatRoomFragment implements
messageFormManager.setEditMessage(message.getMessage());
}
public void onDeleteMessage(Message message) {
presenter.deleteMessage(message);
}
private void showRoomListFragment(int actionId) {
//TODO: oddly sometimes getActivity() yields null. Investigate the situations this might happen
//and fix it, removing this null-check
......@@ -701,4 +715,8 @@ public class RoomFragment extends AbstractChatRoomFragment implements
startActivity(intent);
}
}
public void loadMessages() {
presenter.loadMessages();
}
}
\ No newline at end of file
......@@ -112,7 +112,11 @@ public class RoomPresenter extends BasePresenter<RoomContract.View>
return;
}
if (message.getType() == null && message.getSyncState() == SyncState.SYNCED) {
if (message.getSyncState() == SyncState.DELETE_FAILED) {
view.showMessageDeleteFailure(message);
} else if (message.getSyncState() == SyncState.FAILED) {
view.showMessageSendFailure(message);
} else if (message.getType() == null && message.getSyncState() == SyncState.SYNCED) {
// If message is not a system message show applicable actions.
view.showMessageActions(message);
}
......@@ -147,6 +151,15 @@ public class RoomPresenter extends BasePresenter<RoomContract.View>
);
}
public void acceptMessageDeleteFailure(Message message) {
final Disposable subscription = messageInteractor.acceptDeleteFailure(message)
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe();
addSubscription(subscription);
}
private String buildReplyOrQuoteMarkdown(String baseUrl, Message message, boolean justQuote) {
if (currentRoom == null || message.getUser() == null) {
return "";
......
......@@ -10,11 +10,6 @@ import android.support.v4.util.Pair;
import android.view.View;
import android.widget.TextView;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import chat.rocket.android.BackgroundLooper;
import chat.rocket.android.R;
import chat.rocket.android.RocketChatCache;
......@@ -34,6 +29,10 @@ import chat.rocket.persistence.realm.repositories.RealmPublicSettingRepository;
import chat.rocket.persistence.realm.repositories.RealmRoomRepository;
import chat.rocket.persistence.realm.repositories.RealmRoomRoleRepository;
import chat.rocket.persistence.realm.repositories.RealmUserRepository;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
public class MessageOptionsDialogFragment extends BottomSheetDialogFragment {
......
......@@ -47,20 +47,23 @@ class RoomListFragment : Fragment(), RoomListContract.View {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activity.title = ""
actionId = arguments.getInt("actionId")
roomId = arguments.getString("roomId")
roomType = arguments.getString("roomType")
hostname = arguments.getString("hostname")
token = arguments.getString("token")
userId = arguments.getString("userId")
activity?.title = ""
val args = arguments
args?.let {
actionId = args.getInt("actionId")
roomId = args.getString("roomId")
roomType = args.getString("roomType")
hostname = args.getString("hostname")
token = args.getString("token")
userId = args.getString("userId")
}
}
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? = inflater?.inflate(R.layout.fragment_room_list, container, false)
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = inflater.inflate(R.layout.fragment_room_list, container, false)
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
presenter = RoomListPresenter(context, this)
presenter = RoomListPresenter(context!!, this)
}
override fun onResume() {
......@@ -114,9 +117,9 @@ class RoomListFragment : Fragment(), RoomListContract.View {
}
override fun showPinnedMessages(dataSet: ArrayList<Message>, total: String) {
activity.title = getString(R.string.fragment_room_list_pinned_message_title, total)
activity?.title = getString(R.string.fragment_room_list_pinned_message_title, total)
if (recyclerView.adapter == null) {
recyclerView.adapter = RoomMessagesAdapter(dataSet, hostname, context)
recyclerView.adapter = RoomMessagesAdapter(dataSet, hostname, context!!)
val linearLayoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
recyclerView.layoutManager = linearLayoutManager
if (dataSet.size >= 50) {
......@@ -132,9 +135,9 @@ class RoomListFragment : Fragment(), RoomListContract.View {
}
override fun showFavoriteMessages(dataSet: ArrayList<Message>, total: String) {
activity.title = getString(R.string.fragment_room_list_favorite_message_title, total)
activity?.title = getString(R.string.fragment_room_list_favorite_message_title, total)
if (recyclerView.adapter == null) {
recyclerView.adapter = RoomMessagesAdapter(dataSet, hostname, context)
recyclerView.adapter = RoomMessagesAdapter(dataSet, hostname, context!!)
val linearLayoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
recyclerView.layoutManager = linearLayoutManager
if (dataSet.size >= 50) {
......@@ -150,7 +153,7 @@ class RoomListFragment : Fragment(), RoomListContract.View {
}
override fun showFileList(dataSet: ArrayList<Attachment>, total: String) {
activity.title = getString(R.string.fragment_room_list_file_list_title, total)
activity?.title = getString(R.string.fragment_room_list_file_list_title, total)
if (recyclerView.adapter == null) {
recyclerView.adapter = RoomFileListAdapter(dataSet)
val linearLayoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
......@@ -168,9 +171,9 @@ class RoomListFragment : Fragment(), RoomListContract.View {
}
override fun showMemberList(dataSet: ArrayList<User>, total: String) {
activity.title = getString(R.string.fragment_room_list_member_list_title, total)
activity?.title = getString(R.string.fragment_room_list_member_list_title, total)
if (recyclerView.adapter == null) {
recyclerView.adapter = RoomMemberListAdapter(dataSet, hostname, context)
recyclerView.adapter = RoomMemberListAdapter(dataSet, hostname, context!!)
val linearLayoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
recyclerView.layoutManager = linearLayoutManager
if (dataSet.size >= 50) {
......
package chat.rocket.android.fragment.oauth;
import io.reactivex.android.schedulers.AndroidSchedulers;
import org.json.JSONObject;
import chat.rocket.android.BackgroundLooper;
......@@ -10,6 +9,7 @@ import chat.rocket.android.helper.Logger;
import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.shared.BasePresenter;
import chat.rocket.core.repositories.LoginServiceConfigurationRepository;
import io.reactivex.android.schedulers.AndroidSchedulers;
public class OAuthPresenter extends BasePresenter<OAuthContract.View>
implements OAuthContract.Presenter {
......
package chat.rocket.android.fragment.server_config;
import java.util.List;
import chat.rocket.android.shared.BaseContract;
import chat.rocket.core.models.LoginServiceConfiguration;
......
......@@ -11,6 +11,7 @@ import android.widget.TextView;
import java.util.HashMap;
import java.util.List;
import chat.rocket.android.R;
import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.android.layouthelper.oauth.OAuthProviderInfo;
......
......@@ -3,7 +3,6 @@ package chat.rocket.android.fragment.server_config;
import android.support.annotation.NonNull;
import com.hadisatrio.optional.Optional;
import io.reactivex.android.schedulers.AndroidSchedulers;
import bolts.Task;
import chat.rocket.android.BackgroundLooper;
......@@ -16,6 +15,7 @@ import chat.rocket.core.PublicSettingsConstants;
import chat.rocket.core.models.PublicSetting;
import chat.rocket.core.repositories.LoginServiceConfigurationRepository;
import chat.rocket.core.repositories.PublicSettingRepository;
import io.reactivex.android.schedulers.AndroidSchedulers;
public class LoginPresenter extends BasePresenter<LoginContract.View>
implements LoginContract.Presenter {
......@@ -85,7 +85,7 @@ public class LoginPresenter extends BasePresenter<LoginContract.View>
}
}
return null;
});
}, Task.UI_THREAD_EXECUTOR);
}
private Task<Void> call(String username, String password, Optional<PublicSetting> optional) {
......
......@@ -3,7 +3,6 @@ package chat.rocket.android.fragment.server_config;
import android.support.annotation.NonNull;
import com.hadisatrio.optional.Optional;
import io.reactivex.android.schedulers.AndroidSchedulers;
import chat.rocket.android.BackgroundLooper;
import chat.rocket.android.api.MethodCallHelper;
......@@ -12,6 +11,7 @@ import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.shared.BasePresenter;
import chat.rocket.core.interactors.SessionInteractor;
import chat.rocket.core.models.Session;
import io.reactivex.android.schedulers.AndroidSchedulers;
public class RetryLoginPresenter extends BasePresenter<RetryLoginContract.View>
implements RetryLoginContract.Presenter {
......
package chat.rocket.android.fragment.server_config;
import bolts.Task;
import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.android.shared.BasePresenter;
......@@ -29,6 +30,6 @@ public class TwoStepAuthPresenter extends BasePresenter<TwoStepAuthContract.View
view.showError(task.getError().getMessage());
}
return null;
});
}, Task.UI_THREAD_EXECUTOR);
}
}
......@@ -2,13 +2,14 @@ package chat.rocket.android.fragment.sidebar;
import android.support.annotation.NonNull;
import bolts.Continuation;
import chat.rocket.core.models.RoomSidebar;
import io.reactivex.Flowable;
import java.util.List;
import bolts.Continuation;
import chat.rocket.android.shared.BaseContract;
import chat.rocket.core.models.RoomSidebar;
import chat.rocket.core.models.Spotlight;
import chat.rocket.core.models.User;
import io.reactivex.Flowable;
public interface SidebarMainContract {
......
......@@ -151,6 +151,7 @@ public class SidebarMainFragment extends AbstractFragment implements SidebarMain
adapter.setOnItemClickListener(new RoomListAdapter.OnItemClickListener() {
@Override
public void onItemClick(RoomSidebar roomSidebar) {
searchView.setQuery(null, false);
searchView.clearFocus();
presenter.onRoomSelected(roomSidebar);
}
......
......@@ -5,11 +5,9 @@ import android.os.Bundle;
import android.view.View;
import android.widget.AutoCompleteTextView;
import android.widget.TextView;
import com.hadisatrio.optional.Optional;
import com.jakewharton.rxbinding2.widget.RxTextView;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.realm.Case;
import bolts.Task;
import chat.rocket.android.BackgroundLooper;
......@@ -20,11 +18,14 @@ import chat.rocket.android.helper.Logger;
import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.layouthelper.sidebar.dialog.SuggestUserAdapter;
import chat.rocket.core.interactors.SessionInteractor;
import chat.rocket.persistence.realm.models.ddp.RealmUser;
import chat.rocket.persistence.realm.RealmAutoCompleteAdapter;
import chat.rocket.persistence.realm.models.ddp.RealmUser;
import chat.rocket.persistence.realm.repositories.RealmServerInfoRepository;
import chat.rocket.persistence.realm.repositories.RealmSessionRepository;
import chat.rocket.persistence.realm.repositories.RealmUserRepository;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.realm.Case;
/**
* add Direct RealmMessage.
......
package chat.rocket.android.helper;
import com.hadisatrio.optional.Optional;
import io.reactivex.Flowable;
import io.reactivex.Single;
import chat.rocket.android.fragment.chatroom.RocketChatAbsoluteUrl;
import chat.rocket.core.interactors.SessionInteractor;
import chat.rocket.core.repositories.ServerInfoRepository;
import chat.rocket.core.repositories.UserRepository;
import io.reactivex.Flowable;
import io.reactivex.Single;
public class AbsoluteUrlHelper {
......
......@@ -6,9 +6,11 @@ import android.text.format.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;
import chat.rocket.android.log.RCLog;
/**
......@@ -55,7 +57,7 @@ public class DateTime {
case DAY:
return DAY_FORMAT.format(cal.getTime());
case DATE:
return DATE_FORMAT.format(cal.getTime());
return getDateFormat(cal.getTime());
case TIME:
return TIME_FORMAT.format(cal.getTime());
case DATE_TIME:
......@@ -80,6 +82,22 @@ public class DateTime {
}
}
private static String getDateFormat(Date dateTime) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(dateTime);
Calendar today = Calendar.getInstance();
Calendar yesterday = Calendar.getInstance();
yesterday.add(Calendar.DATE, -1);
if (calendar.get(Calendar.YEAR) == today.get(Calendar.YEAR) && calendar.get(Calendar.DAY_OF_YEAR) == today.get(Calendar.DAY_OF_YEAR)) {
return "Today";
} else if (calendar.get(Calendar.YEAR) == yesterday.get(Calendar.YEAR) && calendar.get(Calendar.DAY_OF_YEAR) == yesterday.get(Calendar.DAY_OF_YEAR)) {
return "Yesterday";
} else {
return DATE_FORMAT.format(dateTime);
}
}
/**
* parse datetime string to ms.
*/
......
......@@ -8,16 +8,18 @@ import android.os.ParcelFileDescriptor;
import android.provider.OpenableColumns;
import android.support.annotation.Nullable;
import android.webkit.MimeTypeMap;
import org.json.JSONObject;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.UUID;
import chat.rocket.android.log.RCLog;
import chat.rocket.core.SyncState;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.persistence.realm.models.ddp.RealmPublicSetting;
import chat.rocket.persistence.realm.models.internal.FileUploading;
import chat.rocket.persistence.realm.RealmHelper;
/**
* utility class for uploading file.
......
package chat.rocket.android.helper;
import io.realm.Realm;
import io.realm.RealmResults;
import java.util.List;
import chat.rocket.persistence.realm.models.ddp.RealmPublicSetting;
import chat.rocket.core.PublicSettingsConstants;
import chat.rocket.persistence.realm.models.ddp.RealmPublicSetting;
import io.realm.Realm;
import io.realm.RealmResults;
/**
* utility class for getting value comprehensibly from public settings list.
......
package chat.rocket.android.helper;
import java.util.concurrent.TimeUnit;
import rx.Observable;
import rx.functions.Func1;
import io.reactivex.Flowable;
import io.reactivex.functions.Function;
/**
* Rx operator and so on.
*/
public class RxHelper {
public static Func1<Observable<? extends Throwable>, Observable<?>> exponentialBackoff(
public static Function<Flowable<? extends Throwable>, Flowable<?>> exponentialBackoff(
int maxRetryCount, long base, TimeUnit unit) {
// ref: https://github.com/ReactiveX/RxJava/blob/a8ba158839b67246a742b6f1531995ffd7545c08/src/main/java/io/reactivex/Observable.java#L9601
return attempts -> attempts
.zipWith(Observable.range(0, maxRetryCount), (error, retryCount) -> retryCount)
.flatMap(retryCount -> Observable.timer(base * (long) Math.pow(2, retryCount), unit));
.zipWith(Flowable.range(0, maxRetryCount), (error, retryCount) -> retryCount)
.flatMap(retryCount -> Flowable.timer(base * (long) Math.pow(2, retryCount), unit));
}
}
......@@ -2,9 +2,8 @@ package chat.rocket.android.helper;
import android.support.annotation.NonNull;
import io.reactivex.Flowable;
import chat.rocket.android.api.rest.ServerPolicyApi;
import io.reactivex.Flowable;
public class ServerPolicyApiValidationHelper {
......
......@@ -2,9 +2,10 @@ package chat.rocket.android.helper;
import android.support.annotation.NonNull;
import io.reactivex.Flowable;
import org.json.JSONObject;
import io.reactivex.Flowable;
public class ServerPolicyHelper {
private static final String DEFAULT_HOST = ".rocket.chat";
......@@ -12,7 +13,7 @@ public class ServerPolicyHelper {
public static String enforceHostname(String hostname) {
if (hostname == null) {
return "demo.rocket.chat";
return "open.rocket.chat";
}
return removeTrailingSlash(removeProtocol(enforceDefaultHost(hostname)));
......
......@@ -4,6 +4,7 @@ import android.support.annotation.Nullable;
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;
......@@ -44,7 +45,8 @@ public abstract class AbstractMessageViewHolder extends ModelViewHolder<PairedMe
* bind the view model.
*/
public final void bind(PairedMessage pairedMessage, boolean autoloadImages) {
if (pairedMessage.target.getSyncState() == SyncState.FAILED) {
if (pairedMessage.target.getSyncState() == SyncState.FAILED ||
pairedMessage.target.getSyncState() == SyncState.DELETE_FAILED) {
errorImageView.setVisibility(View.VISIBLE);
} else {
errorImageView.setVisibility(View.GONE);
......
......@@ -7,6 +7,7 @@ import android.view.View;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import chat.rocket.android.R;
import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.layouthelper.ExtModelListAdapter;
......
package chat.rocket.android.layouthelper.chatroom;
import android.view.View;
import chat.rocket.android.R;
import chat.rocket.android.renderer.MessageRenderer;
import chat.rocket.android.widget.AbsoluteUrl;
......
......@@ -37,7 +37,8 @@ public class MessagePopup {
private static final Action QUOTE_ACTION_INFO = new Action("Quote", null, true);
private static final Action EDIT_ACTION_INFO = new Action("Edit", null, true);
private static final Action COPY_ACTION_INFO = new Action("Copy", null, true);
private final List<Action> defaultActions = new ArrayList<>(4);
private static final Action DELETE_ACTION_INFO = new Action("Delete", null, false);
private final List<Action> defaultActions = new ArrayList<>(5);
private final List<Action> otherActions = new ArrayList<>();
private Message message;
private CompositeDisposable compositeDisposable = new CompositeDisposable();
......@@ -73,6 +74,7 @@ public class MessagePopup {
.subscribe(
pair -> {
EDIT_ACTION_INFO.allowed = pair.second;
DELETE_ACTION_INFO.allowed = pair.second;
List<Action> allActions = singleton.defaultActions;
List<Action> allowedActions = new ArrayList<>(3);
for (int i = 0; i < allActions.size(); i++) {
......@@ -110,6 +112,7 @@ public class MessagePopup {
singleton.defaultActions.add(QUOTE_ACTION_INFO);
singleton.defaultActions.add(EDIT_ACTION_INFO);
singleton.defaultActions.add(COPY_ACTION_INFO);
singleton.defaultActions.add(DELETE_ACTION_INFO);
}
public static MessagePopup take(Message message) {
......@@ -163,6 +166,11 @@ public class MessagePopup {
return singleton;
}
public MessagePopup setDeleteAction(ActionListener actionListener) {
DELETE_ACTION_INFO.actionListener= actionListener;
return singleton;
}
public MessagePopup setQuoteAction(ActionListener actionListener) {
QUOTE_ACTION_INFO.actionListener = actionListener;
return singleton;
......
......@@ -7,10 +7,13 @@ import android.view.ViewGroup
import android.widget.TextView
import chat.rocket.android.R
import chat.rocket.android.helper.DateTime
import chat.rocket.android.helper.Logger
import chat.rocket.android.log.RCLog
import chat.rocket.android.widget.message.RocketChatMessageAttachmentsLayout
import chat.rocket.core.models.Attachment
import kotlinx.android.synthetic.main.day.view.*
import kotlinx.android.synthetic.main.item_room_file.view.*
import java.lang.IllegalArgumentException
import java.sql.Timestamp
/**
......@@ -26,8 +29,16 @@ class RoomFileListAdapter(private var dataSet: List<Attachment>) : RecyclerView.
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val attachment = dataSet[position]
holder.newDay.text = DateTime.fromEpocMs(Timestamp.valueOf(attachment.timestamp).time, DateTime.Format.DATE)
holder.attachment.appendAttachmentView(attachment, true, false)
val timestamp: Timestamp?
try {
timestamp = Timestamp.valueOf(attachment.timestamp)
// If we don't have a timestamp we can parse let's be safe and stop here.
holder.newDay.text = DateTime.fromEpocMs(timestamp.time, DateTime.Format.DATE)
holder.attachment.appendAttachmentView(attachment, true, false)
} catch (e: IllegalArgumentException) {
RCLog.e(e)
Logger.report(e)
}
}
override fun getItemCount(): Int = dataSet.size
......
......@@ -7,13 +7,14 @@ import android.view.View;
import android.view.ViewGroup;
import java.util.List;
import chat.rocket.android.R;
import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.renderer.UserRenderer;
import chat.rocket.android.widget.AbsoluteUrl;
import chat.rocket.core.models.User;
import chat.rocket.persistence.realm.models.ddp.RealmUser;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.android.renderer.UserRenderer;
import chat.rocket.persistence.realm.models.ddp.RealmUser;
/**
* RecyclerView adapter for UsersOfRooms.
......
......@@ -2,9 +2,10 @@ package chat.rocket.android.layouthelper.chatroom.roomlist;
import android.support.annotation.NonNull;
import chat.rocket.core.models.RoomSidebar;
import java.util.List;
import chat.rocket.core.models.Room;
import chat.rocket.core.models.RoomSidebar;
public class ChannelRoomListHeader implements RoomListHeader {
......
......@@ -2,9 +2,10 @@ package chat.rocket.android.layouthelper.chatroom.roomlist;
import android.support.annotation.NonNull;
import chat.rocket.core.models.RoomSidebar;
import java.util.List;
import chat.rocket.core.models.Room;
import chat.rocket.core.models.RoomSidebar;
public class DirectMessageRoomListHeader implements RoomListHeader {
......
......@@ -2,9 +2,10 @@ package chat.rocket.android.layouthelper.chatroom.roomlist;
import android.support.annotation.NonNull;
import chat.rocket.core.models.RoomSidebar;
import java.util.List;
import chat.rocket.core.models.RoomSidebar;
public class FavoriteRoomListHeader implements RoomListHeader {
private final String title;
......
......@@ -5,14 +5,15 @@ import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import chat.rocket.core.models.RoomSidebar;
import chat.rocket.core.models.Spotlight;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import chat.rocket.android.R;
import chat.rocket.android.widget.internal.RoomListItemView;
import chat.rocket.core.models.RoomSidebar;
import chat.rocket.core.models.Spotlight;
public class RoomListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
......
......@@ -2,9 +2,10 @@ package chat.rocket.android.layouthelper.chatroom.roomlist;
import android.support.annotation.NonNull;
import chat.rocket.core.models.RoomSidebar;
import java.util.List;
import chat.rocket.core.models.RoomSidebar;
public interface RoomListHeader {
String getTitle();
......
......@@ -2,9 +2,10 @@ package chat.rocket.android.layouthelper.chatroom.roomlist;
import android.support.annotation.NonNull;
import chat.rocket.core.models.RoomSidebar;
import java.util.List;
import chat.rocket.core.models.RoomSidebar;
public class UnreadRoomListHeader implements RoomListHeader {
private final String title;
......
......@@ -3,6 +3,7 @@ package chat.rocket.android.layouthelper.oauth;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import chat.rocket.android.R;
import chat.rocket.android.fragment.oauth.AbstractOAuthFragment;
import chat.rocket.android.fragment.oauth.FacebookOAuthFragment;
......
......@@ -5,11 +5,12 @@ import android.view.View;
import java.util.Iterator;
import java.util.List;
import chat.rocket.android.R;
import chat.rocket.android.renderer.UserRenderer;
import chat.rocket.android.widget.AbsoluteUrl;
import chat.rocket.persistence.realm.models.ddp.RealmUser;
import chat.rocket.persistence.realm.RealmAutoCompleteAdapter;
import chat.rocket.android.renderer.UserRenderer;
import chat.rocket.persistence.realm.models.ddp.RealmUser;
/**
* adapter to suggest user names.
......
......@@ -5,8 +5,8 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.support.v4.app.RemoteInput;
import android.util.Log;
import chat.rocket.android.push.gcm.GCMIntentService;
......
package chat.rocket.android.push
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.graphics.drawable.Icon
import android.os.Build
import android.os.Bundle
import android.support.annotation.RequiresApi
import android.support.v4.app.NotificationCompat
import android.support.v4.app.NotificationManagerCompat
import android.support.v4.app.RemoteInput
import android.text.Html
import android.text.Spanned
import android.util.Log
import chat.rocket.android.BackgroundLooper
import chat.rocket.android.BuildConfig
import chat.rocket.android.R
import chat.rocket.android.RocketChatCache
import chat.rocket.android.activity.MainActivity
import chat.rocket.android.helper.Logger
import chat.rocket.core.interactors.MessageInteractor
import chat.rocket.core.models.Room
import chat.rocket.core.models.User
import chat.rocket.persistence.realm.repositories.RealmMessageRepository
import chat.rocket.persistence.realm.repositories.RealmRoomRepository
import chat.rocket.persistence.realm.repositories.RealmUserRepository
import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.functions.BiFunction
import okhttp3.HttpUrl
import org.json.JSONObject
import java.io.Serializable
import java.util.*
import java.util.concurrent.atomic.AtomicInteger
import kotlin.collections.HashMap
typealias TupleRoomUser = Pair<Room, User>
typealias TupleGroupIdMessageCount = Pair<Int, AtomicInteger>
object PushManager {
const val EXTRA_NOT_ID = "chat.rocket.android.EXTRA_NOT_ID"
const val EXTRA_HOSTNAME = "chat.rocket.android.EXTRA_HOSTNAME"
const val EXTRA_PUSH_MESSAGE = "chat.rocket.android.EXTRA_PUSH_MESSAGE"
const val EXTRA_ROOM_ID = "chat.rocket.android.EXTRA_ROOM_ID"
private const val REPLY_LABEL = "REPLY"
private const val REMOTE_INPUT_REPLY = "REMOTE_INPUT_REPLY"
// Notifications received from the same server are grouped in a single bundled notification.
// This map associates a host to a group id.
private val groupMap = HashMap<String, TupleGroupIdMessageCount>()
// Map a hostname to a list of push messages that pertain to it.
private val hostToPushMessageList = HashMap<String, MutableList<PushMessage>>()
private val randomizer = Random()
/**
* Handles a receiving push by creating and displaying an appropriate notification based
* on the *data* param bundle received.
*/
@Synchronized
fun handle(context: Context, data: Bundle) {
val appContext = context.applicationContext
val message = data["message"] as String?
val image = data["image"] as String?
val ejson = data["ejson"] as String?
val notId = data["notId"] as String? ?: randomizer.nextInt().toString()
val style = data["style"] as String?
val summaryText = data["summaryText"] as String?
val count = data["count"] as String?
val title = data["title"] as String?
if (ejson == null || message == null || title == null) {
return
}
val lastPushMessage = PushMessage(title, message, image, ejson, count, notId, summaryText, style)
// We should use Timber here
if (BuildConfig.DEBUG) {
Log.d(PushMessage::class.java.simpleName, lastPushMessage.toString())
}
showNotification(appContext, lastPushMessage)
}
/**
* Clear all messages received to a given host the user is signed-in.
*/
fun clearNotificationsByHost(host: String) {
hostToPushMessageList.remove(host)
}
/**
* Remove a notification solely by it's unique id.
*/
fun clearNotificationsByNotificationId(notificationId: Int) {
if (hostToPushMessageList.isNotEmpty()) {
for (entry in hostToPushMessageList.entries) {
entry.value.removeAll {
it.notificationId.toInt() == notificationId
}
}
}
}
/**
* Clear notifications by the host they belong to and its unique id.
*/
fun clearNotificationsByHostAndNotificationId(host: String?, notificationId: Int?) {
if (host == null || notificationId == null) {
return
}
if (hostToPushMessageList.isNotEmpty()) {
val notifications = hostToPushMessageList[host]
notifications?.let {
notifications.removeAll {
it.notificationId.toInt() == notificationId
}
}
}
}
private fun isAndroidVersionAtLeast(minVersion: Int) = Build.VERSION.SDK_INT >= minVersion
private fun getGroupForHost(host: String): TupleGroupIdMessageCount {
val size = groupMap.size
var group = groupMap.get(host)
if (group == null) {
group = TupleGroupIdMessageCount(size + 1, AtomicInteger(0))
groupMap.put(host, group)
}
return group
}
internal fun showNotification(context: Context, lastPushMessage: PushMessage) {
if (lastPushMessage.host == null || lastPushMessage.message == null || lastPushMessage.title == null) {
return
}
val manager: NotificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val notId = lastPushMessage.notificationId.toInt()
val host = lastPushMessage.host
val groupTuple = getGroupForHost(host)
groupTuple.second.incrementAndGet()
val notIdListForHostname: MutableList<PushMessage>? = hostToPushMessageList.get(host)
if (notIdListForHostname == null) {
hostToPushMessageList.put(host, arrayListOf(lastPushMessage))
} else {
notIdListForHostname.add(0, lastPushMessage)
}
if (isAndroidVersionAtLeast(Build.VERSION_CODES.N)) {
val notification = createSingleNotificationForNougatAndAbove(context, lastPushMessage)
val groupNotification = createGroupNotificationForNougatAndAbove(context, lastPushMessage)
notification?.let {
manager.notify(notId, notification)
}
groupNotification?.let {
manager.notify(groupTuple.first, groupNotification)
}
} else {
val notification = createSingleNotification(context, lastPushMessage)
val pushMessageList = hostToPushMessageList.get(host)
notification?.let {
NotificationManagerCompat.from(context).notify(notId, notification)
}
pushMessageList?.let {
if (pushMessageList.size > 1) {
val groupNotification = createGroupNotification(context, lastPushMessage)
groupNotification?.let {
NotificationManagerCompat.from(context).notify(groupTuple.first, groupNotification)
}
}
}
}
}
internal fun createGroupNotification(context: Context, lastPushMessage: PushMessage): Notification? {
with(lastPushMessage) {
if (host == null || message == null || title == null) {
return null
}
val id = lastPushMessage.notificationId.toInt()
val contentIntent = getContentIntent(context, id, lastPushMessage)
val deleteIntent = getDismissIntent(context, lastPushMessage)
val builder = NotificationCompat.Builder(context)
.setWhen(createdAt)
.setContentTitle(title.fromHtml())
.setContentText(message.fromHtml())
.setGroup(host)
.setGroupSummary(true)
.setContentIntent(contentIntent)
.setDeleteIntent(deleteIntent)
.setMessageNotification()
val subText = RocketChatCache(context).getHostSiteName(host)
if (subText.isNotEmpty()) {
builder.setSubText(subText)
}
if (style == null || style == "inbox") {
val pushMessageList = hostToPushMessageList.get(host)
pushMessageList?.let {
val messageCount = pushMessageList.size
val summary = summaryText?.replace("%n%", messageCount.toString())
?.fromHtml() ?: "$messageCount new messages"
builder.setNumber(messageCount)
if (messageCount > 1) {
val firstPush = pushMessageList[0]
val singleConversation = pushMessageList.filter {
firstPush.sender?.username != it.sender?.username
}.isEmpty()
val inbox = NotificationCompat.InboxStyle()
.setBigContentTitle(if (singleConversation) title else summary)
for (push in pushMessageList) {
if (singleConversation) {
inbox.addLine(push.message)
} else {
inbox.addLine("<font color='black'>${push.title}</font> <font color='gray'>${push.message}</font>".fromHtml())
}
}
builder.setStyle(inbox)
} else {
val firstMsg = pushMessageList[0]
if (firstMsg.host == null || firstMsg.message == null || firstMsg.title == null) {
return null
}
val bigText = NotificationCompat.BigTextStyle()
.bigText(firstMsg.message.fromHtml())
.setBigContentTitle(firstMsg.title.fromHtml())
builder.setStyle(bigText)
}
}
} else {
val bigText = NotificationCompat.BigTextStyle()
.bigText(message.fromHtml())
.setBigContentTitle(title.fromHtml())
builder.setStyle(bigText)
}
return builder.build()
}
}
@RequiresApi(Build.VERSION_CODES.N)
internal fun createGroupNotificationForNougatAndAbove(context: Context, lastPushMessage: PushMessage): Notification? {
with(lastPushMessage) {
if (host == null || message == null || title == null) {
return null
}
val manager: NotificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val id = notificationId.toInt()
val contentIntent = getContentIntent(context, id, lastPushMessage, grouped = true)
val deleteIntent = getDismissIntent(context, lastPushMessage)
val builder = Notification.Builder(context)
.setWhen(createdAt)
.setContentTitle(title.fromHtml())
.setContentText(message.fromHtml())
.setGroup(host)
.setGroupSummary(true)
.setContentIntent(contentIntent)
.setDeleteIntent(deleteIntent)
.setMessageNotification(context)
if (isAndroidVersionAtLeast(Build.VERSION_CODES.O)) {
builder.setChannelId(host)
val groupChannel = NotificationChannel(host, host, NotificationManager.IMPORTANCE_HIGH)
groupChannel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
groupChannel.enableLights(false)
groupChannel.enableVibration(true)
groupChannel.setShowBadge(true)
manager.createNotificationChannel(groupChannel)
}
val subText = RocketChatCache(context).getHostSiteName(host)
if (subText.isNotEmpty()) {
builder.setSubText(subText)
}
if (style == null || style == "inbox") {
val pushMessageList = hostToPushMessageList.get(host)
pushMessageList?.let {
val count = pushMessageList.filter {
it.title == title
}.size
builder.setContentTitle(getTitle(count, title))
val inbox = Notification.InboxStyle()
.setBigContentTitle(getTitle(count, title))
for (push in pushMessageList) {
inbox.addLine(push.message)
}
builder.setStyle(inbox)
}
} else {
val bigText = Notification.BigTextStyle()
.bigText(message.fromHtml())
.setBigContentTitle(title.fromHtml())
builder.setStyle(bigText)
}
return builder.build()
}
}
internal fun createSingleNotification(context: Context, lastPushMessage: PushMessage): Notification? {
with(lastPushMessage) {
if (host == null || message == null || title == null) {
return null
}
val id = notificationId.toInt()
val contentIntent = getContentIntent(context, id, lastPushMessage)
val deleteIntent = getDismissIntent(context, lastPushMessage)
val builder = NotificationCompat.Builder(context)
.setWhen(createdAt)
.setContentTitle(title.fromHtml())
.setContentText(message.fromHtml())
.setGroupSummary(false)
.setGroup(host)
.setDeleteIntent(deleteIntent)
.setContentIntent(contentIntent)
.setMessageNotification()
val subText = RocketChatCache(context).getHostSiteName(host)
if (subText.isNotEmpty()) {
builder.setSubText(subText)
}
val pushMessageList = hostToPushMessageList.get(host)
pushMessageList?.let {
val lastPushMsg = pushMessageList.last()
if (lastPushMsg.host == null || lastPushMsg.message == null || lastPushMsg.title == null) {
return null
}
if (pushMessageList.isNotEmpty()) {
val messageCount = pushMessageList.size
val bigText = NotificationCompat.BigTextStyle()
.bigText(lastPushMsg.message.fromHtml())
.setBigContentTitle(lastPushMsg.title.fromHtml())
builder.setStyle(bigText).setNumber(messageCount)
}
}
return builder.build()
}
}
@RequiresApi(Build.VERSION_CODES.N)
internal fun createSingleNotificationForNougatAndAbove(context: Context, lastPushMessage: PushMessage): Notification? {
val manager: NotificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
with(lastPushMessage) {
if (host == null || message == null || title == null) {
return null
}
val id = notificationId.toInt()
val contentIntent = getContentIntent(context, id, lastPushMessage)
val deleteIntent = getDismissIntent(context, lastPushMessage)
val builder = Notification.Builder(context)
.setWhen(createdAt)
.setContentTitle(title.fromHtml())
.setContentText(message.fromHtml())
.setGroup(host)
.setGroupSummary(false)
.setDeleteIntent(deleteIntent)
.setContentIntent(contentIntent)
.setMessageNotification(context)
.addReplyAction(context, lastPushMessage)
if (isAndroidVersionAtLeast(android.os.Build.VERSION_CODES.O)) {
builder.setChannelId(host)
val channel = NotificationChannel(host, host, NotificationManager.IMPORTANCE_HIGH)
channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
channel.enableLights(false)
channel.enableVibration(true)
channel.setShowBadge(true)
manager.createNotificationChannel(channel)
}
val subText = RocketChatCache(context).getHostSiteName(host)
if (subText.isNotEmpty()) {
builder.setSubText(subText)
}
if (style == null || "inbox" == style) {
val pushMessageList = hostToPushMessageList.get(host)
pushMessageList?.let {
val userMessages = pushMessageList.filter {
it.notificationId == lastPushMessage.notificationId
}
val count = pushMessageList.filter {
it.title == title
}.size
builder.setContentTitle(getTitle(count, title))
if (count > 1) {
val inbox = Notification.InboxStyle()
inbox.setBigContentTitle(getTitle(count, title))
for (push in userMessages) {
inbox.addLine(push.message)
}
builder.setStyle(inbox)
} else {
val bigTextStyle = Notification.BigTextStyle()
.bigText(message.fromHtml())
builder.setStyle(bigTextStyle)
}
}
} else {
val bigTextStyle = Notification.BigTextStyle()
.bigText(message.fromHtml())
builder.setStyle(bigTextStyle)
}
return builder.build()
}
}
private fun getTitle(messageCount: Int, title: String): CharSequence {
return if (messageCount > 1) "($messageCount) ${title.fromHtml()}" else title.fromHtml()
}
private fun getDismissIntent(context: Context, pushMessage: PushMessage): PendingIntent {
val deleteIntent = Intent(context, DeleteReceiver::class.java)
.putExtra(EXTRA_NOT_ID, pushMessage.notificationId.toInt())
.putExtra(EXTRA_HOSTNAME, pushMessage.host)
return PendingIntent.getBroadcast(context, pushMessage.notificationId.toInt(), deleteIntent, PendingIntent.FLAG_UPDATE_CURRENT)
}
private fun getContentIntent(context: Context, notificationId: Int, pushMessage: PushMessage, grouped: Boolean = false): PendingIntent {
val notificationIntent = Intent(context, MainActivity::class.java)
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP)
.putExtra(EXTRA_NOT_ID, notificationId)
.putExtra(EXTRA_HOSTNAME, pushMessage.host)
if (!grouped) {
notificationIntent.putExtra(EXTRA_ROOM_ID, pushMessage.rid)
}
return PendingIntent.getActivity(context, randomizer.nextInt(), notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT)
}
// CharSequence extensions
private fun CharSequence.fromHtml(): Spanned {
return Html.fromHtml(this as String)
}
//Notification.Builder extensions
@RequiresApi(Build.VERSION_CODES.N)
private fun Notification.Builder.addReplyAction(ctx: Context, pushMessage: PushMessage): Notification.Builder {
val replyRemoteInput = android.app.RemoteInput.Builder(REMOTE_INPUT_REPLY)
.setLabel(REPLY_LABEL)
.build()
val replyIntent = Intent(ctx, ReplyReceiver::class.java)
replyIntent.putExtra(EXTRA_PUSH_MESSAGE, pushMessage as Serializable)
val pendingIntent = PendingIntent.getBroadcast(
ctx, randomizer.nextInt(), replyIntent, 0)
val replyAction =
Notification.Action.Builder(
Icon.createWithResource(ctx, R.drawable.ic_reply), REPLY_LABEL, pendingIntent)
.addRemoteInput(replyRemoteInput)
.setAllowGeneratedReplies(true)
.build()
this.addAction(replyAction)
return this
}
@RequiresApi(Build.VERSION_CODES.N)
private fun Notification.Builder.setMessageNotification(ctx: Context): Notification.Builder {
val res = ctx.resources
val smallIcon = res.getIdentifier(
"rocket_chat_notification", "drawable", ctx.packageName)
with(this, {
setAutoCancel(true)
setShowWhen(true)
setColor(res.getColor(R.color.colorRed400, ctx.theme))
setSmallIcon(smallIcon)
})
return this
}
// NotificationCompat.Builder extensions
private fun NotificationCompat.Builder.addReplyAction(pushMessage: PushMessage): NotificationCompat.Builder {
val context = this.mContext
val replyRemoteInput = RemoteInput.Builder(REMOTE_INPUT_REPLY)
.setLabel(REPLY_LABEL)
.build()
val replyIntent = Intent(context, ReplyReceiver::class.java)
replyIntent.putExtra(EXTRA_PUSH_MESSAGE, pushMessage as Serializable)
val pendingIntent = PendingIntent.getBroadcast(
context, randomizer.nextInt(), replyIntent, 0)
val replyAction = NotificationCompat.Action.Builder(R.drawable.ic_reply, REPLY_LABEL, pendingIntent)
.addRemoteInput(replyRemoteInput)
.setAllowGeneratedReplies(true)
.build()
this.addAction(replyAction)
return this
}
private fun NotificationCompat.Builder.setMessageNotification(): NotificationCompat.Builder {
val ctx = this.mContext
val res = ctx.resources
val smallIcon = res.getIdentifier(
"rocket_chat_notification", "drawable", ctx.packageName)
with(this, {
setAutoCancel(true)
setShowWhen(true)
color = ctx.resources.getColor(R.color.colorRed400)
setDefaults(Notification.DEFAULT_ALL)
setSmallIcon(smallIcon)
})
return this
}
internal data class PushMessage(
val title: String? = null,
val message: String? = null,
val image: String? = null,
val ejson: String? = null,
val count: String? = null,
val notificationId: String,
val summaryText: String? = null,
val style: String? = null) : Serializable {
val host: String?
val rid: String?
val type: String?
val channelName: String?
val sender: Sender?
val createdAt: Long
init {
val json = if (ejson == null) JSONObject() else JSONObject(ejson)
host = json.optString("host", null)
rid = json.optString("rid", null)
type = json.optString("type", null)
channelName = json.optString("name", null)
val senderJson = json.optString("sender", null)
if (senderJson != null && senderJson != "null") {
sender = Sender(senderJson)
} else {
sender = null
}
createdAt = System.currentTimeMillis()
}
data class Sender(val sender: String) : Serializable {
val _id: String?
val username: String?
val name: String?
init {
val json = JSONObject(sender)
_id = json.optString("_id", null)
username = json.optString("username", null)
name = json.optString("name", null)
}
}
}
/**
* BroadcastReceiver for dismissed notifications.
*/
class DeleteReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val notId = intent?.extras?.getInt(EXTRA_NOT_ID)
val host = intent?.extras?.getString(EXTRA_HOSTNAME)
if (host != null && notId != null) {
clearNotificationsByHostAndNotificationId(host, notId)
}
}
}
/**
* *EXPERIMENTAL*
*
* BroadcastReceiver for notifications' replies using Direct Reply feature (Android >= 7).
*/
class ReplyReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (context == null) {
return
}
synchronized(this) {
val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val message: CharSequence? = extractMessage(intent)
val pushMessage = intent?.extras?.getSerializable(EXTRA_PUSH_MESSAGE) as PushMessage?
if (pushMessage?.host == null) {
return
}
pushMessage.let {
val groupTuple = groupMap.get(pushMessage.host)
val pushes = hostToPushMessageList.get(pushMessage.host)
pushes?.let {
val allMessagesFromSameUser = pushes.filter {
it.sender?._id == pushMessage.sender?._id
}
for (msg in allMessagesFromSameUser) {
manager.cancel(msg.notificationId.toInt())
groupTuple?.second?.decrementAndGet()
}
groupTuple?.let {
val groupNotId = groupTuple.first
val totalNot = groupTuple.second.get()
if (totalNot == 0) {
manager.cancel(groupNotId)
}
}
message?.let {
if (pushMessage.rid == null) {
return
}
val httpUrl = HttpUrl.parse(pushMessage.host)
httpUrl?.let {
val siteUrl = RocketChatCache(context).getSiteUrlFor(httpUrl.host())
if (siteUrl != null) {
sendMessage(siteUrl, message, pushMessage.rid)
}
}
}
}
}
}
}
private fun extractMessage(intent: Intent?): CharSequence? {
val remoteInput: Bundle? =
RemoteInput.getResultsFromIntent(intent)
return remoteInput?.getCharSequence(REMOTE_INPUT_REPLY)
}
// Just kept for reference. We should use this on rewrite with job schedulers
private fun sendMessage(hostname: String, message: CharSequence, roomId: String) {
val roomRepository = RealmRoomRepository(hostname)
val userRepository = RealmUserRepository(hostname)
val messageRepository = RealmMessageRepository(hostname)
val messageInteractor = MessageInteractor(messageRepository, roomRepository)
val singleRoom: Single<Room> = roomRepository.getById(roomId)
.filter({ it.isPresent })
.map({ it.get() })
.firstElement()
.toSingle()
val singleUser: Single<User> = userRepository.getCurrent()
.filter({ it.isPresent })
.map({ it.get() })
.firstElement()
.toSingle()
val roomUserTuple: Single<TupleRoomUser> = Single.zip(
singleRoom,
singleUser,
BiFunction { room, user -> TupleRoomUser(room, user) })
roomUserTuple.flatMap { tuple -> messageInteractor.send(tuple.first, tuple.second, message as String) }
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ _ ->
// Empty
}, { throwable ->
throwable.printStackTrace()
Logger.report(throwable)
})
}
}
}
\ No newline at end of file
package chat.rocket.android.push;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.RequiresApi;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.RemoteInput;
import android.support.v4.util.SparseArrayCompat;
import android.text.Html;
import android.text.Spanned;
import android.util.Log;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import chat.rocket.android.activity.MainActivity;
import chat.rocket.android.helper.ServerPolicyHelper;
import chat.rocket.android.service.ConnectivityManager;
import chat.rocket.android.widget.helper.AvatarHelper;
import chat.rocket.core.models.ServerInfo;
public class PushNotificationHandler implements PushConstants {
private static final String LOG_TAG = "PushNotificationHandler";
private static SparseArrayCompat<ArrayList<String>> messageMap = new SparseArrayCompat<>();
private Random random = new Random();
public static synchronized void cleanUpNotificationStack(int notId) {
messageMap.remove(notId);
}
private synchronized void setNotification(int notId, String message) {
ArrayList<String> messageList = messageMap.get(notId);
if (messageList == null) {
messageList = new ArrayList<>();
messageMap.put(notId, messageList);
}
if (message.isEmpty()) {
messageList.clear();
} else {
messageList.add(message);
}
}
private synchronized ArrayList<String> getMessageList(int notId) {
return messageMap.get(notId);
}
public void showNotificationIfPossible(Context context, Bundle extras) {
// Send a notification if there is a message or title, otherwise just send data
String message = extras.getString(MESSAGE);
String title = extras.getString(TITLE);
String contentAvailable = extras.getString(CONTENT_AVAILABLE);
String forceStart = extras.getString(FORCE_START);
Log.d(LOG_TAG, "message =[" + message + "]");
Log.d(LOG_TAG, "title =[" + title + "]");
Log.d(LOG_TAG, "contentAvailable =[" + contentAvailable + "]");
Log.d(LOG_TAG, "forceStart =[" + forceStart + "]");
if ((message != null && message.length() != 0) ||
(title != null && title.length() != 0)) {
Log.d(LOG_TAG, "build notification");
if (title == null || title.isEmpty()) {
extras.putString(TITLE, getAppName(context));
}
createNotification(context, extras);
}
}
public void createNotification(Context context, Bundle extras) {
NotificationManager notificationManager =
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
String appName = getAppName(context);
String packageName = context.getPackageName();
Resources resources = context.getResources();
String hostname = getHostname(extras);
String roomId = getRoomId(extras);
int notId = parseInt(NOT_ID, extras);
Intent notificationIntent = new Intent(context, MainActivity.class);
notificationIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
notificationIntent.putExtra(PUSH_BUNDLE, extras);
notificationIntent.putExtra(NOT_ID, notId);
if (hostname != null && roomId != null && isValidHostname(context, hostname)) {
notificationIntent.putExtra(HOSTNAME, hostname);
notificationIntent.putExtra(ROOM_ID, roomId);
}
int requestCode = random.nextInt();
PendingIntent contentIntent = PendingIntent
.getActivity(context, requestCode, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
SharedPreferences prefs = context
.getSharedPreferences(PushConstants.COM_ADOBE_PHONEGAP_PUSH, Context.MODE_PRIVATE);
String localIcon = prefs.getString(ICON, null);
String localIconColor = prefs.getString(ICON_COLOR, null);
boolean soundOption = prefs.getBoolean(SOUND, true);
boolean vibrateOption = prefs.getBoolean(VIBRATE, true);
Log.d(LOG_TAG, "stored icon=" + localIcon);
Log.d(LOG_TAG, "stored iconColor=" + localIconColor);
Log.d(LOG_TAG, "stored sound=" + soundOption);
Log.d(LOG_TAG, "stored vibrate=" + vibrateOption);
if (Build.VERSION.SDK_INT >= 26) {
String channelId = "rocket-chat-channel";
CharSequence name = "RocketChatMessage";
int importance = NotificationManager.IMPORTANCE_HIGH;
NotificationChannel channel = new NotificationChannel(channelId, name, importance);
channel.enableLights(true);
notificationManager.createNotificationChannel(channel);
Notification.Builder notificationBuilder = new Notification.Builder(context, channelId)
.setWhen(System.currentTimeMillis())
.setContentTitle(fromHtml(extras.getString(TITLE)))
.setTicker(fromHtml(extras.getString(TITLE)))
.setContentIntent(contentIntent)
.setChannelId(channelId)
.setAutoCancel(true);
setNotificationImportance(extras, channel);
setNotificationVibration(extras, vibrateOption, channel);
setNotificationMessage(notId, extras, notificationBuilder);
setNotificationCount(context, extras, notificationBuilder);
setNotificationSmallIcon(extras, packageName, resources, notificationBuilder,
localIcon);
setNotificationLargeIcon(context, extras, packageName, resources, notificationBuilder);
setNotificationLedColor(extras, channel);
if (soundOption) {
setNotificationSound(context, extras, channel);
}
createActions(context, extras, notificationBuilder, resources, packageName, notId);
notificationManager.notify(notId, notificationBuilder.build());
} else {
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context)
.setWhen(System.currentTimeMillis())
.setContentTitle(fromHtml(extras.getString(TITLE)))
.setTicker(fromHtml(extras.getString(TITLE)))
.setContentIntent(contentIntent)
.setAutoCancel(true);
setNotificationCount(context, extras, notificationBuilder);
setNotificationVibration(extras, vibrateOption, notificationBuilder);
setNotificationIconColor(extras.getString("color"), notificationBuilder, localIconColor);
setNotificationSmallIcon(extras, packageName, resources, notificationBuilder,
localIcon);
setNotificationLargeIcon(context, extras, packageName, resources, notificationBuilder);
if (soundOption) {
setNotificationSound(context, extras, notificationBuilder);
}
setNotificationLedColor(extras, notificationBuilder);
setNotificationPriority(extras, notificationBuilder);
setNotificationMessage(notId, extras, notificationBuilder);
setVisibility(context, extras, notificationBuilder);
createActions(context, extras, notificationBuilder, resources, packageName, notId);
notificationManager.notify(appName, notId, notificationBuilder.build());
}
}
private void createActions(Context context, Bundle extras, NotificationCompat.Builder builder,
Resources resources, String packageName, int notId) {
Log.d(LOG_TAG, "build actions: with in-line");
String actions = extras.getString(ACTIONS);
if (actions == null) {
return;
}
try {
JSONArray actionsArray = new JSONArray(actions);
ArrayList<NotificationCompat.Action> wActions = new ArrayList<>();
for (int i = 0; i < actionsArray.length(); i++) {
int min = 1;
int max = 2000000000;
Random random = new Random();
int uniquePendingIntentRequestCode = random.nextInt((max - min) + 1) + min;
Log.d(LOG_TAG, "adding action");
JSONObject action = actionsArray.getJSONObject(i);
Log.d(LOG_TAG, "adding callback = " + action.getString(CALLBACK));
boolean foreground = action.optBoolean(FOREGROUND, true);
boolean inline = action.optBoolean("inline", false);
Intent intent;
PendingIntent pIntent;
if (inline) {
Log.d(LOG_TAG, "Version: " + android.os.Build.VERSION.SDK_INT + " = "
+ android.os.Build.VERSION_CODES.M);
if (android.os.Build.VERSION.SDK_INT <= android.os.Build.VERSION_CODES.M) {
Log.d(LOG_TAG, "push activity");
intent = new Intent(context, MainActivity.class);
} else {
Log.d(LOG_TAG, "push receiver");
intent = new Intent(context, BackgroundActionButtonHandler.class);
}
updateIntent(intent, action.getString(CALLBACK), extras, foreground, notId);
if (android.os.Build.VERSION.SDK_INT <= android.os.Build.VERSION_CODES.M) {
Log.d(LOG_TAG, "push activity for notId " + notId);
pIntent =
PendingIntent.getActivity(context, uniquePendingIntentRequestCode, intent,
PendingIntent.FLAG_ONE_SHOT);
} else {
Log.d(LOG_TAG, "push receiver for notId " + notId);
pIntent = PendingIntent
.getBroadcast(context, uniquePendingIntentRequestCode, intent,
PendingIntent.FLAG_ONE_SHOT);
}
} else if (foreground) {
intent = new Intent(context, MainActivity.class);
updateIntent(intent, action.getString(CALLBACK), extras, foreground, notId);
pIntent = PendingIntent
.getActivity(context, uniquePendingIntentRequestCode, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
} else {
intent = new Intent(context, BackgroundActionButtonHandler.class);
updateIntent(intent, action.getString(CALLBACK), extras, foreground, notId);
pIntent = PendingIntent
.getBroadcast(context, uniquePendingIntentRequestCode, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
}
NotificationCompat.Action.Builder actionBuilder = new NotificationCompat.Action.Builder(
resources.getIdentifier(action.optString(ICON, ""), DRAWABLE, packageName),
action.getString(TITLE), pIntent);
RemoteInput remoteInput;
if (inline) {
Log.d(LOG_TAG, "build remote input");
String replyLabel = "Enter your reply here";
remoteInput = new RemoteInput.Builder(INLINE_REPLY)
.setLabel(replyLabel)
.build();
actionBuilder.addRemoteInput(remoteInput);
}
NotificationCompat.Action wAction = actionBuilder.build();
wActions.add(actionBuilder.build());
if (inline) {
builder.addAction(wAction);
} else {
builder.addAction(
resources.getIdentifier(action.optString(ICON, ""), DRAWABLE, packageName),
action.getString(TITLE), pIntent);
}
wAction = null;
pIntent = null;
}
builder.extend(new NotificationCompat.WearableExtender().addActions(wActions));
wActions.clear();
} catch (JSONException e) {
// nope
}
}
@RequiresApi(api = Build.VERSION_CODES.KITKAT_WATCH)
private void createActions(Context context, Bundle extras, Notification.Builder builder,
Resources resources, String packageName, int notId) {
Log.d(LOG_TAG, "build actions: with in-line");
String actions = extras.getString(ACTIONS);
if (actions == null) {
return;
}
try {
JSONArray actionsArray = new JSONArray(actions);
ArrayList<Notification.Action> wActions = new ArrayList<>();
for (int i = 0; i < actionsArray.length(); i++) {
int min = 1;
int max = 2000000000;
Random random = new Random();
int uniquePendingIntentRequestCode = random.nextInt((max - min) + 1) + min;
Log.d(LOG_TAG, "adding action");
JSONObject action = actionsArray.getJSONObject(i);
Log.d(LOG_TAG, "adding callback = " + action.getString(CALLBACK));
boolean foreground = action.optBoolean(FOREGROUND, true);
boolean inline = action.optBoolean("inline", false);
Intent intent;
PendingIntent pIntent;
if (inline) {
Log.d(LOG_TAG, "Version: " + android.os.Build.VERSION.SDK_INT + " = "
+ android.os.Build.VERSION_CODES.M);
if (android.os.Build.VERSION.SDK_INT <= android.os.Build.VERSION_CODES.M) {
Log.d(LOG_TAG, "push activity");
intent = new Intent(context, MainActivity.class);
} else {
Log.d(LOG_TAG, "push receiver");
intent = new Intent(context, BackgroundActionButtonHandler.class);
}
updateIntent(intent, action.getString(CALLBACK), extras, foreground, notId);
if (android.os.Build.VERSION.SDK_INT <= android.os.Build.VERSION_CODES.M) {
Log.d(LOG_TAG, "push activity for notId " + notId);
pIntent =
PendingIntent.getActivity(context, uniquePendingIntentRequestCode, intent,
PendingIntent.FLAG_ONE_SHOT);
} else {
Log.d(LOG_TAG, "push receiver for notId " + notId);
pIntent = PendingIntent
.getBroadcast(context, uniquePendingIntentRequestCode, intent,
PendingIntent.FLAG_ONE_SHOT);
}
} else if (foreground) {
intent = new Intent(context, MainActivity.class);
updateIntent(intent, action.getString(CALLBACK), extras, foreground, notId);
pIntent = PendingIntent
.getActivity(context, uniquePendingIntentRequestCode, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
} else {
intent = new Intent(context, BackgroundActionButtonHandler.class);
updateIntent(intent, action.getString(CALLBACK), extras, foreground, notId);
pIntent = PendingIntent
.getBroadcast(context, uniquePendingIntentRequestCode, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
}
Notification.Action.Builder actionBuilder = new Notification.Action.Builder(
resources.getIdentifier(action.optString(ICON, ""), DRAWABLE, packageName),
action.getString(TITLE), pIntent);
android.app.RemoteInput remoteInput;
if (inline) {
Log.d(LOG_TAG, "build remote input");
String replyLabel = "Enter your reply here";
remoteInput = new android.app.RemoteInput.Builder(INLINE_REPLY)
.setLabel(replyLabel)
.build();
actionBuilder.addRemoteInput(remoteInput);
}
Notification.Action wAction = actionBuilder.build();
wActions.add(actionBuilder.build());
if (inline) {
builder.addAction(wAction);
} else {
builder.addAction(
resources.getIdentifier(action.optString(ICON, ""), DRAWABLE, packageName),
action.getString(TITLE), pIntent);
}
wAction = null;
pIntent = null;
}
builder.extend(new Notification.WearableExtender().addActions(wActions));
wActions.clear();
} catch (JSONException e) {
// nope
}
}
private void setNotificationCount(Context context, Bundle extras,
NotificationCompat.Builder builder) {
int count = extractBadgeCount(extras);
if (count >= 0) {
Log.d(LOG_TAG, "count =[" + count + "]");
builder.setNumber(count);
}
}
private void setNotificationCount(Context context, Bundle extras,
Notification.Builder builder) {
int count = extractBadgeCount(extras);
if (count >= 0) {
Log.d(LOG_TAG, "count =[" + count + "]");
builder.setNumber(count);
}
}
private void setVisibility(Context context, Bundle extras, NotificationCompat.Builder builder) {
String visibilityStr = extras.getString(VISIBILITY);
if (visibilityStr == null) {
return;
}
try {
Integer visibility = Integer.parseInt(visibilityStr);
if (visibility >= NotificationCompat.VISIBILITY_SECRET
&& visibility <= NotificationCompat.VISIBILITY_PUBLIC) {
builder.setVisibility(visibility);
} else {
Log.e(LOG_TAG, "Visibility parameter must be between -1 and 1");
}
} catch (NumberFormatException e) {
e.printStackTrace();
}
}
private void setNotificationVibration(Bundle extras, Boolean vibrateOption,
NotificationCompat.Builder builder) {
String vibrationPattern = extras.getString(VIBRATION_PATTERN);
if (vibrationPattern != null) {
String[] items = vibrationPattern.replaceAll("\\[", "").replaceAll("\\]", "").split(",");
long[] results = new long[items.length];
for (int i = 0; i < items.length; i++) {
try {
results[i] = Long.parseLong(items[i].trim());
} catch (NumberFormatException nfe) {
}
}
builder.setVibrate(results);
} else {
if (vibrateOption) {
builder.setDefaults(Notification.DEFAULT_VIBRATE);
}
}
}
@RequiresApi(api = 26)
private void setNotificationVibration(Bundle extras, Boolean vibrateOption,
NotificationChannel channel) {
String vibrationPattern = extras.getString(VIBRATION_PATTERN);
if (vibrationPattern != null) {
String[] items = vibrationPattern.replaceAll("\\[", "").replaceAll("\\]", "").split(",");
long[] results = new long[items.length];
for (int i = 0; i < items.length; i++) {
try {
results[i] = Long.parseLong(items[i].trim());
} catch (NumberFormatException nfe) {
}
}
channel.setVibrationPattern(results);
} else {
if (vibrateOption) {
channel.enableVibration(true);
}
}
}
private void setNotificationMessage(int notId, Bundle extras,
NotificationCompat.Builder builder) {
String message = extras.getString(MESSAGE);
String style = extras.getString(STYLE, STYLE_TEXT);
if (STYLE_INBOX.equals(style)) {
setNotification(notId, message);
builder.setContentText(fromHtml(message));
ArrayList<String> messageList = getMessageList(notId);
Integer sizeList = messageList.size();
if (sizeList > 1) {
String sizeListMessage = sizeList.toString();
String stacking = sizeList + " more";
if (extras.getString(SUMMARY_TEXT) != null) {
stacking = extras.getString(SUMMARY_TEXT);
stacking = stacking.replace("%n%", sizeListMessage);
}
NotificationCompat.InboxStyle notificationInbox = new NotificationCompat.InboxStyle()
.setBigContentTitle(fromHtml(extras.getString(TITLE)))
.setSummaryText(fromHtml(stacking));
for (int i = messageList.size() - 1; i >= 0; i--) {
notificationInbox.addLine(fromHtml(messageList.get(i)));
}
builder.setStyle(notificationInbox);
} else {
NotificationCompat.BigTextStyle bigText = new NotificationCompat.BigTextStyle();
if (message != null) {
bigText.bigText(fromHtml(message));
bigText.setBigContentTitle(fromHtml(extras.getString(TITLE)));
builder.setStyle(bigText);
}
}
} else if (STYLE_PICTURE.equals(style)) {
setNotification(notId, "");
NotificationCompat.BigPictureStyle bigPicture = new NotificationCompat.BigPictureStyle();
bigPicture.bigPicture(getBitmapFromURL(extras.getString(PICTURE), null, null));
bigPicture.setBigContentTitle(fromHtml(extras.getString(TITLE)));
bigPicture.setSummaryText(fromHtml(extras.getString(SUMMARY_TEXT)));
builder.setContentTitle(fromHtml(extras.getString(TITLE)));
builder.setContentText(fromHtml(message));
builder.setStyle(bigPicture);
} else {
setNotification(notId, "");
NotificationCompat.BigTextStyle bigText = new NotificationCompat.BigTextStyle();
if (message != null) {
builder.setContentText(fromHtml(message));
bigText.bigText(fromHtml(message));
bigText.setBigContentTitle(fromHtml(extras.getString(TITLE)));
String summaryText = extras.getString(SUMMARY_TEXT);
if (summaryText != null) {
bigText.setSummaryText(fromHtml(summaryText));
}
builder.setStyle(bigText);
}
}
}
private void setNotificationMessage(int notId, Bundle extras,
Notification.Builder builder) {
String message = extras.getString(MESSAGE);
String style = extras.getString(STYLE, STYLE_TEXT);
if (STYLE_INBOX.equals(style)) {
setNotification(notId, message);
builder.setContentText(fromHtml(message));
ArrayList<String> messageList = getMessageList(notId);
Integer sizeList = messageList.size();
if (sizeList > 1) {
String sizeListMessage = sizeList.toString();
String stacking = sizeList + " more";
if (extras.getString(SUMMARY_TEXT) != null) {
stacking = extras.getString(SUMMARY_TEXT);
stacking = stacking.replace("%n%", sizeListMessage);
}
Notification.InboxStyle notificationInbox = new Notification.InboxStyle()
.setBigContentTitle(fromHtml(extras.getString(TITLE)))
.setSummaryText(fromHtml(stacking));
for (int i = messageList.size() - 1; i >= 0; i--) {
notificationInbox.addLine(fromHtml(messageList.get(i)));
}
builder.setStyle(notificationInbox);
} else {
Notification.BigTextStyle bigText = new Notification.BigTextStyle();
if (message != null) {
bigText.bigText(fromHtml(message));
bigText.setBigContentTitle(fromHtml(extras.getString(TITLE)));
builder.setStyle(bigText);
}
}
} else if (STYLE_PICTURE.equals(style)) {
setNotification(notId, "");
Notification.BigPictureStyle bigPicture = new Notification.BigPictureStyle();
bigPicture.bigPicture(getBitmapFromURL(extras.getString(PICTURE), null, null));
bigPicture.setBigContentTitle(fromHtml(extras.getString(TITLE)));
bigPicture.setSummaryText(fromHtml(extras.getString(SUMMARY_TEXT)));
builder.setContentTitle(fromHtml(extras.getString(TITLE)));
builder.setContentText(fromHtml(message));
builder.setStyle(bigPicture);
} else {
setNotification(notId, "");
Notification.BigTextStyle bigText = new Notification.BigTextStyle();
if (message != null) {
builder.setContentText(fromHtml(message));
bigText.bigText(fromHtml(message));
bigText.setBigContentTitle(fromHtml(extras.getString(TITLE)));
String summaryText = extras.getString(SUMMARY_TEXT);
if (summaryText != null) {
bigText.setSummaryText(fromHtml(summaryText));
}
builder.setStyle(bigText);
}
}
}
private void setNotificationSound(Context context, Bundle extras,
NotificationCompat.Builder builder) {
String soundname = extras.getString(SOUNDNAME);
if (soundname == null) {
soundname = extras.getString(SOUND);
}
if (SOUND_RINGTONE.equals(soundname)) {
builder.setSound(android.provider.Settings.System.DEFAULT_RINGTONE_URI);
} else if (soundname != null && !soundname.contentEquals(SOUND_DEFAULT)) {
Uri sound = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE
+ "://" + context.getPackageName() + "/raw/" + soundname);
Log.d(LOG_TAG, sound.toString());
builder.setSound(sound);
} else {
builder.setSound(android.provider.Settings.System.DEFAULT_NOTIFICATION_URI);
}
}
@RequiresApi(api = 26)
private void setNotificationSound(Context context, Bundle extras,
NotificationChannel channel) {
String soundname = extras.getString(SOUNDNAME);
if (soundname == null) {
soundname = extras.getString(SOUND);
}
if (SOUND_RINGTONE.equals(soundname)) {
channel.setSound(android.provider.Settings.System.DEFAULT_RINGTONE_URI,
Notification.AUDIO_ATTRIBUTES_DEFAULT);
} else if (soundname != null && !soundname.contentEquals(SOUND_DEFAULT)) {
Uri sound = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE
+ "://" + context.getPackageName() + "/raw/" + soundname);
Log.d(LOG_TAG, sound.toString());
channel.setSound(sound, Notification.AUDIO_ATTRIBUTES_DEFAULT);
} else {
channel.setSound(android.provider.Settings.System.DEFAULT_NOTIFICATION_URI,
Notification.AUDIO_ATTRIBUTES_DEFAULT);
}
}
private void setNotificationLedColor(Bundle extras, NotificationCompat.Builder builder) {
String ledColor = extras.getString(LED_COLOR);
if (ledColor == null) {
return;
}
// Converts parse Int Array from ledColor
String[] items = ledColor.replaceAll("\\[", "").replaceAll("\\]", "").split(",");
int[] results = new int[items.length];
for (int i = 0; i < items.length; i++) {
try {
results[i] = Integer.parseInt(items[i].trim());
} catch (NumberFormatException nfe) {
}
}
if (results.length == 4) {
builder.setLights(Color.argb(results[0], results[1], results[2], results[3]), 500, 500);
} else {
Log.e(LOG_TAG, "ledColor parameter must be an array of length == 4 (ARGB)");
}
}
@RequiresApi(api = 26)
private void setNotificationLedColor(Bundle extras, NotificationChannel channel) {
String ledColor = extras.getString(LED_COLOR);
if (ledColor == null) {
return;
}
// Converts parse Int Array from ledColor
String[] items = ledColor.replaceAll("\\[", "").replaceAll("\\]", "").split(",");
int[] results = new int[items.length];
for (int i = 0; i < items.length; i++) {
try {
results[i] = Integer.parseInt(items[i].trim());
} catch (NumberFormatException nfe) {
}
}
if (results.length == 4) {
channel.setLightColor(Color.argb(results[0], results[1], results[2], results[3]));
} else {
Log.e(LOG_TAG, "ledColor parameter must be an array of length == 4 (ARGB)");
}
}
private void setNotificationPriority(Bundle extras, NotificationCompat.Builder builder) {
String priorityStr = extras.getString(PRIORITY);
if (priorityStr == null) {
return;
}
try {
Integer priority = Integer.parseInt(priorityStr);
if (priority >= NotificationCompat.PRIORITY_MIN
&& priority <= NotificationCompat.PRIORITY_MAX) {
builder.setPriority(priority);
} else {
Log.e(LOG_TAG, "Priority parameter must be between -2 and 2");
}
} catch (NumberFormatException e) {
e.printStackTrace();
}
}
@RequiresApi(api = 26)
private void setNotificationImportance(Bundle extras, NotificationChannel channel) {
String priorityStr = extras.getString(PRIORITY);
if (priorityStr == null) {
return;
}
try {
Integer priority = Integer.parseInt(priorityStr);
if (priority >= NotificationCompat.PRIORITY_MIN
&& priority <= NotificationCompat.PRIORITY_MAX) {
channel.setImportance(priority);
} else {
Log.e(LOG_TAG, "Priority parameter must be between -2 and 2");
}
} catch (NumberFormatException e) {
e.printStackTrace();
}
}
private void setNotificationLargeIcon(Context context, Bundle extras, String packageName,
Resources resources, NotificationCompat.Builder builder) {
String hostname = getHostname(extras);
String username = getSenderUsername(extras);
String gcmLargeIcon;
if (username != null && !username.isEmpty()) {
gcmLargeIcon = "https://" + hostname + "/avatar/" + username;
} else {
gcmLargeIcon = extras.getString(IMAGE); // from gcm
}
if (gcmLargeIcon == null || "".equals(gcmLargeIcon)) {
return;
}
if (gcmLargeIcon.startsWith("http://") || gcmLargeIcon.startsWith("https://")) {
builder.setLargeIcon(getBitmapFromURL(gcmLargeIcon, username, context));
Log.d(LOG_TAG, "using remote large-icon from gcm");
} else {
AssetManager assetManager = context.getAssets();
InputStream istr;
try {
istr = assetManager.open(gcmLargeIcon);
Bitmap bitmap = BitmapFactory.decodeStream(istr);
builder.setLargeIcon(bitmap);
Log.d(LOG_TAG, "using assets large-icon from gcm");
} catch (IOException e) {
int largeIconId = resources.getIdentifier(gcmLargeIcon, DRAWABLE, packageName);
if (largeIconId != 0) {
Bitmap largeIconBitmap = BitmapFactory.decodeResource(resources, largeIconId);
builder.setLargeIcon(largeIconBitmap);
Log.d(LOG_TAG, "using resources large-icon from gcm");
} else {
Log.d(LOG_TAG, "Not setting large icon");
}
}
}
}
private void setNotificationLargeIcon(Context context, Bundle extras, String packageName,
Resources resources, Notification.Builder builder) {
String hostname = getHostname(extras);
String username = getSenderUsername(extras);
String gcmLargeIcon;
if (username != null && !username.isEmpty()) {
gcmLargeIcon = "https://" + hostname + "/avatar/" + username;
} else {
gcmLargeIcon = extras.getString(IMAGE); // from gcm
}
if (gcmLargeIcon == null || "".equals(gcmLargeIcon)) {
return;
}
if (gcmLargeIcon.startsWith("http://") || gcmLargeIcon.startsWith("https://")) {
builder.setLargeIcon(getBitmapFromURL(gcmLargeIcon, username, context));
Log.d(LOG_TAG, "using remote large-icon from gcm");
} else {
AssetManager assetManager = context.getAssets();
InputStream istr;
try {
istr = assetManager.open(gcmLargeIcon);
Bitmap bitmap = BitmapFactory.decodeStream(istr);
builder.setLargeIcon(bitmap);
Log.d(LOG_TAG, "using assets large-icon from gcm");
} catch (IOException e) {
int largeIconId = resources.getIdentifier(gcmLargeIcon, DRAWABLE, packageName);
if (largeIconId != 0) {
Bitmap largeIconBitmap = BitmapFactory.decodeResource(resources, largeIconId);
builder.setLargeIcon(largeIconBitmap);
Log.d(LOG_TAG, "using resources large-icon from gcm");
} else {
Log.d(LOG_TAG, "Not setting large icon");
}
}
}
}
private void setNotificationSmallIcon(Bundle extras, String packageName,
Resources resources, NotificationCompat.Builder builder,
String localIcon) {
int iconId = 0;
String icon = extras.getString(ICON);
if (icon != null && !"".equals(icon)) {
iconId = resources.getIdentifier(icon, DRAWABLE, packageName);
Log.d(LOG_TAG, "using icon from plugin options");
} else if (localIcon != null && !"".equals(localIcon)) {
iconId = resources.getIdentifier(localIcon, DRAWABLE, packageName);
Log.d(LOG_TAG, "using icon from plugin options");
}
if (iconId == 0) {
Log.d(LOG_TAG, "no icon resource found - using default icon");
iconId = resources.getIdentifier("rocket_chat_notification", DRAWABLE, packageName);
}
builder.setSmallIcon(iconId);
}
private void setNotificationSmallIcon(Bundle extras, String packageName,
Resources resources, Notification.Builder builder,
String localIcon) {
int iconId = 0;
String icon = extras.getString(ICON);
if (icon != null && !"".equals(icon)) {
iconId = resources.getIdentifier(icon, DRAWABLE, packageName);
Log.d(LOG_TAG, "using icon from plugin options");
} else if (localIcon != null && !"".equals(localIcon)) {
iconId = resources.getIdentifier(localIcon, DRAWABLE, packageName);
Log.d(LOG_TAG, "using icon from plugin options");
}
if (iconId == 0) {
Log.d(LOG_TAG, "no icon resource found - using default icon");
iconId = resources.getIdentifier("rocket_chat_notification", DRAWABLE, packageName);
}
builder.setSmallIcon(iconId);
}
private void setNotificationIconColor(String color, NotificationCompat.Builder builder,
String localIconColor) {
int iconColor = 0;
if (color != null && !"".equals(color)) {
try {
iconColor = Color.parseColor(color);
} catch (IllegalArgumentException e) {
Log.e(LOG_TAG, "couldn't parse color from android options");
}
} else if (localIconColor != null && !"".equals(localIconColor)) {
try {
iconColor = Color.parseColor(localIconColor);
} catch (IllegalArgumentException e) {
Log.e(LOG_TAG, "couldn't parse color from android options");
}
}
if (iconColor != 0) {
builder.setColor(iconColor);
}
}
private void updateIntent(Intent intent, String callback, Bundle extras, boolean foreground,
int notId) {
intent.putExtra(CALLBACK, callback);
intent.putExtra(PUSH_BUNDLE, extras);
intent.putExtra(FOREGROUND, foreground);
intent.putExtra(NOT_ID, notId);
}
public Bitmap getBitmapFromURL(String strURL, String username, Context context) {
try {
URL url = new URL(strURL);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoInput(true);
connection.connect();
InputStream input = connection.getInputStream();
Bitmap bitmap = BitmapFactory.decodeStream(input);
if (bitmap == null && username != null && context != null) {
return AvatarHelper.INSTANCE.getTextBitmap(username, context);
}
return bitmap;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
public static String getAppName(Context context) {
CharSequence appName = context.getPackageManager()
.getApplicationLabel(context.getApplicationInfo());
return (String) appName;
}
private int parseInt(String value, Bundle extras) {
int retval = 0;
try {
retval = Integer.parseInt(extras.getString(value));
} catch (NumberFormatException e) {
Log.e(LOG_TAG, "Number format exception - Error parsing " + value + ": " + e.getMessage());
} catch (Exception e) {
Log.e(LOG_TAG, "Number format exception - Error parsing " + value + ": " + e.getMessage());
}
return retval;
}
private Spanned fromHtml(String source) {
if (source != null) {
return Html.fromHtml(source);
} else {
return null;
}
}
private int extractBadgeCount(Bundle extras) {
int count = -1;
String msgcnt = extras.getString(COUNT);
try {
if (msgcnt != null) {
count = Integer.parseInt(msgcnt);
}
} catch (NumberFormatException e) {
Log.e(LOG_TAG, e.getLocalizedMessage(), e);
}
return count;
}
private String getHostname(Bundle extras) {
try {
JSONObject jsonObject = new JSONObject(extras.getString("ejson", "[]"));
if (!jsonObject.has("host")) {
return null;
}
return ServerPolicyHelper.enforceHostname(jsonObject.getString("host"));
} catch (Exception e) {
return null;
}
}
private String getRoomId(Bundle extras) {
try {
JSONObject jsonObject = new JSONObject(extras.getString("ejson", "[]"));
if (!jsonObject.has("rid")) {
return null;
}
return jsonObject.getString("rid");
} catch (Exception e) {
return null;
}
}
private String getSenderUsername(Bundle extras) {
try {
JSONObject jsonObject = new JSONObject(extras.getString("ejson", "[]"));
return jsonObject.getJSONObject("sender").optString("username");
} catch (JSONException e) {
return null;
}
}
private boolean isValidHostname(Context context, String hostname) {
final List<ServerInfo> serverInfoList =
ConnectivityManager.getInstance(context.getApplicationContext()).getServerList();
for (ServerInfo serverInfo : serverInfoList) {
if (serverInfo.getHostname().equals(hostname)) {
return true;
}
}
return false;
}
}
package chat.rocket.android.push.gcm;
import com.google.android.gms.gcm.GcmListenerService;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import android.os.Bundle;
import android.util.Log;
import com.google.android.gms.gcm.GcmListenerService;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Iterator;
import chat.rocket.android.push.PushConstants;
import chat.rocket.android.push.PushNotificationHandler;
import chat.rocket.android.push.PushManager;
@SuppressLint("NewApi")
public class GCMIntentService extends GcmListenerService implements PushConstants {
......@@ -33,9 +35,7 @@ public class GCMIntentService extends GcmListenerService implements PushConstant
extras = normalizeExtras(applicationContext, extras);
PushNotificationHandler pushNotificationHandler = new PushNotificationHandler();
pushNotificationHandler.showNotificationIfPossible(applicationContext, extras);
PushManager.INSTANCE.handle(applicationContext, extras);
}
/*
......
......@@ -3,13 +3,14 @@ package chat.rocket.android.push.gcm;
import com.google.android.gms.iid.InstanceIDListenerService;
import java.util.List;
import chat.rocket.android.helper.GcmPushSettingHelper;
import chat.rocket.persistence.realm.models.ddp.RealmPublicSetting;
import chat.rocket.persistence.realm.models.internal.GcmPushRegistration;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.persistence.realm.RealmStore;
import chat.rocket.android.service.ConnectivityManager;
import chat.rocket.core.models.ServerInfo;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.persistence.realm.RealmStore;
import chat.rocket.persistence.realm.models.ddp.RealmPublicSetting;
import chat.rocket.persistence.realm.models.internal.GcmPushRegistration;
public class GcmInstanceIDListenerService extends InstanceIDListenerService {
......
package chat.rocket.android.service;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.util.List;
import chat.rocket.core.models.ServerInfo;
import rx.Observable;
import rx.Single;
import io.reactivex.Flowable;
import io.reactivex.Single;
/**
* interfaces used for Activity/Fragment and other UI-related logic.
*/
public interface ConnectivityManagerApi {
void keepAliveServer();
void keepAliveServer();
void addOrUpdateServer(String hostname, @Nullable String name, boolean insecure);
void removeServer(String hostname);
void addOrUpdateServer(String hostname, @Nullable String name, boolean insecure);
Single<Boolean> connect(String hostname);
void removeServer(String hostname);
List<ServerInfo> getServerList();
Single<Boolean> connect(String hostname);
Flowable<ServerConnectivity> getServerConnectivityAsObservable();
List<ServerInfo> getServerList();
int getConnectivityState(@NonNull String hostname);
Observable<ServerConnectivity> getServerConnectivityAsObservable();
void resetConnectivityStateList();
}
package chat.rocket.android.service;
import java.util.List;
import chat.rocket.core.models.ServerInfo;
/**
* interfaces used for RocketChatService and RocketChatwebSocketThread.
*/
/*package*/ interface ConnectivityManagerInternal {
int REASON_CLOSED_BY_USER = 101;
int REASON_NETWORK_ERROR = 102;
int REASON_SERVER_ERROR = 103;
int REASON_UNKNOWN = 104;
void resetConnectivityStateList();
......
package chat.rocket.android.service;
import rx.Single;
import io.reactivex.Single;
public interface ConnectivityServiceInterface {
Single<Boolean> ensureConnectionToServer(String hostname);
......
package chat.rocket.android.service;
import chat.rocket.android.api.DDPClientWrapper;
/**
* reference to get fresh DDPClient instance.
*/
public interface DDPClientRef {
DDPClientWrapper get();
}
package chat.rocket.android.service;
import android.annotation.SuppressLint;
import android.content.ComponentName;
import android.content.Context;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.util.ArrayList;
......@@ -12,250 +14,282 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import chat.rocket.android.RocketChatCache;
import chat.rocket.android.helper.RxHelper;
import chat.rocket.android.log.RCLog;
import chat.rocket.android_ddp.DDPClient;
import chat.rocket.core.models.ServerInfo;
import chat.rocket.persistence.realm.models.RealmBasedServerInfo;
import hugo.weaving.DebugLog;
import rx.Observable;
import rx.Single;
import rx.subjects.PublishSubject;
import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable;
import io.reactivex.Single;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.subjects.BehaviorSubject;
/**
* Connectivity management implementation.
*/
/*package*/ class RealmBasedConnectivityManager
implements ConnectivityManagerApi, ConnectivityManagerInternal {
private final ConcurrentHashMap<String, Integer> serverConnectivityList = new ConcurrentHashMap<>();
private final PublishSubject<ServerConnectivity> connectivitySubject = PublishSubject.create();
private Context appContext;
private final ServiceConnection serviceConnection = new ServiceConnection() {
implements ConnectivityManagerApi, ConnectivityManagerInternal {
private volatile ConcurrentHashMap<String, Integer> serverConnectivityList = new ConcurrentHashMap<>();
private volatile BehaviorSubject<ServerConnectivity> connectivitySubject = BehaviorSubject.createDefault(ServerConnectivity.CONNECTED);
private Context appContext;
private final ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder binder) {
serviceInterface = ((RocketChatService.LocalBinder) binder).getServiceInterface();
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
serviceInterface = null;
}
};
private ConnectivityServiceInterface serviceInterface;
/*package*/ RealmBasedConnectivityManager setContext(Context appContext) {
this.appContext = appContext.getApplicationContext();
return this;
}
@Override
public void resetConnectivityStateList() {
serverConnectivityList.clear();
for (ServerInfo serverInfo : RealmBasedServerInfo.getServerInfoList()) {
serverConnectivityList.put(serverInfo.getHostname(), ServerConnectivity.STATE_DISCONNECTED);
}
}
@Override
public void onServiceConnected(ComponentName componentName, IBinder binder) {
serviceInterface = ((RocketChatService.LocalBinder) binder).getServiceInterface();
public void keepAliveServer() {
RocketChatService.keepAlive(appContext);
if (serviceInterface == null) {
RocketChatService.bind(appContext, serviceConnection);
}
}
@SuppressLint("RxLeakedSubscription")
@DebugLog
@Override
public void onServiceDisconnected(ComponentName componentName) {
serviceInterface = null;
public void ensureConnections() {
String hostname = new RocketChatCache(appContext).getSelectedServerHostname();
if (hostname == null) {
return;
}
connectToServerIfNeeded(hostname, true/* force connect */)
.subscribeOn(Schedulers.io())
.subscribe(connected -> {
if (!connected) {
notifyConnectionLost(hostname, DDPClient.REASON_NETWORK_ERROR);
}
}, error -> {
RCLog.e(error);
notifyConnectionLost(hostname, DDPClient.REASON_NETWORK_ERROR);
});
}
};
private ConnectivityServiceInterface serviceInterface;
@SuppressLint("RxLeakedSubscription")
@Override
public void addOrUpdateServer(String hostname, @Nullable String name, boolean insecure) {
RealmBasedServerInfo.addOrUpdate(hostname, name, insecure);
if (!serverConnectivityList.containsKey(hostname)) {
serverConnectivityList.put(hostname, ServerConnectivity.STATE_DISCONNECTED);
}
connectToServerIfNeeded(hostname, false)
.subscribe(connected -> {
}, RCLog::e);
}
/*package*/ RealmBasedConnectivityManager setContext(Context appContext) {
this.appContext = appContext.getApplicationContext();
return this;
}
@SuppressLint("RxLeakedSubscription")
@Override
public void removeServer(String hostname) {
RealmBasedServerInfo.remove(hostname);
if (serverConnectivityList.containsKey(hostname)) {
disconnectFromServerIfNeeded(hostname)
.subscribe(_val -> {
}, RCLog::e);
}
}
@Override
public void resetConnectivityStateList() {
serverConnectivityList.clear();
for (ServerInfo serverInfo : RealmBasedServerInfo.getServerInfoList()) {
serverConnectivityList.put(serverInfo.getHostname(), ServerConnectivity.STATE_DISCONNECTED);
@Override
public Single<Boolean> connect(String hostname) {
return connectToServerIfNeeded(hostname, false);
}
}
@Override
public void keepAliveServer() {
RocketChatService.keepAlive(appContext);
if (serviceInterface == null) {
RocketChatService.bind(appContext, serviceConnection);
@Override
public List<ServerInfo> getServerList() {
return RealmBasedServerInfo.getServerInfoList();
}
}
@DebugLog
@Override
public void ensureConnections() {
for (String hostname : serverConnectivityList.keySet()) {
connectToServerIfNeeded(hostname, true/* force connect */)
.subscribe(_val -> {
}, RCLog::e);
@Override
public ServerInfo getServerInfoForHost(String hostname) {
return RealmBasedServerInfo.getServerInfoForHost(hostname);
}
}
@Override
public void addOrUpdateServer(String hostname, @Nullable String name, boolean insecure) {
RealmBasedServerInfo.addOrUpdate(hostname, name, insecure);
if (!serverConnectivityList.containsKey(hostname)) {
serverConnectivityList.put(hostname, ServerConnectivity.STATE_DISCONNECTED);
private List<ServerConnectivity> getCurrentConnectivityList() {
ArrayList<ServerConnectivity> list = new ArrayList<>();
for (Map.Entry<String, Integer> entry : serverConnectivityList.entrySet()) {
list.add(new ServerConnectivity(entry.getKey(), entry.getValue()));
}
return list;
}
connectToServerIfNeeded(hostname, false)
.subscribe(_val -> {
}, RCLog::e);
}
@Override
public void removeServer(String hostname) {
RealmBasedServerInfo.remove(hostname);
if (serverConnectivityList.containsKey(hostname)) {
disconnectFromServerIfNeeded(hostname)
.subscribe(_val -> {
}, RCLog::e);
@DebugLog
@Override
public void notifyConnectionEstablished(String hostname, String session) {
if (session != null) {
RealmBasedServerInfo.updateSession(hostname, session);
}
serverConnectivityList.put(hostname, ServerConnectivity.STATE_CONNECTED);
connectivitySubject.onNext(
new ServerConnectivity(hostname, ServerConnectivity.STATE_CONNECTED));
}
}
@Override
public Single<Boolean> connect(String hostname) {
return connectToServerIfNeeded(hostname, false);
}
@Override
public List<ServerInfo> getServerList() {
return RealmBasedServerInfo.getServerInfoList();
}
@Override
public ServerInfo getServerInfoForHost(String hostname) {
return RealmBasedServerInfo.getServerInfoForHost(hostname);
}
private List<ServerConnectivity> getCurrentConnectivityList() {
ArrayList<ServerConnectivity> list = new ArrayList<>();
for (Map.Entry<String, Integer> entry : serverConnectivityList.entrySet()) {
list.add(new ServerConnectivity(entry.getKey(), entry.getValue()));
@DebugLog
@Override
public void notifyConnectionLost(String hostname, int code) {
serverConnectivityList.put(hostname, ServerConnectivity.STATE_DISCONNECTED);
connectivitySubject.onNext(
new ServerConnectivity(hostname, ServerConnectivity.STATE_DISCONNECTED, code));
}
return list;
}
@DebugLog
@Override
public void notifyConnectionEstablished(String hostname, String session) {
RealmBasedServerInfo.updateSession(hostname, session);
serverConnectivityList.put(hostname, ServerConnectivity.STATE_CONNECTED);
connectivitySubject.onNext(
new ServerConnectivity(hostname, ServerConnectivity.STATE_CONNECTED));
}
@DebugLog
@Override
public void notifyConnectionLost(String hostname, int reason) {
serverConnectivityList.put(hostname, ServerConnectivity.STATE_DISCONNECTED);
connectivitySubject.onNext(
new ServerConnectivity(hostname, ServerConnectivity.STATE_DISCONNECTED));
}
@DebugLog
@Override
public void notifyConnecting(String hostname) {
serverConnectivityList.put(hostname, ServerConnectivity.STATE_CONNECTING);
connectivitySubject.onNext(
new ServerConnectivity(hostname, ServerConnectivity.STATE_CONNECTING));
}
@Override
public Observable<ServerConnectivity> getServerConnectivityAsObservable() {
return Observable.concat(Observable.from(getCurrentConnectivityList()), connectivitySubject);
}
@DebugLog
private Single<Boolean> connectToServerIfNeeded(String hostname, boolean forceConnect) {
return Single.defer(() -> {
final int connectivity = serverConnectivityList.get(hostname);
if (!forceConnect && connectivity == ServerConnectivity.STATE_CONNECTED) {
return Single.just(true);
}
if (connectivity == ServerConnectivity.STATE_DISCONNECTING) {
return waitForDisconnected(hostname)
.flatMap(_val -> connectToServerIfNeeded(hostname, forceConnect));
}
if (connectivity == ServerConnectivity.STATE_CONNECTING) {
return waitForConnected(hostname);
}
return connectToServer(hostname)
.doOnError(RCLog::e)
.retryWhen(RxHelper.exponentialBackoff(Integer.MAX_VALUE, 500, TimeUnit.MILLISECONDS));
});
}
private Single<Boolean> disconnectFromServerIfNeeded(String hostname) {
return Single.defer(() -> {
final int connectivity = serverConnectivityList.get(hostname);
if (connectivity == ServerConnectivity.STATE_DISCONNECTED) {
return Single.just(true);
}
if (connectivity == ServerConnectivity.STATE_CONNECTING) {
return waitForConnected(hostname)
.onErrorReturn(err -> true)
.flatMap(_val -> disconnectFromServerIfNeeded(hostname));
}
if (connectivity == ServerConnectivity.STATE_DISCONNECTING) {
return waitForDisconnected(hostname);
}
return disconnectFromServer(hostname)
//.doOnError(RCLog::e)
.retryWhen(RxHelper.exponentialBackoff(3, 500, TimeUnit.MILLISECONDS));
});
}
@DebugLog
private Single<Boolean> waitForConnected(String hostname) {
return connectivitySubject
.filter(serverConnectivity -> hostname.equals(serverConnectivity.hostname))
.map(serverConnectivity -> serverConnectivity.state)
.filter(state ->
state == ServerConnectivity.STATE_CONNECTED
|| state == ServerConnectivity.STATE_DISCONNECTED)
.first()
.toSingle()
.flatMap(state ->
state == ServerConnectivity.STATE_CONNECTED
? Single.just(true)
: Single.error(new ServerConnectivity.DisconnectedException()));
}
@DebugLog
private Single<Boolean> waitForDisconnected(String hostname) {
return connectivitySubject
.filter(serverConnectivity -> hostname.equals(serverConnectivity.hostname))
.map(serverConnectivity -> serverConnectivity.state)
.filter(state -> state == ServerConnectivity.STATE_DISCONNECTED)
.first()
.toSingle()
.map(state -> true);
}
@DebugLog
private Single<Boolean> connectToServer(String hostname) {
return Single.defer(() -> {
if (!serverConnectivityList.containsKey(hostname)) {
return Single.error(new IllegalArgumentException("hostname not found"));
}
if (serverConnectivityList.get(hostname) != ServerConnectivity.STATE_CONNECTED) {
// Mark as CONNECTING except for the case [forceConnect && connected] because
// ensureConnectionToServer doesn't notify ConnectionEstablished/Lost is already connected.
@DebugLog
@Override
public void notifyConnecting(String hostname) {
serverConnectivityList.put(hostname, ServerConnectivity.STATE_CONNECTING);
}
if (serviceInterface != null) {
return serviceInterface.ensureConnectionToServer(hostname);
} else {
return Single.error(new IllegalStateException("not prepared"));
}
});
}
private Single<Boolean> disconnectFromServer(String hostname) {
return Single.defer(() -> {
if (!serverConnectivityList.containsKey(hostname)) {
return Single.error(new IllegalArgumentException("hostname not found"));
}
serverConnectivityList.put(hostname, ServerConnectivity.STATE_DISCONNECTING);
if (serviceInterface != null) {
return serviceInterface.disconnectFromServer(hostname)
// //after disconnection from server, remove HOSTNAME key from HashMap
.doAfterTerminate(() -> serverConnectivityList.remove(hostname));
} else {
return Single.error(new IllegalStateException("not prepared"));
}
});
}
connectivitySubject.onNext(
new ServerConnectivity(hostname, ServerConnectivity.STATE_CONNECTING));
}
@Override
public Flowable<ServerConnectivity> getServerConnectivityAsObservable() {
return connectivitySubject.toFlowable(BackpressureStrategy.LATEST);
}
@Override
public int getConnectivityState(@NonNull String hostname) {
return serverConnectivityList.get(hostname);
}
@DebugLog
private Single<Boolean> connectToServerIfNeeded(String hostname, boolean forceConnect) {
return Single.defer(() -> {
Integer state = serverConnectivityList.get(hostname);
if (state == null) {
state = ServerConnectivity.STATE_DISCONNECTED;
}
final int connectivity = state;
if (!forceConnect && connectivity == ServerConnectivity.STATE_CONNECTED) {
return Single.just(true);
}
if (connectivity == ServerConnectivity.STATE_DISCONNECTING) {
return waitForDisconnected(hostname)
.flatMap(_val -> connectToServerIfNeeded(hostname, forceConnect));
}
if (connectivity == ServerConnectivity.STATE_DISCONNECTED) {
// notifyConnecting(hostname);
}
return connectToServer(hostname)
.retry(exception -> exception instanceof ThreadLooperNotPreparedException)
.onErrorResumeNext(Single.just(false));
});
}
private Single<Boolean> disconnectFromServerIfNeeded(String hostname) {
return Single.defer(() -> {
final int connectivity = serverConnectivityList.get(hostname);
if (connectivity == ServerConnectivity.STATE_DISCONNECTED) {
return Single.just(true);
}
if (connectivity == ServerConnectivity.STATE_CONNECTING) {
return waitForConnected(hostname)
.doOnError(err -> notifyConnectionLost(hostname, DDPClient.REASON_NETWORK_ERROR))
.flatMap(_val -> disconnectFromServerIfNeeded(hostname));
}
if (connectivity == ServerConnectivity.STATE_DISCONNECTING) {
return waitForDisconnected(hostname);
}
return disconnectFromServer(hostname)
.retryWhen(RxHelper.exponentialBackoff(1, 500, TimeUnit.MILLISECONDS));
});
}
@DebugLog
private Single<Boolean> waitForConnected(String hostname) {
return connectivitySubject
.filter(serverConnectivity -> hostname.equals(serverConnectivity.hostname))
.map(serverConnectivity -> serverConnectivity.state)
.filter(state ->
state == ServerConnectivity.STATE_CONNECTED
|| state == ServerConnectivity.STATE_DISCONNECTED)
.firstElement()
.toSingle()
.flatMap(state ->
state == ServerConnectivity.STATE_CONNECTED
? Single.just(true)
: Single.error(new ServerConnectivity.DisconnectedException()));
}
@DebugLog
private Single<Boolean> waitForDisconnected(String hostname) {
return connectivitySubject
.filter(serverConnectivity -> hostname.equals(serverConnectivity.hostname))
.map(serverConnectivity -> serverConnectivity.state)
.filter(state -> state == ServerConnectivity.STATE_DISCONNECTED)
.firstElement()
.toSingle()
.map(state -> true);
}
@DebugLog
private Single<Boolean> connectToServer(String hostname) {
return Single.defer(() -> {
if (!serverConnectivityList.containsKey(hostname)) {
return Single.error(new IllegalArgumentException("hostname not found"));
}
if (serverConnectivityList.get(hostname) != ServerConnectivity.STATE_CONNECTED) {
// Mark as CONNECTING except for the case [forceConnect && connected] because
// ensureConnectionToServer doesn't notify ConnectionEstablished/Lost is already connected.
// serverConnectivityList.put(hostname, ServerConnectivity.STATE_CONNECTING);
}
if (serviceInterface != null) {
return serviceInterface.ensureConnectionToServer(hostname);
} else {
return Single.error(new ThreadLooperNotPreparedException("not prepared"));
}
});
}
private Single<Boolean> disconnectFromServer(String hostname) {
return Single.defer(() -> {
if (!serverConnectivityList.containsKey(hostname)) {
return Single.error(new IllegalArgumentException("hostname not found"));
}
serverConnectivityList.put(hostname, ServerConnectivity.STATE_DISCONNECTING);
if (serviceInterface != null) {
return serviceInterface.disconnectFromServer(hostname)
// //after disconnection from server, remove HOSTNAME key from HashMap
.doAfterTerminate(() -> serverConnectivityList.remove(hostname));
} else {
return Single.error(new IllegalStateException("not prepared"));
}
});
}
private static class ThreadLooperNotPreparedException extends IllegalStateException {
ThreadLooperNotPreparedException(String message) {
super(message);
}
}
}
\ No newline at end of file
......@@ -8,123 +8,145 @@ import android.os.Binder;
import android.os.IBinder;
import android.support.annotation.Nullable;
import java.util.HashMap;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import chat.rocket.android.helper.Logger;
import chat.rocket.android.log.RCLog;
import chat.rocket.persistence.realm.RealmStore;
import hugo.weaving.DebugLog;
import rx.Observable;
import rx.Single;
import io.reactivex.Observable;
import io.reactivex.Single;
/**
* Background service for Rocket.Chat.Application class.
*/
public class RocketChatService extends Service implements ConnectivityServiceInterface {
private ConnectivityManagerInternal connectivityManager;
private HashMap<String, RocketChatWebSocketThread> webSocketThreads;
private Semaphore webSocketThreadLock = new Semaphore(1);
private ConnectivityManagerInternal connectivityManager;
private static volatile Semaphore webSocketThreadLock = new Semaphore(1);
private static volatile RocketChatWebSocketThread currentWebSocketThread;
public class LocalBinder extends Binder {
ConnectivityServiceInterface getServiceInterface() {
return RocketChatService.this;
public class LocalBinder extends Binder {
ConnectivityServiceInterface getServiceInterface() {
return RocketChatService.this;
}
}
private final LocalBinder localBinder = new LocalBinder();
/**
* ensure RocketChatService alive.
*/
/*package*/static void keepAlive(Context context) {
context.startService(new Intent(context, RocketChatService.class));
}
public static void bind(Context context, ServiceConnection serviceConnection) {
context.bindService(
new Intent(context, RocketChatService.class), serviceConnection, Context.BIND_AUTO_CREATE);
}
public static void unbind(Context context, ServiceConnection serviceConnection) {
context.unbindService(serviceConnection);
}
@DebugLog
@Override
public void onCreate() {
super.onCreate();
connectivityManager = ConnectivityManager.getInstanceForInternal(getApplicationContext());
connectivityManager.resetConnectivityStateList();
}
@DebugLog
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
connectivityManager.ensureConnections();
return START_NOT_STICKY;
}
@Override
public Single<Boolean> ensureConnectionToServer(String hostname) { //called via binder.
return getOrCreateWebSocketThread(hostname)
.flatMap(RocketChatWebSocketThread::keepAlive);
}
@Override
public Single<Boolean> disconnectFromServer(String hostname) { //called via binder.
return Single.defer(() -> {
if (!existsThreadForHostname(hostname)) {
return Single.just(true);
}
if (currentWebSocketThread != null) {
return currentWebSocketThread.terminate()
// after disconnection from server
.doAfterTerminate(() -> {
currentWebSocketThread = null;
// remove RealmConfiguration key from HashMap
RealmStore.sStore.remove(hostname);
});
} else {
return Observable.timer(1, TimeUnit.SECONDS).singleOrError()
.flatMap(_val -> disconnectFromServer(hostname));
}
});
}
@DebugLog
private Single<RocketChatWebSocketThread> getOrCreateWebSocketThread(String hostname) {
return Single.defer(() -> {
webSocketThreadLock.acquire();
int connectivityState = ConnectivityManager.getInstance(getApplicationContext()).getConnectivityState(hostname);
boolean isDisconnected = connectivityState != ServerConnectivity.STATE_CONNECTED;
if (currentWebSocketThread != null && existsThreadForHostname(hostname) && !isDisconnected) {
webSocketThreadLock.release();
return Single.just(currentWebSocketThread);
}
if (currentWebSocketThread != null) {
return currentWebSocketThread.terminate()
.doAfterTerminate(() -> currentWebSocketThread = null)
.flatMap(terminated ->
RocketChatWebSocketThread.getStarted(getApplicationContext(), hostname)
.doOnSuccess(thread -> {
currentWebSocketThread = thread;
webSocketThreadLock.release();
})
.doOnError(throwable -> {
currentWebSocketThread = null;
RCLog.e(throwable);
Logger.report(throwable);
webSocketThreadLock.release();
})
);
}
return RocketChatWebSocketThread.getStarted(getApplicationContext(), hostname)
.doOnSuccess(thread -> {
currentWebSocketThread = thread;
webSocketThreadLock.release();
})
.doOnError(throwable -> {
currentWebSocketThread = null;
RCLog.e(throwable);
Logger.report(throwable);
webSocketThreadLock.release();
});
});
}
private boolean existsThreadForHostname(String hostname) {
if (hostname == null || currentWebSocketThread == null) {
return false;
}
return currentWebSocketThread.getName().equals("RC_thread_" + hostname);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return localBinder;
}
}
private final LocalBinder localBinder = new LocalBinder();
/**
* ensure RocketChatService alive.
*/
/*package*/ static void keepAlive(Context context) {
context.startService(new Intent(context, RocketChatService.class));
}
public static void bind(Context context, ServiceConnection serviceConnection) {
context.bindService(
new Intent(context, RocketChatService.class), serviceConnection, Context.BIND_AUTO_CREATE);
}
public static void unbind(Context context, ServiceConnection serviceConnection) {
context.unbindService(serviceConnection);
}
@DebugLog
@Override
public void onCreate() {
super.onCreate();
connectivityManager = ConnectivityManager.getInstanceForInternal(getApplicationContext());
connectivityManager.resetConnectivityStateList();
webSocketThreads = new HashMap<>();
}
@DebugLog
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
connectivityManager.ensureConnections();
return START_NOT_STICKY;
}
@Override
public Single<Boolean> ensureConnectionToServer(String hostname) { //called via binder.
return getOrCreateWebSocketThread(hostname)
.doOnError(err -> {
webSocketThreads.remove(hostname);
connectivityManager.notifyConnectionLost(hostname, ConnectivityManagerInternal.REASON_NETWORK_ERROR);
})
.flatMap(webSocketThreads -> webSocketThreads.keepAlive());
}
@Override
public Single<Boolean> disconnectFromServer(String hostname) { //called via binder.
return Single.defer(() -> {
if (!webSocketThreads.containsKey(hostname)) {
return Single.just(true);
}
RocketChatWebSocketThread thread = webSocketThreads.get(hostname);
if (thread != null) {
return thread.terminate()
// after disconnection from server
.doAfterTerminate(() -> {
// remove RCWebSocket key from HashMap
webSocketThreads.remove(hostname);
// remove RealmConfiguration key from HashMap
RealmStore.sStore.remove(hostname);
});
} else {
return Observable.timer(1, TimeUnit.SECONDS).toSingle()
.flatMap(_val -> disconnectFromServer(hostname));
}
});
}
@DebugLog
private Single<RocketChatWebSocketThread> getOrCreateWebSocketThread(String hostname) {
return Single.defer(() -> {
webSocketThreadLock.acquire();
if (webSocketThreads.containsKey(hostname)) {
RocketChatWebSocketThread thread = webSocketThreads.get(hostname);
webSocketThreadLock.release();
return Single.just(thread);
}
return RocketChatWebSocketThread.getStarted(getApplicationContext(), hostname)
.doOnSuccess(thread -> {
webSocketThreads.put(hostname, thread);
webSocketThreadLock.release();
})
.doOnError(throwable -> {
Logger.report(throwable);
webSocketThreadLock.release();
});
});
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return localBinder;
}
}
......@@ -11,9 +11,9 @@ import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import bolts.Task;
import chat.rocket.android.api.DDPClientWrapper;
import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.android.helper.LogIfError;
import chat.rocket.android.helper.RxHelper;
......@@ -23,6 +23,7 @@ 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.DeletedMessageObserver;
import chat.rocket.android.service.observer.FileUploadingToUrlObserver;
import chat.rocket.android.service.observer.FileUploadingWithUfsObserver;
import chat.rocket.android.service.observer.GcmPushRegistrationObserver;
......@@ -32,433 +33,391 @@ import chat.rocket.android.service.observer.MethodCallObserver;
import chat.rocket.android.service.observer.NewMessageObserver;
import chat.rocket.android.service.observer.PushSettingsObserver;
import chat.rocket.android.service.observer.SessionObserver;
import chat.rocket.android_ddp.DDPClient;
import chat.rocket.android_ddp.DDPClientCallback;
import chat.rocket.android_ddp.rx.RxWebSocketCallback;
import chat.rocket.core.models.ServerInfo;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.persistence.realm.RealmStore;
import chat.rocket.persistence.realm.models.internal.RealmSession;
import hugo.weaving.DebugLog;
import io.reactivex.Completable;
import io.reactivex.Flowable;
import io.reactivex.Single;
import io.reactivex.disposables.CompositeDisposable;
import rx.Completable;
import rx.Single;
import rx.subscriptions.CompositeSubscription;
/**
* Thread for handling WebSocket connection.
*/
public class RocketChatWebSocketThread extends HandlerThread {
private static final Class[] REGISTERABLE_CLASSES = {
LoginServiceConfigurationSubscriber.class,
ActiveUsersSubscriber.class,
UserDataSubscriber.class,
MethodCallObserver.class,
SessionObserver.class,
LoadMessageProcedureObserver.class,
GetUsersOfRoomsProcedureObserver.class,
NewMessageObserver.class,
CurrentUserObserver.class,
FileUploadingToUrlObserver.class,
FileUploadingWithUfsObserver.class,
PushSettingsObserver.class,
GcmPushRegistrationObserver.class
};
private static final long HEARTBEAT_PERIOD_MS = 20000;
private final Context appContext;
private final String hostname;
private final RealmHelper realmHelper;
private final ConnectivityManagerInternal connectivityManager;
private final ArrayList<Registrable> listeners = new ArrayList<>();
private final CompositeDisposable hearbeatDisposable = new CompositeDisposable();
private final CompositeSubscription reconnectSubscription = new CompositeSubscription();
private DDPClientWrapper ddpClient;
private boolean listenersRegistered;
private final DDPClientRef ddpClientRef = new DDPClientRef() {
private static final Class[] REGISTERABLE_CLASSES = {
LoginServiceConfigurationSubscriber.class,
ActiveUsersSubscriber.class,
UserDataSubscriber.class,
MethodCallObserver.class,
SessionObserver.class,
LoadMessageProcedureObserver.class,
GetUsersOfRoomsProcedureObserver.class,
NewMessageObserver.class,
DeletedMessageObserver.class,
CurrentUserObserver.class,
FileUploadingToUrlObserver.class,
FileUploadingWithUfsObserver.class,
PushSettingsObserver.class,
GcmPushRegistrationObserver.class
};
private static final long HEARTBEAT_PERIOD_MS = 20000;
private final Context appContext;
private final String hostname;
private final RealmHelper realmHelper;
private final ConnectivityManagerInternal connectivityManager;
private final ArrayList<Registrable> listeners = new ArrayList<>();
private final CompositeDisposable heartbeatDisposable = new CompositeDisposable();
private final CompositeDisposable reconnectDisposable = new CompositeDisposable();
private boolean listenersRegistered;
private RocketChatWebSocketThread(Context appContext, String hostname) {
super("RC_thread_" + hostname);
this.appContext = appContext;
this.hostname = hostname;
this.realmHelper = RealmStore.getOrCreate(hostname);
this.connectivityManager = ConnectivityManager.getInstanceForInternal(appContext);
}
/**
* build new Thread.
*/
@DebugLog
/* package */ static Single<RocketChatWebSocketThread> getStarted(Context appContext, String hostname) {
return Single.<RocketChatWebSocketThread>create(objectSingleEmitter -> {
new RocketChatWebSocketThread(appContext, hostname) {
@Override
protected void onLooperPrepared() {
try {
super.onLooperPrepared();
objectSingleEmitter.onSuccess(this);
} catch (Exception exception) {
objectSingleEmitter.onError(exception);
}
}
}.start();
}).flatMap(webSocket ->
webSocket.connectWithExponentialBackoff().map(_val -> webSocket));
}
@Override
public DDPClientWrapper get() {
return ddpClient;
protected void onLooperPrepared() {
super.onLooperPrepared();
forceInvalidateTokens();
}
};
private static class KeepAliveTimer {
private long lastTime;
private final long thresholdMs;
private void forceInvalidateTokens() {
realmHelper.executeTransaction(realm -> {
RealmSession session = RealmSession.queryDefaultSession(realm).findFirst();
if (session != null
&& !TextUtils.isEmpty(session.getToken())
&& (session.isTokenVerified() || !TextUtils.isEmpty(session.getError()))) {
session.setTokenVerified(false);
session.setError(null);
}
return null;
}).continueWith(new LogIfError());
}
public KeepAliveTimer(long thresholdMs) {
this.thresholdMs = thresholdMs;
lastTime = System.currentTimeMillis();
/**
* terminate WebSocket thread.
*/
@DebugLog
/* package */ Single<Boolean> terminate() {
if (isAlive()) {
return Single.create(emitter -> {
new Handler(getLooper()).post(() -> {
RCLog.d("thread %s: terminated()", Thread.currentThread().getId());
unregisterListenersAndClose();
connectivityManager.notifyConnectionLost(hostname,
DDPClient.REASON_CLOSED_BY_USER);
RocketChatWebSocketThread.super.quit();
emitter.onSuccess(true);
});
});
} else {
connectivityManager.notifyConnectionLost(hostname,
DDPClient.REASON_NETWORK_ERROR);
super.quit();
return Single.just(true);
}
}
public boolean shouldCheckPrecisely() {
return lastTime + thresholdMs < System.currentTimeMillis();
/**
* THIS METHOD THROWS EXCEPTION!! Use terminate() instead!!
*/
@Deprecated
@Override
public final boolean quit() {
throw new UnsupportedOperationException();
}
public void update() {
lastTime = System.currentTimeMillis();
/**
* synchronize the state of the thread with ServerConfig.
*/
@DebugLog
/* package */ Single<Boolean> keepAlive() {
return checkIfConnectionAlive()
.flatMap(alive -> alive ? Single.just(true) : connectWithExponentialBackoff());
}
}
private final KeepAliveTimer keepAliveTimer = new KeepAliveTimer(20000);
private RocketChatWebSocketThread(Context appContext, String hostname) {
super("RC_thread_" + hostname);
this.appContext = appContext;
this.hostname = hostname;
this.realmHelper = RealmStore.getOrCreate(hostname);
this.connectivityManager = ConnectivityManager.getInstanceForInternal(appContext);
}
/**
* build new Thread.
*/
@DebugLog
public static Single<RocketChatWebSocketThread> getStarted(Context appContext, String hostname) {
return Single.<RocketChatWebSocketThread>fromEmitter(objectSingleEmitter -> {
new RocketChatWebSocketThread(appContext, hostname) {
@Override
protected void onLooperPrepared() {
try {
super.onLooperPrepared();
objectSingleEmitter.onSuccess(this);
} catch (Exception exception) {
objectSingleEmitter.onError(exception);
}
@DebugLog
private Single<Boolean> checkIfConnectionAlive() {
if (DDPClient.get() == null) {
return Single.just(false);
}
}.start();
}).flatMap(webSocket ->
webSocket.connect().map(_val -> webSocket));
}
@Override
protected void onLooperPrepared() {
super.onLooperPrepared();
forceInvalidateTokens();
}
private void forceInvalidateTokens() {
realmHelper.executeTransaction(realm -> {
RealmSession session = RealmSession.queryDefaultSession(realm).findFirst();
if (session != null
&& !TextUtils.isEmpty(session.getToken())
&& (session.isTokenVerified() || !TextUtils.isEmpty(session.getError()))) {
session.setTokenVerified(false);
session.setError(null);
}
return null;
}).continueWith(new LogIfError());
}
/**
* terminate WebSocket thread.
*/
@DebugLog
public Single<Boolean> terminate() {
if (isAlive()) {
return Single.fromEmitter(emitter -> {
new Handler(getLooper()).post(() -> {
RCLog.d("thread %s: terminated()", Thread.currentThread().getId());
unregisterListenersAndClose();
connectivityManager.notifyConnectionLost(hostname,
ConnectivityManagerInternal.REASON_CLOSED_BY_USER);
RocketChatWebSocketThread.super.quit();
emitter.onSuccess(true);
return Single.create(emitter -> {
new Thread() {
@Override
public void run() {
DDPClient.get().ping().continueWith(task -> {
if (task.isFaulted()) {
Exception error = task.getError();
RCLog.e(error);
connectivityManager.notifyConnectionLost(
hostname, DDPClient.REASON_CLOSED_BY_USER);
emitter.onSuccess(false);
} else {
emitter.onSuccess(true);
}
return null;
});
}
}.start();
});
});
} else {
connectivityManager.notifyConnectionLost(hostname,
ConnectivityManagerInternal.REASON_NETWORK_ERROR);
super.quit();
return Single.just(true);
}
}
/**
* THIS METHOD THROWS EXCEPTION!! Use terminate() instead!!
*/
@Deprecated
@Override
public final boolean quit() {
throw new UnsupportedOperationException();
}
/**
* synchronize the state of the thread with ServerConfig.
*/
@DebugLog
public Single<Boolean> keepAlive() {
return checkIfConnectionAlive()
.flatMap(alive -> alive ? Single.just(true) : connectWithExponentialBackoff());
}
@DebugLog
private Single<Boolean> checkIfConnectionAlive() {
if (ddpClient == null) {
return Single.just(false);
}
if (!keepAliveTimer.shouldCheckPrecisely()) {
return Single.just(true);
@DebugLog
private Flowable<Boolean> heartbeat(long interval) {
return Flowable.interval(interval, TimeUnit.MILLISECONDS)
.onBackpressureDrop()
.flatMap(tick -> DDPClient.get().doPing().toFlowable())
.map(callback -> {
if (callback instanceof DDPClientCallback.Ping) {
return true;
}
// ideally we should never get here. We should always receive a DDPClientCallback.Ping
// because we just received a pong. But maybe we received a pong from an unmatched
// ping id which we should ignore. In this case or any other random error, log and
// send false downstream
RCLog.d("heartbeat pong < %s", callback.toString());
return false;
});
}
keepAliveTimer.update();
return Single.fromEmitter(emitter -> {
new Thread() {
@Override
public void run() {
ddpClient.ping().continueWith(task -> {
if (task.isFaulted()) {
Exception error = task.getError();
RCLog.e(error);
emitter.onError(error);
} else {
keepAliveTimer.update();
emitter.onSuccess(true);
private Single<Boolean> connectDDPClient() {
return Single.create(emitter -> {
ServerInfo info = connectivityManager.getServerInfoForHost(hostname);
if (info == null) {
emitter.onSuccess(false);
return;
}
return null;
});
}
}.start();
});
}
@DebugLog
private Flowable<Boolean> heartbeat(long interval) {
return Flowable.interval(interval, TimeUnit.MILLISECONDS)
.onBackpressureDrop()
.flatMap(tick -> ddpClient.doPing().toFlowable())
.map(callback -> {
if (callback instanceof DDPClientCallback.Ping) {
return true;
}
// ideally we should never get here. We should always receive a DDPClientCallback.Ping
// because we just received a pong. But maybe we received a pong from an unmatched
// ping id which we should ignore. In this case or any other random error, log and
// send false downstream
RCLog.d("heartbeat pong < %s", callback.toString());
return false;
});
}
private Single<Boolean> prepareDDPClient() {
// TODO: temporarily replaced checkIfConnectionAlive() call for this single checking if ddpClient is
// null or not. In case it is, build a new client, otherwise just keep connecting with existing one.
return Single.just(ddpClient != null)
.doOnSuccess(alive -> {
if (!alive) {
RCLog.d("DDPClient#build");
ddpClient = DDPClientWrapper.create(hostname);
}
RCLog.d("DDPClient#connect");
connectivityManager.notifyConnecting(hostname);
DDPClient.get().connect(hostname, info.getSession(), info.isSecure())
.onSuccessTask(task -> {
final String newSession = task.getResult().session;
connectivityManager.notifyConnectionEstablished(hostname, newSession);
// handling WebSocket#onClose() callback.
task.getResult().client.getOnCloseCallback().onSuccess(_task -> {
RxWebSocketCallback.Close result = _task.getResult();
if (result.code == DDPClient.REASON_NETWORK_ERROR) {
reconnect();
}
return null;
});
return realmHelper.executeTransaction(realm -> {
RealmSession sessionObj = RealmSession.queryDefaultSession(realm).findFirst();
if (sessionObj == null) {
realm.createOrUpdateObjectFromJson(RealmSession.class,
new JSONObject().put(RealmSession.ID, RealmSession.DEFAULT_ID));
} else {
// invalidate login token.
if (!TextUtils.isEmpty(sessionObj.getToken()) && sessionObj.isTokenVerified()) {
sessionObj.setTokenVerified(false);
sessionObj.setError(null);
}
}
return null;
});
})
.continueWith(task -> {
if (task.isFaulted()) {
emitter.onError(task.getError());
} else {
emitter.onSuccess(true);
}
return null;
});
});
}
private Single<Boolean> connectDDPClient() {
return prepareDDPClient()
.flatMap(_val -> Single.fromEmitter(emitter -> {
ServerInfo info = connectivityManager.getServerInfoForHost(hostname);
if (info == null) {
emitter.onSuccess(false);
}
private void reconnect() {
// if we are already trying to reconnect then return.
if (reconnectDisposable.size() > 0) {
return;
}
RCLog.d("DDPClient#connect");
ddpClient.connect(info.getSession(), info.isSecure())
.onSuccessTask(task -> {
final String newSession = task.getResult().session;
connectivityManager.notifyConnectionEstablished(hostname, newSession);
// handling WebSocket#onClose() callback.
task.getResult().client.getOnCloseCallback().onSuccess(_task -> {
reconnect();
return null;
});
}
forceInvalidateTokens();
reconnectDisposable.add(
connectWithExponentialBackoff()
.subscribe(connected -> {
if (!connected) {
connectivityManager.notifyConnectionLost(hostname,
DDPClient.REASON_NETWORK_ERROR);
}
reconnectDisposable.clear();
}, error -> {
connectivityManager.notifyConnectionLost(hostname,
DDPClient.REASON_NETWORK_ERROR);
logErrorAndUnsubscribe(reconnectDisposable, error);
}
)
);
}
return realmHelper.executeTransaction(realm -> {
RealmSession sessionObj = RealmSession.queryDefaultSession(realm).findFirst();
if (sessionObj == null) {
realm.createOrUpdateObjectFromJson(RealmSession.class,
new JSONObject().put(RealmSession.ID, RealmSession.DEFAULT_ID));
} else {
// invalidate login token.
if (!TextUtils.isEmpty(sessionObj.getToken()) && sessionObj.isTokenVerified()) {
sessionObj.setTokenVerified(false);
sessionObj.setError(null);
}
private void logErrorAndUnsubscribe(CompositeDisposable disposables, Throwable err) {
RCLog.e(err);
disposables.clear();
}
}
return null;
});
})
.continueWith(task -> {
if (task.isFaulted()) {
emitter.onError(task.getError());
} else {
emitter.onSuccess(true);
}
return null;
});
}));
}
private void reconnect() {
// if we are already trying to reconnect then return.
if (reconnectSubscription.hasSubscriptions()) {
return;
private Single<Boolean> connectWithExponentialBackoff() {
return connect()
.retryWhen(RxHelper.exponentialBackoff(1, 250, TimeUnit.MILLISECONDS))
.onErrorResumeNext(Single.just(false));
}
ddpClient.close();
forceInvalidateTokens();
connectivityManager.notifyConnecting(hostname);
// Needed to use subscriptions because of legacy code.
// TODO: Should update to RxJava 2
reconnectSubscription.add(
connectWithExponentialBackoff()
.subscribe(
connected -> {
if (!connected) {
connectivityManager.notifyConnecting(hostname);
}
reconnectSubscription.clear();
},
err -> logErrorAndUnsubscribe(reconnectSubscription, err)
)
);
}
private void logErrorAndUnsubscribe(CompositeSubscription subscriptions, Throwable err) {
RCLog.e(err);
subscriptions.clear();
}
private Single<Boolean> connectWithExponentialBackoff() {
return connect().retryWhen(RxHelper.exponentialBackoff(Integer.MAX_VALUE, 500, TimeUnit.MILLISECONDS));
}
@DebugLog
private Single<Boolean> connect() {
return connectDDPClient()
.flatMap(_val -> Single.fromEmitter(emitter -> {
fetchPublicSettings();
fetchPermissions();
registerListeners();
emitter.onSuccess(true);
}));
}
private Task<Void> fetchPublicSettings() {
return new MethodCallHelper(realmHelper, ddpClientRef).getPublicSettings();
}
private Task<Void> fetchPermissions() {
return new MethodCallHelper(realmHelper, ddpClientRef).getPermissions();
}
@DebugLog
private void registerListeners() {
if (!Thread.currentThread().getName().equals("RC_thread_" + hostname)) {
// execute in Looper.
new Handler(getLooper()).post(this::registerListeners);
return;
@DebugLog
private Single<Boolean> connect() {
return connectDDPClient()
.flatMap(_val -> Single.create(emitter -> {
fetchPublicSettings();
fetchPermissions();
registerListeners();
emitter.onSuccess(true);
}));
}
if (listenersRegistered) {
unregisterListeners();
private Task<Void> fetchPublicSettings() {
return new MethodCallHelper(appContext, realmHelper).getPublicSettings(hostname);
}
List<RealmSession> sessions = realmHelper.executeTransactionForReadResults(realm ->
realm.where(RealmSession.class)
.isNotNull(RealmSession.TOKEN)
.equalTo(RealmSession.TOKEN_VERIFIED, false)
.isNull(RealmSession.ERROR)
.findAll());
if (sessions != null && sessions.size() > 0) {
// if we have a session try to resume it. At this point we're probably recovering from
// a disconnection state
final CompositeSubscription subscriptions = new CompositeSubscription();
MethodCallHelper methodCall = new MethodCallHelper(realmHelper, ddpClientRef);
subscriptions.add(
Completable.defer(() -> {
Task<Void> result = methodCall.loginWithToken(sessions.get(0).getToken());
if (result.isFaulted()) {
return Completable.error(result.getError());
} else {
return Completable.complete();
}
}).retryWhen(RxHelper.exponentialBackoff(Integer.MAX_VALUE, 500, TimeUnit.MILLISECONDS))
.subscribe(
() -> {
createObserversAndRegister();
subscriptions.clear();
},
error -> logErrorAndUnsubscribe(subscriptions, error)
)
);
} else {
// if we don't have any session then just build the observers and register normally
createObserversAndRegister();
private Task<Void> fetchPermissions() {
return new MethodCallHelper(realmHelper).getPermissions();
}
}
@DebugLog
private void createObserversAndRegister() {
for (Class clazz : REGISTERABLE_CLASSES) {
try {
Constructor ctor = clazz.getConstructor(Context.class, String.class, RealmHelper.class,
DDPClientRef.class);
Object obj = ctor.newInstance(appContext, hostname, realmHelper, ddpClientRef);
if (obj instanceof Registrable) {
Registrable registrable = (Registrable) obj;
registrable.register();
listeners.add(registrable);
@DebugLog
private void registerListeners() {
if (!Thread.currentThread().getName().equals("RC_thread_" + hostname)) {
// execute in Looper.
new Handler(getLooper()).post(this::registerListeners);
return;
}
if (listenersRegistered) {
unregisterListeners();
}
List<RealmSession> sessions = realmHelper.executeTransactionForReadResults(realm ->
realm.where(RealmSession.class)
.isNotNull(RealmSession.TOKEN)
.equalTo(RealmSession.TOKEN_VERIFIED, false)
.isNull(RealmSession.ERROR)
.findAll());
if (sessions != null && sessions.size() > 0) {
// if we have a session try to resume it. At this point we're probably recovering from
// a disconnection state
final CompositeDisposable disposables = new CompositeDisposable();
MethodCallHelper methodCall = new MethodCallHelper(realmHelper);
disposables.add(
Completable.defer(() -> {
Task<Void> result = methodCall.loginWithToken(sessions.get(0).getToken());
if (result.isFaulted()) {
return Completable.error(result.getError());
} else {
return Completable.complete();
}
}).retryWhen(RxHelper.exponentialBackoff(3, 500, TimeUnit.MILLISECONDS))
.subscribe(
() -> {
createObserversAndRegister();
disposables.clear();
},
error -> logErrorAndUnsubscribe(disposables, error)
)
);
} else {
// if we don't have any session then just build the observers and register normally
createObserversAndRegister();
}
} catch (Exception exception) {
RCLog.w(exception, "Failed to register listeners!!");
}
}
listenersRegistered = true;
startHeartBeat();
}
private void startHeartBeat() {
hearbeatDisposable.clear();
hearbeatDisposable.add(
heartbeat(HEARTBEAT_PERIOD_MS)
.subscribe(
ponged -> {
if (!ponged) {
RCLog.d("Pong received but didn't match ping id");
}
},
error -> {
RCLog.e(error);
// Stop pinging
hearbeatDisposable.clear();
if (error instanceof DDPClientCallback.Closed) {
RCLog.d("Hearbeat failure: retrying connection...");
reconnect();
}
}
)
);
}
@DebugLog
private void unregisterListenersAndClose() {
unregisterListeners();
if (ddpClient != null) {
ddpClient.close();
ddpClient = null;
@DebugLog
private void createObserversAndRegister() {
for (Class clazz : REGISTERABLE_CLASSES) {
try {
Constructor ctor = clazz.getConstructor(Context.class, String.class, RealmHelper.class);
Object obj = ctor.newInstance(appContext, hostname, realmHelper);
if (obj instanceof Registrable) {
Registrable registrable = (Registrable) obj;
registrable.register();
listeners.add(registrable);
}
} catch (Exception exception) {
RCLog.w(exception, "Failed to register listeners!!");
}
}
listenersRegistered = true;
startHeartBeat();
}
}
@DebugLog
private void unregisterListeners() {
Iterator<Registrable> iterator = listeners.iterator();
while (iterator.hasNext()) {
Registrable registrable = iterator.next();
registrable.unregister();
iterator.remove();
private void startHeartBeat() {
heartbeatDisposable.clear();
heartbeatDisposable.add(
heartbeat(HEARTBEAT_PERIOD_MS)
.subscribe(
ponged -> {
if (!ponged) {
RCLog.d("Pong received but didn't match ping id");
}
},
error -> {
RCLog.e(error);
// Stop pinging
heartbeatDisposable.clear();
if (error instanceof DDPClientCallback.Closed || error instanceof TimeoutException) {
RCLog.d("Hearbeat failure: retrying connection...");
reconnect();
}
}
)
);
}
@DebugLog
private void unregisterListenersAndClose() {
unregisterListeners();
DDPClient.get().close();
}
@DebugLog
private void unregisterListeners() {
Iterator<Registrable> iterator = listeners.iterator();
while (iterator.hasNext()) {
Registrable registrable = iterator.next();
registrable.unregister();
iterator.remove();
}
heartbeatDisposable.clear();
listenersRegistered = false;
}
hearbeatDisposable.clear();
listenersRegistered = false;
}
}
......@@ -4,17 +4,43 @@ package chat.rocket.android.service;
* pair with server's hostname and its connectivity state.
*/
public class ServerConnectivity {
public static final int STATE_CONNECTED = 1;
public static final int STATE_DISCONNECTED = 2;
public static final int STATE_CONNECTING = 3;
/*package*/ static final int STATE_DISCONNECTING = 4;
public static final ServerConnectivity CONNECTED = new ServerConnectivity(null, STATE_CONNECTED);
public final String hostname;
public final int state;
public final int code;
ServerConnectivity(String hostname, int state) {
this.hostname = hostname;
this.state = state;
this.code = -1;
}
public ServerConnectivity(String hostname, int state) {
ServerConnectivity(String hostname, int state, int code) {
this.hostname = hostname;
this.state = state;
this.code = code;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ServerConnectivity that = (ServerConnectivity) o;
return state == that.state;
}
@Override
public int hashCode() {
return state;
}
/**
......
......@@ -3,38 +3,37 @@ package chat.rocket.android.service.ddp;
import android.content.Context;
import android.text.TextUtils;
import chat.rocket.persistence.realm.models.ddp.RealmUser;
import io.reactivex.disposables.Disposable;
import io.realm.Realm;
import io.realm.RealmObject;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.Iterator;
import chat.rocket.android.helper.LogIfError;
import chat.rocket.android.log.RCLog;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.android.service.DDPClientRef;
import chat.rocket.android.service.Registrable;
import chat.rocket.android_ddp.DDPClient;
import chat.rocket.android_ddp.DDPSubscription;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.persistence.realm.models.ddp.RealmUser;
import io.reactivex.disposables.Disposable;
import io.realm.Realm;
import io.realm.RealmObject;
import io.realm.RealmResults;
public abstract class AbstractDDPDocEventSubscriber implements Registrable {
protected final Context context;
protected final String hostname;
protected final RealmHelper realmHelper;
protected final DDPClientRef ddpClientRef;
private boolean isUnsubscribed;
private String subscriptionId;
private Disposable rxSubscription;
protected AbstractDDPDocEventSubscriber(Context context, String hostname,
RealmHelper realmHelper, DDPClientRef ddpClientRef) {
RealmHelper realmHelper) {
this.context = context;
this.hostname = hostname;
this.realmHelper = realmHelper;
this.ddpClientRef = ddpClientRef;
}
protected abstract String getSubscriptionName();
......@@ -69,9 +68,9 @@ public abstract class AbstractDDPDocEventSubscriber implements Registrable {
// just ignore.
}
ddpClientRef.get().subscribe(getSubscriptionName(), params).onSuccess(task -> {
DDPClient.get().subscribe(getSubscriptionName(), params).onSuccess(task -> {
if (isUnsubscribed) {
ddpClientRef.get().unsubscribe(task.getResult().id).continueWith(new LogIfError());
DDPClient.get().unsubscribe(task.getResult().id).continueWith(new LogIfError());
} else {
subscriptionId = task.getResult().id;
}
......@@ -98,7 +97,7 @@ public abstract class AbstractDDPDocEventSubscriber implements Registrable {
}
protected Disposable subscribe() {
return ddpClientRef.get().getSubscriptionCallback()
return DDPClient.get().getSubscriptionCallback()
.filter(event -> event instanceof DDPSubscription.DocEvent)
.cast(DDPSubscription.DocEvent.class)
.filter(event -> isTarget(event.collection))
......@@ -197,8 +196,8 @@ public abstract class AbstractDDPDocEventSubscriber implements Registrable {
if (rxSubscription != null) {
rxSubscription.dispose();
}
if (!TextUtils.isEmpty(subscriptionId) && ddpClientRef.get() != null) {
ddpClientRef.get().unsubscribe(subscriptionId).continueWith(new LogIfError());
if (!TextUtils.isEmpty(subscriptionId) && DDPClient.get() != null) {
DDPClient.get().unsubscribe(subscriptionId).continueWith(new LogIfError());
}
}
}
package chat.rocket.android.service.ddp.base;
import android.content.Context;
import org.json.JSONArray;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.android.service.DDPClientRef;
import chat.rocket.android.service.ddp.AbstractDDPDocEventSubscriber;
import chat.rocket.persistence.realm.RealmHelper;
abstract class AbstractBaseSubscriber extends AbstractDDPDocEventSubscriber {
protected AbstractBaseSubscriber(Context context, String hostname, RealmHelper realmHelper,
DDPClientRef ddpClientRef) {
super(context, hostname, realmHelper, ddpClientRef);
protected AbstractBaseSubscriber(Context context, String hostname, RealmHelper realmHelper) {
super(context, hostname, realmHelper);
}
@Override
......
package chat.rocket.android.service.ddp.base;
import android.content.Context;
import io.realm.RealmObject;
import org.json.JSONException;
import org.json.JSONObject;
import chat.rocket.persistence.realm.models.ddp.RealmUser;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.android.service.DDPClientRef;
import chat.rocket.persistence.realm.models.ddp.RealmUser;
import io.realm.RealmObject;
/**
* "activeUsers" subscriber.
*/
public class ActiveUsersSubscriber extends AbstractBaseSubscriber {
public ActiveUsersSubscriber(Context context, String hostname, RealmHelper realmHelper,
DDPClientRef ddpClientRef) {
super(context, hostname, realmHelper, ddpClientRef);
public ActiveUsersSubscriber(Context context, String hostname, RealmHelper realmHelper) {
super(context, hostname, realmHelper);
}
@Override
......
package chat.rocket.android.service.ddp.base;
import android.content.Context;
import io.realm.RealmObject;
import chat.rocket.persistence.realm.models.ddp.RealmMeteorLoginServiceConfiguration;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.android.service.DDPClientRef;
import chat.rocket.persistence.realm.models.ddp.RealmMeteorLoginServiceConfiguration;
import io.realm.RealmObject;
/**
* meteor.loginServiceConfiguration subscriber
*/
public class LoginServiceConfigurationSubscriber extends AbstractBaseSubscriber {
public LoginServiceConfigurationSubscriber(Context context, String hostname,
RealmHelper realmHelper, DDPClientRef ddpClientRef) {
super(context, hostname, realmHelper, ddpClientRef);
RealmHelper realmHelper) {
super(context, hostname, realmHelper);
}
@Override
......
package chat.rocket.android.service.ddp.base;
import android.content.Context;
import io.realm.RealmObject;
import org.json.JSONException;
import org.json.JSONObject;
import chat.rocket.persistence.realm.models.ddp.RealmUser;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.android.service.DDPClientRef;
import chat.rocket.persistence.realm.models.ddp.RealmUser;
import io.realm.RealmObject;
/**
* "userData" subscriber.
*/
public class UserDataSubscriber extends AbstractBaseSubscriber {
public UserDataSubscriber(Context context, String hostname, RealmHelper realmHelper,
DDPClientRef ddpClientRef) {
super(context, hostname, realmHelper, ddpClientRef);
public UserDataSubscriber(Context context, String hostname, RealmHelper realmHelper) {
super(context, hostname, realmHelper);
}
@Override
......
package chat.rocket.android.service.ddp.stream;
import android.content.Context;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import chat.rocket.android.helper.LogIfError;
import chat.rocket.android.log.RCLog;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.android.service.DDPClientRef;
import chat.rocket.android.service.ddp.AbstractDDPDocEventSubscriber;
import chat.rocket.android_ddp.DDPSubscription;
import chat.rocket.persistence.realm.RealmHelper;
abstract class AbstractStreamNotifyEventSubscriber extends AbstractDDPDocEventSubscriber {
protected AbstractStreamNotifyEventSubscriber(Context context, String hostname,
RealmHelper realmHelper,
DDPClientRef ddpClientRef) {
super(context, hostname, realmHelper, ddpClientRef);
RealmHelper realmHelper) {
super(context, hostname, realmHelper);
}
@Override
......
......@@ -3,15 +3,13 @@ package chat.rocket.android.service.ddp.stream;
import android.content.Context;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.android.service.DDPClientRef;
abstract class AbstractStreamNotifyUserEventSubscriber extends AbstractStreamNotifyEventSubscriber {
protected final String userId;
protected AbstractStreamNotifyUserEventSubscriber(Context context, String hostname,
RealmHelper realmHelper,
DDPClientRef ddpClientRef, String userId) {
super(context, hostname, realmHelper, ddpClientRef);
RealmHelper realmHelper, String userId) {
super(context, hostname, realmHelper);
this.userId = userId;
}
......
package chat.rocket.android.service.ddp.stream;
import android.content.Context;
import io.realm.RealmObject;
import org.json.JSONException;
import org.json.JSONObject;
import chat.rocket.persistence.realm.models.ddp.RealmRoom;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.android.service.DDPClientRef;
import chat.rocket.persistence.realm.models.ddp.RealmRoom;
import io.realm.RealmObject;
public class StreamNotifyUserSubscriptionsChanged extends AbstractStreamNotifyUserEventSubscriber {
public StreamNotifyUserSubscriptionsChanged(Context context, String hostname,
RealmHelper realmHelper, DDPClientRef ddpClientRef,
RealmHelper realmHelper,
String userId) {
super(context, hostname, realmHelper, ddpClientRef, userId);
super(context, hostname, realmHelper, userId);
}
@Override
......
package chat.rocket.android.service.ddp.stream;
import android.content.Context;
import io.realm.RealmObject;
import org.json.JSONException;
import org.json.JSONObject;
import chat.rocket.persistence.realm.models.ddp.RealmMessage;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.android.service.DDPClientRef;
import chat.rocket.persistence.realm.models.ddp.RealmMessage;
import io.realm.RealmObject;
/**
* stream-room-message subscriber.
......@@ -16,8 +16,8 @@ public class StreamRoomMessage extends AbstractStreamNotifyEventSubscriber {
private String roomId;
public StreamRoomMessage(Context context, String hostname,
RealmHelper realmHelper, DDPClientRef ddpClientRef, String roomId) {
super(context, hostname, realmHelper, ddpClientRef);
RealmHelper realmHelper, String roomId) {
super(context, hostname, realmHelper);
this.roomId = roomId;
}
......
......@@ -5,10 +5,9 @@ import android.os.Handler;
import android.os.Looper;
import chat.rocket.android.RocketChatCache;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.android.service.DDPClientRef;
import chat.rocket.android.service.Registrable;
import chat.rocket.android.service.ddp.stream.StreamRoomMessage;
import chat.rocket.persistence.realm.RealmHelper;
/**
* wrapper for managing stream-notify-message depending on RocketChatCache.
......@@ -17,18 +16,16 @@ public class StreamRoomMessageManager implements Registrable {
private final Context context;
private final String hostname;
private final RealmHelper realmHelper;
private final DDPClientRef ddpClientRef;
private final AbstractRocketChatCacheObserver cacheObserver;
private final Handler handler;
private final RocketChatCache rocketChatCache;
private StreamRoomMessage streamRoomMessage;
public StreamRoomMessageManager(Context context, String hostname,
RealmHelper realmHelper, DDPClientRef ddpClientRef) {
RealmHelper realmHelper) {
this.context = context;
this.hostname = hostname;
this.realmHelper = realmHelper;
this.ddpClientRef = ddpClientRef;
this.rocketChatCache = new RocketChatCache(context);
cacheObserver = new AbstractRocketChatCacheObserver(context, realmHelper) {
......@@ -43,7 +40,7 @@ public class StreamRoomMessageManager implements Registrable {
private void registerStreamNotifyMessage(String roomId) {
handler.post(() -> {
streamRoomMessage = new StreamRoomMessage(context, hostname, realmHelper, ddpClientRef, roomId);
streamRoomMessage = new StreamRoomMessage(context, hostname, realmHelper, roomId);
streamRoomMessage.register();
});
}
......
package chat.rocket.android.service.observer;
import android.content.Context;
import io.realm.RealmObject;
import chat.rocket.android.service.Registrable;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.persistence.realm.RealmListObserver;
import chat.rocket.android.service.DDPClientRef;
import chat.rocket.android.service.Registrable;
import io.realm.RealmObject;
abstract class AbstractModelObserver<T extends RealmObject>
implements Registrable, RealmListObserver.Query<T>, RealmListObserver.OnUpdateListener<T> {
......@@ -14,15 +13,13 @@ abstract class AbstractModelObserver<T extends RealmObject>
protected final Context context;
protected final String hostname;
protected final RealmHelper realmHelper;
protected final DDPClientRef ddpClientRef;
private final RealmListObserver observer;
protected AbstractModelObserver(Context context, String hostname,
RealmHelper realmHelper, DDPClientRef ddpClientRef) {
RealmHelper realmHelper) {
this.context = context;
this.hostname = hostname;
this.realmHelper = realmHelper;
this.ddpClientRef = ddpClientRef;
observer = realmHelper.createListObserver(this).setOnUpdateListener(this);
}
......
package chat.rocket.android.service.observer;
import android.content.Context;
import io.realm.Realm;
import io.realm.RealmResults;
import java.util.ArrayList;
import java.util.List;
import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.android.helper.LogIfError;
import chat.rocket.persistence.realm.models.ddp.RealmUser;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.android.service.DDPClientRef;
import chat.rocket.android.service.Registrable;
import chat.rocket.android.service.ddp.stream.StreamNotifyUserSubscriptionsChanged;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.persistence.realm.models.ddp.RealmUser;
import hugo.weaving.DebugLog;
import io.realm.Realm;
import io.realm.RealmResults;
/**
* observe the user with emails.
......@@ -24,9 +24,9 @@ public class CurrentUserObserver extends AbstractModelObserver<RealmUser> {
private ArrayList<Registrable> listeners;
public CurrentUserObserver(Context context, String hostname,
RealmHelper realmHelper, DDPClientRef ddpClientRef) {
super(context, hostname, realmHelper, ddpClientRef);
methodCall = new MethodCallHelper(realmHelper, ddpClientRef);
RealmHelper realmHelper) {
super(context, hostname, realmHelper);
methodCall = new MethodCallHelper(realmHelper);
currentUserExists = false;
}
......@@ -62,7 +62,7 @@ public class CurrentUserObserver extends AbstractModelObserver<RealmUser> {
methodCall.getRoomSubscriptions().onSuccess(task -> {
if (listeners != null) {
Registrable listener = new StreamNotifyUserSubscriptionsChanged(
context, hostname, realmHelper, ddpClientRef, userId);
context, hostname, realmHelper, userId);
listener.register();
listeners.add(listener);
}
......
package chat.rocket.android.service.observer;
import android.content.Context;
import org.json.JSONObject;
import java.util.List;
import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.android.helper.LogIfError;
import chat.rocket.android.log.RCLog;
import chat.rocket.core.SyncState;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.persistence.realm.models.ddp.RealmMessage;
import io.realm.Realm;
import io.realm.RealmResults;
/**
* Observe messages for deletion.
*/
public class DeletedMessageObserver extends AbstractModelObserver<RealmMessage> {
private final MethodCallHelper methodCall;
public DeletedMessageObserver(Context context, String hostname, RealmHelper realmHelper) {
super(context, hostname, realmHelper);
methodCall = new MethodCallHelper(realmHelper);
realmHelper.executeTransaction(realm -> {
// resume pending operations.
RealmResults<RealmMessage> pendingMethodCalls = realm.where(RealmMessage.class)
.equalTo(RealmMessage.SYNC_STATE, SyncState.DELETING)
.findAll();
for (RealmMessage message : pendingMethodCalls) {
message.setSyncState(SyncState.DELETE_NOT_SYNCED);
}
return null;
}).continueWith(new LogIfError());
}
@Override
public RealmResults<RealmMessage> queryItems(Realm realm) {
return realm.where(RealmMessage.class)
.equalTo(RealmMessage.SYNC_STATE, SyncState.DELETE_NOT_SYNCED)
.isNotNull(RealmMessage.ROOM_ID)
.findAll();
}
@Override
public void onUpdateResults(List<RealmMessage> results) {
if (results.isEmpty()) {
return;
}
for(RealmMessage message : results) {
final String messageId = message.getId();
realmHelper.executeTransaction(realm ->
realm.createOrUpdateObjectFromJson(RealmMessage.class, new JSONObject()
.put(RealmMessage.ID, messageId)
.put(RealmMessage.SYNC_STATE, SyncState.DELETING)
)
).onSuccessTask(task -> methodCall.deleteMessage(messageId)
).continueWith(task -> {
if(task.isFaulted()) {
RCLog.w(task.getError());
realmHelper.executeTransaction(realm ->
realm.createOrUpdateObjectFromJson(RealmMessage.class, new JSONObject()
.put(RealmMessage.ID, messageId)
.put(RealmMessage.SYNC_STATE, SyncState.DELETE_FAILED)));
} else {
realmHelper.executeTransaction(realm ->
realm.where(RealmMessage.class)
.equalTo(RealmMessage.ID, messageId)
.findAll()
.deleteAllFromRealm()
);
}
return null;
});
}
}
}
......@@ -2,23 +2,24 @@ package chat.rocket.android.service.observer;
import android.content.Context;
import android.net.Uri;
import chat.rocket.android.helper.OkHttpHelper;
import io.realm.Realm;
import io.realm.RealmResults;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import bolts.Task;
import chat.rocket.android.api.FileUploadingHelper;
import chat.rocket.android.helper.LogIfError;
import chat.rocket.android.helper.OkHttpHelper;
import chat.rocket.android.log.RCLog;
import chat.rocket.core.SyncState;
import chat.rocket.persistence.realm.models.internal.FileUploading;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.android.service.DDPClientRef;
import chat.rocket.persistence.realm.models.internal.FileUploading;
import io.realm.Realm;
import io.realm.RealmResults;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.Request;
......@@ -35,9 +36,9 @@ public class FileUploadingToUrlObserver extends AbstractModelObserver<FileUpload
private FileUploadingHelper methodCall;
public FileUploadingToUrlObserver(Context context, String hostname,
RealmHelper realmHelper, DDPClientRef ddpClientRef) {
super(context, hostname, realmHelper, ddpClientRef);
methodCall = new FileUploadingHelper(realmHelper, ddpClientRef);
RealmHelper realmHelper) {
super(context, hostname, realmHelper);
methodCall = new FileUploadingHelper(realmHelper);
realmHelper.executeTransaction(realm -> {
// resume pending operations.
......
......@@ -2,23 +2,24 @@ package chat.rocket.android.service.observer;
import android.content.Context;
import android.net.Uri;
import chat.rocket.android.helper.OkHttpHelper;
import io.realm.Realm;
import io.realm.RealmResults;
import org.json.JSONObject;
import java.io.InputStream;
import java.util.List;
import bolts.Task;
import chat.rocket.android.api.FileUploadingHelper;
import chat.rocket.android.helper.LogIfError;
import chat.rocket.android.helper.OkHttpHelper;
import chat.rocket.android.log.RCLog;
import chat.rocket.core.SyncState;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.persistence.realm.models.ddp.RealmUser;
import chat.rocket.persistence.realm.models.internal.FileUploading;
import chat.rocket.persistence.realm.models.internal.RealmSession;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.android.service.DDPClientRef;
import io.realm.Realm;
import io.realm.RealmResults;
import okhttp3.MediaType;
import okhttp3.Request;
import okhttp3.RequestBody;
......@@ -31,9 +32,9 @@ public class FileUploadingWithUfsObserver extends AbstractModelObserver<FileUplo
private FileUploadingHelper methodCall;
public FileUploadingWithUfsObserver(Context context, String hostname,
RealmHelper realmHelper, DDPClientRef ddpClientRef) {
super(context, hostname, realmHelper, ddpClientRef);
methodCall = new FileUploadingHelper(realmHelper, ddpClientRef);
RealmHelper realmHelper) {
super(context, hostname, realmHelper);
methodCall = new FileUploadingHelper(realmHelper);
realmHelper.executeTransaction(realm -> {
// resume pending operations.
......
......@@ -13,7 +13,6 @@ import chat.rocket.android.R;
import chat.rocket.android.RocketChatCache;
import chat.rocket.android.api.RaixPushHelper;
import chat.rocket.android.helper.LogIfError;
import chat.rocket.android.service.DDPClientRef;
import chat.rocket.core.SyncState;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.persistence.realm.models.ddp.RealmUser;
......@@ -26,9 +25,8 @@ import io.realm.RealmResults;
*/
public class GcmPushRegistrationObserver extends AbstractModelObserver<GcmPushRegistration> {
public GcmPushRegistrationObserver(Context context, String hostname,
RealmHelper realmHelper,
DDPClientRef ddpClientRef) {
super(context, hostname, realmHelper, ddpClientRef);
RealmHelper realmHelper) {
super(context, hostname, realmHelper);
}
@Override
......@@ -72,7 +70,7 @@ public class GcmPushRegistrationObserver extends AbstractModelObserver<GcmPushRe
final String userId = currentUser != null ? currentUser.getId() : null;
final String pushId = new RocketChatCache(context).getOrCreatePushId();
return new RaixPushHelper(realmHelper, ddpClientRef)
return new RaixPushHelper(realmHelper)
.pushUpdate(pushId, gcmToken, userId);
}
......
package chat.rocket.android.service.observer;
import android.content.Context;
import io.realm.Realm;
import io.realm.RealmResults;
import org.json.JSONObject;
import java.util.List;
import bolts.Task;
import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.android.log.RCLog;
import chat.rocket.core.SyncState;
import chat.rocket.persistence.realm.models.internal.GetUsersOfRoomsProcedure;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.android.service.DDPClientRef;
import chat.rocket.persistence.realm.models.internal.GetUsersOfRoomsProcedure;
import io.realm.Realm;
import io.realm.RealmResults;
/**
* Model observer for executing getUsersOfRooms.
......@@ -23,9 +24,9 @@ public class GetUsersOfRoomsProcedureObserver
private final MethodCallHelper methodCall;
public GetUsersOfRoomsProcedureObserver(Context context, String hostname,
RealmHelper realmHelper, DDPClientRef ddpClientRef) {
super(context, hostname, realmHelper, ddpClientRef);
methodCall = new MethodCallHelper(realmHelper, ddpClientRef);
RealmHelper realmHelper) {
super(context, hostname, realmHelper);
methodCall = new MethodCallHelper(realmHelper);
}
@Override
......
package chat.rocket.android.service.observer;
import android.content.Context;
import org.json.JSONObject;
import java.util.List;
import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.android.log.RCLog;
import chat.rocket.android.service.DDPClientRef;
import chat.rocket.core.SyncState;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.persistence.realm.models.ddp.RealmMessage;
......@@ -11,8 +15,6 @@ import chat.rocket.persistence.realm.models.internal.LoadMessageProcedure;
import io.realm.Realm;
import io.realm.RealmResults;
import io.realm.Sort;
import java.util.List;
import org.json.JSONObject;
/**
* Background process for loading messages.
......@@ -22,9 +24,9 @@ public class LoadMessageProcedureObserver extends AbstractModelObserver<LoadMess
private final MethodCallHelper methodCall;
public LoadMessageProcedureObserver(Context context, String hostname,
RealmHelper realmHelper, DDPClientRef ddpClientRef) {
super(context, hostname, realmHelper, ddpClientRef);
methodCall = new MethodCallHelper(realmHelper, ddpClientRef);
RealmHelper realmHelper) {
super(context, hostname, realmHelper);
methodCall = new MethodCallHelper(realmHelper);
}
@Override
......
......@@ -8,7 +8,7 @@ import java.util.List;
import chat.rocket.android.helper.CheckSum;
import chat.rocket.android.helper.LogIfError;
import chat.rocket.android.service.DDPClientRef;
import chat.rocket.android_ddp.DDPClient;
import chat.rocket.android_ddp.DDPClientCallback;
import chat.rocket.core.SyncState;
import chat.rocket.persistence.realm.RealmHelper;
......@@ -27,8 +27,8 @@ public class MethodCallObserver extends AbstractModelObserver<MethodCall> {
* constructor.
*/
public MethodCallObserver(Context context, String hostname,
RealmHelper realmHelper, DDPClientRef ddpClientRef) {
super(context, hostname, realmHelper, ddpClientRef);
RealmHelper realmHelper) {
super(context, hostname, realmHelper);
realmHelper.executeTransaction(realm -> {
// resume pending operations.
RealmResults<MethodCall> pendingMethodCalls = realm.where(MethodCall.class)
......@@ -99,7 +99,7 @@ public class MethodCallObserver extends AbstractModelObserver<MethodCall> {
.put(MethodCall.ID, methodCallId)
.put(MethodCall.SYNC_STATE, SyncState.SYNCING))
).onSuccessTask(task ->
ddpClientRef.get().rpc(methodCallId, methodName, params, timeout)
DDPClient.get().rpc(methodCallId, methodName, params, timeout)
.onSuccessTask(_task -> realmHelper.executeTransaction(realm -> {
String json = _task.getResult().result;
return realm.createOrUpdateObjectFromJson(MethodCall.class, new JSONObject()
......
package chat.rocket.android.service.observer;
import android.content.Context;
import io.realm.Realm;
import io.realm.RealmResults;
import org.json.JSONObject;
import java.util.List;
import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.android.helper.LogIfError;
import chat.rocket.android.log.RCLog;
import chat.rocket.core.SyncState;
import chat.rocket.persistence.realm.models.ddp.RealmMessage;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.android.service.DDPClientRef;
import chat.rocket.persistence.realm.models.ddp.RealmMessage;
import io.realm.Realm;
import io.realm.RealmResults;
/**
* Observe messages for sending.
......@@ -21,9 +22,9 @@ public class NewMessageObserver extends AbstractModelObserver<RealmMessage> {
private final MethodCallHelper methodCall;
public NewMessageObserver(Context context, String hostname, RealmHelper realmHelper, DDPClientRef ddpClientRef) {
super(context, hostname, realmHelper, ddpClientRef);
methodCall = new MethodCallHelper(realmHelper, ddpClientRef);
public NewMessageObserver(Context context, String hostname, RealmHelper realmHelper) {
super(context, hostname, realmHelper);
methodCall = new MethodCallHelper(realmHelper);
realmHelper.executeTransaction(realm -> {
// resume pending operations.
......
package chat.rocket.android.service.observer;
import android.content.Context;
import io.realm.Realm;
import io.realm.RealmResults;
import java.util.List;
import chat.rocket.android.helper.GcmPushSettingHelper;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.persistence.realm.models.ddp.RealmPublicSetting;
import chat.rocket.persistence.realm.models.internal.GcmPushRegistration;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.android.service.DDPClientRef;
import io.realm.Realm;
import io.realm.RealmResults;
public class PushSettingsObserver extends AbstractModelObserver<RealmPublicSetting> {
public PushSettingsObserver(Context context, String hostname,
RealmHelper realmHelper, DDPClientRef ddpClientRef) {
super(context, hostname, realmHelper, ddpClientRef);
RealmHelper realmHelper) {
super(context, hostname, realmHelper);
}
@Override
......
package chat.rocket.android.service.observer;
import android.content.Context;
import io.realm.Realm;
import io.realm.RealmResults;
import java.util.List;
import chat.rocket.android.RocketChatCache;
import chat.rocket.android.api.RaixPushHelper;
import chat.rocket.android.helper.LogIfError;
import chat.rocket.android.service.internal.StreamRoomMessageManager;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.persistence.realm.models.internal.GetUsersOfRoomsProcedure;
import chat.rocket.persistence.realm.models.internal.LoadMessageProcedure;
import chat.rocket.persistence.realm.models.internal.MethodCall;
import chat.rocket.persistence.realm.models.internal.RealmSession;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.android.service.DDPClientRef;
import chat.rocket.android.service.internal.StreamRoomMessageManager;
import hugo.weaving.DebugLog;
import io.realm.Realm;
import io.realm.RealmResults;
/**
* Observes user is logged into server.
......@@ -29,13 +29,13 @@ public class SessionObserver extends AbstractModelObserver<RealmSession> {
* constructor.
*/
public SessionObserver(Context context, String hostname,
RealmHelper realmHelper, DDPClientRef ddpClientRef) {
super(context, hostname, realmHelper, ddpClientRef);
RealmHelper realmHelper) {
super(context, hostname, realmHelper);
count = 0;
streamNotifyMessage =
new StreamRoomMessageManager(context, hostname, realmHelper, ddpClientRef);
pushHelper = new RaixPushHelper(realmHelper, ddpClientRef);
new StreamRoomMessageManager(context, hostname, realmHelper);
pushHelper = new RaixPushHelper(realmHelper);
}
@Override
......
package chat.rocket.android.service.observer;
import android.content.Context;
import io.realm.Realm;
import io.realm.RealmResults;
import java.util.List;
import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.android.helper.LogIfError;
import chat.rocket.persistence.realm.models.internal.RealmSession;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.android.service.DDPClientRef;
public class TokenLoginObserver extends AbstractModelObserver<RealmSession> {
private final MethodCallHelper methodCall;
public TokenLoginObserver(Context context, String hostname,
RealmHelper realmHelper, DDPClientRef ddpClientRef) {
super(context, hostname, realmHelper, ddpClientRef);
methodCall = new MethodCallHelper(realmHelper, ddpClientRef);
}
@Override
public RealmResults<RealmSession> queryItems(Realm realm) {
return realm.where(RealmSession.class)
.isNotNull(RealmSession.TOKEN)
.equalTo(RealmSession.TOKEN_VERIFIED, false)
.isNull(RealmSession.ERROR)
.findAll();
}
@Override
public void onUpdateResults(List<RealmSession> results) {
if (results.isEmpty()) {
return;
}
RealmSession session = results.get(0);
methodCall.loginWithToken(session.getToken()).continueWith(new LogIfError());
}
}
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="true"
android:interpolator="@android:anim/decelerate_interpolator">
<translate
android:duration="@android:integer/config_mediumAnimTime"
android:fromYDelta="100%p"
android:toYDelta="0">
</translate>
</set>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_mediumAnimTime"
android:fromYDelta="0%p"
android:toYDelta="0">
</translate>
</set>
\ No newline at end of file
......@@ -27,8 +27,11 @@
<string name="sending">Sending…</string>
<string name="not_synced">Not synced</string>
<string name="failed_to_sync">Failed to sync</string>
<string name="failed_to_delete">Failed to delete</string>
<string name="failed_to_delete_message">The message could not be deleted at this time. Please try again later</string>
<string name="resend">Resend</string>
<string name="discard">Discard</string>
<string name="ok">OK</string>
<plurals name="fmt_dialog_view_latest_message_title">
<item quantity="one">New %d message</item>
......@@ -45,7 +48,7 @@
<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_input_hostname_server_hint">open.rocket.chat</string>
<string name="fragment_input_hostname_connect">CONNECT</string>
<string name="fragment_login_username_or_email">Username or email</string>
<string name="fragment_login_password">Password</string>
......
package chat.rocket.android;
import android.content.Context
import chat.rocket.core.utils.Pair
import org.hamcrest.CoreMatchers.equalTo
import org.json.JSONObject
import org.junit.Assert.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.*
import org.mockito.junit.MockitoJUnitRunner
@RunWith(MockitoJUnitRunner::class)
class RocketChatCacheTest {
lateinit var cache: RocketChatCache
@Before
fun setup() {
val mockedContext = mock(Context::class.java)
val mockAppContext = mock(Context::class.java)
`when`(mockedContext.applicationContext).thenReturn(mockAppContext)
cache = spy(RocketChatCache(mockedContext))
}
@Test
fun getServerList_ShouldReturnHostnameList() {
val hostnameList = JSONObject()
.put("http://demo.rocket.chat", "images/logo/logo.png")
.put("http://192.168.0.6:3000", "images/icon.svg")
.toString()
doReturn(hostnameList).`when`(cache).getString("KEY_HOSTNAME_LIST", null)
val expectedServerList = mutableListOf(
Pair("http://192.168.0.6:3000", "http://192.168.0.6:3000/images/icon.svg"),
Pair("http://demo.rocket.chat", "http://demo.rocket.chat/images/logo/logo.png"))
val serverList = cache.serverList
assertThat(serverList, equalTo(expectedServerList))
}
}
\ No newline at end of file
package chat.rocket.android.push
import android.app.Application
import android.content.Context
import android.content.res.Resources
import android.os.Bundle
import chat.rocket.android.BuildConfig
import chat.rocket.android.R
import chat.rocket.android.push.PushManager.PushMessage
import com.nhaarman.mockito_kotlin.*
import org.amshove.kluent.`should equal`
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyString
import org.mockito.MockitoAnnotations
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment
import org.robolectric.annotation.Config
import kotlin.test.assertTrue
@RunWith(RobolectricTestRunner::class)
@Config(constants = BuildConfig::class,
application = PushManagerTest.StubApplication::class,
sdk = intArrayOf(23))
class PushManagerTest {
val EJSON = """
{
"host":"https://open.rocket.chat/",
"rid":"FaXMyHqbNJbPq6Ym9uWiyfQkgekhXywvKw",
"sender":{
"_id":"uWiFa3adOi0adac",
"username":"jean-luc.picard",
"name":"Jean-Luc Picard"
},
"type":"d",
"name":null
}
"""
val EJSON_NO_SENDER = """
{
"host":"https://open.rocket.chat/",
"rid":"FaXMyHqbNJbPq6Ym9uWiyfQkgekhXywvKw",
"sender":null,
"type":"d",
"name":null
}
"""
lateinit var data: Bundle
lateinit var pushManager: PushManager
lateinit var context: Context
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
data = Bundle()
data.putString("message", "Hello")
data.putString("title", "jean-luc.picard")
data.putString("ejson", EJSON)
data.putString("notId", "1")
context = spy(RuntimeEnvironment.application)
pushManager = spy(PushManager)
val res = mock<Resources> {
on { getColor(any()) } doReturn 0
on { getIdentifier(
anyString(), anyString(), any()) } doReturn R.drawable.notification_icon_background
on { getConfiguration() } doReturn RuntimeEnvironment.application.resources.configuration
}
whenever(context.resources).doReturn(res)
whenever(context.applicationContext).doReturn(context)
}
@Test
fun `should create PushMessage without throwing`() {
PushMessage(null, null, null, null, null, "xxx",
null, null)
}
@Test
fun `given data shoud show notification`() {
pushManager.handle(context, data)
val push = PushMessage(title = data["title"] as String,
message = data["message"] as String, ejson = EJSON, notificationId = "1")
verify(pushManager, times(1)).showNotification(context, push)
}
@Test
fun `given required data is missing do not show notification`() {
val bundle = Bundle()
pushManager.handle(context, bundle)
verify(pushManager, never()).showNotification(any(), any())
bundle.putString("title", "jean-luc.picard")
bundle.putString("message", "Hello!")
pushManager.handle(context, bundle)
verify(pushManager, never()).showNotification(any(), any())
bundle.clear()
bundle.putString("ejson", EJSON)
bundle.putString("message", "Hello!")
pushManager.handle(context, bundle)
verify(pushManager, never()).showNotification(any(), any())
bundle.clear()
bundle.putString("ejson", EJSON)
bundle.putString("title", "jean-luc.picard")
pushManager.handle(context, bundle)
verify(pushManager, never()).showNotification(any(), any())
}
@Test
fun `given data should deserialize correctly`() {
pushManager.handle(context, data)
val push = PushMessage(
data.getString("title"),
data.getString("message"),
null,
EJSON,
null,
data.getString("notId"),
null,
null
)
verify(pushManager, times(1)).showNotification(context, push)
push.title `should equal` "jean-luc.picard"
push.message `should equal` "Hello"
val sender = push.sender
assertTrue(sender != null)
sender?._id `should equal` "uWiFa3adOi0adac"
sender?.name `should equal` "Jean-Luc Picard"
sender?.username `should equal` "jean-luc.picard"
}
@Test
fun `given that only sender is missing show notification`() {
val bundle = Bundle()
bundle.putString("title", "jean-luc.picard")
bundle.putString("message", "Hello")
bundle.putString("ejson", EJSON_NO_SENDER)
pushManager.handle(context, bundle)
verify(pushManager, times(1)).showNotification(any(), any())
}
internal class StubApplication : Application()
}
\ No newline at end of file
......@@ -8,83 +8,83 @@ class RestApiHelperTest {
@Test
fun getEndpointUrlForMessagesTest() {
assertEquals("https://demo.rocket.chat/api/v1/channels.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_CHANNEL, "demo.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/groups.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_GROUP, "demo.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/dm.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_DIRECT_MESSAGE, "demo.rocket.chat"))
assertEquals("https://open.rocket.chat/api/v1/channels.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_CHANNEL, "open.rocket.chat"))
assertEquals("https://open.rocket.chat/api/v1/groups.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_GROUP, "open.rocket.chat"))
assertEquals("https://open.rocket.chat/api/v1/dm.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_DIRECT_MESSAGE, "open.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/channels.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_CHANNEL, "https://demo.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/groups.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_GROUP, "https://demo.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/dm.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_DIRECT_MESSAGE, "https://demo.rocket.chat"))
assertEquals("https://open.rocket.chat/api/v1/channels.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_CHANNEL, "https://open.rocket.chat"))
assertEquals("https://open.rocket.chat/api/v1/groups.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_GROUP, "https://open.rocket.chat"))
assertEquals("https://open.rocket.chat/api/v1/dm.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_DIRECT_MESSAGE, "https://open.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/channels.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_CHANNEL, "http://demo.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/groups.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_GROUP, "http://demo.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/dm.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_DIRECT_MESSAGE, "http://demo.rocket.chat"))
assertEquals("https://open.rocket.chat/api/v1/channels.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_CHANNEL, "http://open.rocket.chat"))
assertEquals("https://open.rocket.chat/api/v1/groups.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_GROUP, "http://open.rocket.chat"))
assertEquals("https://open.rocket.chat/api/v1/dm.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_DIRECT_MESSAGE, "http://open.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/channels.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_CHANNEL, "www.demo.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/groups.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_GROUP, "www.demo.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/dm.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_DIRECT_MESSAGE, "www.demo.rocket.chat"))
assertEquals("https://www.open.rocket.chat/api/v1/channels.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_CHANNEL, "www.open.rocket.chat"))
assertEquals("https://www.open.rocket.chat/api/v1/groups.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_GROUP, "www.open.rocket.chat"))
assertEquals("https://www.open.rocket.chat/api/v1/dm.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_DIRECT_MESSAGE, "www.open.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/channels.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_CHANNEL, "https://www.demo.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/groups.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_GROUP, "https://www.demo.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/dm.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_DIRECT_MESSAGE, "https://www.demo.rocket.chat"))
assertEquals("https://www.open.rocket.chat/api/v1/channels.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_CHANNEL, "https://www.open.rocket.chat"))
assertEquals("https://www.open.rocket.chat/api/v1/groups.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_GROUP, "https://www.open.rocket.chat"))
assertEquals("https://www.open.rocket.chat/api/v1/dm.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_DIRECT_MESSAGE, "https://www.open.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/channels.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_CHANNEL, "http://www.demo.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/groups.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_GROUP, "http://www.demo.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/dm.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_DIRECT_MESSAGE, "http://www.demo.rocket.chat"))
assertEquals("https://www.open.rocket.chat/api/v1/channels.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_CHANNEL, "http://www.open.rocket.chat"))
assertEquals("https://www.open.rocket.chat/api/v1/groups.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_GROUP, "http://www.open.rocket.chat"))
assertEquals("https://www.open.rocket.chat/api/v1/dm.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_DIRECT_MESSAGE, "http://www.open.rocket.chat"))
}
@Test
fun getEndpointUrlForFileListTest() {
assertEquals("https://demo.rocket.chat/api/v1/channels.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_CHANNEL, "demo.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/groups.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_GROUP, "demo.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/dm.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_DIRECT_MESSAGE, "demo.rocket.chat"))
assertEquals("https://open.rocket.chat/api/v1/channels.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_CHANNEL, "open.rocket.chat"))
assertEquals("https://open.rocket.chat/api/v1/groups.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_GROUP, "open.rocket.chat"))
assertEquals("https://open.rocket.chat/api/v1/dm.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_DIRECT_MESSAGE, "open.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/channels.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_CHANNEL, "https://demo.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/groups.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_GROUP, "https://demo.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/dm.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_DIRECT_MESSAGE, "https://demo.rocket.chat"))
assertEquals("https://open.rocket.chat/api/v1/channels.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_CHANNEL, "https://open.rocket.chat"))
assertEquals("https://open.rocket.chat/api/v1/groups.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_GROUP, "https://open.rocket.chat"))
assertEquals("https://open.rocket.chat/api/v1/dm.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_DIRECT_MESSAGE, "https://open.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/channels.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_CHANNEL, "http://demo.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/groups.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_GROUP, "http://demo.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/dm.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_DIRECT_MESSAGE, "http://demo.rocket.chat"))
assertEquals("https://open.rocket.chat/api/v1/channels.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_CHANNEL, "http://open.rocket.chat"))
assertEquals("https://open.rocket.chat/api/v1/groups.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_GROUP, "http://open.rocket.chat"))
assertEquals("https://open.rocket.chat/api/v1/dm.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_DIRECT_MESSAGE, "http://open.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/channels.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_CHANNEL, "www.demo.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/groups.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_GROUP, "www.demo.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/dm.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_DIRECT_MESSAGE, "www.demo.rocket.chat"))
assertEquals("https://www.open.rocket.chat/api/v1/channels.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_CHANNEL, "www.open.rocket.chat"))
assertEquals("https://www.open.rocket.chat/api/v1/groups.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_GROUP, "www.open.rocket.chat"))
assertEquals("https://www.open.rocket.chat/api/v1/dm.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_DIRECT_MESSAGE, "www.open.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/channels.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_CHANNEL, "https://www.demo.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/groups.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_GROUP, "https://www.demo.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/dm.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_DIRECT_MESSAGE, "https://www.demo.rocket.chat"))
assertEquals("https://www.open.rocket.chat/api/v1/channels.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_CHANNEL, "https://www.open.rocket.chat"))
assertEquals("https://www.open.rocket.chat/api/v1/groups.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_GROUP, "https://www.open.rocket.chat"))
assertEquals("https://www.open.rocket.chat/api/v1/dm.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_DIRECT_MESSAGE, "https://www.open.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/channels.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_CHANNEL, "http://www.demo.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/groups.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_GROUP, "http://www.demo.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/dm.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_DIRECT_MESSAGE, "http://www.demo.rocket.chat"))
assertEquals("https://www.open.rocket.chat/api/v1/channels.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_CHANNEL, "http://www.open.rocket.chat"))
assertEquals("https://www.open.rocket.chat/api/v1/groups.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_GROUP, "http://www.open.rocket.chat"))
assertEquals("https://www.open.rocket.chat/api/v1/dm.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_DIRECT_MESSAGE, "http://www.open.rocket.chat"))
}
@Test
fun getEndpointUrlForMemberListTest() {
assertEquals("https://demo.rocket.chat/api/v1/channels.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_CHANNEL, "demo.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/groups.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_GROUP, "demo.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/dm.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_DIRECT_MESSAGE, "demo.rocket.chat"))
assertEquals("https://open.rocket.chat/api/v1/channels.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_CHANNEL, "open.rocket.chat"))
assertEquals("https://open.rocket.chat/api/v1/groups.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_GROUP, "open.rocket.chat"))
assertEquals("https://open.rocket.chat/api/v1/dm.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_DIRECT_MESSAGE, "open.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/channels.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_CHANNEL, "https://demo.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/groups.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_GROUP, "https://demo.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/dm.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_DIRECT_MESSAGE, "https://demo.rocket.chat"))
assertEquals("https://open.rocket.chat/api/v1/channels.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_CHANNEL, "https://open.rocket.chat"))
assertEquals("https://open.rocket.chat/api/v1/groups.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_GROUP, "https://open.rocket.chat"))
assertEquals("https://open.rocket.chat/api/v1/dm.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_DIRECT_MESSAGE, "https://open.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/channels.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_CHANNEL, "http://demo.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/groups.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_GROUP, "http://demo.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/dm.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_DIRECT_MESSAGE, "http://demo.rocket.chat"))
assertEquals("https://open.rocket.chat/api/v1/channels.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_CHANNEL, "http://open.rocket.chat"))
assertEquals("https://open.rocket.chat/api/v1/groups.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_GROUP, "http://open.rocket.chat"))
assertEquals("https://open.rocket.chat/api/v1/dm.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_DIRECT_MESSAGE, "http://open.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/channels.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_CHANNEL, "www.demo.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/groups.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_GROUP, "www.demo.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/dm.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_DIRECT_MESSAGE, "www.demo.rocket.chat"))
assertEquals("https://www.open.rocket.chat/api/v1/channels.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_CHANNEL, "www.open.rocket.chat"))
assertEquals("https://www.open.rocket.chat/api/v1/groups.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_GROUP, "www.open.rocket.chat"))
assertEquals("https://www.open.rocket.chat/api/v1/dm.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_DIRECT_MESSAGE, "www.open.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/channels.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_CHANNEL, "https://www.demo.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/groups.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_GROUP, "https://www.demo.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/dm.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_DIRECT_MESSAGE, "https://www.demo.rocket.chat"))
assertEquals("https://www.open.rocket.chat/api/v1/channels.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_CHANNEL, "https://www.open.rocket.chat"))
assertEquals("https://www.open.rocket.chat/api/v1/groups.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_GROUP, "https://www.open.rocket.chat"))
assertEquals("https://www.open.rocket.chat/api/v1/dm.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_DIRECT_MESSAGE, "https://www.open.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/channels.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_CHANNEL, "http://www.demo.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/groups.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_GROUP, "http://www.demo.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/dm.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_DIRECT_MESSAGE, "http://www.demo.rocket.chat"))
assertEquals("https://www.open.rocket.chat/api/v1/channels.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_CHANNEL, "http://www.open.rocket.chat"))
assertEquals("https://www.open.rocket.chat/api/v1/groups.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_GROUP, "http://www.open.rocket.chat"))
assertEquals("https://www.open.rocket.chat/api/v1/dm.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_DIRECT_MESSAGE, "http://www.open.rocket.chat"))
}
@Test
......
......@@ -7,31 +7,31 @@ class UrlHelperTest {
@Test
fun removeUriSchemeTest() {
assertEquals("demo.rocket.chat", UrlHelper.removeUriScheme("https://demo.rocket.chat"))
assertEquals("demo.rocket.chat", UrlHelper.removeUriScheme("http://demo.rocket.chat"))
assertEquals("demo.rocket.chat", UrlHelper.removeUriScheme("demo.rocket.chat"))
assertEquals("open.rocket.chat", UrlHelper.removeUriScheme("https://open.rocket.chat"))
assertEquals("open.rocket.chat", UrlHelper.removeUriScheme("http://open.rocket.chat"))
assertEquals("open.rocket.chat", UrlHelper.removeUriScheme("open.rocket.chat"))
}
@Test
fun getSafeHostnameTest() {
assertEquals("https://demo.rocket.chat", UrlHelper.getSafeHostname("https://demo.rocket.chat"))
assertEquals("https://demo.rocket.chat", UrlHelper.getSafeHostname("http://demo.rocket.chat"))
assertEquals("https://demo.rocket.chat", UrlHelper.getSafeHostname("demo.rocket.chat"))
assertEquals("https://open.rocket.chat", UrlHelper.getSafeHostname("https://open.rocket.chat"))
assertEquals("https://open.rocket.chat", UrlHelper.getSafeHostname("http://open.rocket.chat"))
assertEquals("https://open.rocket.chat", UrlHelper.getSafeHostname("open.rocket.chat"))
}
@Test
fun getUrlTest() {
assertEquals("https://demo.rocket.chat/GENERAL/file.txt", UrlHelper.getSafeUrl("https://demo.rocket.chat/GENERAL/file.txt"))
assertEquals("http://demo.rocket.chat/GENERAL/file.txt", UrlHelper.getSafeUrl("http://demo.rocket.chat/GENERAL/file.txt"))
assertEquals("demo.rocket.chat/GENERAL/file.txt", UrlHelper.getSafeUrl("demo.rocket.chat/GENERAL/file.txt"))
assertEquals("demo.rocket.chat/GENERAL/a%20sample%20file.txt", UrlHelper.getSafeUrl("demo.rocket.chat/GENERAL/a sample file.txt"))
assertEquals("demo.rocket.chat/GENERAL/file.txt", UrlHelper.getSafeUrl("demo.rocket.chat\\/GENERAL\\/file.txt"))
assertEquals("https://open.rocket.chat/GENERAL/file.txt", UrlHelper.getSafeUrl("https://open.rocket.chat/GENERAL/file.txt"))
assertEquals("http://open.rocket.chat/GENERAL/file.txt", UrlHelper.getSafeUrl("http://open.rocket.chat/GENERAL/file.txt"))
assertEquals("open.rocket.chat/GENERAL/file.txt", UrlHelper.getSafeUrl("open.rocket.chat/GENERAL/file.txt"))
assertEquals("open.rocket.chat/GENERAL/a%20sample%20file.txt", UrlHelper.getSafeUrl("open.rocket.chat/GENERAL/a sample file.txt"))
assertEquals("open.rocket.chat/GENERAL/file.txt", UrlHelper.getSafeUrl("open.rocket.chat\\/GENERAL\\/file.txt"))
}
@Test
fun getAttachmentLinkTest() {
assertEquals("https://demo.rocket.chat/file-upload/aFileId/aFileName.txt", UrlHelper.getAttachmentLink("https://demo.rocket.chat", "aFileId", "aFileName.txt"))
assertEquals("https://demo.rocket.chat/file-upload/aFileId/aFileName.txt", UrlHelper.getAttachmentLink("http://demo.rocket.chat", "aFileId", "aFileName.txt"))
assertEquals("https://demo.rocket.chat/file-upload/aFileId/aFileName.txt", UrlHelper.getAttachmentLink("demo.rocket.chat", "aFileId", "aFileName.txt"))
assertEquals("https://open.rocket.chat/file-upload/aFileId/aFileName.txt", UrlHelper.getAttachmentLink("https://open.rocket.chat", "aFileId", "aFileName.txt"))
assertEquals("https://open.rocket.chat/file-upload/aFileId/aFileName.txt", UrlHelper.getAttachmentLink("http://open.rocket.chat", "aFileId", "aFileName.txt"))
assertEquals("https://open.rocket.chat/file-upload/aFileId/aFileName.txt", UrlHelper.getAttachmentLink("open.rocket.chat", "aFileId", "aFileName.txt"))
}
}
\ No newline at end of file
// Top-level build file where you can add configuration options common to all sub-projects/modules.
apply from: rootProject.file('dependencies.gradle')
ext {
compileSdkVersion = 26
targetSdkVersion = 26
buildToolsVersion = "26.0.2"
}
buildscript {
repositories {
google()
jcenter()
maven { url 'https://maven.fabric.io/public' }
maven { url 'http://oss.jfrog.org/artifactory/oss-snapshot-local' }
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.1.60"
classpath 'io.realm:realm-gradle-plugin:4.2.0'
classpath 'com.jakewharton.hugo:hugo-plugin:1.2.1'
classpath 'com.google.gms:google-services:3.0.0'
classpath 'com.github.triplet.gradle:play-publisher:1.1.5'
classpath 'io.fabric.tools:gradle:1.+'
}
// Exclude the version that the android plugin depends on.
configurations.classpath.exclude group: 'com.android.tools.external.lombok'
}
allprojects {
repositories {
google()
jcenter()
mavenCentral()
maven { url 'https://github.com/lijingle1/stetho-realm/raw/master/maven-repo' }
maven { url 'http://dl.bintray.com/amulyakhare/maven' } //for TextDrawable.
maven { url "https://clojars.org/repo/" } //for icepick.
maven { url 'https://jitpack.io' } //for widget-fontawesome.
maven { url "https://maven.google.com" } // for Support Library.
maven { url 'http://oss.jfrog.org/artifactory/oss-snapshot-local' } //for widget-fontawesome.
}
}
// This block encapsulates custom properties and makes them available to all modules in the project.
// You can also create properties to specify versions for dependencies.
// Having consistent versions between modules can avoid conflicts with behavior.
ext {
compileSdkVersion = 26
targetSdkVersion = 26
buildToolsVersion = "26.0.0"
}
task clean(type: Delete) {
delete rootProject.buildDir
......
......@@ -7,10 +7,15 @@
machine:
environment:
ANDROID_HOME: /usr/local/android-sdk-linux
GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx1536m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError"'
GRADLE_OPTS: '-Xmx1024m -Dorg.gradle.jvmargs="-Xmx1024m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError"'
JAVA_OPTS: "-Xms518m -Xmx1536m"
dependencies:
pre:
- sudo service mysql stop; sleep 5
- sudo service mongod stop; sleep 5
- sudo killall postgres; sleep 5
- git fetch --tags
- echo "sdk.dir="$ANDROID_HOME > local.properties
......@@ -26,10 +31,10 @@ dependencies:
- echo y | android update sdk --no-ui --all --filter android-26
- echo y | android update sdk --no-ui --all --filter extra-android-m2repository,extra-android-support
- echo y | android update sdk --no-ui --all --filter extra-google-m2repository,extra-google-google_play_services
- echo y | android update sdk --no-ui --all --filter build-tools-26.0.0
- echo y | android update sdk --no-ui --all --filter build-tools-26.0.2
cache_directories:
- /usr/local/android-sdk-linux/tools
- /usr/local/android-sdk-linux/build-tools/26.0.0
- /usr/local/android-sdk-linux/build-tools/26.0.2
test:
override:
......
ext {
preDexLibs = "true" != System.getenv("CI")
supportLibraryVersion = "25.4.0"
supportLibraryVersion = "27.0.1"
constraintLayoutVersion = "1.0.2"
kotlinVersion = "1.1.4-2"
kotlinVersion = "1.1.51"
okHttpVersion = "3.9.0"
rxbindingVersion = '2.0.0'
supportDependencies = [
designSupportLibrary: "com.android.support:design:${supportLibraryVersion}",
annotation : "com.android.support:support-annotations:${supportLibraryVersion}",
constrainLayout : "com.android.support.constraint:constraint-layout:${constraintLayoutVersion}",
kotlin : "org.jetbrains.kotlin:kotlin-stdlib-jre7:${kotlinVersion}",
constraintLayout : "com.android.support.constraint:constraint-layout:${constraintLayoutVersion}",
cardView : "com.android.support:cardview-v7:${supportLibraryVersion}",
supportV13 : "com.android.support:support-v13:${supportLibraryVersion}",
multidex : "com.android.support:multidex:1.0.2"
]
extraDependencies = [
okHTTP : "com.squareup.okhttp3:okhttp:3.8.0",
okHTTP : "com.squareup.okhttp3:okhttp:${okHttpVersion}",
rxJava : "io.reactivex.rxjava2:rxjava:2.1.0",
boltTask : "com.parse.bolts:bolts-tasks:1.4.0",
rxAndroid : "io.reactivex.rxjava2:rxandroid:2.0.1",
textDrawable : "com.github.rocketchat:textdrawable:1.0.2"
textDrawable : "com.github.rocketchat:textdrawable:1.0.2",
optional : "com.hadisatrio:Optional:v1.0.1"
]
rxbindingDependencies = [
rxBinding : "com.jakewharton.rxbinding2:rxbinding:${rxbindingVersion}",
......@@ -25,6 +28,7 @@ ext {
rxBindingAppcompact: "com.jakewharton.rxbinding2:rxbinding-appcompat-v7:${rxbindingVersion}",
]
}
subprojects {
project.plugins.whenPluginAdded { plugin ->
if ("com.android.build.gradle.AppPlugin" == plugin.class.name) {
......
#Mon Dec 28 10:00:20 PST 2015
#Tue Nov 07 03:05:05 BRST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-3.4-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
apply plugin: 'com.android.library'
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'
}
}
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
......
......@@ -2,21 +2,6 @@ apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'realm-android'
apply plugin: 'com.jakewharton.hugo'
apply plugin: 'me.tatarka.retrolambda'
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$rootProject.ext.kotlinVersion"
classpath 'io.realm:realm-gradle-plugin:2.3.2'
classpath 'me.tatarka:gradle-retrolambda:3.5.0'
classpath 'me.tatarka.retrolambda.projectlombok:lombok.ast:0.2.3.a2'
classpath 'com.jakewharton.hugo:hugo-plugin:1.2.1'
}
}
android {
compileSdkVersion rootProject.ext.compileSdkVersion
......@@ -51,13 +36,11 @@ dependencies {
compile extraDependencies.boltTask
compile supportDependencies.annotation
compile supportDependencies.designSupportLibrary
compile supportDependencies.kotlin
compile extraDependencies.rxAndroid
provided extraDependencies.optional
testCompile "org.jetbrains.kotlin:kotlin-test:$rootProject.ext.kotlinVersion"
testCompile "org.jetbrains.kotlin:kotlin-test-junit:$rootProject.ext.kotlinVersion"
testCompile 'org.json:json:20170516'
testCompile 'org.skyscreamer:jsonassert:1.5.0'
compile 'com.github.akarnokd:rxjava2-interop:0.10.0'
provided 'com.hadisatrio:Optional:v1.0.1'
testCompile 'junit:junit:4.12'
}
package chat.rocket.persistence.realm;
import io.realm.DynamicRealm;
import io.realm.FieldAttribute;
import io.realm.RealmMigration;
import io.realm.RealmObjectSchema;
import io.realm.RealmSchema;
import chat.rocket.persistence.realm.models.ddp.RealmMessage;
import chat.rocket.persistence.realm.models.ddp.RealmPermission;
import chat.rocket.persistence.realm.models.ddp.RealmRole;
import chat.rocket.persistence.realm.models.ddp.RealmRoomRole;
import chat.rocket.persistence.realm.models.ddp.RealmSpotlightRoom;
import chat.rocket.persistence.realm.models.ddp.RealmSpotlightUser;
import chat.rocket.persistence.realm.models.ddp.RealmUser;
import io.realm.DynamicRealm;
import io.realm.FieldAttribute;
import io.realm.RealmMigration;
import io.realm.RealmObjectSchema;
import io.realm.RealmSchema;
public class Migration implements RealmMigration {
@Override
......@@ -68,6 +68,20 @@ public class Migration implements RealmMigration {
if (oldVersion == 4) {
RealmObjectSchema messageSchema = schema.get("RealmMessage");
messageSchema.addField(RealmMessage.EDITED_AT, long.class);
oldVersion++;
}
if (oldVersion == 5) {
RealmObjectSchema userSchema = schema.get("RealmUser");
try {
userSchema.addField(RealmUser.NAME, String.class);
} catch (IllegalArgumentException e) {
if (BuildConfig.DEBUG) {
e.printStackTrace();
}
// ignore; it makes here if the schema for this model was already update before without migration
}
}
}
......
......@@ -8,13 +8,14 @@ import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Filter;
import android.widget.TextView;
import io.realm.Realm;
import io.realm.RealmObject;
import io.realm.RealmResults;
import java.util.Collections;
import java.util.List;
import io.realm.Realm;
import io.realm.RealmObject;
import io.realm.RealmResults;
/**
* ListAdapter for AutoCompleteTextView.
*/
......
......@@ -3,18 +3,23 @@ package chat.rocket.persistence.realm;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Looper;
import io.realm.Realm;
import io.realm.RealmConfiguration;
import io.realm.RealmObject;
import io.realm.RealmQuery;
import io.realm.RealmResults;
import org.json.JSONException;
import java.util.Collections;
import java.util.List;
import bolts.Task;
import bolts.TaskCompletionSource;
import chat.rocket.android.log.RCLog;
import io.reactivex.Flowable;
import io.realm.Realm;
import io.realm.RealmConfiguration;
import io.realm.RealmModel;
import io.realm.RealmObject;
import io.realm.RealmQuery;
import io.realm.RealmResults;
import io.realm.log.RealmLog;
@SuppressLint("NewApi")
public class RealmHelper {
......@@ -164,6 +169,25 @@ public class RealmHelper {
return constructor.getNewInstance(context).initializeWith(this, filter);
}
public static <T extends RealmModel> Flowable<T> copyToRealmOrUpdate(Realm realm, T objectToCopy) {
return Flowable.defer(() -> {
realm.beginTransaction();
try {
T object = realm.copyToRealmOrUpdate(objectToCopy);
realm.commitTransaction();
return Flowable.just(object);
} catch (Throwable e) {
if (realm.isInTransaction()) {
realm.cancelTransaction();
} else {
RealmLog.warn("Could not cancel transaction, not currently in a transaction.");
}
throw e;
}
});
}
public interface Transaction<T> {
T execute(Realm realm) throws JSONException;
}
......
package chat.rocket.persistence.realm;
import java.util.List;
import io.realm.Realm;
import io.realm.RealmChangeListener;
import io.realm.RealmObject;
import io.realm.RealmResults;
import java.util.List;
public class RealmListObserver<T extends RealmObject> extends AbstractRealmResultsObserver<T> {
private final Query<T> query;
private OnUpdateListener<T> onUpdateListener;
......
package chat.rocket.persistence.realm;
import io.realm.Realm;
import io.realm.RealmConfiguration;
import java.util.HashMap;
import chat.rocket.persistence.realm.modules.RocketChatLibraryModule;
import chat.rocket.persistence.realm.modules.RocketChatServerModule;
import io.realm.Realm;
import io.realm.RealmConfiguration;
public class RealmStore {
public static HashMap<String, RealmConfiguration> sStore = new HashMap<>();
......@@ -15,9 +15,7 @@ public class RealmStore {
.name(name + ".realm")
.modules(new RocketChatLibraryModule())
.migration(new Migration())
.schemaVersion(5)
// Just in case
.deleteRealmIfMigrationNeeded()
.schemaVersion(6)
.build();
}
......@@ -49,7 +47,7 @@ public class RealmStore {
sStore.put(name, new RealmConfiguration.Builder()
.name(name + ".realm")
.modules(new RocketChatServerModule())
.deleteRealmIfMigrationNeeded().build());
.build());
}
return new RealmHelper(sStore.get(name));
}
......
package chat.rocket.persistence.realm;
import android.content.Context;
import io.realm.Realm;
import io.realm.RealmConfiguration;
......
......@@ -2,17 +2,18 @@ package chat.rocket.persistence.realm.models;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import io.realm.Realm;
import io.realm.RealmObject;
import io.realm.annotations.PrimaryKey;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
import chat.rocket.core.models.ServerInfo;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.persistence.realm.RealmStore;
import io.realm.Realm;
import io.realm.RealmObject;
import io.realm.annotations.PrimaryKey;
/**
* Backend implementation to store ServerInfo.
......@@ -41,7 +42,7 @@ public class RealmBasedServerInfo extends RealmObject {
.build();
}
public static Realm getRealm() {
public static Realm getServerRealm() {
return RealmStore.getRealm(DB_NAME);
}
......
package chat.rocket.persistence.realm.models.ddp;
import chat.rocket.core.models.Email;
import io.realm.RealmObject;
import io.realm.annotations.PrimaryKey;
import chat.rocket.core.models.Email;
/**
* Login-RealmUser's email.
*/
......
package chat.rocket.persistence.realm.models.ddp;
import io.realm.RealmObject;
import io.realm.annotations.PrimaryKey;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
......@@ -10,6 +8,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import chat.rocket.core.JsonConstants;
import chat.rocket.core.SyncState;
import chat.rocket.core.models.Attachment;
......@@ -21,6 +20,8 @@ import chat.rocket.core.models.WebContent;
import chat.rocket.core.models.WebContentHeaders;
import chat.rocket.core.models.WebContentMeta;
import chat.rocket.core.models.WebContentParsedUrl;
import io.realm.RealmObject;
import io.realm.annotations.PrimaryKey;
/**
* RealmMessage.
......@@ -191,7 +192,7 @@ public class RealmMessage extends RealmObject {
.setRoomId(rid)
.setSyncState(syncstate)
.setTimestamp(ts)
.setMessage(msg)
.setMessage(msg == null ? "" : msg)
.setUser(u != null ? u.asUser() : null)
.setGroupable(groupable)
.setAlias(alias)
......
package chat.rocket.persistence.realm.models.ddp;
import chat.rocket.core.models.LoginServiceConfiguration;
import io.realm.RealmObject;
import io.realm.annotations.PrimaryKey;
import chat.rocket.core.models.LoginServiceConfiguration;
/**
* subscription model for "meteor_accounts_loginServiceConfiguration".
*/
......
package chat.rocket.persistence.realm.models.ddp;
import io.realm.RealmList;
import io.realm.RealmObject;
import io.realm.annotations.PrimaryKey;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
import chat.rocket.core.models.Permission;
import chat.rocket.core.models.Role;
import io.realm.RealmList;
import io.realm.RealmObject;
import io.realm.annotations.PrimaryKey;
public class RealmPermission extends RealmObject {
......
package chat.rocket.persistence.realm.models.ddp;
import chat.rocket.core.models.Preferences;
import io.realm.RealmObject;
import io.realm.annotations.PrimaryKey;
import chat.rocket.core.models.Preferences;
@SuppressWarnings({"PMD.ShortVariable"})
public class RealmPreferences extends RealmObject {
......
package chat.rocket.persistence.realm.models.ddp;
import android.support.annotation.Nullable;
import io.realm.RealmObject;
import io.realm.annotations.PrimaryKey;
import org.json.JSONException;
import org.json.JSONObject;
import chat.rocket.core.JsonConstants;
import chat.rocket.core.models.PublicSetting;
import chat.rocket.persistence.realm.RealmHelper;
import io.realm.RealmObject;
import io.realm.annotations.PrimaryKey;
/**
* public setting model.
......
package chat.rocket.persistence.realm.models.ddp;
import io.realm.RealmObject;
import io.realm.annotations.PrimaryKey;
import org.json.JSONException;
import org.json.JSONObject;
import chat.rocket.core.models.Role;
import io.realm.RealmObject;
import io.realm.annotations.PrimaryKey;
public class RealmRole extends RealmObject {
......
package chat.rocket.persistence.realm.models.ddp;
import io.realm.RealmObject;
import io.realm.annotations.PrimaryKey;
import org.json.JSONException;
import org.json.JSONObject;
import chat.rocket.core.JsonConstants;
import chat.rocket.core.models.Room;
import io.realm.RealmObject;
import io.realm.annotations.PrimaryKey;
/**
* Chat Room(Subscription).
......@@ -142,7 +142,7 @@ public class RealmRoom extends RealmObject {
return Room.builder()
.setId(_id)
.setRoomId(rid)
.setName(name)
.setName(name == null ? "" : name)
.setType(t)
.setOpen(open)
.setAlert(alert)
......
package chat.rocket.persistence.realm.models.ddp;
import io.realm.RealmList;
import io.realm.RealmObject;
import io.realm.annotations.PrimaryKey;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
import chat.rocket.core.models.Role;
import chat.rocket.core.models.RoomRole;
import io.realm.RealmList;
import io.realm.RealmObject;
import io.realm.annotations.PrimaryKey;
public class RealmRoomRole extends RealmObject {
......
package chat.rocket.persistence.realm.models.ddp;
import chat.rocket.core.models.Settings;
import io.realm.RealmObject;
import io.realm.annotations.PrimaryKey;
import chat.rocket.core.models.Settings;
@SuppressWarnings({"PMD.ShortVariable"})
public class RealmSettings extends RealmObject {
......
package chat.rocket.persistence.realm.models.ddp;
import chat.rocket.core.models.SpotlightRoom;
import io.realm.RealmObject;
import io.realm.annotations.PrimaryKey;
import chat.rocket.core.models.SpotlightRoom;
public class RealmSpotlightRoom extends RealmObject {
public interface Columns {
......
package chat.rocket.persistence.realm.models.ddp;
import chat.rocket.core.models.SpotlightUser;
import io.realm.RealmObject;
import io.realm.annotations.PrimaryKey;
import chat.rocket.core.models.SpotlightUser;
public class RealmSpotlightUser extends RealmObject {
public interface Columns {
......
package chat.rocket.persistence.realm.models.internal;
import io.realm.Realm;
import io.realm.RealmObject;
import io.realm.RealmQuery;
import io.realm.annotations.PrimaryKey;
import org.json.JSONException;
import org.json.JSONObject;
import chat.rocket.core.SyncState;
import io.realm.Realm;
import io.realm.RealmObject;
import io.realm.RealmQuery;
import io.realm.annotations.PrimaryKey;
/**
* just stores gcm registration status.
......
package chat.rocket.persistence.realm.models.internal;
import chat.rocket.core.models.RoomHistoryState;
import io.realm.RealmObject;
import io.realm.annotations.PrimaryKey;
import chat.rocket.core.models.RoomHistoryState;
/**
* Load messages in the room.
*/
......
package chat.rocket.persistence.realm.models.internal;
import android.text.TextUtils;
import io.realm.RealmObject;
import io.realm.annotations.PrimaryKey;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.UUID;
import bolts.Task;
import bolts.TaskCompletionSource;
import chat.rocket.android.log.RCLog;
......@@ -14,6 +14,8 @@ import chat.rocket.core.SyncState;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.persistence.realm.RealmObjectObserver;
import chat.rocket.persistence.realm.helpers.LogcatIfError;
import io.realm.RealmObject;
import io.realm.annotations.PrimaryKey;
public class MethodCall extends RealmObject {
......
package chat.rocket.persistence.realm.models.internal;
import android.text.TextUtils;
import io.realm.Realm;
import io.realm.RealmObject;
import io.realm.RealmQuery;
import io.realm.annotations.PrimaryKey;
import org.json.JSONObject;
import chat.rocket.core.models.Session;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.persistence.realm.helpers.LogcatIfError;
import hugo.weaving.DebugLog;
import io.realm.Realm;
import io.realm.RealmObject;
import io.realm.RealmQuery;
import io.realm.annotations.PrimaryKey;
/**
* Login session info.
......
package chat.rocket.persistence.realm.modules;
import io.realm.annotations.RealmModule;
import chat.rocket.persistence.realm.models.RealmBasedServerInfo;
import io.realm.annotations.RealmModule;
@RealmModule(library = true, classes = {RealmBasedServerInfo.class})
public class RocketChatServerModule {
......
......@@ -2,19 +2,20 @@ package chat.rocket.persistence.realm.repositories;
import android.os.Looper;
import android.support.v4.util.Pair;
import com.hadisatrio.optional.Optional;
import io.reactivex.Flowable;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.realm.RealmResults;
import java.util.ArrayList;
import java.util.List;
import chat.rocket.core.models.LoginServiceConfiguration;
import chat.rocket.core.repositories.LoginServiceConfigurationRepository;
import chat.rocket.persistence.realm.RealmStore;
import chat.rocket.persistence.realm.models.ddp.RealmMeteorLoginServiceConfiguration;
import hu.akarnokd.rxjava.interop.RxJavaInterop;
import io.reactivex.Flowable;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.realm.RealmResults;
public class RealmLoginServiceConfigurationRepository extends RealmRepository
implements LoginServiceConfigurationRepository {
......@@ -34,11 +35,10 @@ public class RealmLoginServiceConfigurationRepository extends RealmRepository
return Flowable.empty();
}
return RxJavaInterop.toV2Flowable(
pair.first.where(RealmMeteorLoginServiceConfiguration.class)
return pair.first.where(RealmMeteorLoginServiceConfiguration.class)
.equalTo(RealmMeteorLoginServiceConfiguration.SERVICE, serviceName)
.findAll()
.<RealmResults<RealmMeteorLoginServiceConfiguration>>asObservable());
.<RealmResults<RealmMeteorLoginServiceConfiguration>>asFlowable();
},
pair -> close(pair.first, pair.second)
)
......@@ -57,10 +57,9 @@ public class RealmLoginServiceConfigurationRepository extends RealmRepository
return Flowable.empty();
}
return RxJavaInterop
.toV2Flowable(pair.first.where(RealmMeteorLoginServiceConfiguration.class)
return pair.first.where(RealmMeteorLoginServiceConfiguration.class)
.findAll()
.asObservable());
.asFlowable();
},
pair -> close(pair.first, pair.second)
)
......
......@@ -5,24 +5,24 @@ import android.support.v4.util.Pair;
import com.hadisatrio.optional.Optional;
import io.reactivex.Flowable;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.realm.Realm;
import io.realm.RealmResults;
import io.realm.Sort;
import java.util.ArrayList;
import java.util.List;
import chat.rocket.core.SyncState;
import chat.rocket.core.models.Message;
import chat.rocket.core.models.Room;
import chat.rocket.core.models.User;
import chat.rocket.core.repositories.MessageRepository;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.persistence.realm.RealmStore;
import chat.rocket.persistence.realm.models.ddp.RealmMessage;
import chat.rocket.persistence.realm.models.ddp.RealmUser;
import hu.akarnokd.rxjava.interop.RxJavaInterop;
import io.reactivex.Flowable;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.realm.Realm;
import io.realm.RealmResults;
import io.realm.Sort;
public class RealmMessageRepository extends RealmRepository implements MessageRepository {
......@@ -41,11 +41,10 @@ public class RealmMessageRepository extends RealmRepository implements MessageRe
return Flowable.empty();
}
return RxJavaInterop.toV2Flowable(
pair.first.where(RealmMessage.class)
return pair.first.where(RealmMessage.class)
.equalTo(RealmMessage.ID, messageId)
.findAll()
.<RealmResults<RealmMessage>>asObservable());
.<RealmResults<RealmMessage>>asFlowable();
},
pair -> close(pair.first, pair.second)
)
......@@ -90,16 +89,12 @@ public class RealmMessageRepository extends RealmRepository implements MessageRe
}
realmMessage.setUser(realmUser);
realm.beginTransaction();
final RealmMessage messageToSave = realmMessage;
return RxJavaInterop.toV2Flowable(realm.copyToRealmOrUpdate(realmMessage)
.asObservable())
return RealmHelper.copyToRealmOrUpdate(realm, messageToSave)
.filter(it -> it.isLoaded() && it.isValid())
.firstElement()
.doOnSuccess(it -> realm.commitTransaction())
.doOnError(throwable -> realm.cancelTransaction())
.first(new RealmMessage())
.doOnEvent((realmObject, throwable) -> close(realm, looper))
.toSingle()
.map(realmObject -> true);
});
}
......@@ -116,10 +111,10 @@ public class RealmMessageRepository extends RealmRepository implements MessageRe
realm.beginTransaction();
return RxJavaInterop.toV2Flowable(realm.where(RealmMessage.class)
return realm.where(RealmMessage.class)
.equalTo(RealmMessage.ID, message.getId())
.findAll()
.<RealmResults<RealmMessage>>asObservable())
.<RealmResults<RealmMessage>>asFlowable()
.filter(realmObject -> realmObject.isLoaded() && realmObject.isValid())
.firstElement()
.toSingle()
......@@ -144,11 +139,13 @@ public class RealmMessageRepository extends RealmRepository implements MessageRe
return Flowable.empty();
}
return RxJavaInterop.toV2Flowable(pair.first.where(RealmMessage.class)
return pair.first.where(RealmMessage.class)
.notEqualTo(RealmMessage.SYNC_STATE, SyncState.DELETE_NOT_SYNCED)
.notEqualTo(RealmMessage.SYNC_STATE, SyncState.DELETING)
.equalTo(RealmMessage.ROOM_ID, room.getRoomId())
.isNotNull(RealmMessage.USER)
.findAllSorted(RealmMessage.TIMESTAMP, Sort.DESCENDING)
.asObservable());
.asFlowable();
},
pair -> close(pair.first, pair.second)
)
......@@ -167,12 +164,12 @@ public class RealmMessageRepository extends RealmRepository implements MessageRe
return Flowable.empty();
}
return RxJavaInterop.toV2Flowable(pair.first.where(RealmMessage.class)
return pair.first.where(RealmMessage.class)
.equalTo(RealmMessage.ROOM_ID, room.getId())
.greaterThanOrEqualTo(RealmMessage.TIMESTAMP, room.getLastSeen())
.notEqualTo(RealmMessage.USER_ID, user.getId())
.findAll()
.asObservable());
.asFlowable();
},
pair -> close(pair.first, pair.second)
)
......
......@@ -2,17 +2,17 @@ package chat.rocket.persistence.realm.repositories;
import android.os.Looper;
import android.support.v4.util.Pair;
import com.hadisatrio.optional.Optional;
import io.reactivex.Flowable;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.realm.RealmResults;
import chat.rocket.core.models.Permission;
import chat.rocket.core.repositories.PermissionRepository;
import chat.rocket.persistence.realm.RealmStore;
import chat.rocket.persistence.realm.models.ddp.RealmPermission;
import hu.akarnokd.rxjava.interop.RxJavaInterop;
import io.reactivex.Flowable;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.realm.RealmResults;
public class RealmPermissionRepository extends RealmRepository implements PermissionRepository {
......@@ -30,11 +30,10 @@ public class RealmPermissionRepository extends RealmRepository implements Permis
if (pair.first == null) {
return Flowable.empty();
}
return RxJavaInterop.toV2Flowable(
pair.first.where(RealmPermission.class)
return pair.first.where(RealmPermission.class)
.equalTo(RealmPermission.Columns.ID, id)
.findAll()
.<RealmResults<RealmPermission>>asObservable());
.<RealmResults<RealmPermission>>asFlowable();
},
pair -> close(pair.first, pair.second)
)
......
......@@ -2,17 +2,17 @@ package chat.rocket.persistence.realm.repositories;
import android.os.Looper;
import android.support.v4.util.Pair;
import com.hadisatrio.optional.Optional;
import io.reactivex.Flowable;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.realm.RealmResults;
import chat.rocket.core.models.PublicSetting;
import chat.rocket.core.repositories.PublicSettingRepository;
import chat.rocket.persistence.realm.RealmStore;
import chat.rocket.persistence.realm.models.ddp.RealmPublicSetting;
import hu.akarnokd.rxjava.interop.RxJavaInterop;
import io.reactivex.Flowable;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.realm.RealmResults;
public class RealmPublicSettingRepository extends RealmRepository
implements PublicSettingRepository {
......@@ -31,11 +31,10 @@ public class RealmPublicSettingRepository extends RealmRepository
if (pair.first == null) {
return Flowable.empty();
}
return RxJavaInterop.toV2Flowable(
pair.first.where(RealmPublicSetting.class)
return pair.first.where(RealmPublicSetting.class)
.equalTo(RealmPublicSetting.ID, id)
.findAll()
.<RealmResults<RealmPublicSetting>>asObservable());
.<RealmResults<RealmPublicSetting>>asFlowable();
},
pair -> close(pair.first, pair.second)
)
......
......@@ -2,12 +2,13 @@ package chat.rocket.persistence.realm.repositories;
import android.os.Handler;
import android.os.Looper;
import java.util.List;
import io.realm.Realm;
import io.realm.RealmObject;
import io.realm.RealmResults;
import java.util.List;
public class RealmRepository {
protected void close(Realm realm, Looper looper) {
......
......@@ -2,25 +2,27 @@ package chat.rocket.persistence.realm.repositories;
import android.os.Looper;
import android.support.v4.util.Pair;
import com.hadisatrio.optional.Optional;
import io.reactivex.Flowable;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.realm.Case;
import io.realm.Realm;
import io.realm.RealmResults;
import io.realm.Sort;
import java.util.ArrayList;
import java.util.List;
import chat.rocket.core.SortDirection;
import chat.rocket.core.models.Room;
import chat.rocket.core.models.RoomHistoryState;
import chat.rocket.core.repositories.RoomRepository;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.persistence.realm.RealmStore;
import chat.rocket.persistence.realm.models.ddp.RealmRoom;
import chat.rocket.persistence.realm.models.internal.LoadMessageProcedure;
import hu.akarnokd.rxjava.interop.RxJavaInterop;
import io.reactivex.Flowable;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.realm.Case;
import io.realm.Realm;
import io.realm.RealmResults;
import io.realm.Sort;
public class RealmRoomRepository extends RealmRepository implements RoomRepository {
......@@ -39,10 +41,9 @@ public class RealmRoomRepository extends RealmRepository implements RoomReposito
return Flowable.empty();
}
return RxJavaInterop.toV2Flowable(
pair.first.where(RealmRoom.class)
return pair.first.where(RealmRoom.class)
.findAll()
.asObservable());
.asFlowable();
},
pair -> close(pair.first, pair.second)
)
......@@ -69,13 +70,11 @@ public class RealmRoomRepository extends RealmRepository implements RoomReposito
return Flowable.just(Optional.<RealmRoom>absent());
}
return RxJavaInterop.toV2Flowable(
realmRoom
.<RealmRoom>asObservable()
return realmRoom.<RealmRoom>asFlowable()
.filter(
roomSubscription -> roomSubscription.isLoaded()
&& roomSubscription.isValid())
.map(Optional::of));
.map(Optional::of);
},
pair -> close(pair.first, pair.second)
)
......@@ -106,12 +105,10 @@ public class RealmRoomRepository extends RealmRepository implements RoomReposito
return Flowable.just(Optional.<LoadMessageProcedure>absent());
}
return RxJavaInterop.toV2Flowable(
messageProcedure
.<LoadMessageProcedure>asObservable()
return messageProcedure.<LoadMessageProcedure>asFlowable()
.filter(loadMessageProcedure -> loadMessageProcedure.isLoaded()
&& loadMessageProcedure.isValid())
.map(Optional::of));
.map(Optional::of);
},
pair -> close(pair.first, pair.second)
)
......@@ -142,14 +139,9 @@ public class RealmRoomRepository extends RealmRepository implements RoomReposito
loadMessage.setHasNext(!roomHistoryState.isComplete());
loadMessage.setTimestamp(roomHistoryState.getTimestamp());
realm.beginTransaction();
return RxJavaInterop.toV2Flowable(realm.copyToRealmOrUpdate(loadMessage)
.asObservable())
return RealmHelper.copyToRealmOrUpdate(realm, loadMessage)
.filter(realmObject -> realmObject.isLoaded() && realmObject.isValid())
.firstElement()
.doOnSuccess(it -> realm.commitTransaction())
.doOnError(throwable -> realm.cancelTransaction())
.doOnEvent((realmObject, throwable) -> close(realm, looper))
.toSingle()
.map(realmObject -> true);
......@@ -164,8 +156,7 @@ public class RealmRoomRepository extends RealmRepository implements RoomReposito
if (pair.first == null) {
return Flowable.empty();
}
return RxJavaInterop.toV2Flowable(
pair.first.where(RealmRoom.class)
return pair.first.where(RealmRoom.class)
.like(RealmRoom.NAME, "*" + name + "*", Case.INSENSITIVE)
.beginGroup()
.equalTo(RealmRoom.TYPE, RealmRoom.TYPE_CHANNEL)
......@@ -174,7 +165,7 @@ public class RealmRoomRepository extends RealmRepository implements RoomReposito
.endGroup()
.findAllSorted(RealmRoom.NAME,
direction.equals(SortDirection.ASC) ? Sort.ASCENDING : Sort.DESCENDING)
.asObservable());
.asFlowable();
},
pair -> close(pair.first, pair.second)
)
......@@ -192,15 +183,14 @@ public class RealmRoomRepository extends RealmRepository implements RoomReposito
if (pair.first == null) {
return Flowable.empty();
}
return RxJavaInterop.toV2Flowable(
pair.first.where(RealmRoom.class)
return pair.first.where(RealmRoom.class)
.beginGroup()
.equalTo(RealmRoom.TYPE, RealmRoom.TYPE_CHANNEL)
.or()
.equalTo(RealmRoom.TYPE, RealmRoom.TYPE_PRIVATE)
.endGroup()
.findAllSorted(RealmRoom.LAST_SEEN, Sort.ASCENDING)
.asObservable());
.asFlowable();
},
pair -> close(pair.first, pair.second)
)
......
......@@ -2,11 +2,8 @@ package chat.rocket.persistence.realm.repositories;
import android.os.Looper;
import android.support.v4.util.Pair;
import com.hadisatrio.optional.Optional;
import io.reactivex.Flowable;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.realm.RealmResults;
import chat.rocket.core.models.Room;
import chat.rocket.core.models.RoomRole;
......@@ -15,7 +12,10 @@ import chat.rocket.core.repositories.RoomRoleRepository;
import chat.rocket.persistence.realm.RealmStore;
import chat.rocket.persistence.realm.models.ddp.RealmRoomRole;
import chat.rocket.persistence.realm.models.ddp.RealmUser;
import hu.akarnokd.rxjava.interop.RxJavaInterop;
import io.reactivex.Flowable;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.realm.RealmResults;
public class RealmRoomRoleRepository extends RealmRepository implements RoomRoleRepository {
......@@ -33,12 +33,11 @@ public class RealmRoomRoleRepository extends RealmRepository implements RoomRole
if (pair.first == null) {
return Flowable.empty();
}
return RxJavaInterop.toV2Flowable(
pair.first.where(RealmRoomRole.class)
return pair.first.where(RealmRoomRole.class)
.equalTo(RealmRoomRole.Columns.ROOM_ID, room.getId())
.equalTo(RealmRoomRole.Columns.USER + "." + RealmUser.ID, user.getId())
.findAll()
.<RealmResults<RealmRoomRole>>asObservable());
.<RealmResults<RealmRoomRole>>asFlowable();
},
pair -> close(pair.first, pair.second)
)
......
......@@ -2,21 +2,21 @@ package chat.rocket.persistence.realm.repositories;
import android.os.Looper;
import android.support.v4.util.Pair;
import com.hadisatrio.optional.Optional;
import io.reactivex.Flowable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import chat.rocket.core.models.ServerInfo;
import chat.rocket.core.repositories.ServerInfoRepository;
import chat.rocket.persistence.realm.models.RealmBasedServerInfo;
import hu.akarnokd.rxjava.interop.RxJavaInterop;
import io.reactivex.Flowable;
import io.reactivex.android.schedulers.AndroidSchedulers;
public class RealmServerInfoRepository extends RealmRepository implements ServerInfoRepository {
@Override
public Flowable<Optional<ServerInfo>> getByHostname(String hostname) {
return Flowable.defer(() -> Flowable.using(
() -> new Pair<>(RealmBasedServerInfo.getRealm(), Looper.myLooper()),
() -> new Pair<>(RealmBasedServerInfo.getServerRealm(), Looper.myLooper()),
pair -> {
RealmBasedServerInfo info = pair.first.where(RealmBasedServerInfo.class)
.equalTo(RealmBasedServerInfo.ColumnName.HOSTNAME, hostname)
......@@ -26,10 +26,9 @@ public class RealmServerInfoRepository extends RealmRepository implements Server
return Flowable.just(Optional.<RealmBasedServerInfo>absent());
}
return RxJavaInterop.toV2Flowable(info
.<RealmBasedServerInfo>asObservable()
return info.<RealmBasedServerInfo>asFlowable()
.filter(it -> it.isLoaded() && it.isValid())
.map(Optional::of));
.map(Optional::of);
},
pair -> close(pair.first, pair.second)
)
......
......@@ -2,17 +2,18 @@ package chat.rocket.persistence.realm.repositories;
import android.os.Looper;
import android.support.v4.util.Pair;
import com.hadisatrio.optional.Optional;
import io.reactivex.Flowable;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.realm.Realm;
import chat.rocket.core.models.Session;
import chat.rocket.core.repositories.SessionRepository;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.persistence.realm.RealmStore;
import chat.rocket.persistence.realm.models.internal.RealmSession;
import hu.akarnokd.rxjava.interop.RxJavaInterop;
import io.reactivex.Flowable;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.realm.Realm;
public class RealmSessionRepository extends RealmRepository implements SessionRepository {
......@@ -31,11 +32,10 @@ public class RealmSessionRepository extends RealmRepository implements SessionRe
return Flowable.empty();
}
return RxJavaInterop.toV2Flowable(
pair.first.where(RealmSession.class)
return pair.first.where(RealmSession.class)
.equalTo(RealmSession.ID, id)
.findAll()
.<RealmSession>asObservable());
.<RealmSession>asFlowable();
},
pair -> close(pair.first, pair.second)
)
......@@ -45,7 +45,7 @@ public class RealmSessionRepository extends RealmRepository implements SessionRe
if (realmSessions.size() == 0) {
return Optional.absent();
}
return Optional.of(realmSessions.get(0).asSession());
return Optional.of(realmSessions.get(0).asSession());
}));
}
......@@ -74,14 +74,9 @@ public class RealmSessionRepository extends RealmRepository implements SessionRe
realmSession.setTokenVerified(session.isTokenVerified());
realmSession.setError(session.getError());
realm.beginTransaction();
return RxJavaInterop.toV2Flowable(realm.copyToRealmOrUpdate(realmSession)
.asObservable())
return RealmHelper.copyToRealmOrUpdate(realm, realmSession)
.filter(it -> it != null && it.isLoaded() && it.isValid())
.firstElement()
.doOnSuccess(it -> realm.commitTransaction())
.doOnError(throwable -> realm.cancelTransaction())
.doOnEvent((realmObject, throwable) -> close(realm, looper))
.toSingle()
.map(realmObject -> true);
......
package chat.rocket.persistence.realm.repositories
import android.os.Looper
import android.support.v4.util.Pair
import chat.rocket.core.models.Spotlight
import chat.rocket.core.repositories.SpotlightRepository
import chat.rocket.persistence.realm.RealmStore
import chat.rocket.persistence.realm.models.ddp.RealmSpotlight
import chat.rocket.persistence.realm.models.ddp.RealmSpotlight.Columns
import hu.akarnokd.rxjava.interop.RxJavaInterop
import io.reactivex.Flowable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.realm.Realm
import io.realm.RealmResults
import io.realm.Sort
import java.util.ArrayList
import java.util.*
class RealmSpotlightRepository(private val hostname: String) : RealmRepository(), SpotlightRepository {
......@@ -25,9 +23,9 @@ class RealmSpotlightRepository(private val hostname: String) : RealmRepository()
return@using Flowable.empty()
}
return@using RxJavaInterop.toV2Flowable<RealmResults<RealmSpotlight>>(pair.first.where(RealmSpotlight::class.java)
.findAllSorted(Columns.TYPE, Sort.DESCENDING)
.asObservable())
return@using pair.first.where(RealmSpotlight::class.java)
.findAllSorted(Columns.TYPE, Sort.DESCENDING)
.asFlowable()
}) { pair -> close(pair.first, pair.second) }
.unsubscribeOn(AndroidSchedulers.from(Looper.myLooper()!!))
.filter { realmSpotlightResults -> realmSpotlightResults.isLoaded && realmSpotlightResults.isValid }
......
......@@ -2,20 +2,20 @@ package chat.rocket.persistence.realm.repositories;
import android.os.Looper;
import android.support.v4.util.Pair;
import io.reactivex.Flowable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.realm.Case;
import io.realm.Sort;
import java.util.ArrayList;
import java.util.List;
import chat.rocket.core.SortDirection;
import chat.rocket.core.models.SpotlightRoom;
import chat.rocket.core.repositories.SpotlightRoomRepository;
import chat.rocket.persistence.realm.RealmStore;
import chat.rocket.persistence.realm.models.ddp.RealmRoom;
import chat.rocket.persistence.realm.models.ddp.RealmSpotlightRoom;
import hu.akarnokd.rxjava.interop.RxJavaInterop;
import io.reactivex.Flowable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.realm.Case;
import io.realm.Sort;
public class RealmSpotlightRoomRepository extends RealmRepository implements SpotlightRoomRepository {
......@@ -34,8 +34,7 @@ public class RealmSpotlightRoomRepository extends RealmRepository implements Spo
return Flowable.empty();
}
return RxJavaInterop.toV2Flowable(
pair.first.where(RealmSpotlightRoom.class)
return pair.first.where(RealmSpotlightRoom.class)
.like(RealmSpotlightRoom.Columns.NAME, "*" + name + "*", Case.INSENSITIVE)
.beginGroup()
.equalTo(RealmSpotlightRoom.Columns.TYPE, RealmRoom.TYPE_CHANNEL)
......@@ -43,7 +42,7 @@ public class RealmSpotlightRoomRepository extends RealmRepository implements Spo
.equalTo(RealmSpotlightRoom.Columns.TYPE, RealmRoom.TYPE_PRIVATE)
.endGroup()
.findAllSorted(RealmSpotlightRoom.Columns.NAME, direction.equals(SortDirection.ASC) ? Sort.ASCENDING : Sort.DESCENDING)
.asObservable());
.asFlowable();
},
pair -> close(pair.first, pair.second)
)
......
......@@ -2,19 +2,19 @@ package chat.rocket.persistence.realm.repositories;
import android.os.Looper;
import android.support.v4.util.Pair;
import io.reactivex.Flowable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.realm.Case;
import io.realm.Sort;
import java.util.ArrayList;
import java.util.List;
import chat.rocket.core.SortDirection;
import chat.rocket.core.models.SpotlightUser;
import chat.rocket.core.repositories.SpotlightUserRepository;
import chat.rocket.persistence.realm.RealmStore;
import chat.rocket.persistence.realm.models.ddp.RealmSpotlightUser;
import hu.akarnokd.rxjava.interop.RxJavaInterop;
import io.reactivex.Flowable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.realm.Case;
import io.realm.Sort;
public class RealmSpotlightUserRepository extends RealmRepository implements SpotlightUserRepository {
......@@ -33,8 +33,7 @@ public class RealmSpotlightUserRepository extends RealmRepository implements Spo
return Flowable.empty();
}
return RxJavaInterop.toV2Flowable(
pair.first.where(RealmSpotlightUser.class)
return pair.first.where(RealmSpotlightUser.class)
.beginGroup()
.like(RealmSpotlightUser.Columns.USERNAME, "*" + name + "*", Case.INSENSITIVE)
.isNull(RealmSpotlightUser.Columns.NAME)
......@@ -46,7 +45,7 @@ public class RealmSpotlightUserRepository extends RealmRepository implements Spo
.endGroup()
.findAllSorted(RealmSpotlightUser.Columns.USERNAME,
direction.equals(SortDirection.ASC) ? Sort.ASCENDING : Sort.DESCENDING)
.asObservable());
.asFlowable();
},
pair -> close(pair.first, pair.second)
)
......
......@@ -12,7 +12,6 @@ import chat.rocket.core.models.User;
import chat.rocket.core.repositories.UserRepository;
import chat.rocket.persistence.realm.RealmStore;
import chat.rocket.persistence.realm.models.ddp.RealmUser;
import hu.akarnokd.rxjava.interop.RxJavaInterop;
import io.reactivex.Flowable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.realm.Case;
......@@ -36,10 +35,9 @@ public class RealmUserRepository extends RealmRepository implements UserReposito
return Flowable.empty();
}
return RxJavaInterop.toV2Flowable(
pair.first.where(RealmUser.class)
return pair.first.where(RealmUser.class)
.findAll()
.asObservable());
.asFlowable();
},
pair -> close(pair.first, pair.second))
.unsubscribeOn(AndroidSchedulers.from(Looper.myLooper()))
......@@ -71,11 +69,10 @@ public class RealmUserRepository extends RealmRepository implements UserReposito
return Flowable.empty();
}
return RxJavaInterop.toV2Flowable(
pair.first.where(RealmUser.class)
return pair.first.where(RealmUser.class)
.isNotEmpty(RealmUser.EMAILS)
.findAll()
.<RealmResults<RealmUser>>asObservable());
.<RealmResults<RealmUser>>asFlowable();
},
pair -> close(pair.first, pair.second));
}
......@@ -114,11 +111,9 @@ public class RealmUserRepository extends RealmRepository implements UserReposito
return Flowable.just(Optional.absent());
}
return RxJavaInterop.toV2Flowable(
realmUser
.<RealmUser>asObservable()
return realmUser.<RealmUser>asFlowable()
.filter(user -> user.isLoaded() && user.isValid())
.map(Optional::of));
.map(Optional::of);
}
@Override
......@@ -139,11 +134,10 @@ public class RealmUserRepository extends RealmRepository implements UserReposito
return Flowable.empty();
}
return RxJavaInterop.toV2Flowable(
pair.first.where(RealmUser.class)
return pair.first.where(RealmUser.class)
.like(RealmUser.USERNAME, "*" + name + "*", Case.INSENSITIVE)
.findAllSorted(RealmUser.USERNAME, Sort.DESCENDING)
.asObservable());
.asFlowable();
},
pair -> close(pair.first, pair.second));
}
......
......@@ -2,16 +2,6 @@ apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
buildscript {
repositories {
jcenter()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$rootProject.ext.kotlinVersion"
}
}
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
......@@ -21,7 +11,6 @@ android {
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1"
vectorDrawables.useSupportLibrary = true
}
......@@ -40,7 +29,6 @@ android {
ext {
frescoVersion = '1.4.0'
rxbindingVersion = '2.0.0'
}
dependencies {
compile project(':rocket-chat-core')
......@@ -49,18 +37,18 @@ dependencies {
compile supportDependencies.annotation
compile supportDependencies.cardView
compile supportDependencies.designSupportLibrary
compile supportDependencies.constrainLayout
compile supportDependencies.kotlin
compile supportDependencies.constraintLayout
compile supportDependencies.supportV13
compile rxbindingDependencies.rxBinding
compile rxbindingDependencies.rxBindingSupport
compile "com.android.support:support-v13:$rootProject.ext.supportLibraryVersion"
testCompile "org.jetbrains.kotlin:kotlin-test:$rootProject.ext.kotlinVersion"
testCompile "org.jetbrains.kotlin:kotlin-test-junit:$rootProject.ext.kotlinVersion"
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$rootProject.ext.kotlinVersion"
compile 'org.nibor.autolink:autolink:0.6.0'
compile 'com.github.yusukeiwaki.android-widget:widget-fontawesome:0.0.1'
compile "com.facebook.fresco:fresco:$frescoVersion"
compile "com.facebook.fresco:imagepipeline-okhttp3:$frescoVersion"
compile 'com.caverock:androidsvg:1.2.1'
testCompile "org.jetbrains.kotlin:kotlin-test-junit:$rootProject.ext.kotlinVersion"
testCompile "org.jetbrains.kotlin:kotlin-test:$rootProject.ext.kotlinVersion"
testCompile 'junit:junit:4.12'
testCompile "org.mockito:mockito-core:2.7.19"
}
......@@ -7,9 +7,11 @@ import android.os.Build;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.FrameLayout;
import chat.rocket.android.widget.helper.FrescoHelper;
import com.facebook.drawee.view.SimpleDraweeView;
import chat.rocket.android.widget.helper.FrescoHelper;
public class RocketChatAvatar extends FrameLayout {
private SimpleDraweeView simpleDraweeViewAvatar;
......
......@@ -3,6 +3,7 @@ package chat.rocket.android.widget.helper;
import android.support.annotation.StringRes;
import java.util.HashMap;
import chat.rocket.android.widget.R;
public class IconProvider {
......
......@@ -15,6 +15,7 @@ import android.widget.TextView;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import chat.rocket.android.widget.R;
public class InlineHightlighter {
......
......@@ -5,7 +5,6 @@ import android.text.Selection;
import android.text.Spannable;
import android.text.method.LinkMovementMethod;
import android.text.method.MovementMethod;
import android.text.method.Touch;
import android.text.style.ClickableSpan;
import android.view.MotionEvent;
import android.widget.TextView;
......
......@@ -3,6 +3,7 @@ package chat.rocket.android.widget.helper;
import android.text.SpannableString;
import android.text.Spanned;
import android.widget.TextView;
import org.nibor.autolink.LinkExtractor;
import org.nibor.autolink.LinkSpan;
import org.nibor.autolink.LinkType;
......
......@@ -6,6 +6,7 @@ import android.support.v4.app.Fragment;
import android.support.v7.widget.RecyclerView;
import java.util.List;
import chat.rocket.android.widget.R;
import chat.rocket.android.widget.layouthelper.MessageExtraActionListAdapter;
import chat.rocket.android.widget.message.MessageExtraActionItemPresenter;
......
......@@ -6,6 +6,7 @@ import android.view.View;
import android.view.ViewGroup;
import java.util.List;
import chat.rocket.android.widget.R;
import chat.rocket.android.widget.message.MessageExtraActionItemPresenter;
......
......@@ -16,6 +16,12 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
import com.facebook.drawee.view.SimpleDraweeView;
import java.util.List;
import chat.rocket.android.widget.AbsoluteUrl;
import chat.rocket.android.widget.R;
import chat.rocket.android.widget.helper.FrescoHelper;
......@@ -24,10 +30,6 @@ import chat.rocket.core.models.AttachmentAuthor;
import chat.rocket.core.models.AttachmentField;
import chat.rocket.core.models.AttachmentTitle;
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
import com.facebook.drawee.view.SimpleDraweeView;
import java.util.List;
/**
*/
public class RocketChatMessageAttachmentsLayout extends LinearLayout {
......
......@@ -8,6 +8,7 @@ import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.emojione.Emojione;
import chat.rocket.android.widget.R;
......
......@@ -11,12 +11,14 @@ import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import chat.rocket.android.widget.helper.FrescoHelper;
import com.facebook.drawee.view.SimpleDraweeView;
import java.util.List;
import java.util.Map;
import chat.rocket.android.widget.R;
import chat.rocket.android.widget.helper.FrescoHelper;
import chat.rocket.android.widget.helper.ImageFormat;
import chat.rocket.core.models.WebContent;
import chat.rocket.core.models.WebContentHeaders;
......
......@@ -12,6 +12,12 @@ import android.widget.RelativeLayout;
import com.jakewharton.rxbinding2.widget.RxTextView;
import com.jakewharton.rxbinding2.widget.TextViewAfterTextChangeEvent;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import chat.rocket.android.widget.R;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.annotations.NonNull;
import io.reactivex.disposables.CompositeDisposable;
......@@ -20,11 +26,6 @@ import io.reactivex.functions.Consumer;
import io.reactivex.functions.Function;
import io.reactivex.internal.util.AppendOnlyLinkedArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import chat.rocket.android.widget.R;
public class AutocompleteManager {
private final Map<String, AutocompleteSource> autocompleteSourceMap = new HashMap<>();
......
package chat.rocket.android.widget.message.autocomplete;
import android.support.annotation.NonNull;
import io.reactivex.disposables.Disposable;
public abstract class AutocompleteSource<A extends AutocompleteAdapter, I extends AutocompleteItem> {
......
......@@ -2,19 +2,19 @@ package chat.rocket.android.widget.message.autocomplete.channel;
import android.support.annotation.NonNull;
import io.reactivex.Flowable;
import io.reactivex.Scheduler;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Consumer;
import io.reactivex.functions.Function;
import org.reactivestreams.Publisher;
import java.util.ArrayList;
import java.util.List;
import chat.rocket.android.widget.message.autocomplete.AutocompleteSource;
import chat.rocket.core.interactors.AutocompleteChannelInteractor;
import chat.rocket.core.models.SpotlightRoom;
import io.reactivex.Flowable;
import io.reactivex.Scheduler;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Consumer;
import io.reactivex.functions.Function;
public class ChannelSource extends AutocompleteSource<ChannelAdapter, ChannelItem> {
......
package chat.rocket.android.widget.message.autocomplete.user;
import android.support.annotation.NonNull;
import io.reactivex.Flowable;
import io.reactivex.Scheduler;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Consumer;
import io.reactivex.functions.Function;
import org.reactivestreams.Publisher;
import java.util.ArrayList;
import java.util.List;
import chat.rocket.android.widget.AbsoluteUrl;
import chat.rocket.android.widget.helper.UserStatusProvider;
import chat.rocket.android.widget.message.autocomplete.AutocompleteSource;
import chat.rocket.core.interactors.AutocompleteUserInteractor;
import chat.rocket.core.models.SpotlightUser;
import io.reactivex.Flowable;
import io.reactivex.Scheduler;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Consumer;
import io.reactivex.functions.Function;
public class UserSource extends AutocompleteSource<UserAdapter, UserItem> {
......
......@@ -32,11 +32,11 @@
android:id="@+id/reply_icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginRight="8dp"
android:layout_marginEnd="8dp"
android:layout_centerVertical="true"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:adjustViewBounds="true"
app:srcCompat="@drawable/ic_reply"
app:tint="@color/color_accent" />
......@@ -45,11 +45,11 @@
android:id="@+id/reply_cancel"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:layout_centerVertical="true"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:adjustViewBounds="true"
app:srcCompat="@drawable/ic_close"
app:tint="@color/color_icon_composer" />
......@@ -59,10 +59,10 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignBaseline="@id/reply_username"
android:layout_toEndOf="@+id/reply_thumb"
android:layout_toLeftOf="@+id/reply_cancel"
android:layout_toStartOf="@id/reply_cancel"
android:layout_toRightOf="@+id/reply_thumb"
android:layout_toEndOf="@+id/reply_thumb"
android:layout_toStartOf="@id/reply_cancel"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/color_accent"
......@@ -73,13 +73,13 @@
android:id="@+id/reply_thumb"
android:layout_width="32dp"
android:layout_height="wrap_content"
android:layout_marginRight="4dp"
android:layout_marginEnd="4dp"
android:layout_toRightOf="@+id/reply_icon"
android:layout_toEndOf="@+id/reply_icon"
android:layout_alignBottom="@+id/reply_message"
android:layout_alignTop="@+id/reply_username"
android:layout_centerVertical="true"
android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"
android:layout_toEndOf="@+id/reply_icon"
android:layout_toRightOf="@+id/reply_icon"
android:visibility="gone"
fresco:actualImageScaleType="fitCenter" />
......@@ -88,10 +88,10 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/reply_username"
android:layout_toEndOf="@+id/reply_thumb"
android:layout_toLeftOf="@+id/reply_cancel"
android:layout_toStartOf="@id/reply_cancel"
android:layout_toRightOf="@+id/reply_thumb"
android:layout_toEndOf="@+id/reply_thumb"
android:layout_toStartOf="@id/reply_cancel"
android:ellipsize="end"
android:maxLines="1"
tools:text="Message" />
......@@ -128,29 +128,34 @@
<FrameLayout
android:id="@+id/container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginStart="16dp"
app:layout_constraintBottom_toBottomOf="@+id/editor"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="@+id/editor"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@+id/editor">
app:layout_constraintTop_toTopOf="parent">
<ImageButton
android:id="@+id/button_attach"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:background="?attr/selectableItemBackgroundBorderless"
android:paddingEnd="16dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingStart="16dp"
android:tint="@color/color_icon_composer"
app:srcCompat="@drawable/ic_attach_file_black_24dp" />
<ImageButton
android:id="@+id/button_send"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/selectableItemBackgroundBorderless"
android:paddingEnd="16dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingStart="16dp"
android:tint="@color/color_accent"
app:srcCompat="@drawable/ic_send_black_24dp" />
</FrameLayout>
......
......@@ -53,7 +53,7 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/drawee_server_button"
app:layout_constraintTop_toBottomOf="@+id/text_view_site_name_label"
tools:text="demo.rocket.chat" />
tools:text="open.rocket.chat" />
<ImageView
android:id="@+id/selected_server_dot"
......
plugins {
id "org.jetbrains.kotlin.jvm" version "1.1.4-2"
}
apply plugin: 'org.jetbrains.kotlin.jvm'
apply plugin: 'idea'
apply plugin: 'java'
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile extraDependencies.rxJava
compile extraDependencies.optional
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$rootProject.ext.kotlinVersion"
compile 'com.google.code.findbugs:jsr305:3.0.1'
compile 'com.hadisatrio:Optional:v1.0.1'
compile 'com.google.code.findbugs:jsr305:3.0.2'
compileOnly 'com.google.auto.value:auto-value:1.3'
kapt 'com.google.auto.value:auto-value:1.3'
kapt 'com.gabrielittner.auto.value:auto-value-with:1.0.0'
testCompile 'junit:junit:4.12'
testCompile "org.mockito:mockito-inline:2.8.9"
}
......
......@@ -8,4 +8,7 @@ public interface SyncState {
int SYNCING = 1;
int SYNCED = 2;
int FAILED = 3;
int DELETE_NOT_SYNCED = 4;
int DELETING = 5;
int DELETE_FAILED = 6;
}
package chat.rocket.core.interactors
import io.reactivex.Flowable
import java.util.ArrayList
import chat.rocket.core.SortDirection
import chat.rocket.core.models.Room
import chat.rocket.core.models.SpotlightRoom
......@@ -11,8 +8,10 @@ import chat.rocket.core.repositories.SpotlightRoomRepository
import chat.rocket.core.temp.TempSpotlightRoomCaller
import chat.rocket.core.utils.Pair
import chat.rocket.core.utils.Triple
import io.reactivex.Flowable
import io.reactivex.functions.BiFunction
import io.reactivex.functions.Function3
import java.util.*
class AutocompleteChannelInteractor(private val roomRepository: RoomRepository,
private val spotlightRoomRepository: SpotlightRoomRepository,
......
......@@ -12,7 +12,7 @@ import chat.rocket.core.temp.TempSpotlightUserCaller
import chat.rocket.core.utils.Triple
import io.reactivex.Flowable
import io.reactivex.functions.Function3
import java.util.ArrayList
import java.util.*
class AutocompleteUserInteractor(private val room: Room,
private val userRepository: UserRepository,
......
......@@ -2,11 +2,10 @@ package chat.rocket.core.interactors
import chat.rocket.core.models.Session
import chat.rocket.core.models.User
import io.reactivex.Flowable
import io.reactivex.Single
import chat.rocket.core.repositories.UserRepository
import com.hadisatrio.optional.Optional
import io.reactivex.Flowable
import io.reactivex.Single
import io.reactivex.functions.Function3
class CanCreateRoomInteractor(private val userRepository: UserRepository,
......
......@@ -2,8 +2,14 @@ package chat.rocket.core.interactors
import chat.rocket.core.PermissionsConstants
import chat.rocket.core.PublicSettingsConstants
import chat.rocket.core.models.*
import chat.rocket.core.repositories.*
import chat.rocket.core.models.Message
import chat.rocket.core.models.PublicSetting
import chat.rocket.core.models.Room
import chat.rocket.core.models.User
import chat.rocket.core.repositories.MessageRepository
import chat.rocket.core.repositories.PublicSettingRepository
import chat.rocket.core.repositories.RoomRepository
import chat.rocket.core.repositories.UserRepository
import chat.rocket.core.utils.Pair
import com.hadisatrio.optional.Optional
import io.reactivex.Single
......
package chat.rocket.core.interactors
import com.hadisatrio.optional.Optional
import io.reactivex.Flowable
import io.reactivex.Single
import java.util.UUID
import chat.rocket.core.SyncState
import chat.rocket.core.models.Message
import chat.rocket.core.models.Room
......@@ -11,6 +7,10 @@ import chat.rocket.core.models.RoomHistoryState
import chat.rocket.core.models.User
import chat.rocket.core.repositories.MessageRepository
import chat.rocket.core.repositories.RoomRepository
import com.hadisatrio.optional.Optional
import io.reactivex.Flowable
import io.reactivex.Single
import java.util.*
class MessageInteractor(private val messageRepository: MessageRepository,
private val roomRepository: RoomRepository) {
......@@ -76,7 +76,14 @@ class MessageInteractor(private val messageRepository: MessageRepository,
}
fun delete(message: Message): Single<Boolean> {
return messageRepository.delete(message)
return messageRepository.save(message.withSyncState(SyncState.DELETE_NOT_SYNCED))
}
/**
* Resets the message syncstate to SYNCED after a user has accepted a failed delete
*/
fun acceptDeleteFailure(message: Message): Single<Boolean> {
return messageRepository.save(message.withSyncState(SyncState.SYNCED))
}
fun unreadCountFor(room: Room, user: User): Single<Int> {
......
......@@ -3,7 +3,9 @@ package chat.rocket.core.interactors
import chat.rocket.core.models.Permission
import chat.rocket.core.models.Room
import chat.rocket.core.models.RoomRole
import chat.rocket.core.repositories.*
import chat.rocket.core.repositories.PermissionRepository
import chat.rocket.core.repositories.RoomRoleRepository
import chat.rocket.core.repositories.UserRepository
import chat.rocket.core.utils.Pair
import com.hadisatrio.optional.Optional
import io.reactivex.Single
......
package chat.rocket.core.interactors
import io.reactivex.Flowable
import chat.rocket.core.SortDirection
import chat.rocket.core.models.Room
import chat.rocket.core.repositories.RoomRepository
import io.reactivex.Flowable
class RoomInteractor(private val roomRepository: RoomRepository) {
......
package chat.rocket.core.interactors
import chat.rocket.core.models.Session
import chat.rocket.core.repositories.SessionRepository
import com.hadisatrio.optional.Optional
import io.reactivex.Flowable
import io.reactivex.Single
import chat.rocket.core.models.Session
import chat.rocket.core.repositories.SessionRepository
class SessionInteractor(private val sessionRepository: SessionRepository) {
companion object {
......
package chat.rocket.core.interactors;
import io.reactivex.Flowable;
import java.util.List;
import chat.rocket.core.models.User;
import chat.rocket.core.repositories.UserRepository;
import io.reactivex.Flowable;
public class UserInteractor {
......
......@@ -3,6 +3,7 @@ package chat.rocket.core.models;
import com.google.auto.value.AutoValue;
import java.util.List;
import javax.annotation.Nullable;
@AutoValue
......
......@@ -3,6 +3,7 @@ package chat.rocket.core.models;
import com.google.auto.value.AutoValue;
import java.util.List;
import javax.annotation.Nullable;
@AutoValue
......
......@@ -3,6 +3,7 @@ package chat.rocket.core.models;
import com.google.auto.value.AutoValue;
import java.util.List;
import javax.annotation.Nullable;
@AutoValue
......
......@@ -3,6 +3,7 @@ package chat.rocket.core.models;
import com.google.auto.value.AutoValue;
import java.util.Map;
import javax.annotation.Nullable;
@AutoValue
......
package chat.rocket.core.repositories;
import com.hadisatrio.optional.Optional;
import io.reactivex.Flowable;
import io.reactivex.Single;
import java.util.List;
import chat.rocket.core.models.LoginServiceConfiguration;
import io.reactivex.Flowable;
import io.reactivex.Single;
public interface LoginServiceConfigurationRepository {
......
package chat.rocket.core.repositories;
import com.hadisatrio.optional.Optional;
import io.reactivex.Flowable;
import io.reactivex.Single;
import java.util.List;
import chat.rocket.core.models.Message;
import chat.rocket.core.models.Room;
import chat.rocket.core.models.User;
import io.reactivex.Flowable;
import io.reactivex.Single;
public interface MessageRepository {
......
package chat.rocket.core.repositories;
import com.hadisatrio.optional.Optional;
import io.reactivex.Single;
import chat.rocket.core.models.Permission;
import io.reactivex.Single;
public interface PermissionRepository {
......
package chat.rocket.core.repositories;
import com.hadisatrio.optional.Optional;
import io.reactivex.Single;
import chat.rocket.core.models.PublicSetting;
import io.reactivex.Single;
public interface PublicSettingRepository {
......
package chat.rocket.core.repositories;
import com.hadisatrio.optional.Optional;
import io.reactivex.Flowable;
import io.reactivex.Single;
import java.util.List;
import chat.rocket.core.SortDirection;
import chat.rocket.core.models.Room;
import chat.rocket.core.models.RoomHistoryState;
import io.reactivex.Flowable;
import io.reactivex.Single;
public interface RoomRepository {
......
package chat.rocket.core.repositories;
import com.hadisatrio.optional.Optional;
import io.reactivex.Single;
import chat.rocket.core.models.Room;
import chat.rocket.core.models.RoomRole;
import chat.rocket.core.models.User;
import io.reactivex.Single;
public interface RoomRoleRepository {
......
......@@ -2,9 +2,8 @@ package chat.rocket.core.repositories;
import com.hadisatrio.optional.Optional;
import io.reactivex.Flowable;
import chat.rocket.core.models.ServerInfo;
import io.reactivex.Flowable;
public interface ServerInfoRepository {
......
package chat.rocket.core.repositories;
import com.hadisatrio.optional.Optional;
import io.reactivex.Flowable;
import io.reactivex.Single;
import chat.rocket.core.models.Session;
import io.reactivex.Flowable;
import io.reactivex.Single;
public interface SessionRepository {
......
package chat.rocket.core.repositories;
import io.reactivex.Flowable;
import java.util.List;
import chat.rocket.core.SortDirection;
import chat.rocket.core.models.SpotlightRoom;
import io.reactivex.Flowable;
public interface SpotlightRoomRepository {
......
package chat.rocket.core.repositories;
import io.reactivex.Flowable;
import java.util.List;
import chat.rocket.core.SortDirection;
import chat.rocket.core.models.SpotlightUser;
import io.reactivex.Flowable;
public interface SpotlightUserRepository {
......
package chat.rocket.core.repositories
import chat.rocket.core.models.User
import com.hadisatrio.optional.Optional
import io.reactivex.Flowable
import chat.rocket.core.models.User
interface UserRepository {
......
package chat.rocket.core.interactors;
import static org.mockito.Mockito.*;
import io.reactivex.Flowable;
import io.reactivex.subscribers.TestSubscriber;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
......@@ -12,12 +8,23 @@ import org.mockito.junit.MockitoJUnitRunner;
import java.util.ArrayList;
import java.util.List;
import chat.rocket.core.SortDirection;
import chat.rocket.core.models.Room;
import chat.rocket.core.models.SpotlightRoom;
import chat.rocket.core.repositories.RoomRepository;
import chat.rocket.core.repositories.SpotlightRoomRepository;
import chat.rocket.core.temp.TempSpotlightRoomCaller;
import io.reactivex.Flowable;
import io.reactivex.subscribers.TestSubscriber;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class AutocompleteChannelInteractorTest {
......
......@@ -6,12 +6,14 @@ import chat.rocket.core.models.Message
import chat.rocket.core.models.PublicSetting
import chat.rocket.core.models.Room
import chat.rocket.core.models.User
import chat.rocket.core.repositories.*
import chat.rocket.core.repositories.MessageRepository
import chat.rocket.core.repositories.PublicSettingRepository
import chat.rocket.core.repositories.RoomRepository
import chat.rocket.core.repositories.UserRepository
import com.hadisatrio.optional.Optional
import io.reactivex.Flowable
import io.reactivex.Single
import io.reactivex.observers.TestObserver
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
......
......@@ -8,12 +8,12 @@ import com.hadisatrio.optional.Optional
import io.reactivex.Flowable
import io.reactivex.Single
import io.reactivex.observers.TestObserver
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.*
import org.mockito.Mockito.`when`
import org.mockito.Mockito.any
import org.mockito.junit.MockitoJUnitRunner
@RunWith(MockitoJUnitRunner::class)
......
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