Commit 3beec3e6 authored by Lucio Maciel's avatar Lucio Maciel Committed by GitHub

Merge pull request #441 from RocketChat/release/v1.0.17

[RELEASE] Merge stable release 1.0.17 into master
parents 90e5f8d9 b055ccb7
Your Rocket.Chat.Android version: (make sure you are running the latest)
<!-- Version can be found by opening the side menu and then clicking on the chevron alongside username -->
<!-- Found a bug? List all devices that reproduced it and all that doesn't -->
Mobile device model and OS version: (e.g. "Nexus 7 - Android 6.0.1")
<!-- Don't forget to list the steps to reproduce. Stack traces may help too :) -->
# Auth files
*.jks
app/rocket-chat.json
app/src/release/google-services.json
app/src/release/res/values/api_key_strings.xml
################################# Created by https://www.gitignore.io/api/androidstudio (modified) #################################
# Signing files
.signing/
# Android Studio
/*/build/
/*/local.properties
/*/out
/*/*/build
/*/*/production
*.ipr
*~
*.swp
# NDK
obj/
# IntelliJ IDEA
*.iws
/out/
# User-specific configurations
.idea/
# OS-specific files
.DS_Store?
.Trashes
ehthumbs.db
Thumbs.db
# Package Files #
*.war
*.ear
# virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml)
hs_err_pid*
### AndroidStudio Patch ###
!/gradle/wrapper/gradle-wrapper.jar
# End of https://www.gitignore.io/api/androidstudio
################################# Created by https://www.gitignore.io/api/android (modified) #################################
### Android ###
# Built application files
*.apk
*.ap_
# Files for the Dalvik VM
# Files for the ART/Dalvik VM
*.dex
# Java class files
......@@ -11,15 +63,14 @@
# Generated files
bin/
gen/
out/
# Gradle files
.gradle/
build/
/*/build/
# Local configuration file (sdk path, etc)
local.properties
fabric.properties
# Proguard folder generated by Eclipse
proguard/
......@@ -27,15 +78,66 @@ proguard/
# Log Files
*.log
# Android Studio Files
# Android Studio Navigation editor temp files
.navigation/
# Android Studio captures folder
captures/
# Intellij
*.iml
.idea
# MacOS
.DS_Store
# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild
# Auth files
*.jks
app/rocket-chat.json
app/src/release/google-services.json
app/src/release/res/values/api_key_strings.xml
# Freeline
freeline.py
freeline/
freeline_project_description.json
### Android Patch ###
gen-external-apklibs
# End of https://www.gitignore.io/api/android
################################# Created by https://www.gitignore.io/api/crashlytics #################################
### Crashlytics ###
#Crashlytics
crashlytics-build.properties
com_crashlytics_export_strings.xml
crashlytics.properties
fabric.properties
# End of https://www.gitignore.io/api/crashlytics
################################# Created by https://www.gitignore.io/api/osx #################################
### OSX ###
*.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# End of https://www.gitignore.io/api/osx
......@@ -2,14 +2,53 @@
language: android
jdk: oraclejdk8
sudo: required
android:
components: # Cookbooks version: https://github.com/travis-ci/travis-cookbooks/tree/9c6cd11
- tools # Update preinstalled tools from revision 24.0.2 to 24.4.1
- build-tools-25.0.3 # Match build-tools version used in build.gradle
- platform-tools # Update platform-tools to revision 25.0.3+
- tools # Update tools from revision 24.4.1 to 25.2.5
env:
global:
- API=26 # Android API level 26 by default
- TAG=google_apis # Google APIs by default, alternatively use default
- ABI=armeabi-v7a # ARM ABI v7a by default
- QEMU_AUDIO_DRV=none # Disable emulator audio to avoid warning
- ANDROID_HOME=/usr/local/android-sdk # Depends on the cookbooks version used in the VM
- TOOLS=${ANDROID_HOME}/tools # PATH order matters, exists more than one emulator script
- PATH=${ANDROID_HOME}:${ANDROID_HOME}/emulator:${TOOLS}:${TOOLS}/bin:${ANDROID_HOME}/platform-tools:${PATH}
- ADB_INSTALL_TIMEOUT=20 # minutes (2 minutes by default)
install:
# List and delete unnecessary components to free space
- sdkmanager --list || true
- sdkmanager --uninstall "system-images;android-15;default;armeabi-v7a"
# Update sdk tools to latest version and install/update components
- echo yes | sdkmanager "tools"
- echo yes | sdkmanager "platforms;android-26" # Latest platform required by SDK tools
- echo yes | sdkmanager "platforms;android-${API}" # Android platform required by emulator
- echo yes | sdkmanager "extras;android;m2repository"
- echo yes | sdkmanager "extras;google;m2repository"
- echo yes | sdkmanager "extras;m2repository;com;android;support;constraint;constraint-layout;1.0.2"
- echo yes | sdkmanager "extras;m2repository;com;android;support;constraint;constraint-layout-solver;1.0.2"
# - echo yes | sdkmanager "$EMULATOR" # Install emulator system image
# Create and start emulator
# - echo no | avdmanager create avd -n acib -k "$EMULATOR" -f --abi "$ABI" --tag "$TAG"
# - emulator -avd acib -engine classic -no-window -verbose -qemu -m 512 &
before_script:
- echo y | android update sdk --no-ui --all --filter tools,platform-tools
- echo y | android update sdk --no-ui --all --filter android-25
- echo y | android update sdk --no-ui --all --filter extra-android-m2repository,extra-android-support
- echo y | android update sdk --no-ui --all --filter extra-google-m2repository,extra-google-google_play_services
- echo y | android update sdk --no-ui --all --filter build-tools-25.0.1
# - echo y | android update sdk --no-ui --all --filter tools,platform-tools
# - echo y | android update sdk --no-ui --all --filter android-25
# - echo y | android update sdk --no-ui --all --filter extra-android-m2repository,extra-android-support
# - echo y | android update sdk --no-ui --all --filter extra-google-m2repository,extra-google-google_play_services
# - echo y | android update sdk --no-ui --all --filter build-tools-25.0.3
# - echo yes | sdkmanager "extras;m2repository;com;android;support;constraint;constraint-layout;1.0.2"
# - echo yes | sdkmanager "extras;m2repository;com;android;support;constraint;constraint-layout-solver;1.0.2"
- ./gradlew dependencies
- sed -i -e 's/minSdkVersion = 21/minSdkVersion = 17/g' dependencies.gradle
script:
- ./gradlew checkstyle findbugs pmd
This diff is collapsed.
# UNDER HEAVY DEVELOPMENT for v1.0-beta
* rough design document is [here](https://github.com/RocketChat/Rocket.Chat.Android/blob/doc/README.md)
* Rocket.Chat.Android.Lily is moved to [deprecated_lily](https://github.com/RocketChat/Rocket.Chat.Android/tree/deprecated_lily) branch.
Contribution to [the issue with `v1.0-beta` without asignee](https://github.com/RocketChat/Rocket.Chat.Android/issues?utf8=%E2%9C%93&q=is%3Aopen%20is%3Aissue%20no%3Aassignee%20v1.0-beta) is very Welcome! :smile:
---
[![CircleCI](https://circleci.com/gh/RocketChat/Rocket.Chat.Android/tree/develop.svg?style=shield)](https://circleci.com/gh/RocketChat/Rocket.Chat.Android/tree/develop) [![Build Status](https://travis-ci.org/RocketChat/Rocket.Chat.Android.svg?branch=develop)](https://travis-ci.org/RocketChat/Rocket.Chat.Android)
[![CircleCI](https://circleci.com/gh/RocketChat/Rocket.Chat.Android/tree/develop.svg?style=shield)](https://circleci.com/gh/RocketChat/Rocket.Chat.Android/tree/develop) [![Build Status](https://travis-ci.org/RocketChat/Rocket.Chat.Android.svg?branch=develop)](https://travis-ci.org/RocketChat/Rocket.Chat.Android) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/a81156a8682e4649994270d3670c3c83)](https://www.codacy.com/app/matheusjardimb/Rocket.Chat.Android)
# Rocket.Chat.Android
Rocket.Chat Native Android Application.
![screenshots](https://cloud.githubusercontent.com/assets/11763113/21146109/e730ccce-c193-11e6-8b77-7812c1c1e219.png)
*Warning: This app is not production ready. It is under heavy development and any contributions are welcome.*
## How to build
Retrolambda needs java8 to be installed on your system
......@@ -34,8 +20,8 @@ echo "sdk.dir="$ANDROID_HOME > local.properties
## Bug report & Feature request
Please report via instabug
Please report via [GitHub issue](https://github.com/RocketChat/Rocket.Chat.Android/issues) :)
![Instabug](https://cloud.githubusercontent.com/assets/11763113/20717302/6b0918d2-b698-11e6-86f5-df25813f0158.png)
## Coding Style
Of course, [GitHub issue](https://github.com/RocketChat/Rocket.Chat.Android/issues) is also available :)
Please follow our [coding style](https://github.com/RocketChat/Rocket.Chat.Android/blob/develop/CODING_STYLE.md) when contributing.
......@@ -6,9 +6,9 @@ buildscript {
jcenter()
}
dependencies {
classpath rootProject.ext.androidPlugin
classpath rootProject.ext.retroLambdaPlugin
classpath rootProject.ext.retroLambdaPatch
classpath 'com.android.tools.build:gradle:2.3.3'
classpath 'me.tatarka:gradle-retrolambda:3.6.1'
classpath 'me.tatarka.retrolambda.projectlombok:lombok.ast:0.2.3.a2'
}
}
......@@ -21,8 +21,8 @@ android {
targetCompatibility JavaVersion.VERSION_1_8
}
defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.compileSdkVersion
minSdkVersion 16
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "0.0.8"
}
......@@ -36,8 +36,12 @@ android {
dependencies {
compile project(':log-wrapper')
compile rootProject.ext.supportAnnotations
compile rootProject.ext.okhttp3
compile rootProject.ext.rxJava
compile rootProject.ext.boltsTask
compile "com.android.support:support-annotations:$rootProject.ext.supportLibraryVersion"
compile 'com.squareup.okhttp3:okhttp:3.8.0'
compile 'io.reactivex.rxjava2:rxjava:2.1.0'
compile 'com.parse.bolts:bolts-tasks:1.4.0'
}
\ No newline at end of file
package chat.rocket.android_ddp;
import android.support.annotation.Nullable;
import org.json.JSONArray;
import bolts.Task;
import bolts.TaskCompletionSource;
import chat.rocket.android_ddp.rx.RxWebSocketCallback;
import io.reactivex.Flowable;
import io.reactivex.Maybe;
import okhttp3.OkHttpClient;
import rx.Observable;
public class DDPClient {
// reference: https://github.com/eddflrs/meteor-ddp/blob/master/meteor-ddp.js
......@@ -34,6 +36,10 @@ public class DDPClient {
return task.getTask();
}
public Maybe<DDPClientCallback.Base> doPing(@Nullable String id) {
return impl.ping(id);
}
public Task<DDPClientCallback.RPC> rpc(String method, JSONArray params, String id,
long timeoutMs) {
TaskCompletionSource<DDPClientCallback.RPC> task = new TaskCompletionSource<>();
......@@ -53,7 +59,7 @@ public class DDPClient {
return task.getTask();
}
public Observable<DDPSubscription.Event> getSubscriptionCallback() {
public Flowable<DDPSubscription.Event> getSubscriptionCallback() {
return impl.getDDPSubscription();
}
......
package chat.rocket.android_ddp;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.json.JSONObject;
......@@ -53,6 +54,15 @@ public class DDPClientCallback {
this.id = id;
}
public static class UnMatched extends Base {
@NonNull public String id;
public UnMatched(DDPClient client, @NonNull String id) {
super(client);
this.id = id;
}
}
public static class Timeout extends BaseException {
public Timeout(DDPClient client) {
super(Timeout.class, client);
......
package chat.rocket.android_ddp.rx;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import chat.rocket.android.log.RCLog;
import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable;
import io.reactivex.FlowableOnSubscribe;
import io.reactivex.exceptions.OnErrorNotImplementedException;
import io.reactivex.flowables.ConnectableFlowable;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.WebSocket;
import okhttp3.WebSocketListener;
import rx.Emitter;
import rx.Observable;
import rx.exceptions.OnErrorNotImplementedException;
import rx.functions.Action1;
import rx.observables.ConnectableObservable;
public class RxWebSocket {
public static final int REASON_NETWORK_ERROR = 100;
private OkHttpClient httpClient;
private WebSocket webSocket;
private boolean hadErrorsBefore;
public RxWebSocket(OkHttpClient client) {
httpClient = client;
}
public ConnectableObservable<RxWebSocketCallback.Base> connect(String url) {
public ConnectableFlowable<RxWebSocketCallback.Base> connect(String url) {
final Request request = new Request.Builder().url(url).build();
return Observable.fromEmitter(
new Action1<Emitter<RxWebSocketCallback.Base>>() {
@Override
public void call(Emitter<RxWebSocketCallback.Base> emitter) {
httpClient.newWebSocket(request, new WebSocketListener() {
return Flowable.create(
(FlowableOnSubscribe<RxWebSocketCallback.Base>) emitter -> httpClient
.newWebSocket(request, new WebSocketListener() {
@Override
public void onOpen(WebSocket webSocket, Response response) {
hadErrorsBefore = false;
RxWebSocket.this.webSocket = webSocket;
emitter.onNext(new RxWebSocketCallback.Open(RxWebSocket.this.webSocket, response));
}
......@@ -38,7 +41,11 @@ public class RxWebSocket {
@Override
public void onFailure(WebSocket webSocket, Throwable err, Response response) {
try {
emitter.onError(new RxWebSocketCallback.Failure(webSocket, err, response));
if (!hadErrorsBefore) {
hadErrorsBefore = true;
emitter.onNext(new RxWebSocketCallback.Close(webSocket, REASON_NETWORK_ERROR, err.getMessage()));
emitter.onComplete();
}
} catch (OnErrorNotImplementedException ex) {
RCLog.w(ex, "OnErrorNotImplementedException ignored");
}
......@@ -52,20 +59,23 @@ public class RxWebSocket {
@Override
public void onClosed(WebSocket webSocket, int code, String reason) {
emitter.onNext(new RxWebSocketCallback.Close(webSocket, code, reason));
emitter.onCompleted();
}
});
}
},
Emitter.BackpressureMode.BUFFER
}),
BackpressureStrategy.BUFFER
).publish();
}
public boolean sendText(String message) throws IOException {
if (webSocket == null) {
return false;
}
return webSocket.send(message);
}
public boolean close(int code, String reason) throws IOException {
if (webSocket == null) {
return false;
}
return webSocket.close(code, reason);
}
}
package chat.rocket.android_ddp.rx;
import static android.R.attr.type;
import chat.rocket.android.log.RCLog;
import okhttp3.Response;
import okhttp3.WebSocket;
......@@ -28,25 +26,8 @@ public class RxWebSocketCallback {
public Open(WebSocket websocket, Response response) {
super("Open", websocket);
this.response = response;
}
}
public static class Failure extends Exception {
public WebSocket ws;
public Response response;
public Failure(WebSocket websocket, Throwable err, Response response) {
super(err);
this.ws = websocket;
this.response = response;
}
@Override
public String toString() {
if (response != null) {
return "[" + type + "] " + response.message();
} else {
return super.toString();
if (response != null && response.body() != null) {
this.response.body().close();
}
}
}
......
apply plugin: 'com.android.application'
apply plugin: 'io.fabric'
repositories {
maven { url 'https://maven.fabric.io/public' }
maven { url 'https://github.com/uPhyca/stetho-realm/raw/master/maven-repo' }
}
apply plugin: 'kotlin-android'
apply plugin: 'me.tatarka.retrolambda'
apply plugin: 'realm-android'
apply plugin: 'com.jakewharton.hugo'
apply plugin: 'com.github.triplet.play'
apply from: '../config/quality/quality.gradle'
......@@ -9,18 +16,21 @@ buildscript {
repositories {
jcenter()
mavenCentral()
maven { url 'https://maven.fabric.io/public' }
}
dependencies {
classpath rootProject.ext.androidPlugin
classpath 'com.android.tools.build:gradle:2.3.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$rootProject.ext.kotlinVersion"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
classpath rootProject.ext.retroLambdaPlugin
classpath rootProject.ext.retroLambdaPatch
classpath rootProject.ext.realmPlugin
classpath 'me.tatarka:gradle-retrolambda:3.5.0'
classpath 'me.tatarka.retrolambda.projectlombok:lombok.ast:0.2.3.a2'
classpath 'io.realm:realm-gradle-plugin:2.3.1'
classpath 'com.jakewharton.hugo:hugo-plugin:1.2.1'
classpath 'com.google.gms:google-services:3.0.0'
classpath 'com.github.triplet.gradle:play-publisher:1.1.5'
classpath 'io.fabric.tools:gradle:1.+'
}
// Exclude the version that the android plugin depends on.
......@@ -30,17 +40,18 @@ buildscript {
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
applicationId "chat.rocket.android"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.compileSdkVersion
versionCode 8
versionName "1.0-beta8"
minSdkVersion 16
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 31
versionName "1.0.17"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
multiDexEnabled true
}
signingConfigs {
release {
storeFile project.rootProject.file('Rocket.jks').getCanonicalFile()
......@@ -49,77 +60,120 @@ android {
keyPassword System.getenv("KEY_PASSWORD")
}
}
buildTypes {
debug {
debuggable true
versionNameSuffix '-DEBUG'
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.debug
}
release {
debuggable false
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
}
productFlavors {
qa {
// something - qa builds will have some extra stuff for alpha testers
}
prod {
// another
}
}
packagingOptions {
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/NOTICE.txt'
exclude 'META-INF/rxjava.properties'
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
lintOptions {
//avoiding okio error: https://github.com/square/okhttp/issues/896
lintConfig file("lint.xml")
}
sourceSets {
debug {
manifest.srcFile 'src/debug/AndroidManifest.xml'
}
release {
manifest.srcFile 'src/release/AndroidManifest.xml'
}
}
}
play {
jsonFile = file('rocket-chat.json')
track = "${track}"
}
ext {
playLibVersion = '11.0.4'
stethoVersion = '1.5.0'
stethoOkhttp3Version = '1.5.0'
stethoRealmVersion = '2.1.0'
rxbindingVersion = '2.0.0'
rxlifecycleVersion = '2.1.0'
icepickVersion = '3.2.0'
permissionsdispatcherVersion = '2.4.0'
}
dependencies {
compile project(':log-wrapper')
compile project(':android-ddp')
compile project(':rocket-chat-core')
compile project(':rocket-chat-android-widgets')
compile project(':realm-helpers')
compile rootProject.ext.supportAppCompat
compile rootProject.ext.supportDesign
compile project(':persistence-realm')
qaCompile('com.instabug.library:instabug:3.1.0') {
exclude group: 'io.reactivex'
}
compile "com.android.support:appcompat-v7:$rootProject.ext.supportLibraryVersion"
compile "com.android.support:design:$rootProject.ext.supportLibraryVersion"
compile "com.android.support:support-annotations:$rootProject.ext.supportLibraryVersion"
compile "com.android.support.constraint:constraint-layout:$rootProject.ext.constraintLayoutVersion"
compile 'com.android.support:multidex:1.0.1'
compile 'com.google.firebase:firebase-core:10.0.0'
compile 'com.google.firebase:firebase-crash:10.0.0'
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$rootProject.ext.kotlinVersion"
compile "com.google.firebase:firebase-core:$playLibVersion"
compile "com.google.firebase:firebase-crash:$playLibVersion"
compile 'com.google.android.gms:play-services-gcm:10.0.0'
compile "com.google.android.gms:play-services-gcm:$playLibVersion"
compile rootProject.ext.okhttp3
compile 'com.squareup.okhttp3:okhttp:3.8.0'
compile 'com.facebook.stetho:stetho:1.4.1'
compile 'com.facebook.stetho:stetho-okhttp3:1.4.1'
compile 'com.uphyca:stetho_realm:2.0.1'
debugCompile "com.facebook.stetho:stetho:$stethoVersion"
debugCompile "com.facebook.stetho:stetho-okhttp3:$stethoOkhttp3Version"
debugCompile "com.uphyca:stetho_realm:$stethoRealmVersion"
compile 'com.jakewharton.rxbinding:rxbinding:1.0.0'
compile 'com.jakewharton.rxbinding:rxbinding-support-v4:1.0.0'
compile "com.jakewharton.rxbinding2:rxbinding:$rxbindingVersion"
compile "com.jakewharton.rxbinding2:rxbinding-support-v4:$rxbindingVersion"
compile "com.jakewharton.rxbinding2:rxbinding-appcompat-v7:$rxbindingVersion"
compile 'com.trello:rxlifecycle:1.0'
compile 'com.trello:rxlifecycle-android:1.0'
compile 'com.trello:rxlifecycle-components:1.0'
compile "com.trello.rxlifecycle2:rxlifecycle:$rxlifecycleVersion"
compile "com.trello.rxlifecycle2:rxlifecycle-android:$rxlifecycleVersion"
compile "com.trello.rxlifecycle2:rxlifecycle-components:$rxlifecycleVersion"
compile rootProject.ext.textDrawable
compile 'nl.littlerobots.rxlint:rxlint:1.2'
compile 'frankiesardo:icepick:3.2.0'
provided 'frankiesardo:icepick-processor:3.2.0'
compile 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
compile "frankiesardo:icepick:$icepickVersion"
provided "frankiesardo:icepick-processor:$icepickVersion"
compile "com.github.hotchemi:permissionsdispatcher:$permissionsdispatcherVersion"
annotationProcessor "com.github.hotchemi:permissionsdispatcher-processor:$permissionsdispatcherVersion"
compile('com.crashlytics.sdk.android:crashlytics:2.6.8@aar') {
transitive = true;
}
compile 'com.github.hotchemi:permissionsdispatcher:2.3.0'
annotationProcessor 'com.github.hotchemi:permissionsdispatcher-processor:2.3.0'
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'
}
apply plugin: 'com.google.gms.google-services'
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="chat.rocket.android"
xmlns:tools="http://schemas.android.com/tools">
<application
tools:replace="android:name"
android:name=".RocketChatApplicationDebug"/>
</manifest>
\ No newline at end of file
package chat.rocket.android;
import android.os.StrictMode;
import com.facebook.stetho.Stetho;
import com.uphyca.stetho_realm.RealmInspectorModulesProvider;
public class RocketChatApplicationDebug extends RocketChatApplication {
@Override
public void onCreate() {
super.onCreate();
enableStrictMode();
enableStetho();
}
private void enableStrictMode() {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork() // or .detectAll() for all detectable problems
.penaltyLog()
.build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects()
.penaltyLog()
.build());
}
private void enableStetho() {
Stetho.initialize(Stetho.newInitializerBuilder(this)
.enableDumpapp(Stetho.defaultDumperPluginsProvider(this))
.enableWebKitInspector(RealmInspectorModulesProvider.builder(this).build())
.build());
}
}
\ No newline at end of file
package chat.rocket.android.helper
import android.content.Context
import chat.rocket.android.RocketChatCache
import chat.rocket.android.api.rest.CookieInterceptor
import chat.rocket.android.api.rest.DefaultCookieProvider
import com.facebook.stetho.okhttp3.StethoInterceptor
import java.util.concurrent.TimeUnit
import okhttp3.OkHttpClient
object OkHttpHelper {
fun getClientForUploadFile(): OkHttpClient {
if (httpClientForUploadFile == null) {
httpClientForUploadFile = OkHttpClient.Builder().build()
}
return httpClientForUploadFile ?: throw AssertionError("httpClientForUploadFile set to null by another thread")
}
fun getClientForDownloadFile(context: Context): OkHttpClient {
if(httpClientForDownloadFile == null) {
httpClientForDownloadFile = OkHttpClient.Builder()
.addNetworkInterceptor(StethoInterceptor())
.addInterceptor(CookieInterceptor(DefaultCookieProvider(RocketChatCache(context))))
.build()
}
return httpClientForDownloadFile ?: throw AssertionError("httpClientForDownloadFile set to null by another thread")
}
/**
* Returns the OkHttpClient instance for WebSocket connection.
* @return The OkHttpClient WebSocket connection instance.
*/
fun getClientForWebSocket(): OkHttpClient {
if (httpClientForWS == null) {
httpClientForWS = OkHttpClient.Builder()
.readTimeout(0, TimeUnit.NANOSECONDS)
.build()
}
return httpClientForWS ?: throw AssertionError("httpClientForWS set to null by another thread")
}
private var httpClientForUploadFile: OkHttpClient? = null
private var httpClientForDownloadFile: OkHttpClient? = null
private var httpClientForWS: OkHttpClient? = null
}
\ No newline at end of file
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="chat.rocket.android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<permission
android:name="chat.rocket.android.permission.C2D_MESSAGE"
android:protectionLevel="signature" />
<uses-permission android:name="chat.rocket.android.permission.C2D_MESSAGE" />
android:protectionLevel="signature"/>
<uses-permission android:name="chat.rocket.android.permission.C2D_MESSAGE"/>
<application
android:name=".RocketChatApplication"
......@@ -21,32 +22,35 @@
<activity
android:name=".activity.MainActivity"
android:configChanges="orientation|screenSize"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity
android:name=".activity.AddServerActivity"
android:windowSoftInputMode="adjustResize" />
android:configChanges="orientation|screenSize"
android:windowSoftInputMode="adjustResize"/>
<activity
android:name=".activity.LoginActivity"
android:windowSoftInputMode="adjustResize"
android:configChanges="orientation|screenSize" />
android:configChanges="orientation|screenSize"
android:windowSoftInputMode="adjustResize"/>
<service android:name=".service.RocketChatService" />
<service android:name=".service.RocketChatService"/>
<receiver
android:name="com.google.android.gms.gcm.GcmReceiver"
android:exported="true"
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<category android:name="com.example.gcm" />
<action android:name="com.google.android.c2dm.intent.RECEIVE"/>
<category android:name="com.example.gcm"/>
</intent-filter>
</receiver>
......@@ -54,16 +58,21 @@
android:name=".push.gcm.GCMIntentService"
android:exported="false">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<action android:name="com.google.android.c2dm.intent.RECEIVE"/>
</intent-filter>
</service>
<service
android:name=".push.gcm.GcmInstanceIDListenerService"
android:exported="false">
<intent-filter>
<action android:name="com.google.android.gms.iid.InstanceID" />
<action android:name="com.google.android.gms.iid.InstanceID"/>
</intent-filter>
</service>
</application>
<meta-data
android:name="io.fabric.ApiKey"
android:value="12ac6e94f850aaffcdff52001af77ca415d06a43" />
</application>
</manifest>
\ No newline at end of file
package chat.rocket.android;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Process;
public class BackgroundLooper {
private static HandlerThread handlerThread;
public static Looper get() {
if (handlerThread == null) {
handlerThread = new HandlerThread(
"BackgroundHandlerThread", Process.THREAD_PRIORITY_BACKGROUND);
handlerThread.start();
}
return handlerThread.getLooper();
}
}
package chat.rocket.android;
import android.support.multidex.MultiDexApplication;
import com.facebook.stetho.Stetho;
import com.uphyca.stetho_realm.RealmInspectorModulesProvider;
import io.realm.Realm;
import io.realm.RealmConfiguration;
import chat.rocket.android.helper.OkHttpHelper;
import com.crashlytics.android.Crashlytics;
import io.fabric.sdk.android.Fabric;
import java.util.List;
import chat.rocket.android.helper.OkHttpHelper;
import chat.rocket.android.realm_helper.RealmStore;
import chat.rocket.persistence.realm.RealmStore;
import chat.rocket.android.service.ConnectivityManager;
import chat.rocket.android.service.ServerInfo;
import chat.rocket.core.models.ServerInfo;
import chat.rocket.android.widget.RocketChatWidgets;
import chat.rocket.android.wrappers.InstabugWrapper;
import chat.rocket.persistence.realm.RocketChatPersistenceRealm;
/**
* Customized Application-class for Rocket.Chat
*/
public class RocketChatApplication extends MultiDexApplication {
@Override
public void onCreate() {
super.onCreate();
Fabric.with(this, new Crashlytics());
Realm.init(this);
Realm.setDefaultConfiguration(
new RealmConfiguration.Builder().deleteRealmIfMigrationNeeded().build());
RocketChatPersistenceRealm.init(this);
List<ServerInfo> serverInfoList = ConnectivityManager.getInstance(this).getServerList();
for (ServerInfo serverInfo : serverInfoList) {
RealmStore.put(serverInfo.hostname);
RealmStore.put(serverInfo.getHostname());
}
Stetho.initialize(Stetho.newInitializerBuilder(this)
.enableDumpapp(Stetho.defaultDumperPluginsProvider(this))
.enableWebKitInspector(RealmInspectorModulesProvider.builder(this).build())
.build());
InstabugWrapper.build(this, getString(R.string.instabug_api_key));
//TODO: add periodic trigger for RocketChatService.keepAlive(this) here!
RocketChatWidgets.initialize(this, OkHttpHelper.getClientForDownloadFile(this));
RocketChatWidgets.initialize(this, OkHttpHelper.INSTANCE.getClientForDownloadFile(this));
}
}
\ No newline at end of file
......@@ -3,30 +3,45 @@ package chat.rocket.android;
import android.content.Context;
import android.content.SharedPreferences;
import com.hadisatrio.optional.Optional;
import java.util.UUID;
import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable;
/**
* sharedpreference-based cache.
*/
public class RocketChatCache {
public static final String KEY_SELECTED_SERVER_HOSTNAME = "selectedServerHostname";
public static final String KEY_SELECTED_ROOM_ID = "selectedRoomId";
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";
/**
* get SharedPreference instance for RocketChat application cache.
*/
public static SharedPreferences get(Context context) {
return context.getSharedPreferences("cache", Context.MODE_PRIVATE);
private Context context;
public RocketChatCache(Context context) {
this.context = context.getApplicationContext();
}
public String getSelectedServerHostname() {
return getString(KEY_SELECTED_SERVER_HOSTNAME, null);
}
public static String getSelectedServerHostname(Context context) {
return get(context).getString(KEY_SELECTED_SERVER_HOSTNAME, null);
public void setSelectedServerHostname(String hostname) {
setString(KEY_SELECTED_SERVER_HOSTNAME, hostname);
}
public static String getOrCreatePushId(Context context) {
SharedPreferences preferences = get(context);
public String getSelectedRoomId() {
return getString(KEY_SELECTED_ROOM_ID, null);
}
public void setSelectedRoomId(String roomId) {
setString(KEY_SELECTED_ROOM_ID, roomId);
}
public String getOrCreatePushId() {
SharedPreferences preferences = getSharedPreferences();
if (!preferences.contains(KEY_PUSH_ID)) {
// generates one and save
String newId = UUID.randomUUID().toString().replace("-", "");
......@@ -37,4 +52,45 @@ public class RocketChatCache {
}
return preferences.getString(KEY_PUSH_ID, null);
}
public Flowable<Optional<String>> getSelectedServerHostnamePublisher() {
return getValuePublisher(KEY_SELECTED_SERVER_HOSTNAME);
}
public Flowable<Optional<String>> getSelectedRoomIdPublisher() {
return getValuePublisher(KEY_SELECTED_ROOM_ID);
}
private SharedPreferences getSharedPreferences() {
return context.getSharedPreferences("cache", Context.MODE_PRIVATE);
}
private SharedPreferences.Editor getEditor() {
return getSharedPreferences().edit();
}
private String getString(String key, String defaultValue) {
return getSharedPreferences().getString(key, defaultValue);
}
private void setString(String key, String value) {
getEditor().putString(key, value).apply();
}
private Flowable<Optional<String>> getValuePublisher(final String key) {
return Flowable.create(emitter -> {
SharedPreferences.OnSharedPreferenceChangeListener
listener = (sharedPreferences, changedKey) -> {
if (key.equals(changedKey) && !emitter.isCancelled()) {
String value = getString(key, null);
emitter.onNext(Optional.ofNullable(value));
}
};
emitter.setCancellable(() -> getSharedPreferences()
.unregisterOnSharedPreferenceChangeListener(listener));
getSharedPreferences().registerOnSharedPreferenceChangeListener(listener);
}, BackpressureStrategy.LATEST);
}
}
package chat.rocket.android.activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.support.annotation.Nullable;
import com.hadisatrio.optional.Optional;
import java.util.List;
import chat.rocket.android.LaunchUtil;
import chat.rocket.android.RocketChatCache;
import chat.rocket.android.model.ddp.RoomSubscription;
import chat.rocket.android.helper.Logger;
import chat.rocket.android.push.PushConstants;
import chat.rocket.android.push.PushNotificationHandler;
import chat.rocket.android.realm_helper.RealmStore;
import chat.rocket.android.service.ConnectivityManager;
import chat.rocket.android.service.ServerInfo;
import chat.rocket.core.models.ServerInfo;
import chat.rocket.persistence.realm.RealmStore;
import chat.rocket.persistence.realm.models.ddp.RealmRoom;
import icepick.State;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
abstract class AbstractAuthedActivity extends AbstractFragmentActivity {
@State protected String hostname;
@State protected String roomId;
SharedPreferences.OnSharedPreferenceChangeListener preferenceChangeListener =
(sharedPreferences, key) -> {
if (RocketChatCache.KEY_SELECTED_SERVER_HOSTNAME.equals(key)) {
updateHostnameIfNeeded(sharedPreferences);
} else if (RocketChatCache.KEY_SELECTED_ROOM_ID.equals(key)) {
updateRoomIdIfNeeded(sharedPreferences);
}
};
private RocketChatCache rocketChatCache;
private CompositeDisposable compositeDisposable = new CompositeDisposable();
private boolean isNotification;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
rocketChatCache = new RocketChatCache(this);
if (savedInstanceState == null) {
handleIntent(getIntent());
}
updateHostnameIfNeeded(rocketChatCache.getSelectedServerHostname());
updateRoomIdIfNeeded(rocketChatCache.getSelectedRoomId());
}
@Override
......@@ -48,41 +56,37 @@ abstract class AbstractAuthedActivity extends AbstractFragmentActivity {
}
if (intent.hasExtra(PushConstants.HOSTNAME)) {
SharedPreferences.Editor editor = RocketChatCache.get(this).edit();
editor.putString(RocketChatCache.KEY_SELECTED_SERVER_HOSTNAME,
intent.getStringExtra(PushConstants.HOSTNAME));
rocketChatCache.setSelectedServerHostname(intent.getStringExtra(PushConstants.HOSTNAME));
if (intent.hasExtra(PushConstants.ROOM_ID)) {
editor.putString(RocketChatCache.KEY_SELECTED_ROOM_ID,
intent.getStringExtra(PushConstants.ROOM_ID));
rocketChatCache.setSelectedRoomId(intent.getStringExtra(PushConstants.ROOM_ID));
}
}
if (intent.hasExtra(PushConstants.NOT_ID)) {
isNotification = true;
PushNotificationHandler
.cleanUpNotificationStack(intent.getIntExtra(PushConstants.NOT_ID, 0));
}
editor.apply();
}
}
private void updateHostnameIfNeeded(SharedPreferences prefs) {
String newHostname = prefs.getString(RocketChatCache.KEY_SELECTED_SERVER_HOSTNAME, null);
private void updateHostnameIfNeeded(String newHostname) {
if (hostname == null) {
if (newHostname != null && assertServerRealmStoreExists(newHostname)) {
updateHostname(newHostname);
} else {
recoverFromHostnameError(prefs);
recoverFromHostnameError();
}
} else {
if (hostname.equals(newHostname)) {
// we are good
updateHostname(newHostname);
return;
}
if (assertServerRealmStoreExists(newHostname)) {
updateHostname(newHostname);
} else {
recoverFromHostnameError(prefs);
recoverFromHostnameError();
}
}
}
......@@ -96,7 +100,7 @@ abstract class AbstractAuthedActivity extends AbstractFragmentActivity {
onHostnameUpdated();
}
private void recoverFromHostnameError(SharedPreferences prefs) {
private void recoverFromHostnameError() {
final List<ServerInfo> serverInfoList =
ConnectivityManager.getInstance(getApplicationContext()).getServerList();
if (serverInfoList == null || serverInfoList.size() == 0) {
......@@ -106,36 +110,32 @@ abstract class AbstractAuthedActivity extends AbstractFragmentActivity {
// just connect to the first available
final ServerInfo serverInfo = serverInfoList.get(0);
prefs.edit()
.putString(RocketChatCache.KEY_SELECTED_SERVER_HOSTNAME, serverInfo.hostname)
.remove(RocketChatCache.KEY_SELECTED_ROOM_ID)
.apply();
rocketChatCache.setSelectedServerHostname(serverInfo.getHostname());
rocketChatCache.setSelectedRoomId(null);
}
private void updateRoomIdIfNeeded(SharedPreferences prefs) {
String newRoomId = prefs.getString(RocketChatCache.KEY_SELECTED_ROOM_ID, null);
private void updateRoomIdIfNeeded(String newRoomId) {
if (roomId == null) {
if (newRoomId != null && assertRoomSubscriptionExists(newRoomId, prefs)) {
if (newRoomId != null && assertRoomSubscriptionExists(newRoomId)) {
updateRoomId(newRoomId);
}
} else {
if (!roomId.equals(newRoomId) && assertRoomSubscriptionExists(newRoomId, prefs)) {
if (!roomId.equals(newRoomId) && assertRoomSubscriptionExists(newRoomId)) {
updateRoomId(newRoomId);
}
}
}
private boolean assertRoomSubscriptionExists(String roomId, SharedPreferences prefs) {
private boolean assertRoomSubscriptionExists(String roomId) {
if (!assertServerRealmStoreExists(hostname)) {
return false;
}
RoomSubscription room = RealmStore.get(hostname).executeTransactionForRead(realm ->
realm.where(RoomSubscription.class).equalTo(RoomSubscription.ROOM_ID, roomId).findFirst());
RealmRoom room = RealmStore.get(hostname).executeTransactionForRead(realm ->
realm.where(RealmRoom.class).equalTo(RealmRoom.ROOM_ID, roomId).findFirst());
if (room == null) {
prefs.edit()
.remove(RocketChatCache.KEY_SELECTED_ROOM_ID)
.apply();
rocketChatCache.setSelectedRoomId(null);
return false;
}
return true;
......@@ -155,18 +155,20 @@ abstract class AbstractAuthedActivity extends AbstractFragmentActivity {
@Override
protected void onResume() {
super.onResume();
ConnectivityManager.getInstance(getApplicationContext()).keepAliveServer();
SharedPreferences prefs = RocketChatCache.get(this);
updateHostnameIfNeeded(prefs);
updateRoomIdIfNeeded(prefs);
prefs.registerOnSharedPreferenceChangeListener(preferenceChangeListener);
subscribeToConfigChanges();
ConnectivityManager.getInstance(getApplicationContext()).keepAliveServer();
if (isNotification) {
updateHostnameIfNeeded(rocketChatCache.getSelectedServerHostname());
updateRoomIdIfNeeded(rocketChatCache.getSelectedRoomId());
isNotification = false;
}
}
@Override
protected void onPause() {
SharedPreferences prefs = RocketChatCache.get(this);
prefs.unregisterOnSharedPreferenceChangeListener(preferenceChangeListener);
compositeDisposable.clear();
super.onPause();
}
......@@ -175,4 +177,30 @@ abstract class AbstractAuthedActivity extends AbstractFragmentActivity {
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
}
private void subscribeToConfigChanges() {
compositeDisposable.add(
rocketChatCache.getSelectedServerHostnamePublisher()
.map(Optional::get)
.distinctUntilChanged()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
this::updateHostnameIfNeeded,
Logger::report
)
);
compositeDisposable.add(
rocketChatCache.getSelectedRoomIdPublisher()
.map(Optional::get)
.distinctUntilChanged()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
this::updateRoomIdIfNeeded,
Logger::report
)
);
}
}
......@@ -5,12 +5,10 @@ import android.support.annotation.IdRes;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.view.MotionEvent;
import com.trello.rxlifecycle.components.support.RxAppCompatActivity;
import com.trello.rxlifecycle2.components.support.RxAppCompatActivity;
import chat.rocket.android.helper.OnBackPressListener;
import chat.rocket.android.log.RCLog;
import chat.rocket.android.wrappers.InstabugWrapper;
import icepick.Icepick;
abstract class AbstractFragmentActivity extends RxAppCompatActivity {
......@@ -27,9 +25,8 @@ abstract class AbstractFragmentActivity extends RxAppCompatActivity {
Icepick.saveInstanceState(this, outState);
}
protected abstract
@IdRes
int getLayoutContainerForFragment();
protected abstract int getLayoutContainerForFragment();
@Override
public final void onBackPressed() {
......@@ -71,14 +68,4 @@ abstract class AbstractFragmentActivity extends RxAppCompatActivity {
.addToBackStack(null)
.commit();
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
try {
InstabugWrapper.trackTouchEventOnActivity(event, this);
} catch (IllegalStateException exception) {
RCLog.w(exception, "Instabug error (ignored)");
}
return super.dispatchTouchEvent(event);
}
}
......@@ -8,20 +8,17 @@ import android.support.v4.app.Fragment;
import chat.rocket.android.R;
import chat.rocket.android.fragment.server_config.LoginFragment;
import chat.rocket.android.fragment.server_config.RetryLoginFragment;
import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.model.internal.Session;
import chat.rocket.android.realm_helper.RealmObjectObserver;
import chat.rocket.android.realm_helper.RealmStore;
import chat.rocket.core.interactors.SessionInteractor;
import chat.rocket.android.service.ConnectivityManager;
import chat.rocket.persistence.realm.repositories.RealmSessionRepository;
/**
* Activity for Login, Sign-up, and Retry connecting...
*/
public class LoginActivity extends AbstractFragmentActivity {
public class LoginActivity extends AbstractFragmentActivity implements LoginContract.View {
public static final String KEY_HOSTNAME = "hostname";
private String hostname;
private RealmObjectObserver<Session> sessionObserver;
private LoginContract.Presenter presenter;
@Override
protected int getLayoutContainerForFragment() {
......@@ -32,69 +29,38 @@ public class LoginActivity extends AbstractFragmentActivity {
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String hostname = null;
Intent intent = getIntent();
if (intent == null || intent.getExtras() == null) {
finish();
return;
}
if (intent != null && intent.getExtras() != null) {
hostname = intent.getStringExtra(KEY_HOSTNAME);
if (TextUtils.isEmpty(hostname)) {
finish();
return;
}
sessionObserver = RealmStore.get(hostname)
.createObjectObserver(Session::queryDefaultSession)
.setOnUpdateListener(this::onRenderServerConfigSession);
setContentView(R.layout.simple_screen);
showFragment(new LoginFragment());
presenter = new LoginPresenter(
hostname,
new SessionInteractor(new RealmSessionRepository(hostname)),
ConnectivityManager.getInstance(getApplicationContext())
);
}
@Override
protected void onResume() {
super.onResume();
ConnectivityManager.getInstance(getApplicationContext()).keepAliveServer();
sessionObserver.sub();
presenter.bindView(this);
}
@Override
protected void onDestroy() {
sessionObserver.unsub();
presenter.release();
super.onDestroy();
}
private void onRenderServerConfigSession(Session session) {
if (session == null) {
return;
}
final String token = session.getToken();
if (!TextUtils.isEmpty(token)) {
if (TextUtils.isEmpty(session.getError())) {
finish();
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
} else {
showFragment(new RetryLoginFragment());
}
return;
}
}
@Override
protected void showFragment(Fragment fragment) {
injectHostnameArgTo(fragment);
private void showFragment(Fragment fragment, String hostname) {
setContentView(R.layout.simple_screen);
injectHostnameArgTo(fragment, hostname);
super.showFragment(fragment);
}
@Override
protected void showFragmentWithBackStack(Fragment fragment) {
injectHostnameArgTo(fragment);
super.showFragmentWithBackStack(fragment);
}
private void injectHostnameArgTo(Fragment fragment) {
private void injectHostnameArgTo(Fragment fragment, String hostname) {
Bundle args = fragment.getArguments();
if (args == null) {
args = new Bundle();
......@@ -107,4 +73,20 @@ public class LoginActivity extends AbstractFragmentActivity {
protected void onBackPressedNotHandled() {
moveTaskToBack(true);
}
@Override
public void showLogin(String hostname) {
showFragment(new LoginFragment(), hostname);
}
@Override
public void showRetryLogin(String hostname) {
showFragment(new RetryLoginFragment(), hostname);
}
@Override
public void closeView() {
finish();
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
}
}
package chat.rocket.android.activity;
import chat.rocket.android.shared.BaseContract;
public interface LoginContract {
interface View extends BaseContract.View {
void showLogin(String hostname);
void showRetryLogin(String hostname);
void closeView();
}
interface Presenter extends BaseContract.Presenter<View> {
}
}
package chat.rocket.android.activity;
import android.support.annotation.NonNull;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import chat.rocket.android.BackgroundLooper;
import chat.rocket.android.helper.Logger;
import chat.rocket.android.service.ConnectivityManagerApi;
import chat.rocket.android.shared.BasePresenter;
import chat.rocket.core.interactors.SessionInteractor;
public class LoginPresenter extends BasePresenter<LoginContract.View>
implements LoginContract.Presenter {
private final String hostname;
private final SessionInteractor sessionInteractor;
private final ConnectivityManagerApi connectivityManagerApi;
private boolean isLogging = false;
public LoginPresenter(String hostname,
SessionInteractor sessionInteractor,
ConnectivityManagerApi connectivityManagerApi) {
this.hostname = hostname;
this.sessionInteractor = sessionInteractor;
this.connectivityManagerApi = connectivityManagerApi;
}
@Override
public void bindView(@NonNull LoginContract.View view) {
super.bindView(view);
connectivityManagerApi.keepAliveServer();
if (hostname == null || hostname.length() == 0) {
view.closeView();
return;
}
if (isLogging) {
return;
}
loadSessionState();
}
private void loadSessionState() {
final Disposable subscription = sessionInteractor.getSessionState()
.distinctUntilChanged()
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
state -> {
switch (state) {
case UNAVAILABLE:
isLogging = true;
view.showLogin(hostname);
break;
case INVALID:
isLogging = false;
view.showRetryLogin(hostname);
break;
case VALID:
isLogging = false;
view.closeView();
}
},
Logger::report
);
addSubscription(subscription);
}
}
package chat.rocket.android.activity;
import chat.rocket.android.shared.BaseContract;
public interface MainContract {
interface View extends BaseContract.View {
void showHome();
void showRoom(String hostname, String roomId);
void showUnreadCount(long roomsCount, int mentionsCount);
void showAddServerScreen();
void showLoginScreen();
void showConnectionError();
void showConnecting();
void showConnectionOk();
}
interface Presenter extends BaseContract.Presenter<View> {
void onOpenRoom(String hostname, String roomId);
void onRetryLogin();
void bindViewOnly(View view);
}
}
package chat.rocket.android.activity;
import android.support.annotation.NonNull;
import android.support.v4.util.Pair;
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.service.ConnectivityManagerApi;
import chat.rocket.android.service.ServerConnectivity;
import chat.rocket.android.shared.BasePresenter;
import chat.rocket.core.interactors.CanCreateRoomInteractor;
import chat.rocket.core.interactors.RoomInteractor;
import chat.rocket.core.interactors.SessionInteractor;
import chat.rocket.core.models.Session;
import chat.rocket.core.models.User;
import hu.akarnokd.rxjava.interop.RxJavaInterop;
import io.reactivex.Flowable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
public class MainPresenter extends BasePresenter<MainContract.View>
implements MainContract.Presenter {
private final CanCreateRoomInteractor canCreateRoomInteractor;
private final RoomInteractor roomInteractor;
private final SessionInteractor sessionInteractor;
private final MethodCallHelper methodCallHelper;
private final ConnectivityManagerApi connectivityManagerApi;
private final RocketChatCache rocketChatCache;
public MainPresenter(RoomInteractor roomInteractor,
CanCreateRoomInteractor canCreateRoomInteractor,
SessionInteractor sessionInteractor,
MethodCallHelper methodCallHelper,
ConnectivityManagerApi connectivityManagerApi,
RocketChatCache rocketChatCache) {
this.roomInteractor = roomInteractor;
this.canCreateRoomInteractor = canCreateRoomInteractor;
this.sessionInteractor = sessionInteractor;
this.methodCallHelper = methodCallHelper;
this.connectivityManagerApi = connectivityManagerApi;
this.rocketChatCache = rocketChatCache;
}
@Override
public void bindViewOnly(@NonNull MainContract.View view) {
super.bindView(view);
subscribeToUnreadCount();
subscribeToSession();
setUserOnline();
}
@Override
public void bindView(@NonNull MainContract.View view) {
super.bindView(view);
if (shouldLaunchAddServerActivity()) {
view.showAddServerScreen();
return;
}
openRoom();
subscribeToNetworkChanges();
subscribeToUnreadCount();
subscribeToSession();
setUserOnline();
}
@Override
public void release() {
setUserAway();
super.release();
}
@Override
public void onOpenRoom(String hostname, String roomId) {
final Disposable subscription = canCreateRoomInteractor.canCreate(roomId)
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
allowed -> {
if (allowed) {
view.showRoom(hostname, roomId);
} else {
view.showHome();
}
},
Logger::report
);
addSubscription(subscription);
}
@Override
public void onRetryLogin() {
final Disposable subscription = sessionInteractor.retryLogin()
.subscribe();
addSubscription(subscription);
}
private void openRoom() {
String hostname = rocketChatCache.getSelectedServerHostname();
String roomId = rocketChatCache.getSelectedRoomId();
if (roomId == null || roomId.length() == 0) {
view.showHome();
return;
}
onOpenRoom(hostname, roomId);
}
private void subscribeToUnreadCount() {
final Disposable subscription = Flowable.combineLatest(
roomInteractor.getTotalUnreadRoomsCount(),
roomInteractor.getTotalUnreadMentionsCount(),
(Pair::new)
)
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
pair -> view.showUnreadCount(pair.first, pair.second),
Logger::report
);
addSubscription(subscription);
}
private void subscribeToSession() {
final Disposable subscription = sessionInteractor.getDefault()
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
sessionOptional -> {
Session session = sessionOptional.orNull();
if (session == null || session.getToken() == null) {
view.showLoginScreen();
return;
}
String error = session.getError();
if (error != null && error.length() != 0) {
view.showConnectionError();
return;
}
if (!session.isTokenVerified()) {
view.showConnecting();
return;
}
view.showConnectionOk();
},
Logger::report
);
addSubscription(subscription);
}
private void subscribeToNetworkChanges() {
Disposable disposable = RxJavaInterop.toV2Flowable(connectivityManagerApi.getServerConnectivityAsObservable())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
connectivity -> {
if (connectivity.state == ServerConnectivity.STATE_CONNECTED) {
view.showConnectionOk();
return;
}
view.showConnecting();
},
Logger::report
);
addSubscription(disposable);
}
private void setUserOnline() {
methodCallHelper.setUserPresence(User.STATUS_ONLINE)
.continueWith(new LogIfError());
}
private void setUserAway() {
methodCallHelper.setUserPresence(User.STATUS_AWAY)
.continueWith(new LogIfError());
}
private boolean shouldLaunchAddServerActivity() {
return connectivityManagerApi.getServerList().isEmpty();
}
}
package chat.rocket.android.api;
import android.support.annotation.Nullable;
import chat.rocket.android.helper.OkHttpHelper;
import org.json.JSONArray;
import org.json.JSONException;
import java.util.UUID;
import bolts.Task;
import chat.rocket.android.helper.OkHttpHelper;
import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.log.RCLog;
import chat.rocket.android_ddp.DDPClient;
import chat.rocket.android_ddp.DDPClientCallback;
import chat.rocket.android_ddp.DDPSubscription;
import rx.Observable;
import io.reactivex.Flowable;
import io.reactivex.Maybe;
/**
* DDP client wrapper.
......@@ -22,7 +25,7 @@ public class DDPClientWrapper {
private final String hostname;
private DDPClientWrapper(String hostname) {
ddpClient = new DDPClient(OkHttpHelper.getClientForWebSocket());
ddpClient = new DDPClient(OkHttpHelper.INSTANCE.getClientForWebSocket());
this.hostname = hostname;
}
......@@ -69,7 +72,7 @@ public class DDPClientWrapper {
/**
* Returns Observable for handling DDP subscription.
*/
public Observable<DDPSubscription.Event> getSubscriptionCallback() {
public Flowable<DDPSubscription.Event> getSubscriptionCallback() {
return ddpClient.getSubscriptionCallback();
}
......@@ -122,4 +125,13 @@ public class DDPClientWrapper {
}
});
}
/**
* check WebSocket connectivity with ping.
*/
public Maybe<DDPClientCallback.Base> doPing() {
final String pingId = UUID.randomUUID().toString();
RCLog.d("ping[%s] >", pingId);
return ddpClient.doPing(pingId);
}
}
......@@ -6,7 +6,7 @@ import org.json.JSONObject;
import bolts.Task;
import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.realm_helper.RealmHelper;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.android.service.DDPClientRef;
/**
......@@ -21,16 +21,14 @@ public class FileUploadingHelper extends MethodCallHelper {
super(realmHelper, ddpClientRef);
}
public Task<JSONObject> uploadRequest(String filename, long filesize, String mimeType,
public Task<JSONObject> uploadS3Request(String filename, long filesize, String mimeType,
String roomId) {
return call("slingshot/uploadRequest", TIMEOUT_MS, () -> new JSONArray()
.put("rocketchat-uploads")
.put(new JSONObject()
.put("name", filename)
.put("size", filesize)
.put("type", mimeType))
.put(new JSONObject().put("rid", roomId)))
.onSuccessTask(CONVERT_TO_JSON_OBJECT);
return uploadRequest("rocketchat-uploads", filename, filesize, mimeType, roomId);
}
public Task<JSONObject> uploadGoogleRequest(String filename, long filesize, String mimeType,
String roomId) {
return uploadRequest("rocketchat-uploads-gs", filename, filesize, mimeType, roomId);
}
public Task<Void> sendFileMessage(String roomId, String storageType, JSONObject fileObj) {
......@@ -59,4 +57,17 @@ public class FileUploadingHelper extends MethodCallHelper {
.put(token)
).onSuccessTask(CONVERT_TO_JSON_OBJECT);
}
private Task<JSONObject> uploadRequest(String uploadType, String filename,
long filesize, String mimeType,
String roomId) {
return call("slingshot/uploadRequest", TIMEOUT_MS, () -> new JSONArray()
.put(uploadType)
.put(new JSONObject()
.put("name", filename)
.put("size", filesize)
.put("type", mimeType))
.put(new JSONObject().put("rid", roomId)))
.onSuccessTask(CONVERT_TO_JSON_OBJECT);
}
}
......@@ -7,7 +7,7 @@ import org.json.JSONArray;
import org.json.JSONObject;
import bolts.Task;
import chat.rocket.android.realm_helper.RealmHelper;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.android.service.DDPClientRef;
public class RaixPushHelper extends MethodCallHelper {
......
package chat.rocket.android.api;
public class TwoStepAuthException extends Exception {
public static final String TYPE = "totp-required";
private static final long serialVersionUID = 7063596902054234189L;
public TwoStepAuthException() {
super();
}
public TwoStepAuthException(String message) {
super(message);
}
}
package chat.rocket.android.api.rest;
import android.content.Context;
import chat.rocket.android.RocketChatCache;
import chat.rocket.android.model.ddp.User;
import chat.rocket.android.model.internal.Session;
import chat.rocket.android.realm_helper.RealmHelper;
import chat.rocket.android.realm_helper.RealmStore;
import chat.rocket.persistence.realm.models.ddp.RealmUser;
import chat.rocket.persistence.realm.models.internal.RealmSession;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.persistence.realm.RealmStore;
public class DefaultCookieProvider implements CookieProvider {
private final Context applicationContext;
private RocketChatCache rocketChatCache;
public DefaultCookieProvider(Context context) {
applicationContext = context.getApplicationContext();
public DefaultCookieProvider(RocketChatCache rocketChatCache) {
this.rocketChatCache = rocketChatCache;
}
@Override
......@@ -33,10 +31,10 @@ public class DefaultCookieProvider implements CookieProvider {
return "";
}
final User user = realmHelper.executeTransactionForRead(realm ->
User.queryCurrentUser(realm).findFirst());
final Session session = realmHelper.executeTransactionForRead(realm ->
Session.queryDefaultSession(realm).findFirst());
final RealmUser user = realmHelper.executeTransactionForRead(realm ->
RealmUser.queryCurrentUser(realm).findFirst());
final RealmSession session = realmHelper.executeTransactionForRead(realm ->
RealmSession.queryDefaultSession(realm).findFirst());
if (user == null || session == null) {
return "";
......@@ -46,6 +44,6 @@ public class DefaultCookieProvider implements CookieProvider {
}
private String getHostnameFromCache() {
return RocketChatCache.getSelectedServerHostname(applicationContext);
return rocketChatCache.getSelectedServerHostname();
}
}
......@@ -2,6 +2,9 @@ package chat.rocket.android.api.rest;
import android.support.annotation.NonNull;
import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable;
import io.reactivex.FlowableEmitter;
import org.json.JSONObject;
import java.io.IOException;
......@@ -9,8 +12,6 @@ import okhttp3.Call;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.ResponseBody;
import rx.Emitter;
import rx.Observable;
public class DefaultServerPolicyApi implements ServerPolicyApi {
......@@ -25,23 +26,23 @@ public class DefaultServerPolicyApi implements ServerPolicyApi {
}
@Override
public Observable<Response<JSONObject>> getApiInfoSecurely() {
public Flowable<Response<JSONObject>> getApiInfoSecurely() {
return getApiInfo(SECURE_PROTOCOL);
}
@Override
public Observable<Response<JSONObject>> getApiInfoInsecurely() {
public Flowable<Response<JSONObject>> getApiInfoInsecurely() {
return getApiInfo(INSECURE_PROTOCOL);
}
private Observable<Response<JSONObject>> getApiInfo(@NonNull String protocol) {
return Observable.fromEmitter(responseEmitter -> {
private Flowable<Response<JSONObject>> getApiInfo(@NonNull String protocol) {
return Flowable.create(responseEmitter -> {
final Call call = client.newCall(createRequest(protocol));
call.enqueue(getOkHttpCallback(responseEmitter, protocol));
responseEmitter.setCancellation(call::cancel);
}, Emitter.BackpressureMode.LATEST);
responseEmitter.setCancellable(call::cancel);
}, BackpressureStrategy.LATEST);
}
private Request createRequest(@NonNull String protocol) {
......@@ -51,26 +52,34 @@ public class DefaultServerPolicyApi implements ServerPolicyApi {
.build();
}
private okhttp3.Callback getOkHttpCallback(@NonNull Emitter<Response<JSONObject>> emitter,
private okhttp3.Callback getOkHttpCallback(@NonNull FlowableEmitter<Response<JSONObject>> emitter,
@NonNull String protocol) {
return new okhttp3.Callback() {
@Override
public void onFailure(Call call, IOException ioException) {
if (emitter.isCancelled()) {
return;
}
emitter.onError(ioException);
}
@Override
public void onResponse(Call call, okhttp3.Response response) throws IOException {
if (emitter.isCancelled()) {
return;
}
if (!response.isSuccessful()) {
emitter.onNext(new Response<>(false, protocol, null));
emitter.onCompleted();
emitter.onComplete();
return;
}
final ResponseBody body = response.body();
if (body == null || body.contentLength() == 0) {
emitter.onNext(new Response<>(false, protocol, null));
emitter.onCompleted();
emitter.onComplete();
return;
}
......@@ -80,7 +89,7 @@ public class DefaultServerPolicyApi implements ServerPolicyApi {
emitter.onNext(new Response<>(false, protocol, null));
}
emitter.onCompleted();
emitter.onComplete();
}
};
}
......
package chat.rocket.android.api.rest;
import io.reactivex.Flowable;
import org.json.JSONObject;
import rx.Observable;
public interface ServerPolicyApi {
String SECURE_PROTOCOL = "https://";
String INSECURE_PROTOCOL = "http://";
Observable<Response<JSONObject>> getApiInfoSecurely();
Flowable<Response<JSONObject>> getApiInfoSecurely();
Observable<Response<JSONObject>> getApiInfoInsecurely();
Flowable<Response<JSONObject>> getApiInfoInsecurely();
}
......@@ -6,7 +6,7 @@ import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.trello.rxlifecycle.components.support.RxFragment;
import com.trello.rxlifecycle2.components.support.RxFragment;
/**
* Fragment base class for this Application.
......
......@@ -16,6 +16,7 @@ import hugo.weaving.DebugLog;
public abstract class AbstractWebViewFragment extends AbstractFragment
implements OnBackPressListener {
private boolean isSet = false;
private WebView webview;
private WebViewClient webviewClient = new WebViewClient() {
private boolean error;
......@@ -66,6 +67,10 @@ public abstract class AbstractWebViewFragment extends AbstractFragment
}
private void setupWebView() {
if (isSet) {
return;
}
WebSettings settings = webview.getSettings();
if (settings != null) {
settings.setJavaScriptEnabled(true);
......@@ -80,6 +85,8 @@ public abstract class AbstractWebViewFragment extends AbstractFragment
//refs: https://code.google.com/p/android/issues/detail?id=35288
webview.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
isSet = true;
}
@Override
......@@ -92,6 +99,15 @@ public abstract class AbstractWebViewFragment extends AbstractFragment
}
}
protected WebView getWebview() {
if (webview == null) {
return null;
}
setupWebView();
return webview;
}
protected abstract void navigateToInitialPage(WebView webview);
protected void onPageLoaded(WebView webview, String url) {
......@@ -104,6 +120,4 @@ public abstract class AbstractWebViewFragment extends AbstractFragment
protected boolean onHandleCallback(WebView webview, String url) {
return false;
}
;
}
package chat.rocket.android.fragment.add_server;
import chat.rocket.android.shared.BaseContract;
public interface InputHostnameContract {
interface View extends BaseContract.View {
void showLoader();
void hideLoader();
void showInvalidServerError();
void showConnectionError();
void showHome();
}
interface Presenter extends BaseContract.Presenter<View> {
void connectTo(String hostname);
}
}
package chat.rocket.android.fragment.add_server;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.constraint.ConstraintLayout;
import android.support.design.widget.Snackbar;
import android.view.View;
import android.widget.TextView;
import chat.rocket.android.BuildConfig;
import chat.rocket.android.LaunchUtil;
import chat.rocket.android.R;
import chat.rocket.android.RocketChatCache;
import chat.rocket.android.api.rest.DefaultServerPolicyApi;
import chat.rocket.android.api.rest.ServerPolicyApi;
import chat.rocket.android.fragment.AbstractFragment;
import chat.rocket.android.helper.OkHttpHelper;
import chat.rocket.android.helper.ServerPolicyApiValidationHelper;
import chat.rocket.android.helper.ServerPolicyHelper;
import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.service.ConnectivityManager;
import chat.rocket.android.service.ConnectivityManagerApi;
import rx.Subscription;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;
/**
* Input server host.
*/
public class InputHostnameFragment extends AbstractFragment {
Subscription serverPolicySubscription;
public class InputHostnameFragment extends AbstractFragment implements InputHostnameContract.View {
private InputHostnameContract.Presenter presenter;
private ConstraintLayout container;
private View waitingView;
public InputHostnameFragment() {}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
public InputHostnameFragment() {
Context appContext = getContext().getApplicationContext();
presenter = new InputHostnamePresenter(new RocketChatCache(appContext), ConnectivityManager.getInstance(appContext));
}
@Override
......@@ -38,6 +43,8 @@ public class InputHostnameFragment extends AbstractFragment {
protected void onSetupView() {
setupVersionInfo();
container = rootView.findViewById(R.id.container);
waitingView = rootView.findViewById(R.id.waiting);
rootView.findViewById(R.id.btn_connect).setOnClickListener(view -> handleConnect());
}
......@@ -47,42 +54,18 @@ public class InputHostnameFragment extends AbstractFragment {
}
private void handleConnect() {
final String hostname = ServerPolicyHelper.enforceHostname(getHostname());
final ServerPolicyApi serverPolicyApi =
new DefaultServerPolicyApi(OkHttpHelper.getClientForUploadFile(), hostname);
final ServerPolicyApiValidationHelper validationHelper =
new ServerPolicyApiValidationHelper(serverPolicyApi);
if (serverPolicySubscription != null) {
serverPolicySubscription.unsubscribe();
presenter.connectTo(getHostname());
}
rootView.findViewById(R.id.btn_connect).setEnabled(false);
serverPolicySubscription = ServerPolicyHelper.isApiVersionValid(validationHelper)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnTerminate(() -> rootView.findViewById(R.id.btn_connect).setEnabled(true))
.subscribe(
serverValidation -> {
if (serverValidation.isValid()) {
onServerValid(hostname, serverValidation.usesSecureConnection());
} else {
showError(getString(R.string.input_hostname_invalid_server_message));
}
},
throwable -> {
showError(getString(R.string.connection_error_try_later));
});
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
presenter.bindView(this);
}
@Override
public void onDestroyView() {
if (serverPolicySubscription != null) {
serverPolicySubscription.unsubscribe();
}
presenter.release();
super.onDestroyView();
}
......@@ -92,21 +75,35 @@ public class InputHostnameFragment extends AbstractFragment {
return TextUtils.or(TextUtils.or(editor.getText(), editor.getHint()), "").toString();
}
private void onServerValid(final String hostname, boolean usesSecureConnection) {
RocketChatCache.get(getContext()).edit()
.putString(RocketChatCache.KEY_SELECTED_SERVER_HOSTNAME, hostname)
.apply();
private void showError(String errString) {
Snackbar.make(rootView, errString, Snackbar.LENGTH_LONG).show();
}
@Override
public void showLoader() {
container.setVisibility(View.GONE);
waitingView.setVisibility(View.VISIBLE);
}
ConnectivityManagerApi connectivityManager =
ConnectivityManager.getInstance(getContext().getApplicationContext());
connectivityManager.addOrUpdateServer(hostname, hostname, !usesSecureConnection);
connectivityManager.keepAliveServer();
@Override
public void hideLoader() {
waitingView.setVisibility(View.GONE);
container.setVisibility(View.VISIBLE);
}
LaunchUtil.showMainActivity(getContext());
getActivity().overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
@Override
public void showInvalidServerError() {
showError(getString(R.string.input_hostname_invalid_server_message));
}
private void showError(String errString) {
Snackbar.make(rootView, errString, Snackbar.LENGTH_LONG).show();
@Override
public void showConnectionError() {
showError(getString(R.string.connection_error_try_later));
}
@Override
public void showHome() {
LaunchUtil.showMainActivity(getContext());
getActivity().overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
}
}
package chat.rocket.android.fragment.add_server;
import chat.rocket.android.BackgroundLooper;
import chat.rocket.android.helper.OkHttpHelper;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import chat.rocket.android.RocketChatCache;
import chat.rocket.android.api.rest.DefaultServerPolicyApi;
import chat.rocket.android.api.rest.ServerPolicyApi;
import chat.rocket.android.helper.ServerPolicyApiValidationHelper;
import chat.rocket.android.helper.ServerPolicyHelper;
import chat.rocket.android.service.ConnectivityManagerApi;
import chat.rocket.android.shared.BasePresenter;
public class InputHostnamePresenter extends BasePresenter<InputHostnameContract.View> implements InputHostnameContract.Presenter {
private final RocketChatCache rocketChatCache;
private final ConnectivityManagerApi connectivityManager;
public InputHostnamePresenter(RocketChatCache rocketChatCache, ConnectivityManagerApi connectivityManager) {
this.rocketChatCache = rocketChatCache;
this.connectivityManager = connectivityManager;
}
@Override
public void connectTo(final String hostname) {
view.showLoader();
connectToEnforced(ServerPolicyHelper.enforceHostname(hostname));
}
public void connectToEnforced(final String hostname) {
final ServerPolicyApi serverPolicyApi = new DefaultServerPolicyApi(OkHttpHelper.INSTANCE.getClientForUploadFile(), hostname);
final ServerPolicyApiValidationHelper validationHelper = new ServerPolicyApiValidationHelper(serverPolicyApi);
clearSubscriptions();
final Disposable subscription = ServerPolicyHelper.isApiVersionValid(validationHelper)
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.doOnTerminate(() -> view.hideLoader())
.subscribe(
serverValidation -> {
if (serverValidation.isValid()) {
onServerValid(hostname, serverValidation.usesSecureConnection());
} else {
view.showInvalidServerError();
}
},
throwable -> view.showConnectionError());
addSubscription(subscription);
}
private void onServerValid(String hostname, boolean usesSecureConnection) {
rocketChatCache.setSelectedServerHostname(hostname);
String server = hostname.replace("/", ".");
connectivityManager.addOrUpdateServer(server, server, !usesSecureConnection);
connectivityManager.keepAliveServer();
view.showHome();
}
}
\ No newline at end of file
package chat.rocket.android.fragment.chatroom;
import android.os.Bundle;
import android.support.annotation.DrawableRes;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
......@@ -11,40 +9,51 @@ import android.view.ViewGroup;
import chat.rocket.android.R;
import chat.rocket.android.fragment.AbstractFragment;
import chat.rocket.android.widget.RoomToolbar;
import chat.rocket.core.models.User;
abstract class AbstractChatRoomFragment extends AbstractFragment {
private RoomToolbar roomToolbar;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
roomToolbar = (RoomToolbar) getActivity().findViewById(R.id.activity_main_toolbar);
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
roomToolbar = getActivity().findViewById(R.id.activity_main_toolbar);
return super.onCreateView(inflater, container, savedInstanceState);
}
protected void setToolbarTitle(@StringRes int stringResId) {
if (roomToolbar == null) {
return;
protected void setToolbarTitle(CharSequence title) {
roomToolbar.setTitle(title);
}
roomToolbar.setTitle(stringResId);
protected void showToolbarPrivateChannelIcon() {
roomToolbar.showPrivateChannelIcon();
}
protected void setToolbarTitle(CharSequence title) {
if (roomToolbar == null) {
return;
protected void showToolbarPublicChannelIcon() {
roomToolbar.showPublicChannelIcon();
}
roomToolbar.setTitle(title);
protected void showToolbarUserStatuslIcon(@Nullable String status) {
if (status == null) {
roomToolbar.showUserStatusIcon(RoomToolbar.STATUS_OFFLINE);
} else {
switch (status) {
case User.STATUS_ONLINE:
roomToolbar.showUserStatusIcon(RoomToolbar.STATUS_ONLINE);
break;
case User.STATUS_BUSY:
roomToolbar.showUserStatusIcon(RoomToolbar.STATUS_BUSY);
break;
case User.STATUS_AWAY:
roomToolbar.showUserStatusIcon(RoomToolbar.STATUS_AWAY);
break;
case User.STATUS_OFFLINE:
roomToolbar.showUserStatusIcon(RoomToolbar.STATUS_OFFLINE);
break;
default:
roomToolbar.showUserStatusIcon(RoomToolbar.STATUS_OFFLINE);
break;
}
protected void setToolbarRoomIcon(@DrawableRes int drawableResId) {
if (roomToolbar == null) {
return;
}
roomToolbar.setRoomIcon(drawableResId);
}
}
\ No newline at end of file
......@@ -3,8 +3,8 @@ package chat.rocket.android.fragment.chatroom;
import chat.rocket.android.R;
public class HomeFragment extends AbstractChatRoomFragment {
public HomeFragment() {
}
public HomeFragment() {}
@Override
protected int getLayout() {
......@@ -13,13 +13,6 @@ public class HomeFragment extends AbstractChatRoomFragment {
@Override
protected void onSetupView() {
setToolbarTitle(R.string.home_fragment_title);
}
@Override
public void onResume() {
super.onResume();
setToolbarRoomIcon(0);
setToolbarTitle(R.string.home_fragment_title);
setToolbarTitle(getText(R.string.home_fragment_title));
}
}
\ No newline at end of file
package chat.rocket.android.fragment.chatroom;
import chat.rocket.android.widget.AbsoluteUrl;
import chat.rocket.core.models.ServerInfo;
import chat.rocket.core.models.Session;
import chat.rocket.core.models.User;
public class RocketChatAbsoluteUrl implements AbsoluteUrl {
private final String baseUrl;
private final String userId;
private final String token;
public RocketChatAbsoluteUrl(ServerInfo info, User user, Session session) {
baseUrl = (info.isSecure() ? "https://" : "http://") + info.getHostname();
userId = user.getId();
token = session.getToken();
}
@Override
public String from(String url) {
return url.startsWith("/") ? baseUrl + url + "?rc_uid=" + userId + "&rc_token=" + token : url;
}
}
package chat.rocket.android.fragment.chatroom;
import android.support.annotation.Nullable;
import chat.rocket.core.models.User;
import java.util.List;
import chat.rocket.android.shared.BaseContract;
import chat.rocket.core.models.Message;
import chat.rocket.core.models.Room;
public interface RoomContract {
interface View extends BaseContract.View {
void setupWith(RocketChatAbsoluteUrl rocketChatAbsoluteUrl);
void render(Room room);
void showUserStatus(User user);
void updateHistoryState(boolean hasNext, boolean isLoaded);
void onMessageSendSuccessfully();
void showUnreadCount(int count);
void showMessages(List<Message> messages);
void showMessageSendFailure(Message message);
void autoloadImages();
void manualLoadImages();
}
interface Presenter extends BaseContract.Presenter<View> {
void loadMessages();
void loadMoreMessages();
void onMessageSelected(@Nullable Message message);
void sendMessage(String messageText);
void resendMessage(Message message);
void updateMessage(Message message, String content);
void deleteMessage(Message message);
void onUnreadCount();
void onMarkAsRead();
void refreshRoom();
}
}
......@@ -7,17 +7,16 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.BottomSheetDialogFragment;
import chat.rocket.android.realm_helper.RealmHelper;
import chat.rocket.android.realm_helper.RealmStore;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.persistence.realm.RealmStore;
abstract class AbstractChatRoomDialogFragment extends BottomSheetDialogFragment {
protected RealmHelper realmHelper;
protected String roomId;
protected
@LayoutRes
abstract int getLayout();
protected abstract int getLayout();
protected abstract void onSetupDialog();
......
......@@ -9,9 +9,9 @@ import android.widget.TextView;
import android.widget.Toast;
import chat.rocket.android.R;
import chat.rocket.android.model.SyncState;
import chat.rocket.android.model.internal.FileUploading;
import chat.rocket.android.realm_helper.RealmObjectObserver;
import chat.rocket.core.SyncState;
import chat.rocket.persistence.realm.models.internal.FileUploading;
import chat.rocket.persistence.realm.RealmObjectObserver;
import chat.rocket.android.renderer.FileUploadingRenderer;
/**
......
package chat.rocket.android.fragment.chatroom.dialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.BottomSheetDialog;
import android.support.design.widget.BottomSheetDialogFragment;
import android.support.v4.util.Pair;
import android.view.View;
import android.widget.TextView;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import chat.rocket.android.BackgroundLooper;
import chat.rocket.android.R;
import chat.rocket.android.RocketChatCache;
import chat.rocket.android.helper.Logger;
import chat.rocket.core.interactors.EditMessageInteractor;
import chat.rocket.core.interactors.PermissionInteractor;
import chat.rocket.core.models.Message;
import chat.rocket.core.repositories.MessageRepository;
import chat.rocket.core.repositories.PermissionRepository;
import chat.rocket.core.repositories.PublicSettingRepository;
import chat.rocket.core.repositories.RoomRepository;
import chat.rocket.core.repositories.RoomRoleRepository;
import chat.rocket.core.repositories.UserRepository;
import chat.rocket.persistence.realm.repositories.RealmMessageRepository;
import chat.rocket.persistence.realm.repositories.RealmPermissionRepository;
import chat.rocket.persistence.realm.repositories.RealmPublicSettingRepository;
import chat.rocket.persistence.realm.repositories.RealmRoomRepository;
import chat.rocket.persistence.realm.repositories.RealmRoomRoleRepository;
import chat.rocket.persistence.realm.repositories.RealmUserRepository;
public class MessageOptionsDialogFragment extends BottomSheetDialogFragment {
public final static String ARG_MESSAGE_ID = "messageId";
private CompositeDisposable compositeDisposable = new CompositeDisposable();
private OnMessageOptionSelectedListener internalListener = new OnMessageOptionSelectedListener() {
@Override
public void onEdit(Message message) {
if (externalListener != null) {
externalListener.onEdit(message);
}
}
};
private OnMessageOptionSelectedListener externalListener = null;
public static MessageOptionsDialogFragment create(@NonNull Message message) {
Bundle bundle = new Bundle();
bundle.putString(ARG_MESSAGE_ID, message.getId());
MessageOptionsDialogFragment messageOptionsDialogFragment = new MessageOptionsDialogFragment();
messageOptionsDialogFragment.setArguments(bundle);
return messageOptionsDialogFragment;
}
public void setOnMessageOptionSelectedListener(
OnMessageOptionSelectedListener onMessageOptionSelectedListener) {
externalListener = onMessageOptionSelectedListener;
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
BottomSheetDialog bottomSheetDialog = new BottomSheetDialog(getContext());
bottomSheetDialog.setContentView(R.layout.dialog_message_options);
TextView info = (TextView) bottomSheetDialog.findViewById(R.id.message_options_info);
Bundle args = getArguments();
if (args == null || !args.containsKey(ARG_MESSAGE_ID)) {
info.setText(R.string.message_options_no_message_info);
} else {
setUpDialog(bottomSheetDialog, args.getString(ARG_MESSAGE_ID));
}
return bottomSheetDialog;
}
@Override
public void onDismiss(DialogInterface dialog) {
compositeDisposable.clear();
super.onDismiss(dialog);
}
private void setUpDialog(final BottomSheetDialog bottomSheetDialog, String messageId) {
RocketChatCache cache = new RocketChatCache(bottomSheetDialog.getContext());
String hostname = cache.getSelectedServerHostname();
EditMessageInteractor editMessageInteractor = getEditMessageInteractor(hostname);
MessageRepository messageRepository = new RealmMessageRepository(hostname);
Disposable disposable = messageRepository.getById(messageId)
.flatMap(it -> {
if (!it.isPresent()) {
return Single.just(Pair.<Message, Boolean>create(null, false));
}
Message message = it.get();
return Single.zip(
Single.just(message),
editMessageInteractor.isAllowed(message),
Pair::create
);
})
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
pair -> {
if (pair.second) {
bottomSheetDialog.findViewById(R.id.message_options_info)
.setVisibility(View.GONE);
View editView = bottomSheetDialog.findViewById(R.id.message_options_edit_action);
editView.setVisibility(View.VISIBLE);
editView.setOnClickListener(view -> internalListener.onEdit(pair.first));
} else {
((TextView) bottomSheetDialog.findViewById(R.id.message_options_info))
.setText(R.string.message_options_no_permissions_info);
}
},
throwable -> {
((TextView) bottomSheetDialog.findViewById(R.id.message_options_info))
.setText(R.string.message_options_no_message_info);
Logger.report(throwable);
}
);
compositeDisposable.add(disposable);
}
private EditMessageInteractor getEditMessageInteractor(String hostname) {
UserRepository userRepository = new RealmUserRepository(hostname);
RoomRoleRepository roomRoleRepository = new RealmRoomRoleRepository(hostname);
PermissionRepository permissionRepository = new RealmPermissionRepository(hostname);
PermissionInteractor permissionInteractor = new PermissionInteractor(
userRepository,
roomRoleRepository,
permissionRepository
);
MessageRepository messageRepository = new RealmMessageRepository(hostname);
RoomRepository roomRepository = new RealmRoomRepository(hostname);
PublicSettingRepository publicSettingRepository = new RealmPublicSettingRepository(hostname);
return new EditMessageInteractor(
permissionInteractor,
userRepository,
messageRepository,
roomRepository,
publicSettingRepository
);
}
public interface OnMessageOptionSelectedListener {
void onEdit(Message message);
}
}
package chat.rocket.android.fragment.oauth;
import chat.rocket.android.model.ddp.MeteorLoginServiceConfiguration;
import chat.rocket.core.models.LoginServiceConfiguration;
import okhttp3.HttpUrl;
public class FacebookOAuthFragment extends AbstractOAuthFragment {
......@@ -11,13 +11,13 @@ public class FacebookOAuthFragment extends AbstractOAuthFragment {
}
@Override
protected String generateURL(MeteorLoginServiceConfiguration oauthConfig) {
protected String generateURL(LoginServiceConfiguration oauthConfig) {
return new HttpUrl.Builder().scheme("https")
.host("www.facebook.com")
.addPathSegment("v2.2")
.addPathSegment("dialog")
.addPathSegment("oauth")
.addQueryParameter("client_id", oauthConfig.getAppId())
.addQueryParameter("client_id", oauthConfig.getKey())
.addQueryParameter("redirect_uri", "https://" + hostname + "/_oauth/facebook?close")
.addQueryParameter("display", "popup")
.addQueryParameter("scope", "email")
......
This diff is collapsed.
package chat.rocket.android.helper;
import com.crashlytics.android.Crashlytics;
import com.google.firebase.crash.FirebaseCrash;
public class Logger {
public static void report(Throwable throwable) {
FirebaseCrash.report(throwable);
Crashlytics.logException(throwable);
}
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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