Commit 189a3c69 authored by Rafael Kellermann Streit's avatar Rafael Kellermann Streit Committed by GitHub

Merge pull request #492 from RocketChat/develop

[RELEASE] Merge 1.0.20 release into master
parents 483ae847 9e1504be
......@@ -39,7 +39,7 @@ dependencies {
compile "com.android.support:support-annotations:$rootProject.ext.supportLibraryVersion"
compile 'com.squareup.okhttp3:okhttp:3.8.0'
compile "com.squareup.okhttp3:okhttp:$rootProject.ext.okHttpVersion"
compile 'io.reactivex.rxjava2:rxjava:2.1.0'
......
......@@ -7,6 +7,7 @@ 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'
......@@ -45,8 +46,8 @@ android {
applicationId "chat.rocket.android"
minSdkVersion 16
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 36
versionName "1.0.18"
versionCode 40
versionName "1.0.20"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
multiDexEnabled true
......@@ -102,6 +103,11 @@ android {
release {
manifest.srcFile 'src/release/AndroidManifest.xml'
}
test {
test.java.srcDirs += 'src/test/kotlin'
androidTest.java.srcDirs += 'src/androidTest/kotlin'
}
}
}
......@@ -135,13 +141,15 @@ dependencies {
compile 'com.android.support:multidex:1.0.1'
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$rootProject.ext.kotlinVersion"
testCompile "org.jetbrains.kotlin:kotlin-test:$rootProject.ext.kotlinVersion"
testCompile "org.jetbrains.kotlin:kotlin-test-junit:$rootProject.ext.kotlinVersion"
compile "com.google.firebase:firebase-core:$playLibVersion"
compile "com.google.firebase:firebase-crash:$playLibVersion"
compile "com.google.android.gms:play-services-gcm:$playLibVersion"
compile 'com.squareup.okhttp3:okhttp:3.8.0'
compile "com.squareup.okhttp3:okhttp:$rootProject.ext.okHttpVersion"
debugCompile "com.facebook.stetho:stetho:$stethoVersion"
debugCompile "com.facebook.stetho:stetho-okhttp3:$stethoOkhttp3Version"
......@@ -169,11 +177,18 @@ dependencies {
transitive = true;
}
debugCompile "com.tspoon.traceur:traceur:1.0.1"
provided 'com.parse.bolts:bolts-tasks:1.4.0'
provided 'io.reactivex.rxjava2:rxjava:2.1.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.jetbrains.kotlin:kotlin-test:$rootProject.ext.kotlinVersion"
testCompile "org.jetbrains.kotlin:kotlin-test-junit:$rootProject.ext.kotlinVersion"
}
apply plugin: 'com.google.gms.google-services'
......@@ -2,6 +2,7 @@ package chat.rocket.android;
import android.os.StrictMode;
import com.facebook.stetho.Stetho;
import com.tspoon.traceur.Traceur;
import com.uphyca.stetho_realm.RealmInspectorModulesProvider;
public class RocketChatApplicationDebug extends RocketChatApplication {
......@@ -11,6 +12,11 @@ public class RocketChatApplicationDebug extends RocketChatApplication {
super.onCreate();
enableStrictMode();
enableStetho();
enableTraceur();
}
private void enableTraceur() {
Traceur.enableLogging();
}
private void enableStrictMode() {
......
......@@ -10,6 +10,13 @@ import okhttp3.OkHttpClient
object OkHttpHelper {
fun getClient(): OkHttpClient {
if (httpClient == null) {
httpClient = OkHttpClient()
}
return httpClient ?: throw AssertionError("httpClient set to null by another thread")
}
fun getClientForUploadFile(): OkHttpClient {
if (httpClientForUploadFile == null) {
httpClientForUploadFile = OkHttpClient.Builder().build()
......@@ -21,6 +28,8 @@ object OkHttpHelper {
if(httpClientForDownloadFile == null) {
httpClientForDownloadFile = OkHttpClient.Builder()
.addNetworkInterceptor(StethoInterceptor())
.followRedirects(true)
.followSslRedirects(true)
.addInterceptor(CookieInterceptor(DefaultCookieProvider(RocketChatCache(context))))
.build()
}
......@@ -39,6 +48,7 @@ object OkHttpHelper {
return httpClientForWS ?: throw AssertionError("httpClientForWS set to null by another thread")
}
private var httpClient: OkHttpClient? = null
private var httpClientForUploadFile: OkHttpClient? = null
private var httpClientForDownloadFile: OkHttpClient? = null
private var httpClientForWS: OkHttpClient? = null
......
......@@ -33,10 +33,16 @@
</activity>
<activity
android:name=".activity.AddServerActivity"
android:name=".activity.room.RoomActivity"
android:configChanges="orientation|screenSize"
android:windowSoftInputMode="adjustResize"/>
<activity
android:name=".activity.AddServerActivity"
android:configChanges="orientation|screenSize"
android:windowSoftInputMode="adjustResize"
android:launchMode="singleTop"/>
<activity
android:name=".activity.LoginActivity"
android:configChanges="orientation|screenSize"
......
......@@ -17,7 +17,7 @@ public class LaunchUtil {
*/
public static void showMainActivity(Context context) {
Intent intent = new Intent(context, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
context.startActivity(intent);
}
......@@ -26,7 +26,7 @@ public class LaunchUtil {
*/
public static void showAddServerActivity(Context context) {
Intent intent = new Intent(context, AddServerActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
context.startActivity(intent);
}
......@@ -35,7 +35,7 @@ public class LaunchUtil {
*/
public static void showLoginActivity(Context context, String hostname) {
Intent intent = new Intent(context, LoginActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent.putExtra(LoginActivity.KEY_HOSTNAME, hostname);
context.startActivity(intent);
}
......
package chat.rocket.android;
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;
......@@ -17,6 +20,12 @@ import chat.rocket.persistence.realm.RocketChatPersistenceRealm;
*/
public class RocketChatApplication extends MultiDexApplication {
private static RocketChatApplication instance;
public static RocketChatApplication getInstance() {
return instance;
}
@Override
public void onCreate() {
super.onCreate();
......@@ -30,5 +39,11 @@ public class RocketChatApplication extends MultiDexApplication {
}
RocketChatWidgets.initialize(this, OkHttpHelper.INSTANCE.getClientForDownloadFile(this));
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
}
instance = this;
}
}
\ No newline at end of file
......@@ -5,18 +5,32 @@ import android.content.SharedPreferences;
import com.hadisatrio.optional.Optional;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import chat.rocket.android.helper.Logger;
import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.log.RCLog;
import chat.rocket.core.utils.Pair;
import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable;
import io.reactivex.annotations.NonNull;
import io.reactivex.annotations.Nullable;
/**
* sharedpreference-based cache.
*/
public class RocketChatCache {
private static final String KEY_SELECTED_SERVER_HOSTNAME = "selectedServerHostname";
private static final String KEY_SELECTED_ROOM_ID = "selectedRoomId";
private static final String KEY_PUSH_ID = "pushId";
private static final String KEY_SELECTED_SERVER_HOSTNAME = "KEY_SELECTED_SERVER_HOSTNAME";
private static final String KEY_SELECTED_ROOM_ID = "KEY_SELECTED_ROOM_ID";
private static final String KEY_PUSH_ID = "KEY_PUSH_ID";
private static final String KEY_HOSTNAME_LIST = "KEY_HOSTNAME_LIST";
private Context context;
......@@ -29,15 +43,115 @@ public class RocketChatCache {
}
public void setSelectedServerHostname(String hostname) {
setString(KEY_SELECTED_SERVER_HOSTNAME, hostname);
String newHostname = null;
if (hostname != null) {
newHostname = hostname.toLowerCase();
}
setString(KEY_SELECTED_SERVER_HOSTNAME, newHostname);
}
public void addHostname(@NonNull String hostname, @Nullable String hostnameAvatarUri, String siteName) {
String hostnameList = getString(KEY_HOSTNAME_LIST, null);
try {
JSONObject json;
if (hostnameList == null) {
json = new JSONObject();
} else {
json = new JSONObject(hostnameList);
}
JSONObject serverInfoJson = new JSONObject();
serverInfoJson.put("avatar", hostnameAvatarUri);
serverInfoJson.put("sitename", siteName);
// Replace server avatar uri if exists.
json.put(hostname, hostnameAvatarUri == null ? JSONObject.NULL : serverInfoJson);
setString(KEY_HOSTNAME_LIST, json.toString());
} catch (JSONException e) {
RCLog.e(e);
}
}
public List<Pair<String, Pair<String, String>>> getServerList() {
String json = getString(KEY_HOSTNAME_LIST, null);
if (json == null) {
return Collections.emptyList();
}
try {
JSONObject jsonObj = new JSONObject(json);
List<Pair<String, Pair<String, String>>> serverList = new ArrayList<>();
for (Iterator<String> iter = jsonObj.keys(); iter.hasNext();) {
String hostname = iter.next();
JSONObject serverInfoJson = jsonObj.getJSONObject(hostname);
serverList.add(new Pair<>(hostname, new Pair<>(
"http://" + hostname + "/" + serverInfoJson.getString("avatar"),
serverInfoJson.getString("sitename"))));
}
return serverList;
} catch (JSONException e) {
RCLog.e(e);
}
return Collections.emptyList();
}
public void removeHostname(String hostname) {
String json = getString(KEY_HOSTNAME_LIST, null);
if (TextUtils.isEmpty(json)) {
return;
}
try {
JSONObject jsonObj = new JSONObject(json);
jsonObj.remove(hostname);
String result = jsonObj.length() == 0 ? null : jsonObj.toString();
setString(KEY_HOSTNAME_LIST, result);
} catch (JSONException e) {
RCLog.e(e);
}
}
@Nullable
public String getFirstLoggedHostnameIfAny() {
String json = getString(KEY_HOSTNAME_LIST, null);
if (json != null) {
try {
JSONObject jsonObj = new JSONObject(json);
if (jsonObj.length() > 0 && jsonObj.keys().hasNext()) {
// Returns the first hostname on the list.
return jsonObj.keys().next();
}
} catch (JSONException e) {
RCLog.e(e);
}
}
return null;
}
public String getSelectedRoomId() {
return getString(KEY_SELECTED_ROOM_ID, null);
try {
JSONObject jsonObject = getSelectedRoomIdJsonObject();
return jsonObject.optString(getSelectedServerHostname(), null);
} catch (JSONException e) {
RCLog.e(e);
Logger.report(e);
}
return null;
}
public void setSelectedRoomId(String roomId) {
setString(KEY_SELECTED_ROOM_ID, roomId);
try {
JSONObject jsonObject = getSelectedRoomIdJsonObject();
jsonObject.put(getSelectedServerHostname(), roomId);
setString(KEY_SELECTED_ROOM_ID, jsonObject.toString());
} catch (JSONException e) {
RCLog.e(e);
Logger.report(e);
}
}
private JSONObject getSelectedRoomIdJsonObject() throws JSONException {
String json = getString(KEY_SELECTED_ROOM_ID, null);
if (json == null) {
return new JSONObject();
}
return new JSONObject(json);
}
public String getOrCreatePushId() {
......@@ -69,7 +183,7 @@ public class RocketChatCache {
return getSharedPreferences().edit();
}
private String getString(String key, String defaultValue) {
public String getString(String key, String defaultValue) {
return getSharedPreferences().getString(key, defaultValue);
}
......@@ -93,4 +207,17 @@ public class RocketChatCache {
getSharedPreferences().registerOnSharedPreferenceChangeListener(listener);
}, BackpressureStrategy.LATEST);
}
public void removeSelectedRoomId(String currentHostname) {
try {
JSONObject selectedRoomIdJsonObject = getSelectedRoomIdJsonObject();
selectedRoomIdJsonObject.remove(currentHostname);
String result = selectedRoomIdJsonObject.length() == 0 ?
null : selectedRoomIdJsonObject.toString();
setString(KEY_SELECTED_ROOM_ID, result);
} catch (JSONException e) {
Logger.report(e);
RCLog.e(e);
}
}
}
......@@ -6,6 +6,9 @@ import android.support.annotation.Nullable;
import com.hadisatrio.optional.Optional;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.List;
import chat.rocket.android.LaunchUtil;
......@@ -61,6 +64,8 @@ abstract class AbstractAuthedActivity extends AbstractFragmentActivity {
if (intent.hasExtra(PushConstants.ROOM_ID)) {
rocketChatCache.setSelectedRoomId(intent.getStringExtra(PushConstants.ROOM_ID));
}
} else {
updateHostnameIfNeeded(rocketChatCache.getSelectedServerHostname());
}
if (intent.hasExtra(PushConstants.NOT_ID)) {
......@@ -194,7 +199,8 @@ abstract class AbstractAuthedActivity extends AbstractFragmentActivity {
compositeDisposable.add(
rocketChatCache.getSelectedRoomIdPublisher()
.map(Optional::get)
.distinctUntilChanged()
.map(this::convertStringToJsonObject)
.map(jsonObject -> jsonObject.optString(rocketChatCache.getSelectedServerHostname(), null))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
......@@ -203,4 +209,11 @@ abstract class AbstractAuthedActivity extends AbstractFragmentActivity {
)
);
}
private JSONObject convertStringToJsonObject(String json) throws JSONException {
if (json == null) {
return new JSONObject();
}
return new JSONObject(json);
}
}
package chat.rocket.android.activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.IdRes;
import android.support.annotation.Nullable;
......@@ -13,9 +14,16 @@ import icepick.Icepick;
abstract class AbstractFragmentActivity extends RxAppCompatActivity {
public static final String EXTRA_FINISH_ON_BACK_PRESS = "EXTRA_FINISH_ON_BACK_PRESS";
private boolean finishOnBackPress;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
if (intent != null) {
finishOnBackPress = intent.getBooleanExtra(EXTRA_FINISH_ON_BACK_PRESS, false);
}
Icepick.restoreInstanceState(this, savedInstanceState);
}
......@@ -30,8 +38,13 @@ abstract class AbstractFragmentActivity extends RxAppCompatActivity {
@Override
public final void onBackPressed() {
if (!onBackPress()) {
onBackPressedNotHandled();
if (finishOnBackPress) {
super.onBackPressed();
finish();
} else {
if (!onBackPress()) {
onBackPressedNotHandled();
}
}
}
......@@ -62,10 +75,28 @@ abstract class AbstractFragmentActivity extends RxAppCompatActivity {
.commit();
}
protected void showFragmentWithTagWithBackStack(Fragment fragment, String tag) {
getSupportFragmentManager().beginTransaction()
.replace(getLayoutContainerForFragment(), fragment, tag)
.addToBackStack(null)
.commit();
}
protected void showFragmentWithTag(Fragment fragment, String tag) {
getSupportFragmentManager().beginTransaction()
.replace(getLayoutContainerForFragment(), fragment, tag)
.commit();
}
protected void showFragmentWithBackStack(Fragment fragment) {
getSupportFragmentManager().beginTransaction()
.replace(getLayoutContainerForFragment(), fragment)
.addToBackStack(null)
.commit();
}
@Nullable
protected Fragment findFragmentByTag(String tag) {
return getSupportFragmentManager().findFragmentByTag(tag);
}
}
package chat.rocket.android.activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat;
import android.support.v4.widget.SlidingPaneLayout;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.facebook.drawee.view.SimpleDraweeView;
import java.util.List;
import chat.rocket.android.LaunchUtil;
import chat.rocket.android.R;
......@@ -16,9 +27,13 @@ import chat.rocket.android.fragment.sidebar.SidebarMainFragment;
import chat.rocket.android.helper.KeyboardHelper;
import chat.rocket.android.service.ConnectivityManager;
import chat.rocket.android.widget.RoomToolbar;
import chat.rocket.android.widget.helper.FrescoHelper;
import chat.rocket.core.interactors.CanCreateRoomInteractor;
import chat.rocket.core.interactors.RoomInteractor;
import chat.rocket.core.interactors.SessionInteractor;
import chat.rocket.core.repositories.PublicSettingRepository;
import chat.rocket.core.utils.Pair;
import chat.rocket.persistence.realm.repositories.RealmPublicSettingRepository;
import chat.rocket.persistence.realm.repositories.RealmRoomRepository;
import chat.rocket.persistence.realm.repositories.RealmSessionRepository;
import chat.rocket.persistence.realm.repositories.RealmUserRepository;
......@@ -33,7 +48,6 @@ public class MainActivity extends AbstractAuthedActivity implements MainContract
private SlidingPaneLayout pane;
private MainContract.Presenter presenter;
@Override
protected int getLayoutContainerForFragment() {
return R.id.activity_main_container;
}
......@@ -51,8 +65,16 @@ public class MainActivity extends AbstractAuthedActivity implements MainContract
@Override
protected void onResume() {
super.onResume();
if (presenter != null) {
if (hostname == null || presenter == null) {
hostname = new RocketChatCache(getApplicationContext()).getSelectedServerHostname();
if (hostname == null) {
showAddServerScreen();
} else {
onHostnameUpdated();
}
} else {
presenter.bindViewOnly(this);
presenter.loadSignedInServers(hostname);
}
}
......@@ -65,30 +87,49 @@ public class MainActivity extends AbstractAuthedActivity implements MainContract
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() {
pane.setPanelSlideListener(new SlidingPaneLayout.PanelSlideListener() {
@Override
public void onPanelSlide(View view, float v) {
//Ref: ActionBarDrawerToggle#setProgress
toolbar.setNavigationIconProgress(v);
}
if (pane != null) {
pane.setPanelSlideListener(new SlidingPaneLayout.PanelSlideListener() {
@Override
public void onPanelSlide(View view, float v) {
//Ref: ActionBarDrawerToggle#setProgress
toolbar.setNavigationIconProgress(v);
}
@Override
public void onPanelOpened(View view) {
toolbar.setNavigationIconVerticalMirror(true);
}
@Override
public void onPanelOpened(View view) {
toolbar.setNavigationIconVerticalMirror(true);
}
@Override
public void onPanelClosed(View view) {
toolbar.setNavigationIconVerticalMirror(false);
}
});
@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);
}
}
});
}
toolbar.setNavigationOnClickListener(view -> {
if (pane.isSlideable() && !pane.isOpen()) {
pane.openPane();
}
});
if (toolbar != null) {
toolbar.setNavigationOnClickListener(view -> {
if (pane.isSlideable() && !pane.isOpen()) {
pane.openPane();
}
});
}
}
private boolean closeSidebarIfNeeded() {
......@@ -120,24 +161,39 @@ public class MainActivity extends AbstractAuthedActivity implements MainContract
new RealmSessionRepository(hostname)
);
PublicSettingRepository publicSettingRepository = new RealmPublicSettingRepository(hostname);
RocketChatCache rocketChatCache = new RocketChatCache(this);
presenter = new MainPresenter(
roomInteractor,
createRoomInteractor,
sessionInteractor,
new MethodCallHelper(this, hostname),
ConnectivityManager.getInstance(getApplicationContext()),
new RocketChatCache(this)
rocketChatCache,
publicSettingRepository
);
updateSidebarMainFragment();
presenter.bindView(this);
presenter.loadSignedInServers(hostname);
roomId = rocketChatCache.getSelectedRoomId();
}
private void updateSidebarMainFragment() {
closeSidebarIfNeeded();
String selectedServerHostname = new RocketChatCache(this).getSelectedServerHostname();
Fragment sidebarFragment = findFragmentByTag(selectedServerHostname);
if (sidebarFragment == null) {
sidebarFragment = SidebarMainFragment.create(selectedServerHostname);
}
getSupportFragmentManager().beginTransaction()
.replace(R.id.sidebar_fragment_container, SidebarMainFragment.create(hostname))
.replace(R.id.sidebar_fragment_container, sidebarFragment, selectedServerHostname)
.commit();
getSupportFragmentManager().executePendingTransactions();
}
@Override
......@@ -165,7 +221,7 @@ public class MainActivity extends AbstractAuthedActivity implements MainContract
@Override
public void showUnreadCount(long roomsCount, int mentionsCount) {
toolbar.setUnreadBudge((int) roomsCount, mentionsCount);
toolbar.setUnreadBadge((int) roomsCount, mentionsCount);
}
@Override
......@@ -195,16 +251,94 @@ public class MainActivity extends AbstractAuthedActivity implements MainContract
R.string.server_config_activity_authenticating, Snackbar.LENGTH_INDEFINITE));
}
public void showLogoutMessage() {
statusTicker.updateStatus(StatusTicker.STATUS_LOGGING_OUT,
Snackbar.make(findViewById(getLayoutContainerForFragment()),
"Logging Out...", 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);
}
}
serverListContainer.addView(addServerButton);
}
}
private void changeServerIfNeeded(String serverHostname) {
if (!hostname.equalsIgnoreCase(serverHostname)) {
RocketChatCache rocketChatCache = new RocketChatCache(getApplicationContext());
rocketChatCache.setSelectedServerHostname(serverHostname);
}
}
@DebugLog
public void hideLogoutMessage() {
statusTicker.updateStatus(StatusTicker.STATUS_DISMISS, null);
}
@DebugLog
public void onLogout() {
if (new RocketChatCache(getApplicationContext()).getSelectedServerHostname() == null) {
LaunchUtil.showMainActivity(this);
} else {
onHostnameUpdated();
}
}
@DebugLog
public void beforeLogoutCleanUp() {
presenter.beforeLogoutCleanUp();
}
//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;
private int status;
private Snackbar snackbar;
......
package chat.rocket.android.activity;
import java.util.List;
import chat.rocket.android.shared.BaseContract;
import chat.rocket.core.utils.Pair;
public interface MainContract {
......@@ -21,6 +24,8 @@ public interface MainContract {
void showConnecting();
void showConnectionOk();
void showSignedInServers(List<Pair<String, Pair<String, String>>> serverList);
}
interface Presenter extends BaseContract.Presenter<View> {
......@@ -30,5 +35,9 @@ public interface MainContract {
void onRetryLogin();
void bindViewOnly(View view);
void loadSignedInServers(String hostname);
void beforeLogoutCleanUp();
}
}
package chat.rocket.android.activity;
import android.support.annotation.NonNull;
import android.support.v4.util.Pair;
import com.hadisatrio.optional.Optional;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.List;
import chat.rocket.android.BackgroundLooper;
import chat.rocket.android.RocketChatCache;
import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.android.helper.LogIfError;
import chat.rocket.android.helper.Logger;
import chat.rocket.android.log.RCLog;
import chat.rocket.android.service.ConnectivityManagerApi;
import chat.rocket.android.service.ServerConnectivity;
import chat.rocket.android.shared.BasePresenter;
import chat.rocket.core.PublicSettingsConstants;
import chat.rocket.core.interactors.CanCreateRoomInteractor;
import chat.rocket.core.interactors.RoomInteractor;
import chat.rocket.core.interactors.SessionInteractor;
import chat.rocket.core.models.PublicSetting;
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;
......@@ -30,19 +41,21 @@ public class MainPresenter extends BasePresenter<MainContract.View>
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) {
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
......@@ -53,6 +66,22 @@ public class MainPresenter extends BasePresenter<MainContract.View>
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);
......@@ -104,6 +133,32 @@ public class MainPresenter extends BasePresenter<MainContract.View>
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();
......
package chat.rocket.android.activity.room
import android.os.Bundle
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 kotlinx.android.synthetic.main.activity_room.*
class RoomActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_room)
setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
val extras = intent.extras
val roomListFragment = RoomListFragment.newInstance(extras.getInt("actionId"),
extras.getString("roomId"),
extras.getString("roomType"),
extras.getString("hostname"),
extras.getString("token"),
extras.getString("userId"))
addFragment(roomListFragment, "roomListFragment")
}
override fun onSupportNavigateUp(): Boolean {
onBackPressed()
return true
}
private fun addFragment(fragment: Fragment, tag: String) {
supportFragmentManager
.beginTransaction()
.add(R.id.fragment_container, fragment, tag)
.commit()
}
}
\ No newline at end of file
......@@ -2,7 +2,7 @@ package chat.rocket.android.api;
import android.content.Context;
import android.util.Patterns;
import chat.rocket.persistence.realm.models.ddp.RealmSpotlight;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
......@@ -17,7 +17,6 @@ import chat.rocket.android.service.ConnectivityManager;
import chat.rocket.android.service.DDPClientRef;
import chat.rocket.android_ddp.DDPClientCallback;
import chat.rocket.core.SyncState;
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.RealmMessage;
......@@ -25,6 +24,7 @@ import chat.rocket.persistence.realm.models.ddp.RealmPermission;
import chat.rocket.persistence.realm.models.ddp.RealmPublicSetting;
import chat.rocket.persistence.realm.models.ddp.RealmRoom;
import chat.rocket.persistence.realm.models.ddp.RealmRoomRole;
import chat.rocket.persistence.realm.models.ddp.RealmSpotlight;
import chat.rocket.persistence.realm.models.ddp.RealmSpotlightRoom;
import chat.rocket.persistence.realm.models.ddp.RealmSpotlightUser;
import chat.rocket.persistence.realm.models.internal.MethodCall;
......@@ -52,7 +52,7 @@ public class MethodCallHelper {
*/
public MethodCallHelper(Context context, String hostname) {
this.context = context.getApplicationContext();
this.realmHelper = RealmStore.get(hostname);
this.realmHelper = RealmStore.getOrCreate(hostname);
ddpClientRef = null;
}
......@@ -260,18 +260,12 @@ public class MethodCallHelper {
* Logout.
*/
public Task<Void> logout() {
return call("logout", TIMEOUT_MS).onSuccessTask(task ->
realmHelper.executeTransaction(realm -> {
realm.delete(RealmSession.class);
//check whether the server list is empty
if (!ConnectivityManager.getInstance(context).getServerList().isEmpty()){
//for each server in serverList -> remove the server
for (ServerInfo server: ConnectivityManager.getInstance(context).getServerList()) {
ConnectivityManager.getInstance(context.getApplicationContext()).removeServer(server.getHostname());
}
}
return null;
}));
return call("logout", TIMEOUT_MS).onSuccessTask(task -> {
if (task.isFaulted()) {
return Task.forError(task.getError());
}
return null;
});
}
/**
......
package chat.rocket.android.api.rest
import chat.rocket.android.helper.UrlHelper
import chat.rocket.core.models.Room
import okhttp3.HttpUrl
import okhttp3.Request
/**
* Helper class for dealing with Rest API calls.
*
* @see <a href="https://rocket.chat/docs/developer-guides/rest-api">https://rocket.chat/docs/developer-guides/rest-api</a>.
*/
object RestApiHelper {
/**
* Returns an OkHttp3 request for pinned messages.
*
* @param roomId The ID of the room.
* @param roomType The type of the room.
* @param hostname The server hostname.
* @param token The token.
* @param userId The user Id.
* @param offset The offset to paging which specifies the first entry to return from a collection.
* @return An OkHttp3 request.
*/
fun getRequestForPinnedMessages(roomId: String,
roomType: String,
hostname: String,
token: String,
userId: String,
offset: String): Request {
val parsedHttpUrl = HttpUrl.parse(getEndpointUrlForMessages(roomType, hostname))
?.newBuilder()
?.addQueryParameter("roomId", roomId)
?.addQueryParameter("query", "{\"pinned\":true}")
?.addQueryParameter("offset", offset)
?.build()
return Request.Builder()
.url(parsedHttpUrl)
.get()
.addHeader("X-Auth-Token", token)
.addHeader("X-User-Id", userId)
.build()
}
/**
* Returns an OkHttp3 request for favorite messages.
*
* @param roomId The ID of the room.
* @param roomType The type of the room.
* @param hostname The server hostname.
* @param token The token.
* @param userId The user Id.
* @param offset The offset to paging which specifies the first entry to return from a collection.
* @return An OkHttp3 request.
*/
fun getRequestForFavoriteMessages(roomId: String,
roomType: String,
hostname: String,
token: String,
userId: String,
offset: String): Request {
val parsedHttpUrl = HttpUrl.parse(getEndpointUrlForMessages(roomType, hostname))
?.newBuilder()
?.addQueryParameter("roomId", roomId)
?.addQueryParameter("query", "{\"starred._id\":{\"\$in\":[\"$userId\"] } }")
?.addQueryParameter("offset", offset)
?.build()
return Request.Builder()
.url(parsedHttpUrl)
.get()
.addHeader("X-Auth-Token", token)
.addHeader("X-User-Id", userId)
.build()
}
/**
* Returns an OkHttp3 request for file list.
*
* @param roomId The ID of the room.
* @param roomType The type of the room.
* @param hostname The server hostname.
* @param token The token.
* @param userId The user Id.
* @param offset The offset to paging which specifies the first entry to return from a collection.
* @return An OkHttp3 request.
*/
fun getRequestForFileList(roomId: String,
roomType: String,
hostname: String,
token: String,
userId: String,
offset: String): Request {
val parsedHttpUrl = HttpUrl.parse(getEndpointUrlForFileList(roomType, hostname))
?.newBuilder()
?.addQueryParameter("roomId", roomId)
?.addQueryParameter("offset", offset)
?.build()
return Request.Builder()
.url(parsedHttpUrl)
.get()
.addHeader("X-Auth-Token", token)
.addHeader("X-User-Id", userId)
.build()
}
/**
* Returns an OkHttp3 request for member list.
*
* @param roomId The ID of the room.
* @param roomType The type of the room.
* @param hostname The server hostname.
* @param token The token.
* @param userId The user Id.
* @param offset The offset to paging which specifies the first entry to return from a collection.
* @return An OkHttp3 request.
*/
fun getRequestForMemberList(roomId: String,
roomType: String,
hostname: String,
token: String,
userId: String,
offset: String): Request {
val parsedHttpUrl = HttpUrl.parse(getEndpointUrlForMemberList(roomType, hostname))
?.newBuilder()
?.addQueryParameter("roomId", roomId)
?.addQueryParameter("offset", offset)
?.build()
return Request.Builder()
.url(parsedHttpUrl)
.get()
.addHeader("X-Auth-Token", token)
.addHeader("X-User-Id", userId)
.build()
}
/**
* Returns a Rest API endpoint URL for favorite or pinned messages accordingly with the room type and the server hostname.
*
* @param roomType The type of the room.
* @param hostname The server hostname.
* @return A Rest API URL endpoint.
*/
fun getEndpointUrlForMessages(roomType: String, hostname: String): String =
UrlHelper.getSafeHostname(hostname) + getRestApiUrlForMessages(roomType)
/**
* Returns a Rest API endpoint URL for file list accordingly with the room type and the server hostname.
*
* @param roomType The type of the room.
* @param hostname The server hostname.
* @return A Rest API URL endpoint.
*/
fun getEndpointUrlForFileList(roomType: String, hostname: String): String =
UrlHelper.getSafeHostname(hostname) + getRestApiUrlForFileList(roomType)
/**
* Returns a Rest API endpoint URL for member list accordingly with the room type and the server hostname.
*
* @param roomType The type of the room.
* @param hostname The server hostname.
* @return A Rest API URL endpoint.
*/
fun getEndpointUrlForMemberList(roomType: String, hostname: String): String =
UrlHelper.getSafeHostname(hostname) + getRestApiUrlForMemberList(roomType)
/**
* Returns the correspondent Rest API URL accordingly with the room type to get its favorite or pinned messages.
*
* @param roomType The type of the room.
* @return A Rest API URL or null if the room type does not match.
*/
fun getRestApiUrlForMessages(roomType: String): String? {
var restApiUrl: String? = null
when (roomType) {
Room.TYPE_CHANNEL -> restApiUrl = "/api/v1/channels.messages"
Room.TYPE_PRIVATE -> restApiUrl = "/api/v1/groups.messages"
Room.TYPE_DIRECT_MESSAGE -> restApiUrl = "/api/v1/dm.messages"
}
return restApiUrl
}
/**
* Returns the correspondent Rest API URL accordingly with the room type to get its file list.
*
* @param roomType The type of the room.
* @return A Rest API URL or null if the room type does not match.
*/
fun getRestApiUrlForFileList(roomType: String): String? {
var restApiUrl: String? = null
when (roomType) {
Room.TYPE_CHANNEL -> restApiUrl = "/api/v1/channels.files"
Room.TYPE_PRIVATE -> restApiUrl = "/api/v1/groups.files"
Room.TYPE_DIRECT_MESSAGE -> restApiUrl = "/api/v1/dm.files"
}
return restApiUrl
}
/**
* Returns the correspondent Rest API URL accordingly with the room type to get its members list.
*
* @param roomType The type of the room.
* @return A Rest API URL or null if the room type does not match.
*/
fun getRestApiUrlForMemberList(roomType: String): String? {
var restApiUrl: String? = null
when (roomType) {
Room.TYPE_CHANNEL -> restApiUrl = "/api/v1/channels.members"
Room.TYPE_PRIVATE -> restApiUrl = "/api/v1/groups.members"
Room.TYPE_DIRECT_MESSAGE -> restApiUrl = "/api/v1/dm.members"
}
return restApiUrl
}
}
\ No newline at end of file
......@@ -72,7 +72,7 @@ public class InputHostnameFragment extends AbstractFragment implements InputHost
private String getHostname() {
final TextView editor = (TextView) rootView.findViewById(R.id.editor_hostname);
return TextUtils.or(TextUtils.or(editor.getText(), editor.getHint()), "").toString();
return TextUtils.or(TextUtils.or(editor.getText(), editor.getHint()), "").toString().toLowerCase();
}
private void showError(String errString) {
......
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;
......@@ -46,7 +47,10 @@ public class InputHostnamePresenter extends BasePresenter<InputHostnameContract.
view.showInvalidServerError();
}
},
throwable -> view.showConnectionError());
throwable -> {
Logger.report(throwable);
view.showConnectionError();
});
addSubscription(subscription);
}
......
......@@ -22,6 +22,7 @@ abstract class AbstractChatRoomFragment extends AbstractFragment {
}
protected void setToolbarTitle(CharSequence title) {
roomToolbar.hideChannelIcons();
roomToolbar.setTitle(title);
}
......
......@@ -21,4 +21,12 @@ public class RocketChatAbsoluteUrl implements AbsoluteUrl {
public String from(String url) {
return url.startsWith("/") ? baseUrl + url + "?rc_uid=" + userId + "&rc_token=" + token : url;
}
}
public String getUserId() {
return userId;
}
public String getToken() {
return token;
}
}
\ No newline at end of file
......@@ -22,6 +22,10 @@ public interface RoomContract {
void onMessageSendSuccessfully();
void disableMessageInput();
void enableMessageInput();
void showUnreadCount(int count);
void showMessages(List<Message> messages);
......
......@@ -13,31 +13,27 @@ import android.support.v13.view.inputmethod.InputContentInfoCompat;
import android.support.v4.app.DialogFragment;
import android.support.v4.os.BuildCompat;
import android.support.v4.util.Pair;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v4.widget.SlidingPaneLayout;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import chat.rocket.android.fragment.sidebar.SidebarMainFragment;
import chat.rocket.android.widget.RoomToolbar;
import chat.rocket.core.models.User;
import java.lang.reflect.Field;
import com.hadisatrio.optional.Optional;
import java.util.ArrayList;
import java.util.List;
import chat.rocket.android.BackgroundLooper;
import chat.rocket.android.R;
import chat.rocket.android.activity.room.RoomActivity;
import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.android.fragment.chatroom.dialog.FileUploadProgressDialogFragment;
import chat.rocket.android.fragment.chatroom.dialog.MessageOptionsDialogFragment;
import chat.rocket.android.fragment.chatroom.dialog.UsersOfRoomDialogFragment;
import chat.rocket.android.fragment.sidebar.SidebarMainFragment;
import chat.rocket.android.helper.AbsoluteUrlHelper;
import chat.rocket.android.helper.FileUploadHelper;
import chat.rocket.android.helper.LoadMoreScrollListener;
import chat.rocket.android.helper.Logger;
import chat.rocket.android.helper.OnBackPressListener;
import chat.rocket.android.helper.RecyclerViewAutoScrollManager;
import chat.rocket.android.helper.RecyclerViewScrolledToBottomListener;
......@@ -53,11 +49,11 @@ import chat.rocket.android.layouthelper.extra_action.upload.AbstractUploadAction
import chat.rocket.android.layouthelper.extra_action.upload.AudioUploadActionItem;
import chat.rocket.android.layouthelper.extra_action.upload.ImageUploadActionItem;
import chat.rocket.android.layouthelper.extra_action.upload.VideoUploadActionItem;
import chat.rocket.android.log.RCLog;
import chat.rocket.android.renderer.RocketChatUserStatusProvider;
import chat.rocket.android.service.ConnectivityManager;
import chat.rocket.android.service.temp.DeafultTempSpotlightRoomCaller;
import chat.rocket.android.service.temp.DefaultTempSpotlightUserCaller;
import chat.rocket.android.widget.RoomToolbar;
import chat.rocket.android.widget.internal.ExtraActionPickerDialogFragment;
import chat.rocket.android.widget.message.MessageFormLayout;
import chat.rocket.android.widget.message.autocomplete.AutocompleteManager;
......@@ -69,6 +65,7 @@ import chat.rocket.core.interactors.MessageInteractor;
import chat.rocket.core.interactors.SessionInteractor;
import chat.rocket.core.models.Message;
import chat.rocket.core.models.Room;
import chat.rocket.core.models.User;
import chat.rocket.persistence.realm.RealmStore;
import chat.rocket.persistence.realm.repositories.RealmMessageRepository;
import chat.rocket.persistence.realm.repositories.RealmRoomRepository;
......@@ -77,9 +74,6 @@ import chat.rocket.persistence.realm.repositories.RealmSessionRepository;
import chat.rocket.persistence.realm.repositories.RealmSpotlightRoomRepository;
import chat.rocket.persistence.realm.repositories.RealmSpotlightUserRepository;
import chat.rocket.persistence.realm.repositories.RealmUserRepository;
import com.hadisatrio.optional.Optional;
import com.jakewharton.rxbinding2.support.v4.widget.RxDrawerLayout;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
......@@ -98,561 +92,577 @@ public class RoomFragment extends AbstractChatRoomFragment implements
ModelListAdapter.OnItemLongClickListener<PairedMessage>,
RoomContract.View {
private static final int DIALOG_ID = 1;
private static final String HOSTNAME = "hostname";
private static final String ROOM_ID = "roomId";
private static final int DIALOG_ID = 1;
private static final String HOSTNAME = "hostname";
private static final String ROOM_ID = "roomId";
private String hostname;
private String roomId;
private LoadMoreScrollListener scrollListener;
private MessageFormManager messageFormManager;
private RecyclerView messageRecyclerView;
private RecyclerViewAutoScrollManager recyclerViewAutoScrollManager;
protected AbstractNewMessageIndicatorManager newMessageIndicatorManager;
protected Snackbar unreadIndicator;
private boolean previousUnreadMessageExists;
private MessageListAdapter messageListAdapter;
private AutocompleteManager autocompleteManager;
private List<AbstractExtraActionItem> extraActionItems;
private CompositeDisposable compositeDisposable = new CompositeDisposable();
protected RoomContract.Presenter presenter;
private RealmRoomRepository roomRepository;
private RealmUserRepository userRepository;
private MethodCallHelper methodCallHelper;
private AbsoluteUrlHelper absoluteUrlHelper;
private Message edittingMessage = null;
private SlidingPaneLayout pane;
private SidebarMainFragment sidebarFragment;
public RoomFragment() {}
/**
* create fragment with roomId.
*/
public static RoomFragment create(String hostname, String roomId) {
Bundle args = new Bundle();
args.putString(HOSTNAME, hostname);
args.putString(ROOM_ID, roomId);
RoomFragment fragment = new RoomFragment();
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle args = getArguments();
hostname = args.getString(HOSTNAME);
roomId = args.getString(ROOM_ID);
roomRepository = new RealmRoomRepository(hostname);
MessageInteractor messageInteractor = new MessageInteractor(
new RealmMessageRepository(hostname),
roomRepository
);
userRepository = new RealmUserRepository(hostname);
absoluteUrlHelper = new AbsoluteUrlHelper(
hostname,
new RealmServerInfoRepository(),
userRepository,
new SessionInteractor(new RealmSessionRepository(hostname))
);
methodCallHelper = new MethodCallHelper(getContext(), hostname);
presenter = new RoomPresenter(
roomId,
userRepository,
messageInteractor,
roomRepository,
absoluteUrlHelper,
methodCallHelper,
ConnectivityManager.getInstance(getContext())
);
if (savedInstanceState == null) {
presenter.loadMessages();
}
}
@Override
protected int getLayout() {
return R.layout.fragment_room;
}
@Override
protected void onSetupView() {
pane = getActivity().findViewById(R.id.sliding_pane);
messageRecyclerView = rootView.findViewById(R.id.messageRecyclerView);
messageListAdapter = new MessageListAdapter(getContext(), hostname);
messageRecyclerView.setAdapter(messageListAdapter);
messageListAdapter.setOnItemClickListener(this);
messageListAdapter.setOnItemLongClickListener(this);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, true);
messageRecyclerView.setLayoutManager(linearLayoutManager);
recyclerViewAutoScrollManager = new RecyclerViewAutoScrollManager(linearLayoutManager) {
@Override
protected void onAutoScrollMissed() {
if (newMessageIndicatorManager != null) {
presenter.onUnreadCount();
}
}
};
messageListAdapter.registerAdapterDataObserver(recyclerViewAutoScrollManager);
scrollListener = new LoadMoreScrollListener(linearLayoutManager, 40) {
@Override
public void requestMoreItem() {
presenter.loadMoreMessages();
}
};
messageRecyclerView.addOnScrollListener(scrollListener);
messageRecyclerView.addOnScrollListener(new RecyclerViewScrolledToBottomListener(linearLayoutManager, 1, this::markAsReadIfNeeded));
newMessageIndicatorManager = new AbstractNewMessageIndicatorManager() {
@Override
protected void onShowIndicator(int count, boolean onlyAlreadyShown) {
if ((onlyAlreadyShown && unreadIndicator != null && unreadIndicator.isShown()) || !onlyAlreadyShown) {
unreadIndicator = getUnreadCountIndicatorView(count);
unreadIndicator.show();
}
}
private String hostname;
private String token;
private String userId;
private String roomId;
private String roomType;
private LoadMoreScrollListener scrollListener;
private MessageFormManager messageFormManager;
private RecyclerView messageRecyclerView;
private RecyclerViewAutoScrollManager recyclerViewAutoScrollManager;
protected AbstractNewMessageIndicatorManager newMessageIndicatorManager;
protected Snackbar unreadIndicator;
private boolean previousUnreadMessageExists;
private MessageListAdapter messageListAdapter;
private AutocompleteManager autocompleteManager;
private List<AbstractExtraActionItem> extraActionItems;
private CompositeDisposable compositeDisposable = new CompositeDisposable();
protected RoomContract.Presenter presenter;
@Override
protected void onHideIndicator() {
if (unreadIndicator != null && unreadIndicator.isShown()) {
unreadIndicator.dismiss();
private RealmRoomRepository roomRepository;
private RealmUserRepository userRepository;
private MethodCallHelper methodCallHelper;
private AbsoluteUrlHelper absoluteUrlHelper;
private Message edittingMessage = null;
private RoomToolbar toolbar;
private SlidingPaneLayout pane;
private SidebarMainFragment sidebarFragment;
public RoomFragment() {
}
/**
* create fragment with roomId.
*/
public static RoomFragment create(String hostname, String roomId) {
Bundle args = new Bundle();
args.putString(HOSTNAME, hostname);
args.putString(ROOM_ID, roomId);
RoomFragment fragment = new RoomFragment();
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
Bundle args = getArguments();
hostname = args.getString(HOSTNAME);
roomId = args.getString(ROOM_ID);
roomRepository = new RealmRoomRepository(hostname);
MessageInteractor messageInteractor = new MessageInteractor(
new RealmMessageRepository(hostname),
roomRepository
);
userRepository = new RealmUserRepository(hostname);
absoluteUrlHelper = new AbsoluteUrlHelper(
hostname,
new RealmServerInfoRepository(),
userRepository,
new SessionInteractor(new RealmSessionRepository(hostname))
);
methodCallHelper = new MethodCallHelper(getContext(), hostname);
presenter = new RoomPresenter(
roomId,
userRepository,
messageInteractor,
roomRepository,
absoluteUrlHelper,
methodCallHelper,
ConnectivityManager.getInstance(getContext())
);
if (savedInstanceState == null) {
presenter.loadMessages();
}
}
};
setupSidebar();
setupSideMenu();
setupMessageComposer();
setupMessageActions();
}
private void setupMessageActions() {
extraActionItems = new ArrayList<>(3); // fixed number as of now
extraActionItems.add(new ImageUploadActionItem());
extraActionItems.add(new AudioUploadActionItem());
extraActionItems.add(new VideoUploadActionItem());
}
private void scrollToLatestMessage() {
if (messageRecyclerView != null)
messageRecyclerView.scrollToPosition(0);
}
protected Snackbar getUnreadCountIndicatorView(int count) {
// TODO: replace with another custom View widget, not to hide message composer.
final String caption = getResources().getQuantityString(
R.plurals.fmt_dialog_view_latest_message_title, count, count);
return Snackbar.make(rootView, caption, Snackbar.LENGTH_LONG)
.setAction(R.string.dialog_view_latest_message_action, view -> scrollToLatestMessage());
}
@Override
public void onDestroyView() {
RecyclerView.Adapter adapter = messageRecyclerView.getAdapter();
if (adapter != null)
adapter.unregisterAdapterDataObserver(recyclerViewAutoScrollManager);
compositeDisposable.clear();
if (autocompleteManager != null) {
autocompleteManager.dispose();
autocompleteManager = null;
}
super.onDestroyView();
}
@Override
public void onItemClick(PairedMessage pairedMessage) {
presenter.onMessageSelected(pairedMessage.target);
}
@Override
public boolean onItemLongClick(PairedMessage pairedMessage) {
MessageOptionsDialogFragment messageOptionsDialogFragment = MessageOptionsDialogFragment
.create(pairedMessage.target);
messageOptionsDialogFragment.setOnMessageOptionSelectedListener(message -> {
messageOptionsDialogFragment.dismiss();
onEditMessage(message);
});
messageOptionsDialogFragment.show(getChildFragmentManager(), "MessageOptionsDialogFragment");
return true;
}
private void setupSideMenu() {
View sideMenu = rootView.findViewById(R.id.room_side_menu);
sideMenu.findViewById(R.id.btn_users).setOnClickListener(view -> {
UsersOfRoomDialogFragment.create(roomId, hostname)
.show(getFragmentManager(), "UsersOfRoomDialogFragment");
closeSideMenuIfNeeded();
});
DrawerLayout drawerLayout = rootView.findViewById(R.id.drawer_layout);
if (drawerLayout != null && pane != null) {
compositeDisposable.add(RxDrawerLayout.drawerOpen(drawerLayout, GravityCompat.END)
.compose(bindToLifecycle())
.subscribe(
opened -> {
try {
Field fieldSlidable = pane.getClass().getDeclaredField("mCanSlide");
fieldSlidable.setAccessible(true);
fieldSlidable.setBoolean(pane, !opened);
} catch (Exception exception) {
RCLog.w(exception);
}
@Override
protected int getLayout() {
return R.layout.fragment_room;
}
@Override
protected void onSetupView() {
pane = getActivity().findViewById(R.id.sliding_pane);
messageRecyclerView = rootView.findViewById(R.id.messageRecyclerView);
messageListAdapter = new MessageListAdapter(getContext(), hostname);
messageRecyclerView.setAdapter(messageListAdapter);
messageListAdapter.setOnItemClickListener(this);
messageListAdapter.setOnItemLongClickListener(this);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, true);
messageRecyclerView.setLayoutManager(linearLayoutManager);
recyclerViewAutoScrollManager = new RecyclerViewAutoScrollManager(linearLayoutManager) {
@Override
protected void onAutoScrollMissed() {
if (newMessageIndicatorManager != null) {
presenter.onUnreadCount();
}
}
};
messageListAdapter.registerAdapterDataObserver(recyclerViewAutoScrollManager);
scrollListener = new LoadMoreScrollListener(linearLayoutManager, 40) {
@Override
public void requestMoreItem() {
presenter.loadMoreMessages();
}
};
messageRecyclerView.addOnScrollListener(scrollListener);
messageRecyclerView.addOnScrollListener(new RecyclerViewScrolledToBottomListener(linearLayoutManager, 1, this::markAsReadIfNeeded));
newMessageIndicatorManager = new AbstractNewMessageIndicatorManager() {
@Override
protected void onShowIndicator(int count, boolean onlyAlreadyShown) {
if ((onlyAlreadyShown && unreadIndicator != null && unreadIndicator.isShown()) || !onlyAlreadyShown) {
unreadIndicator = getUnreadCountIndicatorView(count);
unreadIndicator.show();
}
}
@Override
protected void onHideIndicator() {
if (unreadIndicator != null && unreadIndicator.isShown()) {
unreadIndicator.dismiss();
}
},
Logger::report
)
);
}
};
setupToolbar();
setupSidebar();
setupMessageComposer();
setupMessageActions();
}
}
private void setupSidebar() {
SlidingPaneLayout subPane = getActivity().findViewById(R.id.sub_sliding_pane);
RoomToolbar toolbar = getActivity().findViewById(R.id.activity_main_toolbar);
sidebarFragment = (SidebarMainFragment) getActivity().getSupportFragmentManager().findFragmentById(R.id.sidebar_fragment_container);
private void setupMessageActions() {
extraActionItems = new ArrayList<>(3); // fixed number as of now
extraActionItems.add(new ImageUploadActionItem());
extraActionItems.add(new AudioUploadActionItem());
extraActionItems.add(new VideoUploadActionItem());
}
pane.setPanelSlideListener(new SlidingPaneLayout.PanelSlideListener() {
@Override
public void onPanelSlide(View view, float v) {
messageFormManager.enableComposingText(false);
sidebarFragment.clearSearchViewFocus();
//Ref: ActionBarDrawerToggle#setProgress
toolbar.setNavigationIconProgress(v);
}
@Override
public void onPanelOpened(View view) {
toolbar.setNavigationIconVerticalMirror(true);
}
@Override
public void onPanelClosed(View view) {
messageFormManager.enableComposingText(true);
toolbar.setNavigationIconVerticalMirror(false);
subPane.closePane();
closeUserActionContainer();
}
});
toolbar.setNavigationOnClickListener(view -> {
if (pane.isSlideable() && !pane.isOpen()) {
pane.openPane();
}
});
}
public void closeUserActionContainer() {
sidebarFragment.closeUserActionContainer();
}
private boolean closeSideMenuIfNeeded() {
DrawerLayout drawerLayout = rootView.findViewById(R.id.drawer_layout);
if (drawerLayout != null && drawerLayout.isDrawerOpen(GravityCompat.END)) {
drawerLayout.closeDrawer(GravityCompat.END);
return true;
}
return false;
}
private void setupMessageComposer() {
final MessageFormLayout messageFormLayout = rootView.findViewById(R.id.messageComposer);
messageFormManager = new MessageFormManager(messageFormLayout, this::showExtraActionSelectionDialog);
messageFormManager.setSendMessageCallback(this::sendMessage);
messageFormLayout.setEditTextCommitContentListener(this::onCommitContent);
autocompleteManager = new AutocompleteManager(rootView.findViewById(R.id.messageListRelativeLayout));
autocompleteManager.registerSource(
new ChannelSource(
new AutocompleteChannelInteractor(
roomRepository,
new RealmSpotlightRoomRepository(hostname),
new DeafultTempSpotlightRoomCaller(methodCallHelper)
),
AndroidSchedulers.from(BackgroundLooper.get()),
AndroidSchedulers.mainThread()
)
);
Disposable disposable = Single.zip(
absoluteUrlHelper.getRocketChatAbsoluteUrl(),
roomRepository.getById(roomId).first(Optional.absent()),
Pair::create
)
.subscribe(
pair -> {
if (pair.first.isPresent() && pair.second.isPresent()) {
autocompleteManager.registerSource(
new UserSource(
new AutocompleteUserInteractor(
pair.second.get(),
userRepository,
new RealmMessageRepository(hostname),
new RealmSpotlightUserRepository(hostname),
new DefaultTempSpotlightUserCaller(methodCallHelper)
private void scrollToLatestMessage() {
if (messageRecyclerView != null)
messageRecyclerView.scrollToPosition(0);
}
protected Snackbar getUnreadCountIndicatorView(int count) {
// TODO: replace with another custom View widget, not to hide message composer.
final String caption = getResources().getQuantityString(
R.plurals.fmt_dialog_view_latest_message_title, count, count);
return Snackbar.make(rootView, caption, Snackbar.LENGTH_LONG)
.setAction(R.string.dialog_view_latest_message_action, view -> scrollToLatestMessage());
}
@Override
public void onDestroyView() {
RecyclerView.Adapter adapter = messageRecyclerView.getAdapter();
if (adapter != null)
adapter.unregisterAdapterDataObserver(recyclerViewAutoScrollManager);
compositeDisposable.clear();
if (autocompleteManager != null) {
autocompleteManager.dispose();
autocompleteManager = null;
}
super.onDestroyView();
}
@Override
public void onItemClick(PairedMessage pairedMessage) {
presenter.onMessageSelected(pairedMessage.target);
}
@Override
public boolean onItemLongClick(PairedMessage pairedMessage) {
MessageOptionsDialogFragment messageOptionsDialogFragment = MessageOptionsDialogFragment
.create(pairedMessage.target);
messageOptionsDialogFragment.setOnMessageOptionSelectedListener(message -> {
messageOptionsDialogFragment.dismiss();
onEditMessage(message);
});
messageOptionsDialogFragment.show(getChildFragmentManager(), "MessageOptionsDialogFragment");
return true;
}
private void setupToolbar() {
toolbar = getActivity().findViewById(R.id.activity_main_toolbar);
toolbar.getMenu().clear();
toolbar.inflateMenu(R.menu.menu_room);
toolbar.setNavigationOnClickListener(view -> {
if (pane.isSlideable() && !pane.isOpen()) {
pane.openPane();
}
});
toolbar.setOnMenuItemClickListener(menuItem -> {
switch (menuItem.getItemId()) {
case R.id.action_pinned_messages:
showRoomListFragment(R.id.action_pinned_messages);
break;
case R.id.action_favorite_messages:
showRoomListFragment(R.id.action_favorite_messages);
break;
// case R.id.action_file_list:
// showRoomListFragment(R.id.action_file_list);
// break;
case R.id.action_member_list:
showRoomListFragment(R.id.action_member_list);
break;
default:
return super.onOptionsItemSelected(menuItem);
}
return true;
});
}
private void setupSidebar() {
SlidingPaneLayout subPane = getActivity().findViewById(R.id.sub_sliding_pane);
sidebarFragment = (SidebarMainFragment) getActivity().getSupportFragmentManager().findFragmentById(R.id.sidebar_fragment_container);
pane.setPanelSlideListener(new SlidingPaneLayout.PanelSlideListener() {
@Override
public void onPanelSlide(View view, float v) {
messageFormManager.enableComposingText(false);
sidebarFragment.clearSearchViewFocus();
//Ref: ActionBarDrawerToggle#setProgress
toolbar.setNavigationIconProgress(v);
}
@Override
public void onPanelOpened(View view) {
toolbar.setNavigationIconVerticalMirror(true);
}
@Override
public void onPanelClosed(View view) {
messageFormManager.enableComposingText(true);
toolbar.setNavigationIconVerticalMirror(false);
subPane.closePane();
closeUserActionContainer();
}
});
}
public void closeUserActionContainer() {
sidebarFragment.closeUserActionContainer();
}
private void setupMessageComposer() {
final MessageFormLayout messageFormLayout = rootView.findViewById(R.id.messageComposer);
messageFormManager = new MessageFormManager(messageFormLayout, this::showExtraActionSelectionDialog);
messageFormManager.setSendMessageCallback(this::sendMessage);
messageFormLayout.setEditTextCommitContentListener(this::onCommitContent);
autocompleteManager = new AutocompleteManager(rootView.findViewById(R.id.messageListRelativeLayout));
autocompleteManager.registerSource(
new ChannelSource(
new AutocompleteChannelInteractor(
roomRepository,
new RealmSpotlightRoomRepository(hostname),
new DeafultTempSpotlightRoomCaller(methodCallHelper)
),
pair.first.get(),
RocketChatUserStatusProvider.INSTANCE,
AndroidSchedulers.from(BackgroundLooper.get()),
AndroidSchedulers.mainThread()
)
)
);
Disposable disposable = Single.zip(
absoluteUrlHelper.getRocketChatAbsoluteUrl(),
roomRepository.getById(roomId).first(Optional.absent()),
Pair::create
)
.subscribe(
pair -> {
if (pair.first.isPresent() && pair.second.isPresent()) {
autocompleteManager.registerSource(
new UserSource(
new AutocompleteUserInteractor(
pair.second.get(),
userRepository,
new RealmMessageRepository(hostname),
new RealmSpotlightUserRepository(hostname),
new DefaultTempSpotlightUserCaller(methodCallHelper)
),
pair.first.get(),
RocketChatUserStatusProvider.INSTANCE,
AndroidSchedulers.from(BackgroundLooper.get()),
AndroidSchedulers.mainThread()
)
);
}
},
throwable -> {
}
);
}
},
throwable -> {
}
compositeDisposable.add(disposable);
autocompleteManager.bindTo(
messageFormLayout.getEditText(),
messageFormLayout
);
}
compositeDisposable.add(disposable);
autocompleteManager.bindTo(
messageFormLayout.getEditText(),
messageFormLayout
);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode != AbstractUploadActionItem.RC_UPL || resultCode != Activity.RESULT_OK) {
return;
}
if (data == null || data.getData() == null) {
return;
}
uploadFile(data.getData());
}
private void uploadFile(Uri uri) {
String uplId = new FileUploadHelper(getContext(), RealmStore.get(hostname))
.requestUploading(roomId, uri);
if (!TextUtils.isEmpty(uplId)) {
FileUploadProgressDialogFragment.create(hostname, roomId, uplId)
.show(getFragmentManager(), "FileUploadProgressDialogFragment");
} else {
// show error.
}
}
private void markAsReadIfNeeded() {
presenter.onMarkAsRead();
}
@Override
public void onResume() {
super.onResume();
presenter.bindView(this);
closeSideMenuIfNeeded();
}
@Override
public void onPause() {
presenter.release();
super.onPause();
}
private void showExtraActionSelectionDialog() {
final DialogFragment fragment = ExtraActionPickerDialogFragment
.create(new ArrayList<>(extraActionItems));
fragment.setTargetFragment(this, DIALOG_ID);
fragment.show(getFragmentManager(), "ExtraActionPickerDialogFragment");
}
@Override
public void onItemSelected(int itemId) {
for (AbstractExtraActionItem extraActionItem : extraActionItems) {
if (extraActionItem.getItemId() == itemId) {
RoomFragmentPermissionsDispatcher.onExtraActionSelectedWithCheck(RoomFragment.this, extraActionItem);
return;
}
}
}
@Override
public boolean onBackPressed() {
if (edittingMessage != null) {
edittingMessage = null;
messageFormManager.clearComposingText();
return true;
}
return closeSideMenuIfNeeded();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
RoomFragmentPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
}
@NeedsPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
protected void onExtraActionSelected(MessageExtraActionBehavior action) {
action.handleItemSelectedOnFragment(RoomFragment.this);
}
private boolean onCommitContent(InputContentInfoCompat inputContentInfo, int flags,
Bundle opts, String[] supportedMimeTypes) {
boolean supported = false;
for (final String mimeType : supportedMimeTypes) {
if (inputContentInfo.getDescription().hasMimeType(mimeType)) {
supported = true;
break;
}
}
if (!supported) {
return false;
}
if (BuildCompat.isAtLeastNMR1()
&& (flags & InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) {
try {
inputContentInfo.requestPermission();
} catch (Exception e) {
return false;
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode != AbstractUploadActionItem.RC_UPL || resultCode != Activity.RESULT_OK) {
return;
}
if (data == null || data.getData() == null) {
return;
}
uploadFile(data.getData());
}
Uri linkUri = inputContentInfo.getLinkUri();
if (linkUri == null) {
return false;
private void uploadFile(Uri uri) {
String uplId = new FileUploadHelper(getContext(), RealmStore.get(hostname))
.requestUploading(roomId, uri);
if (!TextUtils.isEmpty(uplId)) {
FileUploadProgressDialogFragment.create(hostname, roomId, uplId)
.show(getFragmentManager(), "FileUploadProgressDialogFragment");
} else {
// show error.
}
}
sendMessage(linkUri.toString());
private void markAsReadIfNeeded() {
presenter.onMarkAsRead();
}
try {
inputContentInfo.releasePermission();
} catch (Exception e) {
@Override
public void onResume() {
super.onResume();
presenter.bindView(this);
}
return true;
}
@Override
public void onPause() {
presenter.release();
super.onPause();
}
private void sendMessage(String messageText) {
if (edittingMessage == null) {
presenter.sendMessage(messageText);
} else {
presenter.updateMessage(edittingMessage, messageText);
private void showExtraActionSelectionDialog() {
final DialogFragment fragment = ExtraActionPickerDialogFragment
.create(new ArrayList<>(extraActionItems));
fragment.setTargetFragment(this, DIALOG_ID);
fragment.show(getFragmentManager(), "ExtraActionPickerDialogFragment");
}
}
@Override
public void setupWith(RocketChatAbsoluteUrl rocketChatAbsoluteUrl) {
messageListAdapter.setAbsoluteUrl(rocketChatAbsoluteUrl);
}
@Override
public void onItemSelected(int itemId) {
for (AbstractExtraActionItem extraActionItem : extraActionItems) {
if (extraActionItem.getItemId() == itemId) {
RoomFragmentPermissionsDispatcher.onExtraActionSelectedWithCheck(RoomFragment.this, extraActionItem);
return;
}
}
}
@Override
public void render(Room room) {
setToolbarTitle(room.getName());
@Override
public boolean onBackPressed() {
if (edittingMessage != null) {
edittingMessage = null;
messageFormManager.clearComposingText();
}
return false;
}
boolean unreadMessageExists = room.isAlert();
if (newMessageIndicatorManager != null && previousUnreadMessageExists && !unreadMessageExists) {
newMessageIndicatorManager.reset();
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
RoomFragmentPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
}
previousUnreadMessageExists = unreadMessageExists;
if (room.isChannel()) {
showToolbarPublicChannelIcon();
return;
@NeedsPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
protected void onExtraActionSelected(MessageExtraActionBehavior action) {
action.handleItemSelectedOnFragment(RoomFragment.this);
}
if (room.isPrivate()) {
showToolbarPrivateChannelIcon();
private boolean onCommitContent(InputContentInfoCompat inputContentInfo, int flags,
Bundle opts, String[] supportedMimeTypes) {
boolean supported = false;
for (final String mimeType : supportedMimeTypes) {
if (inputContentInfo.getDescription().hasMimeType(mimeType)) {
supported = true;
break;
}
}
if (!supported) {
return false;
}
if (BuildCompat.isAtLeastNMR1()
&& (flags & InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) {
try {
inputContentInfo.requestPermission();
} catch (Exception e) {
return false;
}
}
Uri linkUri = inputContentInfo.getLinkUri();
if (linkUri == null) {
return false;
}
sendMessage(linkUri.toString());
try {
inputContentInfo.releasePermission();
} catch (Exception e) {
}
return true;
}
private void sendMessage(String messageText) {
if (edittingMessage == null) {
presenter.sendMessage(messageText);
} else {
presenter.updateMessage(edittingMessage, messageText);
}
}
@Override
public void setupWith(RocketChatAbsoluteUrl rocketChatAbsoluteUrl) {
token = rocketChatAbsoluteUrl.getToken();
userId = rocketChatAbsoluteUrl.getUserId();
messageListAdapter.setAbsoluteUrl(rocketChatAbsoluteUrl);
}
@Override
public void render(Room room) {
roomType = room.getType();
setToolbarTitle(room.getName());
boolean unreadMessageExists = room.isAlert();
if (newMessageIndicatorManager != null && previousUnreadMessageExists && !unreadMessageExists) {
newMessageIndicatorManager.reset();
}
previousUnreadMessageExists = unreadMessageExists;
if (room.isChannel()) {
showToolbarPublicChannelIcon();
return;
}
if (room.isPrivate()) {
showToolbarPrivateChannelIcon();
}
if (room.isLivechat()) {
showToolbarLivechatChannelIcon();
}
}
if (room.isLivechat()) {
showToolbarLivechatChannelIcon();
@Override
public void showUserStatus(User user) {
showToolbarUserStatuslIcon(user.getStatus());
}
}
@Override
public void showUserStatus(User user) {
showToolbarUserStatuslIcon(user.getStatus());
}
@Override
public void updateHistoryState(boolean hasNext, boolean isLoaded) {
if (messageRecyclerView == null || !(messageRecyclerView.getAdapter() instanceof MessageListAdapter)) {
return;
}
@Override
public void updateHistoryState(boolean hasNext, boolean isLoaded) {
if (messageRecyclerView == null || !(messageRecyclerView.getAdapter() instanceof MessageListAdapter)) {
return;
MessageListAdapter adapter = (MessageListAdapter) messageRecyclerView.getAdapter();
if (isLoaded) {
scrollListener.setLoadingDone();
}
adapter.updateFooter(hasNext, isLoaded);
}
MessageListAdapter adapter = (MessageListAdapter) messageRecyclerView.getAdapter();
if (isLoaded) {
scrollListener.setLoadingDone();
@Override
public void onMessageSendSuccessfully() {
scrollToLatestMessage();
messageFormManager.onMessageSend();
edittingMessage = null;
}
adapter.updateFooter(hasNext, isLoaded);
}
@Override
public void onMessageSendSuccessfully() {
scrollToLatestMessage();
messageFormManager.onMessageSend();
edittingMessage = null;
}
@Override
public void disableMessageInput() {
messageFormManager.enableComposingText(false);
}
@Override
public void showUnreadCount(int count) {
newMessageIndicatorManager.updateNewMessageCount(count);
}
@Override
public void enableMessageInput() {
messageFormManager.enableComposingText(true);
}
@Override
public void showMessages(List<Message> messages) {
if (messageListAdapter == null) {
return;
@Override
public void showUnreadCount(int count) {
newMessageIndicatorManager.updateNewMessageCount(count);
}
messageListAdapter.updateData(messages);
}
@Override
public void showMessageSendFailure(Message message) {
new AlertDialog.Builder(getContext())
.setPositiveButton(R.string.resend,
(dialog, which) -> presenter.resendMessage(message))
.setNegativeButton(android.R.string.cancel, null)
.setNeutralButton(R.string.discard,
(dialog, which) -> presenter.deleteMessage(message))
.show();
}
@Override
public void showMessages(List<Message> messages) {
if (messageListAdapter == null) {
return;
}
messageListAdapter.updateData(messages);
}
@Override
public void autoloadImages() {
messageListAdapter.setAutoloadImages(true);
}
@Override
public void showMessageSendFailure(Message message) {
new AlertDialog.Builder(getContext())
.setPositiveButton(R.string.resend,
(dialog, which) -> presenter.resendMessage(message))
.setNegativeButton(android.R.string.cancel, null)
.setNeutralButton(R.string.discard,
(dialog, which) -> presenter.deleteMessage(message))
.show();
}
@Override
public void manualLoadImages() {
messageListAdapter.setAutoloadImages(false);
}
@Override
public void autoloadImages() {
messageListAdapter.setAutoloadImages(true);
}
private void onEditMessage(Message message) {
edittingMessage = message;
messageFormManager.setEditMessage(message.getMessage());
}
@Override
public void manualLoadImages() {
messageListAdapter.setAutoloadImages(false);
}
private void onEditMessage(Message message) {
edittingMessage = message;
messageFormManager.setEditMessage(message.getMessage());
}
private void showRoomListFragment(int actionId) {
Intent intent = new Intent(getActivity(), RoomActivity.class).putExtra("actionId", actionId)
.putExtra("roomId", roomId)
.putExtra("roomType", roomType)
.putExtra("hostname", hostname)
.putExtra("token", token)
.putExtra("userId", userId);
startActivity(intent);
}
}
\ No newline at end of file
......@@ -119,6 +119,7 @@ public class RoomPresenter extends BasePresenter<RoomContract.View>
@Override
public void sendMessage(String messageText) {
view.disableMessageInput();
final Disposable subscription = getRoomUserPair()
.flatMap(pair -> messageInteractor.send(pair.first, pair.second, messageText))
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
......@@ -128,8 +129,12 @@ public class RoomPresenter extends BasePresenter<RoomContract.View>
if (success) {
view.onMessageSendSuccessfully();
}
view.enableMessageInput();
},
Logger::report
throwable -> {
view.enableMessageInput();
Logger.report(throwable);
}
);
addSubscription(subscription);
......@@ -148,6 +153,7 @@ public class RoomPresenter extends BasePresenter<RoomContract.View>
@Override
public void updateMessage(Message message, String content) {
view.disableMessageInput();
final Disposable subscription = getCurrentUser()
.flatMap(user -> messageInteractor.update(message, user, content))
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
......@@ -157,8 +163,12 @@ public class RoomPresenter extends BasePresenter<RoomContract.View>
if (success) {
view.onMessageSendSuccessfully();
}
view.enableMessageInput();
},
Logger::report
throwable -> {
view.enableMessageInput();
Logger.report(throwable);
}
);
addSubscription(subscription);
......
package chat.rocket.android.fragment.chatroom.dialog;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.TextView;
import com.hadisatrio.optional.Optional;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
import chat.rocket.android.BackgroundLooper;
import chat.rocket.android.R;
import chat.rocket.android.fragment.chatroom.RocketChatAbsoluteUrl;
import chat.rocket.android.helper.AbsoluteUrlHelper;
import chat.rocket.android.helper.LogIfError;
import chat.rocket.android.helper.Logger;
import chat.rocket.android.layouthelper.chatroom.dialog.RoomUserAdapter;
import chat.rocket.android.log.RCLog;
import chat.rocket.core.SyncState;
import chat.rocket.core.interactors.SessionInteractor;
import chat.rocket.persistence.realm.models.internal.GetUsersOfRoomsProcedure;
import chat.rocket.persistence.realm.RealmObjectObserver;
import chat.rocket.android.service.ConnectivityManager;
import chat.rocket.persistence.realm.repositories.RealmServerInfoRepository;
import chat.rocket.persistence.realm.repositories.RealmSessionRepository;
import chat.rocket.persistence.realm.repositories.RealmUserRepository;
/**
* Dialog to show members in a room.
*/
public class UsersOfRoomDialogFragment extends AbstractChatRoomDialogFragment {
private String hostname;
private RealmObjectObserver<GetUsersOfRoomsProcedure> procedureObserver;
private int previousSyncState;
private CompositeDisposable compositeDisposable = new CompositeDisposable();
public UsersOfRoomDialogFragment() {
}
/**
* create UsersOfRoomDialogFragment with required parameters.
*/
public static UsersOfRoomDialogFragment create(String roomId, String hostname) {
Bundle args = new Bundle();
args.putString("hostname", hostname);
args.putString("roomId", roomId);
UsersOfRoomDialogFragment fragment = new UsersOfRoomDialogFragment();
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
procedureObserver = realmHelper
.createObjectObserver(realm ->
realm.where(GetUsersOfRoomsProcedure.class).equalTo("roomId", roomId))
.setOnUpdateListener(this::onUpdateGetUsersOfRoomProcedure);
previousSyncState = SyncState.NOT_SYNCED;
if (savedInstanceState == null) {
requestGetUsersOfRoom();
}
}
@Override
protected void handleArgs(@NonNull Bundle args) {
super.handleArgs(args);
hostname = args.getString("hostname");
}
@Override
protected int getLayout() {
return R.layout.dialog_users_of_room;
}
@Override
protected void onSetupDialog() {
AbsoluteUrlHelper absoluteUrlHelper = new AbsoluteUrlHelper(
hostname,
new RealmServerInfoRepository(),
new RealmUserRepository(hostname),
new SessionInteractor(new RealmSessionRepository(hostname))
);
compositeDisposable.add(
absoluteUrlHelper.getRocketChatAbsoluteUrl()
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
this::setupView,
Logger::report
)
);
}
private void setupView(Optional<RocketChatAbsoluteUrl> rocketChatAbsoluteUrlOptional) {
compositeDisposable.clear();
if (!rocketChatAbsoluteUrlOptional.isPresent()) {
return;
}
RecyclerView recyclerView = (RecyclerView) getDialog().findViewById(R.id.recyclerview);
recyclerView.setLayoutManager(new GridLayoutManager(getContext(), 2));
recyclerView.setAdapter(
new RoomUserAdapter(getContext(), realmHelper, rocketChatAbsoluteUrlOptional.get(), hostname));
}
private void requestGetUsersOfRoom() {
realmHelper.executeTransaction(realm -> {
realm.createOrUpdateObjectFromJson(GetUsersOfRoomsProcedure.class, new JSONObject()
.put("roomId", roomId)
.put("syncstate", SyncState.NOT_SYNCED)
.put("showAll", true));
return null;
}).onSuccessTask(task -> {
ConnectivityManager.getInstance(getContext().getApplicationContext())
.keepAliveServer();
return task;
}).continueWith(new LogIfError());
}
@Override
public void onResume() {
super.onResume();
procedureObserver.sub();
}
@Override
public void onPause() {
procedureObserver.unsub();
super.onPause();
}
private void onUpdateGetUsersOfRoomProcedure(GetUsersOfRoomsProcedure procedure) {
if (procedure == null) {
return;
}
int syncState = procedure.getSyncState();
if (previousSyncState != syncState) {
onSyncStateUpdated(syncState);
previousSyncState = syncState;
}
if (syncState == SyncState.SYNCED) {
onRenderTotalCount(procedure.getTotal());
try {
JSONArray array = new JSONArray(procedure.getRecords());
ArrayList<String> users = new ArrayList<>();
for (int i = 0; i < array.length(); i++) {
Object userObject = array.get(i);
if (userObject instanceof JSONObject) {
JSONObject user = (JSONObject) userObject;
users.add(user.getString("username"));
} else {
users.add((String) userObject);
}
}
onRenderUsers(users);
} catch (JSONException exception) {
RCLog.e(exception);
}
}
}
/**
* called only if prevSyncstate != newSyncstate.
*/
private void onSyncStateUpdated(int newSyncState) {
boolean show = newSyncState == SyncState.NOT_SYNCED || newSyncState == SyncState.SYNCING;
getDialog().findViewById(R.id.waiting).setVisibility(show ? View.VISIBLE : View.GONE);
}
/**
* called only if syncstate = SYNCED.
*/
private void onRenderTotalCount(long total) {
TextView userCount = (TextView) getDialog().findViewById(R.id.room_user_count);
userCount.setText(getResources().getQuantityString(R.plurals.fmt_room_user_count, (int) total, total));
}
/**
* called only if syncstate = SYNCED.
*/
private void onRenderUsers(List<String> usernames) {
RecyclerView recyclerView = (RecyclerView) getDialog().findViewById(R.id.recyclerview);
if (recyclerView != null && recyclerView.getAdapter() instanceof RoomUserAdapter) {
((RoomUserAdapter) recyclerView.getAdapter()).setUsernames(usernames);
}
}
}
package chat.rocket.android.fragment.chatroom.list
import chat.rocket.core.models.Message
import chat.rocket.core.models.User
/**
* Created by Filipe de Lima Brito (filipedelimabrito@gmail.com) on 9/22/17.
*/
interface RoomListContract {
interface View {
/**
* Shows a pinned message list of a room.
*
* @param dataSet The pinned message data set to show.
* @param total The total number of pinned messages.
*/
fun showPinnedMessages(dataSet: ArrayList<Message>, total: String)
/**
* Shows a favorite message list of a room.
*
* @param dataSet The favorite message data set to show.
* @param total The total number of favorite messages.
*/
fun showFavoriteMessages(dataSet: ArrayList<Message>, total: String)
/**
* Shows a file list of a room.
*
* @param dataSet The file data set to show.
* @param total The total number of files.
*/
fun showFileList(dataSet: ArrayList<String>, total: String)
/**
* Shows a list of members of a room.
*
* @param dataSet The member data set to show.
* @param total The total number of members.
*/
fun showMemberList(dataSet: ArrayList<User>, total: String)
/**
* Shows a message (e.g. An error or successful message after a request).
*
* @param message The message to show.
*/
fun showMessage(message: String)
/**
* Shows a waiting view whenever a (long) process is taken.
*
* @param shouldShow The Boolean value that indicates whether the view should be showed.
*/
fun showWaitingView(shouldShow: Boolean)
}
interface Presenter {
/**
* Requests the pinned messages of a room.
*
* @param roomId The room ID to process the request.
* @param roomType The room type to process the request.
* @param hostname The server hostname to process the request.
* @param token The token to process the request.
* @param userId The user ID to process the request.
* @param offset The offset to process the request.
*/
fun requestPinnedMessages(roomId: String,
roomType: String,
hostname: String,
token: String,
userId: String,
offset: Int)
/**
* Requests the favorite messages of a room.
*
* @param roomId The room ID to process the request.
* @param roomType The room type to process the request.
* @param hostname The server hostname to process the request.
* @param token The token to process the request.
* @param userId The user ID to process the request.
* @param offset The offset to process the request.
*/
fun requestFavoriteMessages(roomId: String,
roomType: String,
hostname: String,
token: String,
userId: String,
offset: Int)
/**
* Requests the file list of a room.
*
* @param roomId The room ID to process the request.
* @param roomType The room type to process the request.
* @param hostname The server hostname to process the request.
* @param token The token to process the request.
* @param userId The user ID to process the request.
* @param offset The offset to process the request.
*/
fun requestFileList(roomId: String,
roomType: String,
hostname: String,
token: String,
userId: String,
offset: Int)
/**
* Requests the member list of a room.
*
* @param roomId The room ID to process the request.
* @param roomType The room type to process the request.
* @param hostname The server hostname to process the request.
* @param token The token to process the request.
* @param userId The user ID to process the request.
* @param offset The offset to process the request.
*/
fun requestMemberList(roomId: String,
roomType: String,
hostname: String,
token: String,
userId: String,
offset: Int)
/**
* Immediately cancels any running request.
*/
fun cancelRequest()
}
}
\ No newline at end of file
package chat.rocket.android.fragment.chatroom.list
import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import chat.rocket.android.R
import chat.rocket.android.helper.EndlessRecyclerViewScrollListener
import chat.rocket.android.layouthelper.chatroom.list.RoomMemberListAdapter
import chat.rocket.android.layouthelper.chatroom.list.RoomMessagesAdapter
import chat.rocket.core.models.Message
import chat.rocket.core.models.User
import kotlinx.android.synthetic.main.fragment_room_list.*
import java.util.*
/**
* Created by Filipe de Lima Brito (filipedelimabrito@gmail.com) on 9/22/17.
*/
class RoomListFragment : Fragment(), RoomListContract.View {
companion object {
fun newInstance(actionId: Int,
roomId: String,
roomType: String,
hostname: String,
token: String,
userId: String): RoomListFragment {
val args = Bundle()
args.putInt("actionId", actionId)
args.putString("roomId", roomId)
args.putString("roomType", roomType)
args.putString("hostname", hostname)
args.putString("token", token)
args.putString("userId", userId)
val roomFileListDialogFragment = RoomListFragment()
roomFileListDialogFragment.arguments = args
return roomFileListDialogFragment
}
}
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")
}
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?) {
super.onViewCreated(view, savedInstanceState)
presenter = RoomListPresenter(context, this)
}
override fun onResume() {
super.onResume()
if (!isDataRequested) {
requestData(0)
isDataRequested = true
}
}
override fun onPause() {
super.onPause()
presenter.cancelRequest()
}
private fun requestData(offset: Int) {
when (actionId) {
R.id.action_pinned_messages -> {
presenter.requestPinnedMessages(roomId,
roomType,
hostname,
token,
userId,
offset)
}
R.id.action_favorite_messages -> {
presenter.requestFavoriteMessages(roomId,
roomType,
hostname,
token,
userId,
offset)
}
R.id.action_member_list -> {
presenter.requestMemberList(roomId,
roomType,
hostname,
token,
userId,
offset)
}
}
}
override fun showPinnedMessages(dataSet: ArrayList<Message>, total: String) {
activity.title = getString(R.string.fragment_room_list_pinned_message_title, total)
if (recyclerView.adapter == null) {
recyclerView.adapter = RoomMessagesAdapter(dataSet, hostname, context)
val linearLayoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
recyclerView.layoutManager = linearLayoutManager
if (dataSet.size >= 50) {
recyclerView.addOnScrollListener(object : EndlessRecyclerViewScrollListener(linearLayoutManager) {
override fun onLoadMore(page: Int, totalItemsCount: Int, recyclerView: RecyclerView?) {
loadNextDataFromApi(page)
}
})
}
} else {
(recyclerView.adapter as RoomMessagesAdapter).addDataSet(dataSet)
}
}
override fun showFavoriteMessages(dataSet: ArrayList<Message>, total: String) {
activity.title = getString(R.string.fragment_room_list_favorite_message_title, total)
if (recyclerView.adapter == null) {
recyclerView.adapter = RoomMessagesAdapter(dataSet, hostname, context)
val linearLayoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
recyclerView.layoutManager = linearLayoutManager
if (dataSet.size >= 50) {
recyclerView.addOnScrollListener(object : EndlessRecyclerViewScrollListener(linearLayoutManager) {
override fun onLoadMore(page: Int, totalItemsCount: Int, recyclerView: RecyclerView?) {
loadNextDataFromApi(page)
}
})
}
} else {
(recyclerView.adapter as RoomMessagesAdapter).addDataSet(dataSet)
}
}
// TODO (after REST api fixes)
override fun showFileList(dataSet: ArrayList<String>, total: String) {}
override fun showMemberList(dataSet: ArrayList<User>, total: String) {
activity.title = getString(R.string.fragment_room_list_member_list_title, total)
if (recyclerView.adapter == null) {
recyclerView.adapter = RoomMemberListAdapter(dataSet, hostname, context)
val linearLayoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
recyclerView.layoutManager = linearLayoutManager
if (dataSet.size >= 50) {
recyclerView.addOnScrollListener(object : EndlessRecyclerViewScrollListener(linearLayoutManager) {
override fun onLoadMore(page: Int, totalItemsCount: Int, recyclerView: RecyclerView?) {
loadNextDataFromApi(page)
}
})
}
} else {
(recyclerView.adapter as RoomMemberListAdapter).addDataSet(dataSet)
}
}
override fun showMessage(message: String) {
messageText.text = message
messageText.visibility = View.VISIBLE
}
override fun showWaitingView(shouldShow: Boolean) {
if (shouldShow) {
waitingView.visibility = View.VISIBLE
} else {
waitingView.visibility = View.GONE
}
}
private fun loadNextDataFromApi(page: Int) {
requestData(page * 50)
}
private var actionId: Int = 0
private lateinit var roomId: String
private lateinit var roomType: String
private lateinit var hostname: String
private lateinit var token: String
private lateinit var userId: String
private lateinit var presenter: RoomListContract.Presenter
private var isDataRequested: Boolean = false
}
\ No newline at end of file
package chat.rocket.android.fragment.chatroom.list
import android.content.Context
import android.os.Handler
import android.util.Log
import chat.rocket.android.R
import chat.rocket.android.api.rest.RestApiHelper
import chat.rocket.android.helper.OkHttpHelper
import chat.rocket.core.SyncState
import chat.rocket.core.models.Message
import chat.rocket.core.models.User
import okhttp3.Call
import okhttp3.Callback
import okhttp3.Response
import org.json.JSONException
import org.json.JSONObject
import java.io.IOException
import java.sql.Timestamp
/**
* Created by Filipe de Lima Brito (filipedelimabrito@gmail.com) on 9/22/17.
*/
class RoomListPresenter(val context: Context, val view: RoomListContract.View) : RoomListContract.Presenter {
override fun requestPinnedMessages(roomId: String,
roomType: String,
hostname: String,
token: String,
userId: String,
offset: Int) {
view.showWaitingView(true)
OkHttpHelper.getClient()
.newCall(RestApiHelper.getRequestForPinnedMessages(roomId,
roomType,
hostname,
token,
userId,
offset.toString()))
.enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
if (!call.isCanceled) {
val message = e.message
if (message != null) {
showErrorMessage(message)
}
}
}
@Throws(IOException::class)
override fun onResponse(call: Call, response: Response) {
if (response.isSuccessful) {
val result = response.body()?.string()
if (result != null) {
handleMessagesJson(result, true)
}
} else {
showErrorMessage(response.message())
}
}
})
}
override fun requestFavoriteMessages(roomId: String,
roomType: String,
hostname: String,
token: String,
userId: String,
offset: Int) {
view.showWaitingView(true)
OkHttpHelper.getClient()
.newCall(RestApiHelper.getRequestForFavoriteMessages(roomId,
roomType,
hostname,
token,
userId,
offset.toString()))
.enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
if (!call.isCanceled) {
val message = e.message
if (message != null) {
showErrorMessage(message)
}
}
}
@Throws(IOException::class)
override fun onResponse(call: Call, response: Response) {
if (response.isSuccessful) {
val result = response.body()?.string()
if (result != null) {
handleMessagesJson(result, false)
}
} else {
showErrorMessage(response.message())
}
}
})
}
// TODO (after the REST api fixes)
override fun requestFileList(roomId: String,
roomType: String,
hostname: String,
token: String,
userId: String,
offset: Int) {}
override fun requestMemberList(roomId: String,
roomType: String,
hostname: String,
token: String,
userId: String,
offset: Int) {
view.showWaitingView(true)
OkHttpHelper.getClient()
.newCall(RestApiHelper.getRequestForMemberList(roomId,
roomType,
hostname,
token,
userId,
offset.toString()))
.enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
if (!call.isCanceled) {
val message = e.message
if (message != null) {
showErrorMessage(message)
}
}
}
@Throws(IOException::class)
override fun onResponse(call: Call, response: Response) {
if (response.isSuccessful) {
val result = response.body()?.string()
if (result != null) {
handleMembersJson(result)
}
} else {
showErrorMessage(response.message())
}
}
})
}
override fun cancelRequest() {
OkHttpHelper.getClient().dispatcher().cancelAll()
}
private fun handleMessagesJson(json: String, isPinnedMessage: Boolean) {
try {
val jSONObject = JSONObject(json)
val messagesJSONArray = jSONObject.getJSONArray("messages")
val total = messagesJSONArray.length()
val dataSet = ArrayList<Message>(total)
(0 until total).mapTo(dataSet) {
val messageJsonObject = messagesJSONArray.getJSONObject(it)
val userJsonObject = messageJsonObject.getJSONObject("u")
val timestampString = messageJsonObject.optString("ts")
val timestamp = if (timestampString.isBlank()) {
0
} else {
Timestamp.valueOf(timestampString.replace("T", " ").replace("Z", "")).time
}
val editedAtString = messageJsonObject.optString("_updatedAt")
val editedAt = if (editedAtString.isBlank()) {
0
} else {
Timestamp.valueOf(editedAtString.replace("T", " ").replace("Z", "")).time
}
Message.builder()
.setId(messageJsonObject.optString("_id"))
.setRoomId(messageJsonObject.optString("rid"))
.setMessage(messageJsonObject.optString("msg"))
.setUser(getUserFromJsonObject(userJsonObject))
.setTimestamp(timestamp)
.setEditedAt(editedAt)
.setGroupable(messageJsonObject.optBoolean("groupable"))
.setSyncState(SyncState.SYNCED)
.build()
}
if (dataSet.isEmpty() && !hasItem) {
showEmptyViewMessage(context.getString(R.string.fragment_room_list_no_favorite_message_to_show))
} else {
if (dataSet.isNotEmpty()) {
hasItem = true
if (isPinnedMessage) {
showPinnedMessageList(dataSet, jSONObject.optString("total"))
} else {
showFavoriteMessageList(dataSet, jSONObject.optString("total"))
}
}
}
} catch (exception: JSONException) {
showInvalidRequest()
}
}
private fun handleMembersJson(json: String) {
try {
val jsonObject = JSONObject(json)
val membersJsonArray = jsonObject.getJSONArray("members")
val total = membersJsonArray.length()
val dataSet = ArrayList<User>(total)
(0 until total).mapTo(dataSet) {
getUserFromJsonObject(membersJsonArray.getJSONObject(it))
}
if (dataSet.isEmpty() && !hasItem) {
showEmptyViewMessage(context.getString(R.string.fragment_room_list_no_member_list_to_show))
} else {
if (dataSet.isNotEmpty()) {
hasItem = true
showMemberList(dataSet, jsonObject.optString("total"))
}
}
}catch (exception: JSONException) {
showInvalidRequest()
}
}
private fun getUserFromJsonObject(jsonObject: JSONObject): User {
return User.builder()
.setId(jsonObject.optString("_id"))
.setName(jsonObject.optString("name"))
.setUsername(jsonObject.optString("username"))
.setStatus(jsonObject.optString("status"))
.setUtcOffset(jsonObject.optLong("utcOffset").toDouble())
.build()
}
private fun showPinnedMessageList(dataSet: ArrayList<Message>, total: String) {
mainHandler.post {
view.showWaitingView(false)
view.showPinnedMessages(dataSet, total)
}
}
private fun showFavoriteMessageList(dataSet: ArrayList<Message>, total: String) {
mainHandler.post {
view.showWaitingView(false)
view.showFavoriteMessages(dataSet, total)
}
}
private fun showMemberList(dataSet: ArrayList<User>, total: String) {
mainHandler.post {
view.showWaitingView(false)
view.showMemberList(dataSet, total)
}
}
private fun showInvalidRequest() {
mainHandler.post {
view.showWaitingView(false)
view.showMessage(context.getString(R.string.fragment_room_list_could_not_load_your_request, context.getString(R.string.make_sure_your_server_version_is_up_to_date)))
}
}
private fun showEmptyViewMessage(message: String) {
mainHandler.post {
view.showWaitingView(false)
view.showMessage(message)
}
}
private fun showErrorMessage(message: String) {
mainHandler.post {
view.showWaitingView(false)
view.showMessage(context.getString(R.string.fragment_room_list_could_not_load_your_request, message))
}
}
private val mainHandler = Handler(context.mainLooper)
private var hasItem: Boolean = false
}
\ No newline at end of file
......@@ -2,6 +2,7 @@ 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;
......@@ -22,6 +23,8 @@ public interface SidebarMainContract {
void filterRoomSidebarList(CharSequence term);
void show(User user);
void onLogoutCleanUp();
}
interface Presenter extends BaseContract.Presenter<View> {
......@@ -42,6 +45,8 @@ public interface SidebarMainContract {
void onUserOffline();
void onLogout();
void onLogout(Continuation<Void, Object> continuation);
void beforeLogoutCleanUp();
}
}
\ No newline at end of file
package chat.rocket.android.fragment.sidebar;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
......@@ -8,13 +9,23 @@ import android.support.v4.app.DialogFragment;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.SearchView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CompoundButton;
import android.widget.TextView;
import com.jakewharton.rxbinding2.support.v7.widget.RxSearchView;
import com.jakewharton.rxbinding2.widget.RxCompoundButton;
import java.util.ArrayList;
import java.util.List;
import bolts.Task;
import chat.rocket.android.BuildConfig;
import chat.rocket.android.R;
import chat.rocket.android.RocketChatCache;
import chat.rocket.android.activity.MainActivity;
import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.android.fragment.AbstractFragment;
import chat.rocket.android.fragment.sidebar.dialog.AddChannelDialogFragment;
......@@ -39,15 +50,9 @@ import chat.rocket.persistence.realm.repositories.RealmServerInfoRepository;
import chat.rocket.persistence.realm.repositories.RealmSessionRepository;
import chat.rocket.persistence.realm.repositories.RealmSpotlightRepository;
import chat.rocket.persistence.realm.repositories.RealmUserRepository;
import com.jakewharton.rxbinding2.support.v7.widget.RxSearchView;
import com.jakewharton.rxbinding2.widget.RxCompoundButton;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import java.util.ArrayList;
import java.util.List;
public class SidebarMainFragment extends AbstractFragment implements SidebarMainContract.View {
private SidebarMainContract.Presenter presenter;
private RoomListAdapter adapter;
......@@ -87,26 +92,42 @@ public class SidebarMainFragment extends AbstractFragment implements SidebarMain
new SessionInteractor(new RealmSessionRepository(hostname))
);
RocketChatCache rocketChatCache = new RocketChatCache(getContext().getApplicationContext());
presenter = new SidebarMainPresenter(
hostname,
new RoomInteractor(new RealmRoomRepository(hostname)),
userRepository,
new RocketChatCache(getContext()),
rocketChatCache,
absoluteUrlHelper,
new MethodCallHelper(getContext(), hostname),
new RealmSpotlightRepository(hostname)
);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = super.onCreateView(inflater, container, savedInstanceState);
presenter.bindView(this);
return view;
}
@Override
public void onDestroyView() {
presenter.release();
super.onDestroyView();
}
@Override
public void onResume() {
super.onResume();
presenter.bindView(this);
}
@Override
public void onPause() {
presenter.release();
super.onPause();
}
......@@ -219,11 +240,16 @@ public class SidebarMainFragment extends AbstractFragment implements SidebarMain
);
}
private void showUserActionContainer(boolean show) {
public void showUserActionContainer(boolean show) {
rootView.findViewById(R.id.user_action_outer_container)
.setVisibility(show ? View.VISIBLE : View.GONE);
}
public void toggleUserActionContainer(boolean checked) {
CompoundButton toggleUserAction = rootView.findViewById(R.id.toggle_user_action);
toggleUserAction.setChecked(checked);
}
@Override
public void showScreen() {
rootView.setVisibility(View.VISIBLE);
......@@ -280,7 +306,7 @@ public class SidebarMainFragment extends AbstractFragment implements SidebarMain
));
roomListHeaders.add(new LivechatRoomListHeader(
getString(R.string.fragment_sidebar_main_livechat_title)
getString(R.string.fragment_sidebar_main_livechat_title)
));
roomListHeaders.add(new ChannelRoomListHeader(
......@@ -295,12 +321,32 @@ public class SidebarMainFragment extends AbstractFragment implements SidebarMain
adapter.setRoomListHeaders(roomListHeaders);
}
@Override
public void onLogoutCleanUp() {
Activity activity = getActivity();
if (activity != null && activity instanceof MainActivity) {
((MainActivity) activity).hideLogoutMessage();
((MainActivity) activity).onLogout();
presenter.onLogout(task -> {
if (task.isFaulted()) {
return Task.forError(task.getError());
}
return null;
});
}
}
private void setupLogoutButton() {
rootView.findViewById(R.id.btn_logout).setOnClickListener(view -> {
presenter.onLogout();
closeUserActionContainer();
// destroy Activity on logout to be able to recreate most of the environment
this.getActivity().finish();
// Clear relative data and set new hostname if any.
presenter.beforeLogoutCleanUp();
final Activity activity = getActivity();
if (activity != null && activity instanceof MainActivity) {
((MainActivity) activity).showLogoutMessage();
// Clear subscriptions on MainPresenter.
((MainActivity) activity).beforeLogoutCleanUp();
}
});
}
......
......@@ -6,13 +6,18 @@ import android.support.v4.util.Pair;
import java.util.ArrayList;
import java.util.List;
import bolts.Continuation;
import bolts.Task;
import chat.rocket.android.BackgroundLooper;
import chat.rocket.android.RocketChatApplication;
import chat.rocket.android.RocketChatCache;
import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.android.helper.AbsoluteUrlHelper;
import chat.rocket.android.helper.LogIfError;
import chat.rocket.android.helper.Logger;
import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.service.ConnectivityManager;
import chat.rocket.android.service.ConnectivityManagerApi;
import chat.rocket.android.shared.BasePresenter;
import chat.rocket.core.interactors.RoomInteractor;
import chat.rocket.core.models.Room;
......@@ -21,6 +26,8 @@ import chat.rocket.core.models.Spotlight;
import chat.rocket.core.models.User;
import chat.rocket.core.repositories.SpotlightRepository;
import chat.rocket.core.repositories.UserRepository;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.persistence.realm.RealmStore;
import chat.rocket.persistence.realm.repositories.RealmSpotlightRepository;
import io.reactivex.Flowable;
import io.reactivex.android.schedulers.AndroidSchedulers;
......@@ -131,8 +138,31 @@ public class SidebarMainPresenter extends BasePresenter<SidebarMainContract.View
}
@Override
public void onLogout() {
methodCallHelper.logout().continueWith(new LogIfError());
public void onLogout(Continuation<Void, Object> continuation) {
methodCallHelper.logout().continueWith(task -> {
if (task.isFaulted()) {
Logger.report(task.getError());
return Task.forError(task.getError());
}
return task.onSuccess(continuation);
});
}
@Override
public void beforeLogoutCleanUp() {
clearSubscriptions();
String currentHostname = rocketChatCache.getSelectedServerHostname();
RealmHelper realmHelper = RealmStore.getOrCreate(currentHostname);
realmHelper.executeTransaction(realm -> {
realm.deleteAll();
ConnectivityManagerApi connectivityManagerApi = ConnectivityManager.getInstance(RocketChatApplication.getInstance());
connectivityManagerApi.removeServer(currentHostname);
rocketChatCache.removeHostname(currentHostname);
rocketChatCache.removeSelectedRoomId(currentHostname);
rocketChatCache.setSelectedServerHostname(rocketChatCache.getFirstLoggedHostnameIfAny());
view.onLogoutCleanUp();
return null;
});
}
@Override
......
package chat.rocket.android.helper
import android.support.v7.widget.GridLayoutManager
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.support.v7.widget.StaggeredGridLayoutManager
/**
* Created by Filipe de Lima Brito (filipedelimabrito@gmail.com) on 9/21/17.
* Source info: https://github.com/codepath/android_guides/wiki/Endless-Scrolling-with-AdapterViews-and-RecyclerView
*/
abstract class EndlessRecyclerViewScrollListener : RecyclerView.OnScrollListener {
private var visibleThreshold = 5 // The minimum amount of items to have below of the current scroll position before loading more.
private var currentPage = 0 // The current offset index of data loaded
private var previousTotalItemCount = 0 // The total number of items in the dataset after the last load
private var loading = true // True if we are still waiting for the last set of data to load.
private val startingPageIndex = 0 // Sets the starting page index
private var layoutManager: RecyclerView.LayoutManager
constructor(layoutManager: LinearLayoutManager) {
this.layoutManager = layoutManager
}
constructor(layoutManager: GridLayoutManager) {
this.layoutManager = layoutManager
visibleThreshold *= layoutManager.spanCount
}
constructor(layoutManager: StaggeredGridLayoutManager) {
this.layoutManager = layoutManager
visibleThreshold *= layoutManager.spanCount
}
private fun getLastVisibleItem(lastVisibleItemPositions: IntArray): Int {
var maxSize = 0
for (i in lastVisibleItemPositions.indices) {
if (i == 0) {
maxSize = lastVisibleItemPositions[i]
} else if (lastVisibleItemPositions[i] > maxSize) {
maxSize = lastVisibleItemPositions[i]
}
}
return maxSize
}
// This happens many times a second during a scroll, so be wary of the code you place here.
// We are given a few useful parameters to help us work out if we need to load some more data,
// but first we check if we are waiting for the previous load to finish.
override fun onScrolled(view: RecyclerView?, dx: Int, dy: Int) {
var lastVisibleItemPosition = 0
val totalItemCount = layoutManager.itemCount
when (layoutManager) {
is StaggeredGridLayoutManager -> {
val lastVisibleItemPositions = (layoutManager as StaggeredGridLayoutManager).findLastVisibleItemPositions(null)
// get maximum element within the list
lastVisibleItemPosition = getLastVisibleItem(lastVisibleItemPositions)
}
is GridLayoutManager -> lastVisibleItemPosition = (layoutManager as GridLayoutManager).findLastVisibleItemPosition()
is LinearLayoutManager -> lastVisibleItemPosition = (layoutManager as LinearLayoutManager).findLastVisibleItemPosition()
}
// If the total item count is zero and the previous isn't, assume the
// list is invalidated and should be reset back to initial state
// If it’s still loading, we check to see if the dataset count has
// changed, if so we conclude it has finished loading and update the current page
// number and total item count.
// If it isn’t currently loading, we check to see if we have breached
// the visibleThreshold and need to reload more data.
// If we do need to reload some more data, we execute onLoadMore to fetch the data.
// threshold should reflect how many total columns there are too
// If the total item count is zero and the previous isn't, assume the
// list is invalidated and should be reset back to initial state
if (totalItemCount < previousTotalItemCount) {
this.currentPage = this.startingPageIndex
this.previousTotalItemCount = totalItemCount
if (totalItemCount == 0) {
this.loading = true
}
}
// If it’s still loading, we check to see if the dataset count has
// changed, if so we conclude it has finished loading and update the current page
// number and total item count.
if (loading && totalItemCount > previousTotalItemCount) {
loading = false
previousTotalItemCount = totalItemCount
}
// If it isn’t currently loading, we check to see if we have breached
// the visibleThreshold and need to reload more data.
// If we do need to reload some more data, we execute onLoadMore to fetch the data.
// threshold should reflect how many total columns there are too
if (!loading && lastVisibleItemPosition + visibleThreshold > totalItemCount) {
currentPage++
onLoadMore(currentPage, totalItemCount, view)
loading = true
}
}
// Call this method whenever performing new searches
fun resetState() {
this.currentPage = this.startingPageIndex
this.previousTotalItemCount = 0
this.loading = true
}
// Defines the process for actually loading more data based on page
abstract fun onLoadMore(page: Int, totalItemsCount: Int, recyclerView: RecyclerView?)
}
\ No newline at end of file
package chat.rocket.android.helper;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
@SuppressWarnings("PMD.AbstractNaming")
public abstract class LoadMoreScrollListener extends RecyclerView.OnScrollListener {
private final LinearLayoutManager layoutManager;
private final int loadThreshold;
private boolean isLoading;
/**
* constructor. loadThreshold is better to set to 0.4 * total.
*/
public LoadMoreScrollListener(LinearLayoutManager layoutManager, int loadThreshold) {
this.layoutManager = layoutManager;
this.loadThreshold = loadThreshold;
setLoadingDone();
}
@Override
public void onScrolled(RecyclerView recyclerView, int deltaX, int deltaY) {
super.onScrolled(recyclerView, deltaX, deltaY);
final int visibleItemCount = recyclerView.getChildCount();
final int totalItemCount = layoutManager.getItemCount();
final int firstVisibleItem = layoutManager.findFirstVisibleItemPosition();
if (!isLoading
&& firstVisibleItem + visibleItemCount >= totalItemCount - loadThreshold
&& visibleItemCount < totalItemCount
&& deltaY < 0) {
isLoading = true;
requestMoreItem();
}
}
public void setLoadingDone() {
isLoading = false;
}
public abstract void requestMoreItem();
}
\ No newline at end of file
package chat.rocket.android.helper
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
abstract class LoadMoreScrollListener(private val layoutManager: LinearLayoutManager, private val visibleThreshold: Int) : RecyclerView.OnScrollListener() {
private var isLoading: Boolean = false
override fun onScrolled(recyclerView: RecyclerView, deltaX: Int, deltaY: Int) {
super.onScrolled(recyclerView, deltaX, deltaY)
val visibleItemCount = recyclerView.childCount
val totalItemCount = layoutManager.itemCount
val firstVisibleItem = layoutManager.findFirstVisibleItemPosition()
if (!isLoading
&& (firstVisibleItem + visibleItemCount) >= (totalItemCount - visibleThreshold)
&& visibleItemCount < totalItemCount
&& deltaY < 0) {
isLoading = true
requestMoreItem()
}
}
fun setLoadingDone() {
isLoading = false
}
abstract fun requestMoreItem()
}
\ No newline at end of file
package chat.rocket.android.helper
object UrlHelper {
/**
* Returns an URI whit no scheme (HTTP or HTTPS)
*
* @param uri The URI.
* @return The URI whit no scheme (HTTP or HTTPS)
*/
fun removeUriScheme(uri: String) = uri.replace("http://", "").replace("https://", "")
/**
* Returns the hostname with the security protocol (scheme) HTTPS.
*
* @param hostname The hostname.
* @return The hostname with the security protocol (scheme) HTTPS.
*/
fun getSafeHostname(hostname: String): String =
"https://" + hostname.replace("http://", "").replace("https://", "")
/**
* Returns an URL with no spaces and inverted slashes.
*
* @param url The URL.
* @return The URL with no spaces and inverted slashes.
*/
fun getUrl(url: String) =
url.replace(" ", "%20").replace("\\", "")
/**
* Returns an URL for a file.
*
* @param path The path to the file.
* @param userId The user ID.
* @param token The token.
* @return The URL for a file
*/
fun getUrlForFile(path: String, userId: String, token: String): String =
"https://" + removeUriScheme(getUrl(path)) + "?rc_uid=$userId" + "&rc_token=$token"
}
\ No newline at end of file
......@@ -34,8 +34,8 @@ public abstract class AbstractMessageViewHolder extends ModelViewHolder<PairedMe
subUsername = itemView.findViewById(R.id.sub_username);
timestamp = itemView.findViewById(R.id.timestamp);
userAndTimeContainer = itemView.findViewById(R.id.user_and_timestamp_container);
newDayContainer = itemView.findViewById(R.id.newday_container);
newDayText = itemView.findViewById(R.id.newday_text);
newDayContainer = itemView.findViewById(R.id.dayContainer);
newDayText = itemView.findViewById(R.id.day);
this.absoluteUrl = absoluteUrl;
this.hostname = hostname;
}
......
package chat.rocket.android.layouthelper.chatroom.list
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import chat.rocket.android.R
import chat.rocket.android.widget.message.RocketChatMessageLayout
import kotlinx.android.synthetic.main.item_room_file.view.*
/**
* Created by Filipe de Lima Brito (filipedelimabrito@gmail.com) on 9/22/17.
*/
class RoomFileListAdapter(private var dataSet: List<String>) : RecyclerView.Adapter<RoomFileListAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_room_file, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.fileNameLink.setText(dataSet[position])
}
override fun getItemCount(): Int = dataSet.size
fun setDataSet(dataSet: List<String>) {
this.dataSet = dataSet
notifyDataSetChanged()
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val fileNameLink : RocketChatMessageLayout = itemView.fileLink
}
}
\ No newline at end of file
package chat.rocket.android.layouthelper.chatroom.list
import android.content.Context
import android.graphics.drawable.Drawable
import android.support.graphics.drawable.VectorDrawableCompat
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import chat.rocket.android.R
import chat.rocket.android.widget.RocketChatAvatar
import chat.rocket.android.widget.helper.AvatarHelper
import chat.rocket.android.widget.helper.DrawableHelper
import chat.rocket.core.models.User
import kotlinx.android.synthetic.main.item_room_member.view.*
/**
* Created by Filipe de Lima Brito (filipedelimabrito@gmail.com) on 9/22/17.
*/
class RoomMemberListAdapter(private var dataSet: List<User>, private val hostname: String, private val context: Context) : RecyclerView.Adapter<RoomMemberListAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_room_member, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val user = dataSet[position]
holder.name.text = user.name
val userStatusDrawable: Drawable? = VectorDrawableCompat.create(context.resources, chat.rocket.android.widget.R.drawable.ic_user_status_black_24dp, null)?.mutate()
DrawableHelper.wrapDrawable(userStatusDrawable)
when (user.status) {
User.STATUS_ONLINE -> DrawableHelper.tintDrawable(userStatusDrawable, context, chat.rocket.android.widget.R.color.color_user_status_online)
User.STATUS_BUSY -> DrawableHelper.tintDrawable(userStatusDrawable, context, chat.rocket.android.widget.R.color.color_user_status_busy)
User.STATUS_AWAY -> DrawableHelper.tintDrawable(userStatusDrawable, context, chat.rocket.android.widget.R.color.color_user_status_away)
User.STATUS_OFFLINE -> DrawableHelper.tintDrawable(userStatusDrawable, context, chat.rocket.android.widget.R.color.color_user_status_offline)
}
holder.status.setImageDrawable(userStatusDrawable)
val username = user.username
if (username != null) {
holder.username.text = context.getString(R.string.username, username)
val placeholderDrawable = AvatarHelper.getTextDrawable(username, holder.userAvatar.context)
holder.userAvatar.loadImage(AvatarHelper.getUri(hostname, username), placeholderDrawable)
} else {
holder.userAvatar.visibility = View.GONE
holder.username.visibility = View.GONE
}
}
override fun getItemCount(): Int = dataSet.size
fun addDataSet(dataSet: List<User>) {
val previousDataSetSize = this.dataSet.size
this.dataSet += dataSet
notifyItemRangeInserted(previousDataSetSize, dataSet.size)
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val userAvatar: RocketChatAvatar = itemView.userAvatar
val name: TextView = itemView.name
val status: ImageView = itemView.status
val username: TextView = itemView.username
}
}
\ No newline at end of file
package chat.rocket.android.layouthelper.chatroom.list
import android.content.Context
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import chat.rocket.android.R
import chat.rocket.android.helper.DateTime
import chat.rocket.android.widget.RocketChatAvatar
import chat.rocket.android.widget.helper.AvatarHelper
import chat.rocket.android.widget.message.RocketChatMessageAttachmentsLayout
import chat.rocket.android.widget.message.RocketChatMessageLayout
import chat.rocket.android.widget.message.RocketChatMessageUrlsLayout
import chat.rocket.core.models.Message
import kotlinx.android.synthetic.main.day.view.*
import kotlinx.android.synthetic.main.item_room_message.view.*
/**
* Created by Filipe de Lima Brito (filipedelimabrito@gmail.com) on 9/22/17.
*/
class RoomMessagesAdapter(private var dataSet: List<Message>, private val hostname: String, private val context: Context) : RecyclerView.Adapter<RoomMessagesAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_room_message, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val message = dataSet[position]
holder.newDay.text = DateTime.fromEpocMs(message.timestamp, DateTime.Format.DATE)
val user = message.user
if (user != null) {
if (user.name.isNullOrBlank()) {
holder.name.visibility = View.GONE
} else {
holder.name.text = message.user?.name
}
val username = user.username
if (username != null) {
val placeholderDrawable = AvatarHelper.getTextDrawable(username, holder.userAvatar.context)
holder.userAvatar.loadImage(AvatarHelper.getUri(hostname, username), placeholderDrawable)
holder.username.text = context.getString(R.string.username, username)
} else {
holder.userAvatar.visibility = View.GONE
holder.username.visibility = View.GONE
}
}
holder.messageBody.setText(message.message)
val webContents = message.webContents
if (webContents == null || webContents.isEmpty()) {
holder.messageUrl.visibility = View.GONE
} else {
holder.messageUrl.setUrls(message.webContents, true)
}
val attachments = message.attachments
if (attachments == null || attachments.isEmpty()) {
holder.messageAttachment.visibility = View.GONE
} else {
// holder.messageAttachment.setAbsoluteUrl(absoluteUrl)
// holder.messageAttachment.setAttachments(attachments, true)
// holder.messageAttachment.visibility = View.VISIBLE
}
}
override fun getItemCount(): Int = dataSet.size
fun addDataSet(dataSet: List<Message>) {
val previousDataSetSize = this.dataSet.size
this.dataSet += dataSet
notifyItemRangeInserted(previousDataSetSize, dataSet.size)
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val newDay: TextView = itemView.day
val userAvatar: RocketChatAvatar = itemView.userAvatar
val username: TextView = itemView.username
val name: TextView = itemView.name
val messageBody: RocketChatMessageLayout = itemView.messageBody
val messageUrl: RocketChatMessageUrlsLayout = itemView.messageUrl
val messageAttachment: RocketChatMessageAttachmentsLayout = itemView.messageAttachment
}
}
\ No newline at end of file
package chat.rocket.android.layouthelper.chatroom.dialog;
package chat.rocket.android.layouthelper.chatroom.list;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
......
package chat.rocket.android.layouthelper.chatroom.dialog;
package chat.rocket.android.layouthelper.chatroom.list;
import android.support.v7.widget.RecyclerView;
import android.view.View;
......
package chat.rocket.android.layouthelper.extra_action.upload;
import android.content.Intent;
import chat.rocket.android.R;
public class FileUploadActionItem extends AbstractUploadActionItem {
@Override
public int getItemId() {
return 13;
}
@Override
protected Intent getIntentForPickFile() {
Intent intent = new Intent();
intent.setType("*/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
return Intent.createChooser(intent, "Select File to Upload");
}
@Override
public int getIcon() {
return R.drawable.ic_insert_drive_file_white_24dp;
}
@Override
public int getTitle() {
return R.string.doc_upload_message_spec_title;
}
}
......@@ -39,6 +39,7 @@ 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 {
......@@ -497,7 +498,7 @@ public class PushNotificationHandler implements PushConstants {
setNotification(notId, "");
NotificationCompat.BigPictureStyle bigPicture = new NotificationCompat.BigPictureStyle();
bigPicture.bigPicture(getBitmapFromURL(extras.getString(PICTURE)));
bigPicture.bigPicture(getBitmapFromURL(extras.getString(PICTURE), null, null));
bigPicture.setBigContentTitle(fromHtml(extras.getString(TITLE)));
bigPicture.setSummaryText(fromHtml(extras.getString(SUMMARY_TEXT)));
......@@ -566,7 +567,7 @@ public class PushNotificationHandler implements PushConstants {
setNotification(notId, "");
Notification.BigPictureStyle bigPicture = new Notification.BigPictureStyle();
bigPicture.bigPicture(getBitmapFromURL(extras.getString(PICTURE)));
bigPicture.bigPicture(getBitmapFromURL(extras.getString(PICTURE), null, null));
bigPicture.setBigContentTitle(fromHtml(extras.getString(TITLE)));
bigPicture.setSummaryText(fromHtml(extras.getString(SUMMARY_TEXT)));
......@@ -726,13 +727,22 @@ public class PushNotificationHandler implements PushConstants {
private void setNotificationLargeIcon(Context context, Bundle extras, String packageName,
Resources resources, NotificationCompat.Builder builder) {
String gcmLargeIcon = extras.getString(IMAGE); // from gcm
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));
builder.setLargeIcon(getBitmapFromURL(gcmLargeIcon, username, context));
Log.d(LOG_TAG, "using remote large-icon from gcm");
} else {
AssetManager assetManager = context.getAssets();
......@@ -757,13 +767,22 @@ public class PushNotificationHandler implements PushConstants {
private void setNotificationLargeIcon(Context context, Bundle extras, String packageName,
Resources resources, Notification.Builder builder) {
String gcmLargeIcon = extras.getString(IMAGE); // from gcm
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));
builder.setLargeIcon(getBitmapFromURL(gcmLargeIcon, username, context));
Log.d(LOG_TAG, "using remote large-icon from gcm");
} else {
AssetManager assetManager = context.getAssets();
......@@ -854,17 +873,23 @@ public class PushNotificationHandler implements PushConstants {
intent.putExtra(NOT_ID, notId);
}
public Bitmap getBitmapFromURL(String strURL) {
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();
return BitmapFactory.decodeStream(input);
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;
e.printStackTrace();
return null;
}
}
......@@ -937,6 +962,15 @@ public class PushNotificationHandler implements PushConstants {
}
}
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();
......
......@@ -6,7 +6,7 @@ import chat.rocket.android.R
import chat.rocket.android.helper.DateTime
import chat.rocket.android.widget.AbsoluteUrl
import chat.rocket.android.widget.RocketChatAvatar
import chat.rocket.android.widget.helper.UserAvatarHelper
import chat.rocket.android.widget.helper.AvatarHelper
import chat.rocket.android.widget.message.RocketChatMessageAttachmentsLayout
import chat.rocket.android.widget.message.RocketChatMessageLayout
import chat.rocket.android.widget.message.RocketChatMessageUrlsLayout
......@@ -21,12 +21,12 @@ class MessageRenderer(val message: Message, val autoLoadImage: Boolean) {
fun showAvatar(rocketChatAvatarWidget: RocketChatAvatar, hostname: String) {
val username: String? = message.user?.username
if (username != null) {
val placeholderDrawable = UserAvatarHelper.getTextDrawable(username, rocketChatAvatarWidget.context)
val placeholderDrawable = AvatarHelper.getTextDrawable(username, rocketChatAvatarWidget.context)
if (message.avatar != null) {
// Load user's avatar image from Oauth provider URI.
rocketChatAvatarWidget.loadImage(message.avatar, placeholderDrawable)
} else {
rocketChatAvatarWidget.loadImage(UserAvatarHelper.getUri(hostname, username), placeholderDrawable)
rocketChatAvatarWidget.loadImage(AvatarHelper.getUri(hostname, username), placeholderDrawable)
}
} else {
rocketChatAvatarWidget.visibility = View.GONE
......
......@@ -4,7 +4,7 @@ import android.view.View
import android.widget.ImageView
import android.widget.TextView
import chat.rocket.android.widget.RocketChatAvatar
import chat.rocket.android.widget.helper.UserAvatarHelper
import chat.rocket.android.widget.helper.AvatarHelper
import chat.rocket.core.models.User
class UserRenderer(val user: User) {
......@@ -15,7 +15,7 @@ class UserRenderer(val user: User) {
fun showAvatar(rocketChatAvatarWidget: RocketChatAvatar, hostname: String) {
val username: String? = user.username
if (username != null) {
rocketChatAvatarWidget.loadImage(UserAvatarHelper.getUri(hostname, username), UserAvatarHelper.getTextDrawable(username, rocketChatAvatarWidget.context))
rocketChatAvatarWidget.loadImage(AvatarHelper.getUri(hostname, username), AvatarHelper.getTextDrawable(username, rocketChatAvatarWidget.context))
} else {
rocketChatAvatarWidget.visibility = View.GONE
}
......
......@@ -7,10 +7,11 @@ import android.os.IBinder;
import android.support.annotation.Nullable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import chat.rocket.android.helper.RxHelper;
import chat.rocket.android.log.RCLog;
import chat.rocket.core.models.ServerInfo;
......@@ -25,7 +26,7 @@ import rx.subjects.PublishSubject;
*/
/*package*/ class RealmBasedConnectivityManager
implements ConnectivityManagerApi, ConnectivityManagerInternal {
private final HashMap<String, Integer> serverConnectivityList = new HashMap<>();
private final ConcurrentHashMap<String, Integer> serverConnectivityList = new ConcurrentHashMap<>();
private final PublishSubject<ServerConnectivity> connectivitySubject = PublishSubject.create();
private Context appContext;
private final ServiceConnection serviceConnection = new ServiceConnection() {
......
......@@ -12,7 +12,6 @@ import java.util.HashMap;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import chat.rocket.android.activity.MainActivity;
import chat.rocket.persistence.realm.RealmStore;
import hugo.weaving.DebugLog;
import rx.Observable;
......@@ -93,12 +92,6 @@ public class RocketChatService extends Service implements ConnectivityServiceInt
webSocketThreads.remove(hostname);
// remove RealmConfiguration key from HashMap
RealmStore.sStore.remove(hostname);
// clear "cache" SharedPreference
this.getSharedPreferences("cache", 0).edit().clear().apply();
// start a fresh new MainActivity
Intent intent = new Intent(this, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
this.startActivity(intent);
});
} else {
return Observable.timer(1, TimeUnit.SECONDS).toSingle()
......
......@@ -255,6 +255,10 @@ public class RocketChatWebSocketThread extends HandlerThread {
return prepareDDPClient()
.flatMap(_val -> Single.fromEmitter(emitter -> {
ServerInfo info = connectivityManager.getServerInfoForHost(hostname);
if (info == null) {
emitter.onSuccess(false);
return;
}
RCLog.d("DDPClient#connect");
ddpClient.connect(info.getSession(), info.isSecure())
.onSuccessTask(task -> {
......
package chat.rocket.android.service.observer;
import android.content.Context;
import com.google.android.gms.gcm.GoogleCloudMessaging;
import com.google.android.gms.iid.InstanceID;
import android.content.Context;
import io.realm.Realm;
import io.realm.RealmResults;
import java.io.IOException;
import java.util.List;
import bolts.Task;
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.models.ddp.RealmPublicSetting;
import chat.rocket.core.PublicSettingsConstants;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.persistence.realm.models.ddp.RealmUser;
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;
/**
* call raix:push-update if needed.
......@@ -83,13 +82,6 @@ public class GcmPushRegistrationObserver extends AbstractModelObserver<GcmPushRe
}
private String getSenderId() {
final String senderId = RealmPublicSetting
.getString(realmHelper, PublicSettingsConstants.Push.GCM_PROJECT_NUMBER, "").trim();
if (senderId.length() != 0) {
return senderId;
}
return context.getString(R.string.gcm_sender_id);
}
......
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:viewportHeight="48.0"
android:viewportWidth="48.0"
android:width="24dp">
android:width="24dp"
android:height="24dp"
android:viewportHeight="48.0"
android:viewportWidth="48.0">
<path
android:fillColor="#A3000000"
android:pathData="M44.99,23.47C44.99,21.42 44.38,19.45 43.16,17.62C42.07,15.97 40.54,14.52 38.62,13.29C34.91,10.92 30.03,9.62 24.88,9.62C23.16,9.62 21.47,9.77 19.82,10.05C18.8,9.1 17.61,8.24 16.35,7.57C9.6,4.3 4,7.49 4,7.49C4,7.49 9.21,11.76 8.36,15.49C6.03,17.8 4.77,20.58 4.77,23.47C4.77,23.48 4.77,23.49 4.77,23.5C4.77,23.51 4.77,23.51 4.77,23.52C4.77,26.42 6.03,29.2 8.36,31.5C9.21,35.24 4,39.5 4,39.5C4,39.5 9.6,42.69 16.35,39.43C17.61,38.75 18.8,37.89 19.82,36.94C21.47,37.23 23.16,37.37 24.88,37.37C30.03,37.37 34.91,36.07 38.62,33.7C40.54,32.48 42.07,31.02 43.16,29.38C44.38,27.55 44.99,25.58 44.99,23.53C44.99,23.52 44.99,23.51 44.99,23.5L44.99,23.47ZM24.88,12.53C34.41,12.53 42.14,17.45 42.14,23.52C42.14,29.6 34.41,34.52 24.88,34.52C22.76,34.52 20.73,34.28 18.85,33.83C16.94,36.12 12.74,39.31 8.67,38.28C9.99,36.86 11.96,34.45 11.54,30.5C9.09,28.6 7.63,26.17 7.63,23.52C7.63,17.45 15.35,12.53 24.88,12.53Z"
android:strokeColor="#00000000"
android:strokeWidth="1"/>
android:fillColor="#FFFFFFFF"
android:pathData="M44.99,23.47C44.99,21.42 44.38,19.45 43.16,17.62C42.07,15.97 40.54,14.52 38.62,13.29C34.91,10.92 30.03,9.62 24.88,9.62C23.16,9.62 21.47,9.77 19.82,10.05C18.8,9.1 17.61,8.24 16.35,7.57C9.6,4.3 4,7.49 4,7.49C4,7.49 9.21,11.76 8.36,15.49C6.03,17.8 4.77,20.58 4.77,23.47C4.77,23.48 4.77,23.49 4.77,23.5C4.77,23.51 4.77,23.51 4.77,23.52C4.77,26.42 6.03,29.2 8.36,31.5C9.21,35.24 4,39.5 4,39.5C4,39.5 9.6,42.69 16.35,39.43C17.61,38.75 18.8,37.89 19.82,36.94C21.47,37.23 23.16,37.37 24.88,37.37C30.03,37.37 34.91,36.07 38.62,33.7C40.54,32.48 42.07,31.02 43.16,29.38C44.38,27.55 44.99,25.58 44.99,23.53C44.99,23.52 44.99,23.51 44.99,23.5L44.99,23.47ZM24.88,12.53C34.41,12.53 42.14,17.45 42.14,23.52C42.14,29.6 34.41,34.52 24.88,34.52C22.76,34.52 20.73,34.28 18.85,33.83C16.94,36.12 12.74,39.31 8.67,38.28C9.99,36.86 11.96,34.45 11.54,30.5C9.09,28.6 7.63,26.17 7.63,23.52C7.63,17.45 15.35,12.53 24.88,12.53Z" />
<path
android:fillColor="#A3000000"
android:pathData="M24.88,26.17C26.15,26.17 27.17,25.14 27.17,23.88C27.17,22.61 26.15,21.59 24.88,21.59C23.62,21.59 22.59,22.61 22.59,23.88C22.59,25.14 23.62,26.17 24.88,26.17ZM32.85,26.17C34.12,26.17 35.14,25.14 35.14,23.88C35.14,22.61 34.12,21.59 32.85,21.59C31.59,21.59 30.56,22.61 30.56,23.88C30.56,25.14 31.59,26.17 32.85,26.17ZM16.91,26.17C18.18,26.17 19.2,25.14 19.2,23.88C19.2,22.62 18.18,21.59 16.91,21.59C15.65,21.59 14.62,22.62 14.62,23.88C14.62,25.14 15.65,26.17 16.91,26.17L16.91,26.17Z"
android:strokeColor="#00000000"
android:strokeWidth="1"/>
<path
android:fillColor="#33000000"
android:pathData="M24.88,33.08C22.76,33.08 20.73,32.86 18.85,32.48C17.17,34.23 13.69,36.59 10.1,36.5C9.62,37.22 9.11,37.8 8.67,38.28C12.74,39.31 16.94,36.12 18.85,33.83C20.73,34.28 22.76,34.52 24.88,34.52C34.34,34.52 42.01,29.68 42.13,23.67C42.01,28.88 34.34,33.08 24.88,33.08L24.88,33.08Z"
android:strokeColor="#00000000"
android:strokeWidth="1"/>
android:fillColor="#FFFFFFFF"
android:pathData="M24.88,26.17C26.15,26.17 27.17,25.14 27.17,23.88C27.17,22.61 26.15,21.59 24.88,21.59C23.62,21.59 22.59,22.61 22.59,23.88C22.59,25.14 23.62,26.17 24.88,26.17ZM32.85,26.17C34.12,26.17 35.14,25.14 35.14,23.88C35.14,22.61 34.12,21.59 32.85,21.59C31.59,21.59 30.56,22.61 30.56,23.88C30.56,25.14 31.59,26.17 32.85,26.17ZM16.91,26.17C18.18,26.17 19.2,25.14 19.2,23.88C19.2,22.62 18.18,21.59 16.91,21.59C15.65,21.59 14.62,22.62 14.62,23.88C14.62,25.14 15.65,26.17 16.91,26.17L16.91,26.17Z" />
</vector>
<!-- drawable/close_circle_outline.xml -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="#FF000000"
android:pathData="M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2C6.47,2 2,6.47 2,12C2,17.53 6.47,22 12,22C17.53,22 22,17.53 22,12C22,6.47 17.53,2 12,2M14.59,8L12,10.59L9.41,8L8,9.41L10.59,12L8,14.59L9.41,16L12,13.41L14.59,16L16,14.59L13.41,12L16,9.41L14.59,8Z" />
</vector>
\ No newline at end of file
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M6,2c-1.1,0 -1.99,0.9 -1.99,2L4,20c0,1.1 0.89,2 1.99,2L18,22c1.1,0 2,-0.9 2,-2L20,8l-6,-6L6,2zM13,9L13,3.5L18.5,9L13,9z"/>
</vector>
......@@ -17,7 +17,9 @@
<chat.rocket.android.widget.RoomToolbar
android:id="@+id/activity_main_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
android:layout_height="wrap_content"
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
</android.support.design.widget.AppBarLayout>
<FrameLayout
......
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
tools:context="chat.rocket.android.fragment.chatroom.RoomFragment">
<include layout="@layout/fragment_room_main" />
<include layout="@layout/room_side_menu" />
</LinearLayout>
\ No newline at end of file
......@@ -18,7 +18,9 @@
<chat.rocket.android.widget.RoomToolbar
android:id="@+id/activity_main_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
android:layout_height="wrap_content"
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
</android.support.design.widget.AppBarLayout>
<FrameLayout
......
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="chat.rocket.android.activity.room.RoomActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
</android.support.design.widget.AppBarLayout>
<FrameLayout
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/dayContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<View
android:layout_width="0px"
android:layout_height="1dp"
android:layout_weight="1"
android:background="@color/colorDivider" />
<TextView
android:id="@+id/day"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:textAppearance="@style/TextAppearance.RocketChat.Message.Day"
tools:text="2016/01/23" />
<View
android:layout_width="0px"
android:layout_height="1dp"
android:layout_weight="1"
android:background="@color/colorDivider" />
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:id="@+id/room_user_titlebar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:paddingEnd="@dimen/margin_16"
android:paddingStart="@dimen/margin_16">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:text="@string/users_of_room_title"
android:textAppearance="@style/TextAppearance.AppCompat.Title" />
<TextView
android:id="@+id/room_user_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
</FrameLayout>
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" />
</LinearLayout>
<chat.rocket.android.widget.WaitingView
android:id="@+id/waiting"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</FrameLayout>
\ No newline at end of file
......@@ -44,7 +44,7 @@
android:layout_marginTop="@dimen/margin_8"
android:hint="@string/fragment_input_hostname_server_hint"
android:imeOptions="actionSend"
android:inputType="textWebEditText"
android:inputType="textUri"
android:maxLines="1"
app:layout_constraintTop_toBottomOf="@+id/hostnameTextView"
app:layout_constraintBottom_toTopOf="@+id/btn_connect"
......
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawer_layout"
android:id="@+id/messageListRelativeLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
tools:context="chat.rocket.android.fragment.chatroom.RoomFragment">
<include layout="@layout/fragment_room_main" />
<FrameLayout
<android.support.v7.widget.RecyclerView
android:id="@+id/messageRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="end"
android:clickable="true"
android:theme="@style/AppTheme.Dark">
android:scrollbars="vertical"
android:layout_above="@+id/messageComposer" />
<include layout="@layout/room_side_menu" />
</FrameLayout>
</android.support.v4.widget.DrawerLayout>
\ No newline at end of file
<chat.rocket.android.widget.message.MessageFormLayout
android:id="@+id/messageComposer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:theme="@style/Theme.AppCompat.Light" />
</RelativeLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<chat.rocket.android.widget.WaitingView
android:id="@+id/waitingView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true" />
<TextView
android:id="@+id/messageText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/margin_16"
android:layout_marginStart="@dimen/margin_16"
android:layout_marginRight="@dimen/margin_16"
android:layout_marginEnd="@dimen/margin_16"
android:layout_centerInParent="true"
android:gravity="center"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:visibility="gone" />
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical" />
</RelativeLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/messageListRelativeLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white">
<android.support.v7.widget.RecyclerView
android:id="@+id/messageRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
android:layout_above="@+id/messageComposer" />
<chat.rocket.android.widget.message.MessageFormLayout
android:id="@+id/messageComposer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:theme="@style/Theme.AppCompat.Light" />
</RelativeLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/margin_16">
<chat.rocket.android.widget.message.RocketChatMessageLayout
android:id="@+id/fileLink"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/margin_16"
android:layout_marginStart="@dimen/margin_16" />
<View
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_weight="1"
android:background="@color/colorDivider"
android:layout_marginTop="@dimen/margin_8"
app:layout_constraintTop_toBottomOf="@+id/fileLink" />
</android.support.constraint.ConstraintLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/margin_16"
android:paddingRight="@dimen/margin_16"
android:paddingStart="@dimen/margin_16"
android:paddingLeft="@dimen/margin_16"
android:paddingEnd="@dimen/margin_16">
<chat.rocket.android.widget.RocketChatAvatar
android:id="@+id/userAvatar"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginTop="4dp"
app:layout_constraintTop_toTopOf="parent"/>
<android.support.constraint.ConstraintLayout
android:id="@+id/container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/margin_16"
android:layout_marginStart="@dimen/margin_16"
app:layout_constraintLeft_toRightOf="@+id/userAvatar" >
<ImageView
android:id="@+id/status"
android:layout_width="8dp"
android:layout_height="8dp"
app:layout_constraintTop_toTopOf="@+id/name"
app:layout_constraintBottom_toBottomOf="@+id/name"
app:srcCompat="@drawable/ic_user_status_black_24dp" />
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="4dp"
android:layout_marginStart="4dp"
android:ellipsize="end"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.RocketChat.Message.Name"
app:layout_constraintLeft_toRightOf="@+id/status"
tools:text="John Doe" />
<TextView
android:id="@+id/username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.RocketChat.Message.Username"
app:layout_constraintTop_toBottomOf="@+id/name"
app:layout_constraintLeft_toLeftOf="@+id/name"
tools:text="\@john.doe" />
</android.support.constraint.ConstraintLayout>
<View
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_weight="1"
android:background="@color/colorDivider"
android:layout_marginTop="20dp"
app:layout_constraintTop_toBottomOf="@+id/container" />
</android.support.constraint.ConstraintLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingRight="@dimen/margin_16"
android:paddingStart="@dimen/margin_16"
android:paddingLeft="@dimen/margin_16"
android:paddingEnd="@dimen/margin_16"
android:paddingBottom="@dimen/margin_16">
<include
android:id="@+id/dayLayout"
layout="@layout/day" />
<chat.rocket.android.widget.RocketChatAvatar
android:id="@+id/userAvatar"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginTop="20dp"
app:layout_constraintTop_toBottomOf="@+id/dayLayout" />
<android.support.constraint.ConstraintLayout
android:id="@+id/userContainer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_16"
android:layout_marginLeft="@dimen/margin_16"
android:layout_marginStart="@dimen/margin_16"
app:layout_constraintTop_toBottomOf="@+id/dayLayout"
app:layout_constraintLeft_toRightOf="@+id/userAvatar">
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.RocketChat.Message.Name"
tools:text="John Doe" />
<TextView
android:id="@+id/username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.RocketChat.Message.Username"
app:layout_constraintLeft_toLeftOf="@+id/name"
app:layout_constraintTop_toBottomOf="@+id/name"
tools:text="\@john.doe" />
</android.support.constraint.ConstraintLayout>
<android.support.constraint.ConstraintLayout
android:id="@+id/messageContainer"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginLeft="@dimen/margin_16"
android:layout_marginStart="@dimen/margin_16"
app:layout_constraintTop_toBottomOf="@+id/userContainer"
app:layout_constraintLeft_toRightOf="@+id/userAvatar"
app:layout_constraintRight_toRightOf="parent">
<chat.rocket.android.widget.message.RocketChatMessageLayout
android:id="@+id/messageBody"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<chat.rocket.android.widget.message.RocketChatMessageUrlsLayout
android:id="@+id/messageUrl"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="@+id/messageBody"
app:layout_constraintTop_toBottomOf="@+id/messageBody" />
<chat.rocket.android.widget.message.RocketChatMessageAttachmentsLayout
android:id="@+id/messageAttachment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="@+id/messageUrl"
app:layout_constraintTop_toBottomOf="@+id/messageUrl" />
</android.support.constraint.ConstraintLayout>
</android.support.constraint.ConstraintLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/newday_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<View
android:layout_width="0px"
android:layout_height="1dp"
android:layout_weight="1"
android:background="@color/newday_color" />
<TextView
android:id="@+id/newday_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:textColor="@color/newday_text_color"
android:textSize="8sp"
android:textStyle="bold"
tools:text="2016/01/23" />
<View
android:layout_width="0px"
android:layout_height="1dp"
android:layout_weight="1"
android:background="@color/newday_color" />
</LinearLayout>
\ No newline at end of file
......@@ -7,7 +7,7 @@
android:orientation="vertical"
android:theme="@style/AppTheme">
<include layout="@layout/list_item_message_newday" />
<include layout="@layout/day" />
<FrameLayout
android:layout_width="match_parent"
......
......@@ -7,7 +7,7 @@
android:orientation="vertical"
android:theme="@style/AppTheme">
<include layout="@layout/list_item_message_newday" />
<include layout="@layout/day" />
<FrameLayout
android:layout_width="match_parent"
......
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/room_side_menu"
android:layout_width="48dp"
android:layout_height="match_parent"
android:layout_gravity="end"
android:orientation="vertical">
<io.github.yusukeiwaki.android.widget.FontAwesomeButton
android:layout_width="48dp"
android:layout_height="48dp"
android:enabled="false"
android:text="@string/fa_search"
android:textSize="24dp" />
<io.github.yusukeiwaki.android.widget.FontAwesomeButton
android:id="@+id/btn_users"
android:layout_width="48dp"
android:layout_height="48dp"
android:text="@string/fa_users"
android:textSize="24dp" />
<io.github.yusukeiwaki.android.widget.FontAwesomeButton
android:layout_width="48dp"
android:layout_height="48dp"
android:enabled="false"
android:text="@string/fa_at"
android:textSize="24dp" />
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.SlidingPaneLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/sub_sliding_pane"
android:layout_width="280dp"
android:layout_height="match_parent"
android:layout_gravity="start"
android:theme="@style/AppTheme.Dark">
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/sub_sliding_pane"
android:layout_width="280dp"
android:layout_height="match_parent"
android:layout_gravity="start"
android:theme="@style/AppTheme.Dark">
<android.support.v4.widget.NestedScrollView
android:layout_width="96dp"
android:layout_height="match_parent"
android:layout_gravity="start"
android:background="?attr/colorPrimaryDark">
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="start"
android:background="?attr/colorPrimaryDark">
<LinearLayout
android:id="@+id/server_list_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<android.support.constraint.ConstraintLayout
android:id="@+id/btn_add_server"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:layout_height="80dp"
android:background="?selectableItemBackground"
android:descendantFocusability="afterDescendants">
<ImageButton
<io.github.yusukeiwaki.android.widget.FontAwesomeTextView
android:id="@+id/fa_add_server"
style="@style/Base.Widget.AppCompat.Button.Borderless"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_margin="@dimen/margin_8"
android:src="@mipmap/ic_launcher" />
android:background="@null"
android:clickable="false"
android:duplicateParentState="true"
android:text="@string/fa_plus"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<io.github.yusukeiwaki.android.widget.FontAwesomeButton
style="@style/Base.Widget.AppCompat.Button.Borderless"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_margin="@dimen/margin_8"
android:text="@string/fa_plus" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:duplicateParentState="true"
android:ellipsize="end"
android:gravity="center_vertical"
android:maxLines="1"
android:text="@string/add_new_team"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:textColor="@android:color/white"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/fa_add_server"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
<FrameLayout
android:id="@+id/sidebar_fragment_container"
android:layout_width="280dp"
android:layout_height="match_parent" />
android:id="@+id/sidebar_fragment_container"
android:layout_width="280dp"
android:layout_height="match_parent" />
</android.support.v4.widget.SlidingPaneLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/action_pinned_messages"
android:title="@string/menu_pinned_messages"
app:showAsAction="never" />
<item android:id="@+id/action_favorite_messages"
android:title="@string/menu_favorite_messages"
app:showAsAction="never" />
<!--<item android:id="@+id/action_file_list"-->
<!--android:title="@string/menu_file_list"-->
<!--app:showAsAction="never" />-->
<item android:id="@+id/action_member_list"
android:title="@string/menu_member_list"
app:showAsAction="never" />
</menu>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#044b76</color>
<color name="colorPrimaryDark">#FF04436a</color>
<color name="colorAccent">#FF2D91FA</color>
<color name="colorAccentLight">#FF6CB1FA</color>
<color name="colorAccentDark">#FF287DD7</color>
<color name="colorAccent_a40">#662D91FA</color>
<color name="textColorLink">#008ce3</color>
<color name="colorRed400">#FFEF5350</color>
<color name="colorPrimary">#044b76</color>
<color name="colorPrimaryDark">#FF04436a</color>
<color name="colorAccent">#FF2D91FA</color>
<color name="colorAccentLight">#FF6CB1FA</color>
<color name="colorAccentDark">#FF287DD7</color>
<color name="colorAccent_a40">#662D91FA</color>
<color name="textColorLink">#008ce3</color>
<color name="colorRed400">#FFEF5350</color>
<color name="colorPrimaryTextDark">#DE000000</color>
<color name="colorSecondaryTextDark">#8A000000</color>
<color name="colorDivider">#1F000000</color>
</resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="fa_chevron_down" translatable="false">&#xf078;</string>
<string name="fa_twitter" translatable="false">&#xf099;</string>
<string name="fa_github" translatable="false">&#xf09b;</string>
<string name="fa_google" translatable="false">&#xf1a0;</string>
<string name="fa_facebook_official" translatable="false">&#xf230;</string>
<string name="fa_plus" translatable="false">&#xf067;</string>
<string name="fa_sign_out" translatable="false">&#xf08b;</string>
<string name="fa_search" translatable="false">&#xf002;</string>
<string name="fa_users" translatable="false">&#xf0c0;</string>
<string name="fa_at" translatable="false">&#xf1fa;</string>
</resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="TextAppearance.RocketChat.Message.Day" parent="TextAppearance.AppCompat">
<item name="android:textSize">12sp</item>
<item name="android:textColor">@color/colorPrimaryTextDark</item>
</style>
<style name="TextAppearance.RocketChat.Message.SubUsername" parent="TextAppearance.AppCompat.Body1">
<item name="android:textColor">#5f000000</item>
</style>
<style name="TextAppearance.RocketChat.Message.Name" parent="TextAppearance.AppCompat.Body1">
<item name="android:textColor">@color/colorPrimaryTextDark</item>
<item name="android:fontFamily">sans-serif-condensed</item>
<item name="android:textSize">16sp</item>
<item name="android:maxLines">1</item>
<item name="android:ellipsize">end</item>
</style>
<style name="TextAppearance.RocketChat.Message.Username" parent="TextAppearance.AppCompat.Body2">
<item name="android:textColor">@color/colorSecondaryTextDark</item>
<item name="android:fontFamily">sans-serif-condensed</item>
<item name="android:textSize">14sp</item>
<item name="android:maxLines">1</item>
<item name="android:ellipsize">end</item>
</style>
</resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="newday_color">#e0e0e0</color>
<color name="newday_text_color">#444444</color>
<style name="TextAppearance.RocketChat.Message.Username" parent="TextAppearance.AppCompat.Body2">
<item name="android:textColor">#bb000000</item>
</style>
<style name="TextAppearance.RocketChat.Message.SubUsername" parent="TextAppearance.AppCompat.Body1">
<item name="android:textColor">#5f000000</item>
</style>
</resources>
\ No newline at end of file
......@@ -13,12 +13,17 @@
<string name="dialog_add_channel_private">private</string>
<string name="dialog_add_channel_read_only">read only</string>
<string name="fragment_room_list_pinned_message_title">Pinned Messages (%s)</string>
<string name="fragment_room_list_favorite_message_title">Favorite Messages (%s)</string>
<string name="fragment_room_list_file_list_title">File list (%s)</string>
<string name="fragment_room_list_member_list_title">Member list (%s)</string>
<string name="fragment_room_list_could_not_load_your_request">Could not load your request.\n%s.</string>
<string name="fragment_room_list_no_pinned_message_to_show">No pinned message to show</string>
<string name="fragment_room_list_no_favorite_message_to_show">No favorite message to show</string>
<string name="fragment_room_list_no_file_list_to_show">No file list to show</string>
<string name="fragment_room_list_no_member_list_to_show">No member list to show</string>
<string name="start_of_conversation">Start of conversation</string>
<string name="users_of_room_title">Members List</string>
<plurals name="fmt_room_user_count">
<item quantity="one">Total: %,d user</item>
<item quantity="other">Total: %,d users</item>
</plurals>
<string name="sending">Sending…</string>
<string name="not_synced">Not synced</string>
<string name="failed_to_sync">Failed to sync</string>
......@@ -36,6 +41,7 @@
<string name="dialog_user_registration_email">Email</string>
<string name="dialog_user_registration_username">Username</string>
<string name="sub_username">\@%s</string>
<string name="username">\@%s</string>
<string name="dialog_user_registration_password">Password</string>
<string name="fragment_home_welcome_message">Welcome to Rocket.Chat.Android\nSelect a channel from the drawer.</string>
<string name="fragment_input_hostname_hostname">Hostname</string>
......@@ -57,6 +63,7 @@
<string name="video_upload_message_spec_title">Attach video</string>
<string name="input_hostname_invalid_server_message">Invalid server version</string>
<string name="make_sure_your_server_version_is_up_to_date">Make sure your Rocket.Chat server version is up to date</string>
<string name="connection_error_try_later">There\'s a connection error. Please try later.</string>
<string name="version_info_text">Version: %s</string>
......@@ -68,4 +75,11 @@
<string name="edit_message">Edit message</string>
<string name="message_options_no_message_info">Ooops. Something\'s up!</string>
<string name="message_options_no_permissions_info">You have no permissions</string>
<string name="menu_pinned_messages">Pinned messages</string>
<string name="menu_favorite_messages">Favorite messages</string>
<string name="menu_file_list">File list</string>
<string name="menu_member_list">Member list</string>
<string name="add_new_team">Add new Team</string>
</resources>
......@@ -9,6 +9,13 @@ import okhttp3.OkHttpClient
object OkHttpHelper {
fun getClient(): OkHttpClient {
if (httpClient == null) {
httpClient = OkHttpClient()
}
return httpClient ?: throw AssertionError("httpClient set to null by another thread")
}
fun getClientForUploadFile(): OkHttpClient {
if (httpClientForUploadFile == null) {
httpClientForUploadFile = OkHttpClient.Builder().build()
......@@ -19,6 +26,8 @@ object OkHttpHelper {
fun getClientForDownloadFile(context: Context): OkHttpClient {
if(httpClientForDownloadFile == null) {
httpClientForDownloadFile = OkHttpClient.Builder()
.followRedirects(true)
.followSslRedirects(true)
.addInterceptor(CookieInterceptor(DefaultCookieProvider(RocketChatCache(context))))
.build()
}
......@@ -37,7 +46,8 @@ object OkHttpHelper {
return httpClientForWS ?: throw AssertionError("httpClientForWS set to null by another thread")
}
private var httpClient: OkHttpClient? = null
private var httpClientForUploadFile: OkHttpClient? = null
private var httpClientForDownloadFile: OkHttpClient? = null
private var httpClientForWS: OkHttpClient? = null
}
\ No newline at end of file
}
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.api.rest
import chat.rocket.core.models.Room
import org.junit.Test
import kotlin.test.assertEquals
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_PRIVATE, "demo.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/dm.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_DIRECT_MESSAGE, "demo.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_PRIVATE, "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://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_PRIVATE, "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://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_PRIVATE, "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.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_PRIVATE, "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.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_PRIVATE, "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"))
}
@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_PRIVATE, "demo.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/dm.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_DIRECT_MESSAGE, "demo.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_PRIVATE, "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://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_PRIVATE, "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://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_PRIVATE, "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.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_PRIVATE, "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.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_PRIVATE, "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"))
}
@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_PRIVATE, "demo.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/dm.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_DIRECT_MESSAGE, "demo.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_PRIVATE, "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://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_PRIVATE, "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://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_PRIVATE, "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.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_PRIVATE, "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.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_PRIVATE, "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"))
}
@Test
fun getRestApiUrlForMessagesTest() {
assertEquals("/api/v1/channels.messages", RestApiHelper.getRestApiUrlForMessages(Room.TYPE_CHANNEL))
assertEquals("/api/v1/groups.messages", RestApiHelper.getRestApiUrlForMessages(Room.TYPE_PRIVATE))
assertEquals("/api/v1/dm.messages", RestApiHelper.getRestApiUrlForMessages(Room.TYPE_DIRECT_MESSAGE))
}
@Test
fun getRestApiUrlForFileListTest() {
assertEquals("/api/v1/channels.files", RestApiHelper.getRestApiUrlForFileList(Room.TYPE_CHANNEL))
assertEquals("/api/v1/groups.files", RestApiHelper.getRestApiUrlForFileList(Room.TYPE_PRIVATE))
assertEquals("/api/v1/dm.files", RestApiHelper.getRestApiUrlForFileList(Room.TYPE_DIRECT_MESSAGE))
}
@Test
fun getRestApiUrlForMemberListTest() {
assertEquals("/api/v1/channels.members", RestApiHelper.getRestApiUrlForMemberList(Room.TYPE_CHANNEL))
assertEquals("/api/v1/groups.members", RestApiHelper.getRestApiUrlForMemberList(Room.TYPE_PRIVATE))
assertEquals("/api/v1/dm.members", RestApiHelper.getRestApiUrlForMemberList(Room.TYPE_DIRECT_MESSAGE))
}
}
\ No newline at end of file
package chat.rocket.android.helper
import org.junit.Test
import kotlin.test.assertEquals
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"))
}
@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"))
}
@Test
fun getUrlTest() {
assertEquals("https://demo.rocket.chat/GENERAL/file.txt", UrlHelper.getUrl("https://demo.rocket.chat/GENERAL/file.txt"))
assertEquals("http://demo.rocket.chat/GENERAL/file.txt", UrlHelper.getUrl("http://demo.rocket.chat/GENERAL/file.txt"))
assertEquals("demo.rocket.chat/GENERAL/file.txt", UrlHelper.getUrl("demo.rocket.chat/GENERAL/file.txt"))
assertEquals("demo.rocket.chat/GENERAL/a%20sample%20file.txt", UrlHelper.getUrl("demo.rocket.chat/GENERAL/a sample file.txt"))
assertEquals("demo.rocket.chat/GENERAL/file.txt", UrlHelper.getUrl("demo.rocket.chat\\/GENERAL\\/file.txt"))
}
@Test
fun getUrlForFileTest() {
assertEquals("https://demo.rocket.chat/GENERAL/file.txt?rc_uid=userId&rc_token=token", UrlHelper.getUrlForFile("https://demo.rocket.chat/GENERAL/file.txt","userId", "token"))
assertEquals("https://demo.rocket.chat/GENERAL/file.txt?rc_uid=userId&rc_token=token", UrlHelper.getUrlForFile("http://demo.rocket.chat/GENERAL/file.txt","userId", "token"))
assertEquals("https://demo.rocket.chat/GENERAL/file.txt?rc_uid=userId&rc_token=token", UrlHelper.getUrlForFile("demo.rocket.chat/GENERAL/file.txt","userId", "token"))
assertEquals("https://demo.rocket.chat/GENERAL/a%20sample%20file.txt?rc_uid=userId&rc_token=token", UrlHelper.getUrlForFile("demo.rocket.chat/GENERAL/a sample file.txt","userId", "token"))
assertEquals("https://demo.rocket.chat/GENERAL/file.txt?rc_uid=userId&rc_token=token", UrlHelper.getUrlForFile("demo.rocket.chat\\/GENERAL\\/file.txt","userId", "token"))
}
}
\ No newline at end of file
......@@ -22,7 +22,8 @@ ext {
buildToolsVersion = "26.0.0"
supportLibraryVersion = "25.4.0"
constraintLayoutVersion = "1.0.2"
kotlinVersion = "1.1.4-2"
kotlinVersion = "1.1.4-3"
okHttpVersion = "3.9.0"
}
task clean(type: Delete) {
......
......@@ -29,11 +29,17 @@ public class RealmLoginServiceConfigurationRepository extends RealmRepository
public Single<Optional<LoginServiceConfiguration>> getByName(String serviceName) {
return Single.defer(() -> Flowable.using(
() -> new Pair<>(RealmStore.getRealm(hostname), Looper.myLooper()),
pair -> RxJavaInterop.toV2Flowable(
pair.first.where(RealmMeteorLoginServiceConfiguration.class)
.equalTo(RealmMeteorLoginServiceConfiguration.SERVICE, serviceName)
.findAll()
.<RealmResults<RealmMeteorLoginServiceConfiguration>>asObservable()),
pair -> {
if (pair.first == null) {
return Flowable.empty();
}
return RxJavaInterop.toV2Flowable(
pair.first.where(RealmMeteorLoginServiceConfiguration.class)
.equalTo(RealmMeteorLoginServiceConfiguration.SERVICE, serviceName)
.findAll()
.<RealmResults<RealmMeteorLoginServiceConfiguration>>asObservable());
},
pair -> close(pair.first, pair.second)
)
.unsubscribeOn(AndroidSchedulers.from(Looper.myLooper()))
......@@ -46,10 +52,16 @@ public class RealmLoginServiceConfigurationRepository extends RealmRepository
public Flowable<List<LoginServiceConfiguration>> getAll() {
return Flowable.defer(() -> Flowable.using(
() -> new Pair<>(RealmStore.getRealm(hostname), Looper.myLooper()),
pair -> RxJavaInterop
.toV2Flowable(pair.first.where(RealmMeteorLoginServiceConfiguration.class)
.findAll()
.asObservable()),
pair -> {
if (pair.first == null) {
return Flowable.empty();
}
return RxJavaInterop
.toV2Flowable(pair.first.where(RealmMeteorLoginServiceConfiguration.class)
.findAll()
.asObservable());
},
pair -> close(pair.first, pair.second)
)
.unsubscribeOn(AndroidSchedulers.from(Looper.myLooper()))
......
......@@ -36,11 +36,17 @@ public class RealmMessageRepository extends RealmRepository implements MessageRe
public Single<Optional<Message>> getById(String messageId) {
return Single.defer(() -> Flowable.using(
() -> new Pair<>(RealmStore.getRealm(hostname), Looper.myLooper()),
pair -> RxJavaInterop.toV2Flowable(
pair.first.where(RealmMessage.class)
.equalTo(RealmMessage.ID, messageId)
.findAll()
.<RealmResults<RealmMessage>>asObservable()),
pair -> {
if (pair.first == null) {
return Flowable.empty();
}
return RxJavaInterop.toV2Flowable(
pair.first.where(RealmMessage.class)
.equalTo(RealmMessage.ID, messageId)
.findAll()
.<RealmResults<RealmMessage>>asObservable());
},
pair -> close(pair.first, pair.second)
)
.unsubscribeOn(AndroidSchedulers.from(Looper.myLooper()))
......@@ -133,11 +139,17 @@ public class RealmMessageRepository extends RealmRepository implements MessageRe
public Flowable<List<Message>> getAllFrom(Room room) {
return Flowable.defer(() -> Flowable.using(
() -> new Pair<>(RealmStore.getRealm(hostname), Looper.myLooper()),
pair -> RxJavaInterop.toV2Flowable(pair.first.where(RealmMessage.class)
.equalTo(RealmMessage.ROOM_ID, room.getRoomId())
.isNotNull(RealmMessage.USER)
.findAllSorted(RealmMessage.TIMESTAMP, Sort.DESCENDING)
.asObservable()),
pair -> {
if (pair.first == null) {
return Flowable.empty();
}
return RxJavaInterop.toV2Flowable(pair.first.where(RealmMessage.class)
.equalTo(RealmMessage.ROOM_ID, room.getRoomId())
.isNotNull(RealmMessage.USER)
.findAllSorted(RealmMessage.TIMESTAMP, Sort.DESCENDING)
.asObservable());
},
pair -> close(pair.first, pair.second)
)
.unsubscribeOn(AndroidSchedulers.from(Looper.myLooper()))
......@@ -150,12 +162,18 @@ public class RealmMessageRepository extends RealmRepository implements MessageRe
public Single<Integer> unreadCountFor(Room room, User user) {
return Single.defer(() -> Flowable.using(
() -> new Pair<>(RealmStore.getRealm(hostname), Looper.myLooper()),
pair -> RxJavaInterop.toV2Flowable(pair.first.where(RealmMessage.class)
.equalTo(RealmMessage.ROOM_ID, room.getId())
.greaterThanOrEqualTo(RealmMessage.TIMESTAMP, room.getLastSeen())
.notEqualTo(RealmMessage.USER_ID, user.getId())
.findAll()
.asObservable()),
pair -> {
if (pair.first == null) {
return Flowable.empty();
}
return RxJavaInterop.toV2Flowable(pair.first.where(RealmMessage.class)
.equalTo(RealmMessage.ROOM_ID, room.getId())
.greaterThanOrEqualTo(RealmMessage.TIMESTAMP, room.getLastSeen())
.notEqualTo(RealmMessage.USER_ID, user.getId())
.findAll()
.asObservable());
},
pair -> close(pair.first, pair.second)
)
.unsubscribeOn(AndroidSchedulers.from(Looper.myLooper()))
......
......@@ -26,11 +26,16 @@ public class RealmPermissionRepository extends RealmRepository implements Permis
public Single<Optional<Permission>> getById(String id) {
return Single.defer(() -> Flowable.using(
() -> new Pair<>(RealmStore.getRealm(hostname), Looper.myLooper()),
pair -> RxJavaInterop.toV2Flowable(
pair.first.where(RealmPermission.class)
.equalTo(RealmPermission.Columns.ID, id)
.findAll()
.<RealmResults<RealmPermission>>asObservable()),
pair -> {
if (pair.first == null) {
return Flowable.empty();
}
return RxJavaInterop.toV2Flowable(
pair.first.where(RealmPermission.class)
.equalTo(RealmPermission.Columns.ID, id)
.findAll()
.<RealmResults<RealmPermission>>asObservable());
},
pair -> close(pair.first, pair.second)
)
.unsubscribeOn(AndroidSchedulers.from(Looper.myLooper()))
......
......@@ -27,11 +27,16 @@ public class RealmPublicSettingRepository extends RealmRepository
public Single<Optional<PublicSetting>> getById(String id) {
return Single.defer(() -> Flowable.using(
() -> new Pair<>(RealmStore.getRealm(hostname), Looper.myLooper()),
pair -> RxJavaInterop.toV2Flowable(
pair.first.where(RealmPublicSetting.class)
.equalTo(RealmPublicSetting.ID, id)
.findAll()
.<RealmResults<RealmPublicSetting>>asObservable()),
pair -> {
if (pair.first == null) {
return Flowable.empty();
}
return RxJavaInterop.toV2Flowable(
pair.first.where(RealmPublicSetting.class)
.equalTo(RealmPublicSetting.ID, id)
.findAll()
.<RealmResults<RealmPublicSetting>>asObservable());
},
pair -> close(pair.first, pair.second)
)
.unsubscribeOn(AndroidSchedulers.from(Looper.myLooper()))
......
......@@ -34,10 +34,16 @@ public class RealmRoomRepository extends RealmRepository implements RoomReposito
public Flowable<List<Room>> getAll() {
return Flowable.defer(() -> Flowable.using(
() -> new Pair<>(RealmStore.getRealm(hostname), Looper.myLooper()),
pair -> RxJavaInterop.toV2Flowable(
pair.first.where(RealmRoom.class)
.findAll()
.asObservable()),
pair -> {
if (pair.first == null) {
return Flowable.empty();
}
return RxJavaInterop.toV2Flowable(
pair.first.where(RealmRoom.class)
.findAll()
.asObservable());
},
pair -> close(pair.first, pair.second)
)
.unsubscribeOn(AndroidSchedulers.from(Looper.myLooper()))
......@@ -51,6 +57,10 @@ public class RealmRoomRepository extends RealmRepository implements RoomReposito
return Flowable.defer(() -> Flowable.using(
() -> new Pair<>(RealmStore.getRealm(hostname), Looper.myLooper()),
pair -> {
if (pair.first == null) {
return Flowable.empty();
}
RealmRoom realmRoom = pair.first.where(RealmRoom.class)
.equalTo(RealmRoom.ROOM_ID, roomId)
.findFirst();
......@@ -84,6 +94,9 @@ public class RealmRoomRepository extends RealmRepository implements RoomReposito
return Flowable.defer(() -> Flowable.using(
() -> new Pair<>(RealmStore.getRealm(hostname), Looper.myLooper()),
pair -> {
if (pair.first == null) {
return Flowable.empty();
}
LoadMessageProcedure messageProcedure = pair.first.where(LoadMessageProcedure.class)
.equalTo(LoadMessageProcedure.ID, roomId)
......@@ -147,17 +160,22 @@ public class RealmRoomRepository extends RealmRepository implements RoomReposito
public Flowable<List<Room>> getSortedLikeName(String name, SortDirection direction, int limit) {
return Flowable.defer(() -> Flowable.using(
() -> new Pair<>(RealmStore.getRealm(hostname), Looper.myLooper()),
pair -> RxJavaInterop.toV2Flowable(
pair.first.where(RealmRoom.class)
.like(RealmRoom.NAME, "*" + name + "*", Case.INSENSITIVE)
.beginGroup()
.equalTo(RealmRoom.TYPE, RealmRoom.TYPE_CHANNEL)
.or()
.equalTo(RealmRoom.TYPE, RealmRoom.TYPE_PRIVATE)
.endGroup()
.findAllSorted(RealmRoom.NAME,
direction.equals(SortDirection.ASC) ? Sort.ASCENDING : Sort.DESCENDING)
.asObservable()),
pair -> {
if (pair.first == null) {
return Flowable.empty();
}
return RxJavaInterop.toV2Flowable(
pair.first.where(RealmRoom.class)
.like(RealmRoom.NAME, "*" + name + "*", Case.INSENSITIVE)
.beginGroup()
.equalTo(RealmRoom.TYPE, RealmRoom.TYPE_CHANNEL)
.or()
.equalTo(RealmRoom.TYPE, RealmRoom.TYPE_PRIVATE)
.endGroup()
.findAllSorted(RealmRoom.NAME,
direction.equals(SortDirection.ASC) ? Sort.ASCENDING : Sort.DESCENDING)
.asObservable());
},
pair -> close(pair.first, pair.second)
)
.unsubscribeOn(AndroidSchedulers.from(Looper.myLooper()))
......@@ -170,15 +188,20 @@ public class RealmRoomRepository extends RealmRepository implements RoomReposito
public Flowable<List<Room>> getLatestSeen(int limit) {
return Flowable.defer(() -> Flowable.using(
() -> new Pair<>(RealmStore.getRealm(hostname), Looper.myLooper()),
pair -> RxJavaInterop.toV2Flowable(
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()),
pair -> {
if (pair.first == null) {
return Flowable.empty();
}
return RxJavaInterop.toV2Flowable(
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());
},
pair -> close(pair.first, pair.second)
)
.unsubscribeOn(AndroidSchedulers.from(Looper.myLooper()))
......
......@@ -29,12 +29,17 @@ public class RealmRoomRoleRepository extends RealmRepository implements RoomRole
public Single<Optional<RoomRole>> getFor(Room room, User user) {
return Single.defer(() -> Flowable.using(
() -> new Pair<>(RealmStore.getRealm(hostname), Looper.myLooper()),
pair -> RxJavaInterop.toV2Flowable(
pair.first.where(RealmRoomRole.class)
.equalTo(RealmRoomRole.Columns.ROOM_ID, room.getId())
.equalTo(RealmRoomRole.Columns.USER + "." + RealmUser.ID, user.getId())
.findAll()
.<RealmResults<RealmRoomRole>>asObservable()),
pair -> {
if (pair.first == null) {
return Flowable.empty();
}
return RxJavaInterop.toV2Flowable(
pair.first.where(RealmRoomRole.class)
.equalTo(RealmRoomRole.Columns.ROOM_ID, room.getId())
.equalTo(RealmRoomRole.Columns.USER + "." + RealmUser.ID, user.getId())
.findAll()
.<RealmResults<RealmRoomRole>>asObservable());
},
pair -> close(pair.first, pair.second)
)
.unsubscribeOn(AndroidSchedulers.from(Looper.myLooper()))
......
......@@ -26,11 +26,17 @@ public class RealmSessionRepository extends RealmRepository implements SessionRe
public Flowable<Optional<Session>> getById(int id) {
return Flowable.defer(() -> Flowable.using(
() -> new Pair<>(RealmStore.getRealm(hostname), Looper.myLooper()),
pair -> RxJavaInterop.toV2Flowable(
pair.first.where(RealmSession.class)
.equalTo(RealmSession.ID, id)
.findAll()
.<RealmSession>asObservable()),
pair -> {
if (pair.first == null) {
return Flowable.empty();
}
return RxJavaInterop.toV2Flowable(
pair.first.where(RealmSession.class)
.equalTo(RealmSession.ID, id)
.findAll()
.<RealmSession>asObservable());
},
pair -> close(pair.first, pair.second)
)
.unsubscribeOn(AndroidSchedulers.from(Looper.myLooper()))
......
......@@ -20,7 +20,12 @@ class RealmSpotlightRepository(private val hostname: String) : RealmRepository()
override fun getSuggestionsFor(term: String, limit: Int): Flowable<List<Spotlight>> {
return Flowable.defer { Flowable.using<RealmResults<RealmSpotlight>, Pair<Realm, Looper>>({
Pair(RealmStore.getRealm(hostname), Looper.myLooper())
}, { pair -> RxJavaInterop.toV2Flowable<RealmResults<RealmSpotlight>>(pair.first.where(RealmSpotlight::class.java)
}, { pair ->
if (pair.first == null) {
return@using Flowable.empty()
}
return@using RxJavaInterop.toV2Flowable<RealmResults<RealmSpotlight>>(pair.first.where(RealmSpotlight::class.java)
.findAllSorted(Columns.TYPE, Sort.DESCENDING)
.asObservable())
}) { pair -> close(pair.first, pair.second) }
......
......@@ -29,16 +29,22 @@ public class RealmSpotlightRoomRepository extends RealmRepository implements Spo
public Flowable<List<SpotlightRoom>> getSuggestionsFor(String name, SortDirection direction, int limit) {
return Flowable.defer(() -> Flowable.using(
() -> new Pair<>(RealmStore.getRealm(hostname), Looper.myLooper()),
pair -> RxJavaInterop.toV2Flowable(
pair.first.where(RealmSpotlightRoom.class)
.like(RealmSpotlightRoom.Columns.NAME, "*" + name + "*", Case.INSENSITIVE)
.beginGroup()
.equalTo(RealmSpotlightRoom.Columns.TYPE, RealmRoom.TYPE_CHANNEL)
.or()
.equalTo(RealmSpotlightRoom.Columns.TYPE, RealmRoom.TYPE_PRIVATE)
.endGroup()
.findAllSorted(RealmSpotlightRoom.Columns.NAME, direction.equals(SortDirection.ASC) ? Sort.ASCENDING : Sort.DESCENDING)
.asObservable()),
pair -> {
if (pair.first == null) {
return Flowable.empty();
}
return RxJavaInterop.toV2Flowable(
pair.first.where(RealmSpotlightRoom.class)
.like(RealmSpotlightRoom.Columns.NAME, "*" + name + "*", Case.INSENSITIVE)
.beginGroup()
.equalTo(RealmSpotlightRoom.Columns.TYPE, RealmRoom.TYPE_CHANNEL)
.or()
.equalTo(RealmSpotlightRoom.Columns.TYPE, RealmRoom.TYPE_PRIVATE)
.endGroup()
.findAllSorted(RealmSpotlightRoom.Columns.NAME, direction.equals(SortDirection.ASC) ? Sort.ASCENDING : Sort.DESCENDING)
.asObservable());
},
pair -> close(pair.first, pair.second)
)
.unsubscribeOn(AndroidSchedulers.from(Looper.myLooper()))
......
......@@ -28,20 +28,26 @@ public class RealmSpotlightUserRepository extends RealmRepository implements Spo
public Flowable<List<SpotlightUser>> getSuggestionsFor(String name, SortDirection direction, int limit) {
return Flowable.defer(() -> Flowable.using(
() -> new Pair<>(RealmStore.getRealm(hostname), Looper.myLooper()),
pair -> RxJavaInterop.toV2Flowable(
pair.first.where(RealmSpotlightUser.class)
.beginGroup()
.like(RealmSpotlightUser.Columns.USERNAME, "*" + name + "*", Case.INSENSITIVE)
.isNull(RealmSpotlightUser.Columns.NAME)
.endGroup()
.or()
.beginGroup()
.like(RealmSpotlightUser.Columns.NAME, "*" + name + "*", Case.INSENSITIVE)
.isNotNull(RealmSpotlightUser.Columns.USERNAME)
.endGroup()
.findAllSorted(RealmSpotlightUser.Columns.USERNAME,
direction.equals(SortDirection.ASC) ? Sort.ASCENDING : Sort.DESCENDING)
.asObservable()),
pair -> {
if (pair.first == null) {
return Flowable.empty();
}
return RxJavaInterop.toV2Flowable(
pair.first.where(RealmSpotlightUser.class)
.beginGroup()
.like(RealmSpotlightUser.Columns.USERNAME, "*" + name + "*", Case.INSENSITIVE)
.isNull(RealmSpotlightUser.Columns.NAME)
.endGroup()
.or()
.beginGroup()
.like(RealmSpotlightUser.Columns.NAME, "*" + name + "*", Case.INSENSITIVE)
.isNotNull(RealmSpotlightUser.Columns.USERNAME)
.endGroup()
.findAllSorted(RealmSpotlightUser.Columns.USERNAME,
direction.equals(SortDirection.ASC) ? Sort.ASCENDING : Sort.DESCENDING)
.asObservable());
},
pair -> close(pair.first, pair.second)
)
.unsubscribeOn(AndroidSchedulers.from(Looper.myLooper()))
......
......@@ -31,10 +31,16 @@ public class RealmUserRepository extends RealmRepository implements UserReposito
public Flowable<List<User>> getAll() {
return Flowable.defer(() -> Flowable.using(
() -> new Pair<>(RealmStore.getRealm(hostname), Looper.myLooper()),
pair -> RxJavaInterop.toV2Flowable(
pair.first.where(RealmUser.class)
.findAll()
.asObservable()),
pair -> {
if (pair.first == null) {
return Flowable.empty();
}
return RxJavaInterop.toV2Flowable(
pair.first.where(RealmUser.class)
.findAll()
.asObservable());
},
pair -> close(pair.first, pair.second))
.unsubscribeOn(AndroidSchedulers.from(Looper.myLooper()))
.filter(roomSubscriptions -> roomSubscriptions != null && roomSubscriptions.isLoaded()
......@@ -60,11 +66,17 @@ public class RealmUserRepository extends RealmRepository implements UserReposito
private Flowable<RealmResults<RealmUser>> realmGetCurrent() {
return Flowable.using(
() -> new Pair<>(RealmStore.getRealm(hostname), Looper.myLooper()),
pair -> RxJavaInterop.toV2Flowable(
pair -> {
if (pair.first == null) {
return Flowable.empty();
}
return RxJavaInterop.toV2Flowable(
pair.first.where(RealmUser.class)
.isNotEmpty(RealmUser.EMAILS)
.findAll()
.<RealmResults<RealmUser>>asObservable()),
.<RealmResults<RealmUser>>asObservable());
},
pair -> close(pair.first, pair.second));
}
......@@ -90,6 +102,10 @@ public class RealmUserRepository extends RealmRepository implements UserReposito
}
private Flowable<Optional<RealmUser>> realmQueryUsername(Realm realm, String username) {
if (realm == null) {
return Flowable.empty();
}
RealmUser realmUser = realm.where(RealmUser.class)
.equalTo(RealmUser.USERNAME, username)
.findFirst();
......@@ -118,11 +134,17 @@ public class RealmUserRepository extends RealmRepository implements UserReposito
private Flowable<RealmResults<RealmUser>> realmGetSortedLikeName(String name) {
return Flowable.using(
() -> new Pair<>(RealmStore.getRealm(hostname), Looper.myLooper()),
pair -> RxJavaInterop.toV2Flowable(
pair.first.where(RealmUser.class)
.like(RealmUser.USERNAME, "*" + name + "*", Case.INSENSITIVE)
.findAllSorted(RealmUser.USERNAME, Sort.DESCENDING)
.asObservable()),
pair -> {
if (pair.first == null) {
return Flowable.empty();
}
return RxJavaInterop.toV2Flowable(
pair.first.where(RealmUser.class)
.like(RealmUser.USERNAME, "*" + name + "*", Case.INSENSITIVE)
.findAllSorted(RealmUser.USERNAME, Sort.DESCENDING)
.asObservable());
},
pair -> close(pair.first, pair.second));
}
......
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
buildscript {
repositories {
......@@ -62,7 +63,7 @@ dependencies {
compile 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
compile 'com.squareup.okhttp3:okhttp:3.8.0'
compile "com.squareup.okhttp3:okhttp:$rootProject.ext.okHttpVersion"
compile 'com.github.yusukeiwaki.android-widget:widget-fontawesome:0.0.1'
......
......@@ -65,7 +65,7 @@ public class RoomToolbar extends Toolbar {
privateChannelDrawable = VectorDrawableCompat.create(getResources(), R.drawable.ic_lock_white_24dp, null);
publicChannelDrawable = VectorDrawableCompat.create(getResources(), R.drawable.ic_hashtag_white_24dp, null);
livechatChannelDrawable = VectorDrawableCompat.create(getResources(), R.drawable.ic_livechat_white_24dp, null);
userStatusDrawable = VectorDrawableCompat.create(getResources(), R.drawable.ic_user_status_black_24dp, null);
userStatusDrawable = VectorDrawableCompat.create(getResources(), R.drawable.ic_user_status_black_24dp, null).mutate();
}
private void setNavigationIcon(Context context) {
......@@ -92,6 +92,11 @@ public class RoomToolbar extends Toolbar {
toolbarText.setText(title);
}
public void hideChannelIcons() {
roomTypeImage.setVisibility(GONE);
userStatusImage.setVisibility(GONE);
}
public void showPrivateChannelIcon() {
roomTypeImage.setImageDrawable(privateChannelDrawable);
userStatusImage.setVisibility(GONE);
......@@ -137,7 +142,7 @@ public class RoomToolbar extends Toolbar {
userStatusImage.setVisibility(VISIBLE);
}
public void setUnreadBudge(int numUnreadChannels, int numMentionsSum) {
public void setUnreadBadge(int numUnreadChannels, int numMentionsSum) {
if (getNavigationIcon() == null) {
return;
}
......
package chat.rocket.android.widget.helper
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Typeface
import android.graphics.drawable.Drawable
import chat.rocket.android.widget.AbsoluteUrl
import com.amulyakhare.textdrawable.TextDrawable
import java.net.URLEncoder
object UserAvatarHelper {
/**
* Created by Filipe de Lima Brito (filipedelimabrito@gmail.com) on 8/02/17.
*/
object AvatarHelper {
/**
* Returns the user avatar URI.
......@@ -45,6 +49,7 @@ object UserAvatarHelper {
* @param username The username.
* @param context The context.
* @return A drawable with username initials.
* @see getTextBitmap
* @see getUsernameInitials
*/
fun getTextDrawable(username: String, context: Context): Drawable {
......@@ -57,6 +62,19 @@ object UserAvatarHelper {
.buildRoundRect(getUsernameInitials(username), getUserAvatarBackgroundColor(username), round)
}
/**
* Returns a bitmap with username initials.
*
* @param username The username.
* @param context The context.
* @return A bitmap with username initials.
* @see getTextDrawable
*/
fun getTextBitmap(username: String, context: Context): Bitmap {
val textDrawable = getTextDrawable(username, context)
return DrawableHelper.getBitmapFromDrawable(textDrawable, 96, 96)
}
/**
* Returns a string with the username initials. For example: username John.Doe returns JD initials.
*
......@@ -70,15 +88,15 @@ object UserAvatarHelper {
val splitUsername = username.split(".")
val splitCount = splitUsername.size
if (splitCount > 1 && splitUsername[0].isNotEmpty() && splitUsername[splitCount-1].isNotEmpty()) {
return if (splitCount > 1 && splitUsername[0].isNotEmpty() && splitUsername[splitCount-1].isNotEmpty()) {
val firstInitial = splitUsername[0].substring(0, 1)
val secondInitial = splitUsername[splitCount-1].substring(0, 1)
return (firstInitial + secondInitial).toUpperCase()
(firstInitial + secondInitial).toUpperCase()
} else {
if (username.length > 1) {
return username.substring(0, 2).toUpperCase()
username.substring(0, 2).toUpperCase()
} else {
return username.substring(0, 1).toUpperCase()
username.substring(0, 1).toUpperCase()
}
}
}
......
package chat.rocket.android.widget.helper;
import android.view.View;
/**
* A {@linkplain View.OnClickListener click listener} that debounces multiple clicks posted in the
* same frame. A click on one button disables all buttons for that frame.
*
* Shamelessly copied from butterknife -> https://github.com/JakeWharton/butterknife/blob/master/butterknife/src/main/java/butterknife/internal/DebouncingOnClickListener.java
*/
public abstract class DebouncingOnClickListener implements View.OnClickListener {
static boolean enabled = true;
private static final Runnable ENABLE_AGAIN = new Runnable() {
@Override public void run() {
enabled = true;
}
};
@Override public final void onClick(View v) {
if (enabled) {
enabled = false;
v.post(ENABLE_AGAIN);
doClick(v);
}
}
public abstract void doClick(View v);
}
package chat.rocket.android.widget.helper
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.support.v4.content.ContextCompat
import android.support.v4.graphics.drawable.DrawableCompat
/**
* Created by Filipe de Lima Brito (filipedelimabrito@gmail.com) on 8/29/17.
*/
object DrawableHelper {
/**
* Returns a bitmap from drawable.
*
* @param drawable The drawable to get the bitmap.
* @return A bitmap.
*/
fun getBitmapFromDrawable(drawable: Drawable, intrinsicWidth: Int = 1, intrinsicHeight: Int = 1): Bitmap {
if (drawable is BitmapDrawable) {
return drawable.bitmap
}
val textDrawableIntrinsicWidth = drawable.intrinsicWidth
var textDrawableIntrinsicHeight = drawable.intrinsicHeight
val width = if (textDrawableIntrinsicWidth > 0) textDrawableIntrinsicWidth else intrinsicWidth
val height = if (textDrawableIntrinsicHeight > 0) textDrawableIntrinsicHeight else intrinsicHeight
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
drawable.setBounds(0, 0, canvas.width, canvas.height)
drawable.draw(canvas)
return bitmap
}
/**
* Wraps a drawable to be used for example for tinting.
*
* @param drawable The drawable to wrap.
* @see tintDrawable
*/
fun wrapDrawable(drawable: Drawable) {
DrawableCompat.wrap(drawable)
fun wrapDrawable(drawable: Drawable?) {
if (drawable != null) {
DrawableCompat.wrap(drawable)
}
}
/**
......@@ -25,7 +59,9 @@ object DrawableHelper {
* @param resId The resource id color to tint the drawable.
* @see wrapDrawable
*/
fun tintDrawable(drawable: Drawable, context: Context, resId: Int) {
DrawableCompat.setTint(drawable, ContextCompat.getColor(context, resId))
fun tintDrawable(drawable: Drawable?, context: Context, resId: Int) {
if (drawable != null) {
DrawableCompat.setTint(drawable, ContextCompat.getColor(context, resId))
}
}
}
\ No newline at end of file
......@@ -2,35 +2,26 @@ package chat.rocket.android.widget.internal
import android.annotation.TargetApi
import android.content.Context
import android.content.res.TypedArray
import android.graphics.drawable.Drawable
import android.os.Build
import android.support.annotation.ColorRes
import android.support.annotation.StringRes
import android.support.graphics.drawable.VectorDrawableCompat
import android.util.AttributeSet
import android.view.View
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import chat.rocket.android.widget.R
import chat.rocket.android.widget.helper.DrawableHelper
import kotlinx.android.synthetic.main.room_list_item.view.*
/**
* Room list-item view used in sidebar.
*/
class RoomListItemView : FrameLayout {
lateinit private var roomId: String
lateinit private var roomTypeImage: ImageView
lateinit private var userStatusImage: ImageView
lateinit private var roomNameText: TextView
lateinit private var alertCountText: TextView
lateinit private var privateChannelDrawable: Drawable
lateinit private var publicChannelDrawable: Drawable
lateinit private var livechatChannelDrawable: Drawable
lateinit private var userStatusDrawable: Drawable
private val privateChannelDrawable: Drawable? = VectorDrawableCompat.create(resources, R.drawable.ic_lock_white_24dp, null)
private val publicChannelDrawable: Drawable? = VectorDrawableCompat.create(resources, R.drawable.ic_hashtag_white_24dp, null)
private val liveChatChannelDrawable: Drawable? = VectorDrawableCompat.create(resources, R.drawable.ic_livechat_white_24dp, null)
private val userStatusDrawable: Drawable? = VectorDrawableCompat.create(resources, R.drawable.ic_user_status_black_24dp, null)?.mutate()
constructor(context: Context) : super(context) {
initialize(context)
......@@ -50,7 +41,7 @@ class RoomListItemView : FrameLayout {
}
private fun initialize(context: Context) {
layoutParams = LinearLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT)
layoutParams = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT)
val array = context
.theme
......@@ -60,16 +51,6 @@ class RoomListItemView : FrameLayout {
array.recycle()
View.inflate(context, R.layout.room_list_item, this)
roomTypeImage = findViewById(R.id.image_room_type)
userStatusImage = findViewById(R.id.image_user_status)
roomNameText = findViewById(R.id.text_room_name)
alertCountText = findViewById(R.id.text_alert_count)
privateChannelDrawable = VectorDrawableCompat.create(resources, R.drawable.ic_lock_white_24dp, null)!!
publicChannelDrawable = VectorDrawableCompat.create(resources, R.drawable.ic_hashtag_white_24dp, null)!!
livechatChannelDrawable = VectorDrawableCompat.create(resources, R.drawable.ic_livechat_white_24dp, null)!!
userStatusDrawable = VectorDrawableCompat.create(resources, R.drawable.ic_user_status_black_24dp, null)!!
}
fun setRoomId(roomId: String) {
......@@ -78,10 +59,10 @@ class RoomListItemView : FrameLayout {
fun setUnreadCount(count: Int) {
if (count > 0) {
alertCountText.text = count.toString()
alertCountText.visibility = View.VISIBLE
alertCount.text = count.toString()
alertCount.visibility = View.VISIBLE
} else {
alertCountText.visibility = View.GONE
alertCount.visibility = View.GONE
}
}
......@@ -90,25 +71,25 @@ class RoomListItemView : FrameLayout {
}
fun setRoomName(roomName: String) {
roomNameText.text = roomName
name.text = roomName
}
fun showPrivateChannelIcon() {
roomTypeImage.setImageDrawable(privateChannelDrawable)
userStatusImage.visibility = View.GONE
roomTypeImage.visibility = View.VISIBLE
type.setImageDrawable(privateChannelDrawable)
userStatus.visibility = View.GONE
type.visibility = View.VISIBLE
}
fun showPublicChannelIcon() {
roomTypeImage.setImageDrawable(publicChannelDrawable)
userStatusImage.visibility = View.GONE
roomTypeImage.visibility = View.VISIBLE
type.setImageDrawable(publicChannelDrawable)
userStatus.visibility = View.GONE
type.visibility = View.VISIBLE
}
fun showLivechatChannelIcon() {
roomTypeImage.setImageDrawable(livechatChannelDrawable)
userStatusImage.visibility = View.GONE
roomTypeImage.visibility = View.VISIBLE
type.setImageDrawable(liveChatChannelDrawable)
userStatus.visibility = View.GONE
type.visibility = View.VISIBLE
}
fun showOnlineUserStatusIcon() {
......@@ -130,8 +111,8 @@ class RoomListItemView : FrameLayout {
private fun prepareDrawableAndShow(@ColorRes resId: Int) {
DrawableHelper.wrapDrawable(userStatusDrawable)
DrawableHelper.tintDrawable(userStatusDrawable, context, resId)
userStatusImage.setImageDrawable(userStatusDrawable)
roomTypeImage.visibility = View.GONE
userStatusImage.visibility = View.VISIBLE
userStatus.setImageDrawable(userStatusDrawable)
type.visibility = View.GONE
userStatus.visibility = View.VISIBLE
}
}
\ No newline at end of file
......@@ -18,6 +18,7 @@ import android.widget.ImageButton;
import android.widget.LinearLayout;
import chat.rocket.android.widget.R;
import chat.rocket.android.widget.helper.DebouncingOnClickListener;
public class MessageFormLayout extends LinearLayout {
......@@ -57,18 +58,18 @@ public class MessageFormLayout extends LinearLayout {
attachButton = composer.findViewById(R.id.button_attach);
attachButton.setOnClickListener(new OnClickListener() {
attachButton.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void onClick(View view) {
public void doClick(View view) {
onExtraActionSelectionClick();
}
});
sendButton = composer.findViewById(R.id.button_send);
sendButton.setOnClickListener(new OnClickListener() {
sendButton.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void onClick(View view) {
public void doClick(View v) {
String messageText = getText();
if (messageText.length() > 0 && submitTextListener != null) {
submitTextListener.onSubmitText(messageText);
......@@ -163,8 +164,8 @@ public class MessageFormLayout extends LinearLayout {
}
public void setEnabled(boolean enabled) {
getEditor().setEnabled(enabled);
composer.findViewById(R.id.button_send).setEnabled(enabled);
sendButton.setEnabled(enabled);
attachButton.setEnabled(enabled);
}
public void setEditTextCommitContentListener(
......
......@@ -4,13 +4,11 @@ import android.graphics.drawable.Drawable;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import chat.rocket.android.widget.AbsoluteUrl;
import chat.rocket.android.widget.R;
import chat.rocket.android.widget.RocketChatAvatar;
import chat.rocket.android.widget.helper.UserAvatarHelper;
import chat.rocket.android.widget.helper.AvatarHelper;
import chat.rocket.android.widget.message.autocomplete.AutocompleteViewHolder;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
public class UserViewHolder extends AutocompleteViewHolder<UserItem> {
private final TextView titleTextView;
......@@ -45,8 +43,8 @@ public class UserViewHolder extends AutocompleteViewHolder<UserItem> {
}
if (avatar != null) {
String absoluteUri = UserAvatarHelper.INSTANCE.getAbsoluteUri(userItem.getAbsoluteUrl(), suggestion);
Drawable placeholderDrawable = UserAvatarHelper.INSTANCE.getTextDrawable(suggestion, itemView.getContext());
String absoluteUri = AvatarHelper.INSTANCE.getAbsoluteUri(userItem.getAbsoluteUrl(), suggestion);
Drawable placeholderDrawable = AvatarHelper.INSTANCE.getTextDrawable(suggestion, itemView.getContext());
avatar.loadImage(absoluteUri, placeholderDrawable);
}
......
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true"
android:color="@android:color/white" />
<item android:color="@color/color_timestamp"/>
</selector>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/serverstatus_selected"/>
<stroke
android:width="1dp"
android:color="@color/serverstatus_outline"/>
<size
android:width="16dp"
android:height="16dp"/>
</shape>
......@@ -25,7 +25,7 @@
app:layout_constraintRight_toLeftOf="@+id/container"
app:layout_constraintBottom_toBottomOf="parent"/>
<android.support.constraint.ConstraintLayout
<FrameLayout
android:id="@+id/container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
......@@ -53,5 +53,5 @@
android:tint="@color/color_accent"
app:srcCompat="@drawable/ic_send_black_24dp"
android:background="?attr/selectableItemBackgroundBorderless"/>
</android.support.constraint.ConstraintLayout>
</FrameLayout>
</android.support.constraint.ConstraintLayout>
\ No newline at end of file
......@@ -10,13 +10,13 @@
android:paddingBottom="10dp">
<ImageView
android:id="@+id/image_room_type"
android:id="@+id/type"
android:layout_width="20dp"
android:layout_height="wrap_content"
android:visibility="gone" />
<ImageView
android:id="@+id/image_user_status"
android:id="@+id/userStatus"
android:layout_marginLeft="5dp"
android:layout_marginStart="5dp"
android:layout_marginRight="5dp"
......@@ -26,7 +26,7 @@
android:visibility="gone" />
<TextView
android:id="@+id/text_room_name"
android:id="@+id/name"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="0.9"
......@@ -40,7 +40,7 @@
android:textAppearance="@style/TextAppearance.AppCompat.Body2" />
<TextView
android:id="@+id/text_alert_count"
android:id="@+id/alertCount"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="0.1"
......
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:fresco="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="80dp"
tools:background="#044b76">
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/drawee_server_button"
style="@style/Base.Widget.AppCompat.Button.Borderless"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
fresco:actualImageScaleType="fitXY" />
<TextView
android:id="@+id/text_view_site_name_label"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:ellipsize="end"
android:gravity="bottom"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:textColor="@android:color/white"
app:layout_constraintEnd_toEndOf="@+id/text_view_server_label"
app:layout_constraintStart_toStartOf="@+id/text_view_server_label"
app:layout_constraintTop_toTopOf="@+id/drawee_server_button"
tools:text="Rocket.Chat" />
<TextView
android:id="@+id/text_view_server_label"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginEnd="24dp"
android:layout_marginRight="24dp"
android:ellipsize="end"
android:gravity="top"
android:maxLines="1"
android:textAllCaps="false"
android:textColor="@color/color_embed_hostname"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent"
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" />
<ImageView
android:id="@+id/selected_server_dot"
android:layout_width="8dp"
android:layout_height="8dp"
android:layout_marginBottom="8dp"
android:layout_marginEnd="16dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/text_view_site_name_label"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/serverstatus_selected"
tools:visibility="visible" />
</android.support.constraint.ConstraintLayout>
\ No newline at end of file
......@@ -14,4 +14,6 @@
<color name="color_user_status_busy">#FFF44336</color> <!-- Red 500-->
<color name="color_user_status_away">#FFFFC107</color> <!-- Amber 500-->
<color name="color_user_status_offline">#FF607D8B</color> <!-- Blue Grey 500-->
<color name="serverstatus_selected">#FFFFFFFF</color>
<color name="serverstatus_outline">#c2c2c2</color>
</resources>
\ No newline at end of file
import chat.rocket.android.widget.helper.AvatarHelper
import org.junit.Test
import kotlin.test.assertEquals
/**
* Created by Filipe de Lima Brito (filipedelimabrito@gmail.com) on 8/2/17.
*/
class AvatarHelperTest {
@Test
fun getUsernameInitialsTest() {
assertEquals("?", AvatarHelper.getUsernameInitials(""))
assertEquals("?", AvatarHelper.getUsernameInitials("?"))
assertEquals("F", AvatarHelper.getUsernameInitials("f"))
assertEquals("B", AvatarHelper.getUsernameInitials("B"))
assertEquals("FO", AvatarHelper.getUsernameInitials("Fo"))
assertEquals("FO", AvatarHelper.getUsernameInitials("FO"))
assertEquals("FO", AvatarHelper.getUsernameInitials("fOo"))
assertEquals("FO", AvatarHelper.getUsernameInitials("FOO"))
assertEquals("FO", AvatarHelper.getUsernameInitials("F.O"))
assertEquals("FO", AvatarHelper.getUsernameInitials("F.o"))
assertEquals("FB", AvatarHelper.getUsernameInitials("Foo.bar"))
assertEquals("FB", AvatarHelper.getUsernameInitials("Foobar.bar"))
assertEquals("FZ", AvatarHelper.getUsernameInitials("Foobar.bar.zab"))
assertEquals("..", AvatarHelper.getUsernameInitials(".."))
assertEquals("..", AvatarHelper.getUsernameInitials("..."))
assertEquals(".F", AvatarHelper.getUsernameInitials(".Foo."))
assertEquals("FO", AvatarHelper.getUsernameInitials("Foo.."))
}
}
\ No newline at end of file
import chat.rocket.android.widget.helper.UserAvatarHelper
import org.junit.Test
import kotlin.test.assertEquals
class UserAvatarHelperTest {
@Test
fun getUsernameInitialsTest() {
assertEquals("?", UserAvatarHelper.getUsernameInitials(""))
assertEquals("?", UserAvatarHelper.getUsernameInitials("?"))
assertEquals("F", UserAvatarHelper.getUsernameInitials("f"))
assertEquals("B", UserAvatarHelper.getUsernameInitials("B"))
assertEquals("FO", UserAvatarHelper.getUsernameInitials("Fo"))
assertEquals("FO", UserAvatarHelper.getUsernameInitials("FO"))
assertEquals("FO", UserAvatarHelper.getUsernameInitials("fOo"))
assertEquals("FO", UserAvatarHelper.getUsernameInitials("FOO"))
assertEquals("FO", UserAvatarHelper.getUsernameInitials("F.O"))
assertEquals("FO", UserAvatarHelper.getUsernameInitials("F.o"))
assertEquals("FB", UserAvatarHelper.getUsernameInitials("Foo.bar"))
assertEquals("FB", UserAvatarHelper.getUsernameInitials("Foobar.bar"))
assertEquals("FZ", UserAvatarHelper.getUsernameInitials("Foobar.bar.zab"))
assertEquals("..", UserAvatarHelper.getUsernameInitials(".."))
assertEquals("..", UserAvatarHelper.getUsernameInitials("..."))
assertEquals(".F", UserAvatarHelper.getUsernameInitials(".Foo."))
assertEquals("FO", UserAvatarHelper.getUsernameInitials("Foo.."))
}
}
\ No newline at end of file
......@@ -15,6 +15,9 @@ public abstract class User {
public abstract String getId();
@Nullable
public abstract String getName();
@Nullable
public abstract String getUsername();
......@@ -38,6 +41,8 @@ public abstract class User {
public abstract Builder setId(String id);
public abstract Builder setName(String name);
public abstract Builder setUsername(String username);
public abstract Builder setStatus(String status);
......
......@@ -67,7 +67,7 @@ class EditMessageInteractorTest {
val allowEdit = allowEditPublicSettings(true)
val allowEditTimeout = allowEditTimeLimitPublicSetting()
`when`(userRepository.current).thenReturn(Flowable.just(Optional.of(user)))
`when`(userRepository.getCurrent()).thenReturn(Flowable.just(Optional.of(user)))
`when`(publicSettingRepository.getById(PublicSettingsConstants.Message.ALLOW_EDITING))
.thenReturn(Single.just(Optional.of(allowEdit)))
`when`(publicSettingRepository.getById(PublicSettingsConstants.Message.ALLOW_EDITING_BLOCK_TIMEOUT))
......@@ -93,7 +93,7 @@ class EditMessageInteractorTest {
val allowEdit = allowEditPublicSettings(false)
val allowEditTimeout = allowEditTimeLimitPublicSetting()
`when`(userRepository.current).thenReturn(Flowable.just(Optional.of(user)))
`when`(userRepository.getCurrent()).thenReturn(Flowable.just(Optional.of(user)))
`when`(publicSettingRepository.getById(PublicSettingsConstants.Message.ALLOW_EDITING))
.thenReturn(Single.just(Optional.of(allowEdit)))
`when`(publicSettingRepository.getById(PublicSettingsConstants.Message.ALLOW_EDITING_BLOCK_TIMEOUT))
......@@ -123,7 +123,7 @@ class EditMessageInteractorTest {
val allowEdit = allowEditPublicSettings(true)
val allowEditTimeout = allowEditTimeLimitPublicSetting(1)
`when`(userRepository.current).thenReturn(Flowable.just(Optional.of(user)))
`when`(userRepository.getCurrent()).thenReturn(Flowable.just(Optional.of(user)))
`when`(publicSettingRepository.getById(PublicSettingsConstants.Message.ALLOW_EDITING))
.thenReturn(Single.just(Optional.of(allowEdit)))
`when`(publicSettingRepository.getById(PublicSettingsConstants.Message.ALLOW_EDITING_BLOCK_TIMEOUT))
......@@ -155,7 +155,7 @@ class EditMessageInteractorTest {
val anotherUser = mock(User::class.java)
`when`(anotherUser.id).thenReturn("another id")
`when`(userRepository.current).thenReturn(Flowable.just(Optional.of(anotherUser)))
`when`(userRepository.getCurrent()).thenReturn(Flowable.just(Optional.of(anotherUser)))
`when`(publicSettingRepository.getById(PublicSettingsConstants.Message.ALLOW_EDITING))
.thenReturn(Single.just(Optional.of(allowEdit)))
`when`(publicSettingRepository.getById(PublicSettingsConstants.Message.ALLOW_EDITING_BLOCK_TIMEOUT))
......@@ -184,7 +184,7 @@ class EditMessageInteractorTest {
val allowEdit = allowEditPublicSettings(true)
val allowEditTimeout = allowEditTimeLimitPublicSetting()
`when`(userRepository.current).thenReturn(Flowable.just(Optional.of(user)))
`when`(userRepository.getCurrent()).thenReturn(Flowable.just(Optional.of(user)))
`when`(publicSettingRepository.getById(PublicSettingsConstants.Message.ALLOW_EDITING))
.thenReturn(Single.just(Optional.of(allowEdit)))
`when`(publicSettingRepository.getById(PublicSettingsConstants.Message.ALLOW_EDITING_BLOCK_TIMEOUT))
......@@ -213,7 +213,7 @@ class EditMessageInteractorTest {
val allowEdit = allowEditPublicSettings(true)
val allowEditTimeout = allowEditTimeLimitPublicSetting()
`when`(userRepository.current).thenReturn(Flowable.just(Optional.of(user)))
`when`(userRepository.getCurrent()).thenReturn(Flowable.just(Optional.of(user)))
`when`(publicSettingRepository.getById(PublicSettingsConstants.Message.ALLOW_EDITING))
.thenReturn(Single.just(Optional.of(allowEdit)))
`when`(publicSettingRepository.getById(PublicSettingsConstants.Message.ALLOW_EDITING_BLOCK_TIMEOUT))
......@@ -242,7 +242,7 @@ class EditMessageInteractorTest {
val allowEdit = allowEditPublicSettings(false)
val allowEditTimeout = allowEditTimeLimitPublicSetting()
`when`(userRepository.current).thenReturn(Flowable.just(Optional.of(user)))
`when`(userRepository.getCurrent()).thenReturn(Flowable.just(Optional.of(user)))
`when`(publicSettingRepository.getById(PublicSettingsConstants.Message.ALLOW_EDITING))
.thenReturn(Single.just(Optional.of(allowEdit)))
`when`(publicSettingRepository.getById(PublicSettingsConstants.Message.ALLOW_EDITING_BLOCK_TIMEOUT))
......
......@@ -52,7 +52,7 @@ class PermissionInteractorTest {
@Test
fun isAllowedReturnsFalseWhenWithoutCurrentUser() {
`when`(userRepository.current)
`when`(userRepository.getCurrent())
.thenReturn(Flowable.just(Optional.absent()))
val testObserver = TestObserver<Boolean>()
......@@ -67,7 +67,7 @@ class PermissionInteractorTest {
fun isAllowedReturnsFalseWhenWithoutRoomRoleAndPermission() {
val permissionId = "permission"
`when`(userRepository.current)
`when`(userRepository.getCurrent())
.thenReturn(Flowable.just(Optional.of(user)))
`when`(roomRoleRepository.getFor(any(Room::class.java), any(User::class.java)))
......@@ -88,7 +88,7 @@ class PermissionInteractorTest {
fun isAllowedReturnsFalseWhenWithoutRoomRole() {
val permissionId = "permission"
`when`(userRepository.current)
`when`(userRepository.getCurrent())
.thenReturn(Flowable.just(Optional.of(user)))
`when`(roomRoleRepository.getFor(any(Room::class.java), any(User::class.java)))
......@@ -109,7 +109,7 @@ class PermissionInteractorTest {
fun isAllowedReturnsFalseWhenWithoutPermission() {
val permissionId = "permission"
`when`(userRepository.current)
`when`(userRepository.getCurrent())
.thenReturn(Flowable.just(Optional.of(user)))
`when`(roomRoleRepository.getFor(any(Room::class.java), any(User::class.java)))
......@@ -131,7 +131,7 @@ class PermissionInteractorTest {
val permissionId = "permission"
`when`(userRepository.current)
`when`(userRepository.getCurrent())
.thenReturn(Flowable.just(Optional.of(user)))
`when`(roomRoleRepository.getFor(any(Room::class.java), any(User::class.java)))
......@@ -153,7 +153,7 @@ class PermissionInteractorTest {
val permissionId = "permission"
`when`(userRepository.current)
`when`(userRepository.getCurrent())
.thenReturn(Flowable.just(Optional.of(user)))
`when`(roomRole.roles).thenReturn(getSomeRoles())
......@@ -179,7 +179,7 @@ class PermissionInteractorTest {
val permissionId = "permission"
`when`(userRepository.current)
`when`(userRepository.getCurrent())
.thenReturn(Flowable.just(Optional.of(user)))
`when`(roomRole.roles).thenReturn(getMoreRoles())
......
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