Compare commits
82 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
6a92e73c12 | |
|
|
90abae925d | |
|
|
5692f4c407 | |
|
|
fb0dc47b82 | |
|
|
33903efadc | |
|
|
5c9e255350 | |
|
|
7eb1d8bb21 | |
|
|
aaafae44e5 | |
|
|
f84fae765d | |
|
|
fffda150e5 | |
|
|
87fd17084e | |
|
|
11d2b2de43 | |
|
|
f12c9414eb | |
|
|
7250a60f1d | |
|
|
38e52e5ba9 | |
|
|
7a3c3f11fd | |
|
|
1e3acae437 | |
|
|
25492e93da | |
|
|
90d88a9e97 | |
|
|
3fcf47c4f1 | |
|
|
e7be976085 | |
|
|
f0965d3dfa | |
|
|
cee02ad925 | |
|
|
d004fb7686 | |
|
|
21c368cc39 | |
|
|
617ce9494a | |
|
|
2b3ee88ea7 | |
|
|
c436d06efd | |
|
|
96f91a0da1 | |
|
|
2790890222 | |
|
|
8d77683453 | |
|
|
b23ee3581c | |
|
|
1fa0aba9db | |
|
|
164a9eee9c | |
|
|
e3edb0a7a7 | |
|
|
46295137a8 | |
|
|
e1b58249b9 | |
|
|
2a0e33c6e7 | |
|
|
be01ad5792 | |
|
|
861675674f | |
|
|
3aacc52b04 | |
|
|
22da8c58e8 | |
|
|
bc1ffc1b35 | |
|
|
abaf35300b | |
|
|
dd08a07afc | |
|
|
ccd4b80011 | |
|
|
b5554a6761 | |
|
|
af8569d147 | |
|
|
4b0fd5945f | |
|
|
488d15a22b | |
|
|
2013a7ad56 | |
|
|
129bbf5b18 | |
|
|
3bf46be7e1 | |
|
|
e0f57761e7 | |
|
|
a73fed8fee | |
|
|
c79a77f52a | |
|
|
435ab8617c | |
|
|
b970b7c673 | |
|
|
9d6aa9c445 | |
|
|
e3666b32b3 | |
|
|
e97bce4061 | |
|
|
a2fe86ec09 | |
|
|
a2f2dc5c88 | |
|
|
5ef88a5ccc | |
|
|
d9878a8035 | |
|
|
bd63ca8570 | |
|
|
876f634923 | |
|
|
29b5d1b95e | |
|
|
0a9c3d531d | |
|
|
169f4ffec8 | |
|
|
c664bf7314 | |
|
|
cfa1c96229 | |
|
|
ef2a6efb90 | |
|
|
7b12898a76 | |
|
|
317bcce28e | |
|
|
0b4e2eb14d | |
|
|
6266c8356b | |
|
|
2efd0a5b29 | |
|
|
bf045ba8e2 | |
|
|
cf47237e49 | |
|
|
6e73850d06 | |
|
|
468da1f2b9 |
53
build.gradle
53
build.gradle
|
|
@ -1,9 +1,8 @@
|
|||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'me.tatarka.retrolambda'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
compileSdkVersion 25
|
||||
buildToolsVersion "25.0.3"
|
||||
compileSdkVersion compileSdk
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
|
|
@ -15,39 +14,29 @@ android {
|
|||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven { url 'https://maven.fabric.io/public' }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(path: ':libraries:components')
|
||||
api project(path: ':libraries:components')
|
||||
api 'javax.inject:javax.inject:1'
|
||||
|
||||
compile 'net.danlew:android.joda:2.9.9'
|
||||
compile 'com.android.support:multidex:1.0.1'
|
||||
compile 'io.reactivex:rxandroid:1.2.1'
|
||||
compile 'io.reactivex:rxjava:1.3.0'
|
||||
api 'net.danlew:android.joda:2.9.9'
|
||||
api 'com.android.support:multidex:1.0.3'
|
||||
|
||||
provided 'com.android.support:appcompat-v7:25.3.1'
|
||||
provided 'com.android.support:recyclerview-v7:25.3.1'
|
||||
api "io.reactivex.rxjava2:rxandroid:$rxAndroidVersion"
|
||||
api "io.reactivex.rxjava2:rxjava:$rxJavaVersion"
|
||||
|
||||
provided 'com.squareup.retrofit2:retrofit:2.3.0'
|
||||
provided('com.google.http-client:google-http-client-jackson2:1.22.0') {
|
||||
exclude(group: 'org.apache.httpcomponents', module: 'httpclient')
|
||||
compileOnly "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
|
||||
compileOnly "com.android.support:recyclerview-v7:$supportLibraryVersion"
|
||||
compileOnly "com.android.support:appcompat-v7:$supportLibraryVersion"
|
||||
|
||||
compileOnly "android.arch.lifecycle:extensions:$lifecycleVersion"
|
||||
|
||||
compileOnly "com.squareup.retrofit2:retrofit:$retrofitVersion"
|
||||
|
||||
compileOnly 'ru.touchin:logansquare:1.4.3'
|
||||
|
||||
compileOnly("com.crashlytics.sdk.android:crashlytics:$crashlyticsVersion@aar") {
|
||||
transitive = true
|
||||
}
|
||||
|
||||
provided 'com.facebook.fresco:fresco:1.3.0'
|
||||
provided 'com.bluelinelabs:logansquare:1.3.7'
|
||||
|
||||
provided 'com.scottyab:aes-crypto:0.0.4'
|
||||
|
||||
provided('io.socket:socket.io-client:0.9.0') {
|
||||
exclude group: 'org.json', module: 'json'
|
||||
}
|
||||
|
||||
provided('com.crashlytics.sdk.android:crashlytics:2.6.8@aar') {
|
||||
transitive = true;
|
||||
}
|
||||
|
||||
provided 'com.facebook.stetho:stetho:1.5.0'
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,80 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2016 Touch Instinct
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package ru.touchin.templates;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.tozny.crypto.android.AesCbcWithIntegrity;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
import ru.touchin.roboswag.core.utils.ShouldNotHappenException;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 30/08/2016.
|
||||
* Utility class that is providing common methods related to cryptography.
|
||||
*/
|
||||
public final class CryptoUtils {
|
||||
|
||||
/**
|
||||
* Just encrypts bytes by key in good way.
|
||||
* To decrypt them use {@link #simpleDecryptBytes(byte[], String)}.
|
||||
* Dependency needed: compile 'com.scottyab:aes-crypto:+'.
|
||||
*
|
||||
* @param bytesToDecrypt Bytes to encrypt;
|
||||
* @param keyString Encryption key;
|
||||
* @return Encrypted bytes.
|
||||
*/
|
||||
@NonNull
|
||||
public static byte[] simpleEncryptBytes(@NonNull final byte[] bytesToDecrypt, @NonNull final String keyString) {
|
||||
try {
|
||||
final AesCbcWithIntegrity.SecretKeys key = AesCbcWithIntegrity.keys(keyString);
|
||||
final AesCbcWithIntegrity.CipherTextIvMac cipherTextIvMac = AesCbcWithIntegrity.encrypt(bytesToDecrypt, key);
|
||||
return cipherTextIvMac.toString().getBytes(Charset.forName("UTF-8"));
|
||||
} catch (final GeneralSecurityException exception) {
|
||||
throw new ShouldNotHappenException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Just decrypts bytes which are encrypted by {@link #simpleEncryptBytes(byte[], String)}.
|
||||
* Dependency needed: compile 'com.scottyab:aes-crypto:+'.
|
||||
*
|
||||
* @param encryptedBytes Bytes to decrypt;
|
||||
* @param keyString Encryption key;
|
||||
* @return Encrypted bytes.
|
||||
*/
|
||||
@NonNull
|
||||
public static byte[] simpleDecryptBytes(@NonNull final byte[] encryptedBytes, @NonNull final String keyString) {
|
||||
try {
|
||||
final AesCbcWithIntegrity.SecretKeys key = AesCbcWithIntegrity.keys(keyString);
|
||||
final AesCbcWithIntegrity.CipherTextIvMac cipherTextIvMac
|
||||
= new AesCbcWithIntegrity.CipherTextIvMac(new String(encryptedBytes, Charset.forName("UTF-8")));
|
||||
return AesCbcWithIntegrity.decrypt(cipherTextIvMac, key);
|
||||
} catch (final GeneralSecurityException exception) {
|
||||
throw new ShouldNotHappenException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
private CryptoUtils() {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -19,7 +19,9 @@
|
|||
|
||||
package ru.touchin.templates;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.ConnectivityManager;
|
||||
|
|
@ -27,15 +29,19 @@ import android.net.NetworkInfo;
|
|||
import android.net.wifi.WifiManager;
|
||||
import android.provider.Settings;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.reactivex.Completable;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.ObservableEmitter;
|
||||
import ru.touchin.roboswag.core.log.Lc;
|
||||
import ru.touchin.roboswag.core.observables.RxAndroidUtils;
|
||||
import rx.Observable;
|
||||
|
||||
/**
|
||||
* Utility class that is providing common methods related to android device.
|
||||
|
|
@ -171,6 +177,52 @@ public final class DeviceUtils {
|
|||
.distinctUntilChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns observable to observe is device connected to the internet.
|
||||
*
|
||||
* @param context Context to register BroadcastReceiver to check connection to the internet;
|
||||
* @return Observable of internet connection status.
|
||||
*/
|
||||
@NonNull
|
||||
public static Observable<Boolean> observeIsNetworkConnected(@NonNull final Context context) {
|
||||
return Observable.switchOnNext(Observable.fromCallable(() -> {
|
||||
final NetworkStateReceiver networkStateReceiver = new NetworkStateReceiver();
|
||||
return Observable
|
||||
.<Boolean>create(subscriber -> {
|
||||
context.registerReceiver(networkStateReceiver, NetworkStateReceiver.INTENT_FILTER);
|
||||
subscriber.onNext(isNetworkConnected(context));
|
||||
networkStateReceiver.setEmitter(subscriber);
|
||||
})
|
||||
.doOnDispose(() -> context.unregisterReceiver(networkStateReceiver))
|
||||
.onErrorReturnItem(false)
|
||||
.distinctUntilChanged();
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an Observable that depends on network connection.
|
||||
*
|
||||
* @param processObservable - Observable to which we subscribe in the availability of the Internet;
|
||||
*/
|
||||
@NonNull
|
||||
public static Observable<?> createNetworkDependentObservable(@NonNull final Context context, @NonNull final Observable<?> processObservable) {
|
||||
return DeviceUtils.observeIsNetworkConnected(context)
|
||||
.debounce(100, TimeUnit.MILLISECONDS)
|
||||
.switchMap(connected -> !connected
|
||||
? Observable.empty()
|
||||
: processObservable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an Observable that depends on network connection.
|
||||
*
|
||||
* @param processObservable - Observable to which we subscribe in the availability of the Internet;
|
||||
*/
|
||||
@NonNull
|
||||
public static Observable<?> createNetworkDependentObservable(@NonNull final Context context, @NonNull final Completable processObservable) {
|
||||
return createNetworkDependentObservable(context, processObservable.toObservable());
|
||||
}
|
||||
|
||||
private DeviceUtils() {
|
||||
}
|
||||
|
||||
|
|
@ -220,4 +272,27 @@ public final class DeviceUtils {
|
|||
|
||||
}
|
||||
|
||||
private static class NetworkStateReceiver extends BroadcastReceiver {
|
||||
|
||||
private static final IntentFilter INTENT_FILTER = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
|
||||
@Nullable
|
||||
private ConnectivityManager connectivityManager;
|
||||
|
||||
@Nullable
|
||||
private ObservableEmitter<? super Boolean> emitter;
|
||||
|
||||
public void setEmitter(@Nullable final ObservableEmitter<? super Boolean> emitter) {
|
||||
this.emitter = emitter;
|
||||
}
|
||||
|
||||
public void onReceive(@NonNull final Context context, @NonNull final Intent intent) {
|
||||
if (connectivityManager == null) {
|
||||
connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
}
|
||||
if (emitter != null) {
|
||||
emitter.onNext(isNetworkConnected(context));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -30,17 +30,14 @@ import android.support.annotation.NonNull;
|
|||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
|
||||
import ru.touchin.roboswag.components.navigation.activities.ViewControllerActivity;
|
||||
import ru.touchin.roboswag.components.utils.Logic;
|
||||
import ru.touchin.roboswag.components.navigation.activities.BaseActivity;
|
||||
import ru.touchin.roboswag.core.log.Lc;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 11/03/16.
|
||||
* Base class of activity to extends for Touch Instinct related projects.
|
||||
*
|
||||
* @param <TLogic> Type of application's {@link Logic}.
|
||||
*/
|
||||
public abstract class TouchinActivity<TLogic extends Logic> extends ViewControllerActivity<TLogic> {
|
||||
public abstract class TouchinActivity extends BaseActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ import android.support.multidex.MultiDex;
|
|||
import android.util.Log;
|
||||
|
||||
import com.crashlytics.android.Crashlytics;
|
||||
import com.facebook.stetho.Stetho;
|
||||
|
||||
import net.danlew.android.joda.JodaTimeAndroid;
|
||||
|
||||
|
|
@ -38,7 +37,11 @@ import java.util.List;
|
|||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.fabric.sdk.android.Fabric;
|
||||
import ru.touchin.roboswag.components.adapters.ObservableCollectionAdapter;
|
||||
import io.reactivex.Scheduler;
|
||||
import io.reactivex.android.plugins.RxAndroidPlugins;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.disposables.Disposables;
|
||||
import ru.touchin.roboswag.components.navigation.fragments.ViewControllerFragment;
|
||||
import ru.touchin.roboswag.components.utils.UiUtils;
|
||||
import ru.touchin.roboswag.components.views.TypefacedEditText;
|
||||
|
|
@ -49,13 +52,6 @@ import ru.touchin.roboswag.core.log.LcGroup;
|
|||
import ru.touchin.roboswag.core.log.LcLevel;
|
||||
import ru.touchin.roboswag.core.log.LogProcessor;
|
||||
import ru.touchin.roboswag.core.utils.ShouldNotHappenException;
|
||||
import rx.Scheduler;
|
||||
import rx.Subscription;
|
||||
import rx.android.plugins.RxAndroidPlugins;
|
||||
import rx.android.plugins.RxAndroidSchedulersHook;
|
||||
import rx.android.schedulers.AndroidSchedulers;
|
||||
import rx.functions.Action0;
|
||||
import rx.subscriptions.Subscriptions;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 10/03/16.
|
||||
|
|
@ -82,27 +78,15 @@ public abstract class TouchinApp extends Application {
|
|||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
RxAndroidPlugins.getInstance().registerSchedulersHook(new RxAndroidSchedulersHook() {
|
||||
@NonNull
|
||||
@Override
|
||||
public Scheduler getMainThreadScheduler() {
|
||||
return new MainThreadScheduler();
|
||||
}
|
||||
});
|
||||
RxAndroidPlugins.setMainThreadSchedulerHandler(schedulerCallable -> MainThreadScheduler.INSTANCE);
|
||||
JodaTimeAndroid.init(this);
|
||||
if (isDebug()) {
|
||||
enableStrictMode();
|
||||
ObservableCollectionAdapter.setInDebugMode();
|
||||
ViewControllerFragment.setInDebugMode();
|
||||
TypefacedEditText.setInDebugMode();
|
||||
TypefacedTextView.setInDebugMode();
|
||||
Lc.initialize(new ConsoleLogProcessor(LcLevel.VERBOSE), true);
|
||||
UiUtils.UI_LIFECYCLE_LC_GROUP.disable();
|
||||
try {
|
||||
Stetho.initializeWithDefaults(this);
|
||||
} catch (final NoClassDefFoundError error) {
|
||||
Lc.e("Stetho initialization error! Did you forget to add debugCompile 'com.facebook.stetho:stetho:+' to your build.gradle?");
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
final Crashlytics crashlytics = new Crashlytics();
|
||||
|
|
@ -189,6 +173,8 @@ public abstract class TouchinApp extends Application {
|
|||
*/
|
||||
private static class MainThreadScheduler extends Scheduler {
|
||||
|
||||
public static final MainThreadScheduler INSTANCE = new MainThreadScheduler();
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Worker createWorker() {
|
||||
|
|
@ -202,28 +188,28 @@ public abstract class TouchinApp extends Application {
|
|||
|
||||
@NonNull
|
||||
@Override
|
||||
public Subscription schedule(@NonNull final Action0 action) {
|
||||
public Disposable schedule(@NonNull final Runnable action) {
|
||||
if (Looper.getMainLooper().equals(Looper.myLooper())) {
|
||||
action.call();
|
||||
return Subscriptions.unsubscribed();
|
||||
action.run();
|
||||
return Disposables.disposed();
|
||||
}
|
||||
return parentWorker.schedule(action);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Subscription schedule(@NonNull final Action0 action, final long delayTime, @NonNull final TimeUnit unit) {
|
||||
public Disposable schedule(@NonNull final Runnable action, final long delayTime, @NonNull final TimeUnit unit) {
|
||||
return parentWorker.schedule(action, delayTime, unit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsubscribe() {
|
||||
parentWorker.unsubscribe();
|
||||
public void dispose() {
|
||||
parentWorker.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUnsubscribed() {
|
||||
return parentWorker.isUnsubscribed();
|
||||
public boolean isDisposed() {
|
||||
return parentWorker.isDisposed();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,269 +21,29 @@ package ru.touchin.templates;
|
|||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import ru.touchin.roboswag.components.navigation.activities.ViewControllerActivity;
|
||||
import ru.touchin.roboswag.components.utils.Logic;
|
||||
import ru.touchin.roboswag.components.utils.UiUtils;
|
||||
import ru.touchin.roboswag.components.utils.destroyable.BaseDestroyable;
|
||||
import ru.touchin.roboswag.core.log.Lc;
|
||||
import ru.touchin.roboswag.core.utils.ServiceBinder;
|
||||
import ru.touchin.roboswag.core.utils.ShouldNotHappenException;
|
||||
import rx.Completable;
|
||||
import rx.CompletableSubscriber;
|
||||
import rx.Observable;
|
||||
import rx.Single;
|
||||
import rx.SingleSubscriber;
|
||||
import rx.Subscriber;
|
||||
import rx.Subscription;
|
||||
import rx.android.schedulers.AndroidSchedulers;
|
||||
import rx.exceptions.OnErrorThrowable;
|
||||
import rx.functions.Action0;
|
||||
import rx.functions.Action1;
|
||||
import rx.functions.Actions;
|
||||
import rx.subjects.BehaviorSubject;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 10/01/17.
|
||||
* Base class of service to extends for Touch Instinct related projects.
|
||||
* It contains {@link Logic} and could bind to it through {@link #untilDestroy(Observable)} methods.
|
||||
*
|
||||
* @param <TLogic> Type of application's {@link Logic}.
|
||||
*/
|
||||
public abstract class TouchinService<TLogic extends Logic> extends Service {
|
||||
public abstract class TouchinService extends Service {
|
||||
|
||||
//it is needed to hold strong reference to logic
|
||||
private TLogic reference;
|
||||
@NonNull
|
||||
private final Handler postHandler = new Handler();
|
||||
@NonNull
|
||||
private final BehaviorSubject<Boolean> isCreatedSubject = BehaviorSubject.create();
|
||||
|
||||
/**
|
||||
* It should return specific class where all logic will be.
|
||||
*
|
||||
* @return Returns class of specific {@link Logic}.
|
||||
*/
|
||||
@NonNull
|
||||
protected abstract Class<TLogic> getLogicClass();
|
||||
protected final BaseDestroyable destroyable = new BaseDestroyable();
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this));
|
||||
postHandler.post(() -> isCreatedSubject.onNext(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns (and creates if needed) application's logic.
|
||||
*
|
||||
* @return Object which represents application's logic.
|
||||
*/
|
||||
@NonNull
|
||||
protected TLogic getLogic() {
|
||||
synchronized (ViewControllerActivity.class) {
|
||||
if (reference == null) {
|
||||
reference = Logic.getInstance(this, getLogicClass());
|
||||
}
|
||||
}
|
||||
return reference;
|
||||
}
|
||||
|
||||
@SuppressWarnings("CPD-START")
|
||||
//CPD: it is same as in other implementation based on BaseLifecycleBindable
|
||||
/**
|
||||
* Method should be used to guarantee that observable won't be subscribed after onDestroy.
|
||||
* It is automatically subscribing to observable.
|
||||
* Don't forget to process errors if observable can emit them.
|
||||
*
|
||||
* @param observable {@link Observable} to subscribe until onDestroy;
|
||||
* @param <T> Type of emitted by observable items;
|
||||
* @return {@link Subscription} which is wrapping source observable to unsubscribe from it onDestroy.
|
||||
*/
|
||||
@NonNull
|
||||
public <T> Subscription untilDestroy(@NonNull final Observable<T> observable) {
|
||||
return untilDestroy(observable, Actions.empty(), getActionThrowableForAssertion(Lc.getCodePoint(this, 1)), Actions.empty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Method should be used to guarantee that observable won't be subscribed after onDestroy.
|
||||
* It is automatically subscribing to observable and calls onNextAction on every emitted item.
|
||||
* Don't forget to process errors if observable can emit them.
|
||||
*
|
||||
* @param observable {@link Observable} to subscribe until onDestroy;
|
||||
* @param onNextAction Action which will raise on every {@link Subscriber#onNext(Object)} item;
|
||||
* @param <T> Type of emitted by observable items;
|
||||
* @return {@link Subscription} which is wrapping source observable to unsubscribe from it onDestroy.
|
||||
*/
|
||||
@NonNull
|
||||
public <T> Subscription untilDestroy(@NonNull final Observable<T> observable,
|
||||
@NonNull final Action1<T> onNextAction) {
|
||||
return untilDestroy(observable, onNextAction, getActionThrowableForAssertion(Lc.getCodePoint(this, 1)), Actions.empty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Method should be used to guarantee that observable won't be subscribed after onDestroy.
|
||||
* It is automatically subscribing to observable and calls onNextAction and onErrorAction on observable events.
|
||||
* Don't forget to process errors if observable can emit them.
|
||||
*
|
||||
* @param observable {@link Observable} to subscribe until onDestroy;
|
||||
* @param onNextAction Action which will raise on every {@link Subscriber#onNext(Object)} item;
|
||||
* @param onErrorAction Action which will raise on every {@link Subscriber#onError(Throwable)} throwable;
|
||||
* @param <T> Type of emitted by observable items;
|
||||
* @return {@link Subscription} which is wrapping source observable to unsubscribe from it onDestroy.
|
||||
*/
|
||||
@NonNull
|
||||
public <T> Subscription untilDestroy(@NonNull final Observable<T> observable,
|
||||
@NonNull final Action1<T> onNextAction,
|
||||
@NonNull final Action1<Throwable> onErrorAction) {
|
||||
return untilDestroy(observable, onNextAction, onErrorAction, Actions.empty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Method should be used to guarantee that observable won't be subscribed after onDestroy.
|
||||
* It is automatically subscribing to observable and calls onNextAction, onErrorAction and onCompletedAction on observable events.
|
||||
* Don't forget to process errors if observable can emit them.
|
||||
*
|
||||
* @param observable {@link Observable} to subscribe until onDestroy;
|
||||
* @param onNextAction Action which will raise on every {@link Subscriber#onNext(Object)} item;
|
||||
* @param onErrorAction Action which will raise on every {@link Subscriber#onError(Throwable)} throwable;
|
||||
* @param onCompletedAction Action which will raise at {@link Subscriber#onCompleted()} on completion of observable;
|
||||
* @param <T> Type of emitted by observable items;
|
||||
* @return {@link Subscription} which is wrapping source observable to unsubscribe from it onDestroy.
|
||||
*/
|
||||
@NonNull
|
||||
public <T> Subscription untilDestroy(@NonNull final Observable<T> observable,
|
||||
@NonNull final Action1<T> onNextAction,
|
||||
@NonNull final Action1<Throwable> onErrorAction,
|
||||
@NonNull final Action0 onCompletedAction) {
|
||||
return until(observable, isCreatedSubject.map(created -> !created), onNextAction, onErrorAction, onCompletedAction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method should be used to guarantee that single won't be subscribed after onDestroy.
|
||||
* It is automatically subscribing to single.
|
||||
* Don't forget to process errors if single can emit them.
|
||||
*
|
||||
* @param single {@link Single} to subscribe until onDestroy;
|
||||
* @param <T> Type of emitted by single items;
|
||||
* @return {@link Subscription} which is wrapping source single to unsubscribe from it onDestroy.
|
||||
*/
|
||||
@NonNull
|
||||
public <T> Subscription untilDestroy(@NonNull final Single<T> single) {
|
||||
return untilDestroy(single, Actions.empty(), getActionThrowableForAssertion(Lc.getCodePoint(this, 1)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Method should be used to guarantee that single won't be subscribed after onDestroy.
|
||||
* It is automatically subscribing to single and calls onSuccessAction on emitted item.
|
||||
* Don't forget to process errors if single can emit them.
|
||||
*
|
||||
* @param single {@link Single} to subscribe until onDestroy;
|
||||
* @param onSuccessAction Action which will raise on {@link SingleSubscriber#onSuccess(Object)} item;
|
||||
* @param <T> Type of emitted by single items;
|
||||
* @return {@link Subscription} which is wrapping source single to unsubscribe from it onDestroy.
|
||||
*/
|
||||
@NonNull
|
||||
public <T> Subscription untilDestroy(@NonNull final Single<T> single, @NonNull final Action1<T> onSuccessAction) {
|
||||
return untilDestroy(single, onSuccessAction, getActionThrowableForAssertion(Lc.getCodePoint(this, 1)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Method should be used to guarantee that single won't be subscribed after onDestroy.
|
||||
* It is automatically subscribing to single and calls onSuccessAction and onErrorAction on single events.
|
||||
* Don't forget to process errors if single can emit them.
|
||||
*
|
||||
* @param single {@link Single} to subscribe until onDestroy;
|
||||
* @param onSuccessAction Action which will raise on {@link SingleSubscriber#onSuccess(Object)} item;
|
||||
* @param onErrorAction Action which will raise on every {@link SingleSubscriber#onError(Throwable)} throwable;
|
||||
* @param <T> Type of emitted by single items;
|
||||
* @return {@link Subscription} which is wrapping source single to unsubscribe from it onDestroy.
|
||||
*/
|
||||
@NonNull
|
||||
public <T> Subscription untilDestroy(@NonNull final Single<T> single,
|
||||
@NonNull final Action1<T> onSuccessAction,
|
||||
@NonNull final Action1<Throwable> onErrorAction) {
|
||||
return until(single.toObservable(), isCreatedSubject.map(created -> !created), onSuccessAction, onErrorAction, Actions.empty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Method should be used to guarantee that completable won't be subscribed after onDestroy.
|
||||
* It is automatically subscribing to completable.
|
||||
* Don't forget to process errors if completable can emit them.
|
||||
*
|
||||
* @param completable {@link Completable} to subscribe until onDestroy;
|
||||
* @return {@link Subscription} which is wrapping source completable to unsubscribe from it onDestroy.
|
||||
*/
|
||||
@NonNull
|
||||
public Subscription untilDestroy(@NonNull final Completable completable) {
|
||||
return untilDestroy(completable, Actions.empty(), getActionThrowableForAssertion(Lc.getCodePoint(this, 1)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Method should be used to guarantee that completable won't be subscribed after onDestroy.
|
||||
* It is automatically subscribing to completable and calls onCompletedAction on complete.
|
||||
* Don't forget to process errors if completable can emit them.
|
||||
*
|
||||
* @param completable {@link Completable} to subscribe until onDestroy;
|
||||
* @param onCompletedAction Action which will raise on every {@link CompletableSubscriber#onCompleted()} item;
|
||||
* @return {@link Subscription} which is wrapping source completable to unsubscribe from it onDestroy.
|
||||
*/
|
||||
@NonNull
|
||||
public Subscription untilDestroy(@NonNull final Completable completable, @NonNull final Action0 onCompletedAction) {
|
||||
return untilDestroy(completable, onCompletedAction, getActionThrowableForAssertion(Lc.getCodePoint(this, 1)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Method should be used to guarantee that completable won't be subscribed after onDestroy.
|
||||
* It is automatically subscribing to completable and calls onCompletedAction and onErrorAction on completable events.
|
||||
* Don't forget to process errors if completable can emit them.
|
||||
*
|
||||
* @param completable {@link Single} to subscribe until onDestroy;
|
||||
* @param onCompletedAction Action which will raise on {@link CompletableSubscriber#onCompleted()} item;
|
||||
* @param onErrorAction Action which will raise on every {@link CompletableSubscriber#onError(Throwable)} throwable;
|
||||
* @return {@link Subscription} which is wrapping source completable to unsubscribe from it onDestroy.
|
||||
*/
|
||||
@NonNull
|
||||
public Subscription untilDestroy(@NonNull final Completable completable,
|
||||
@NonNull final Action0 onCompletedAction,
|
||||
@NonNull final Action1<Throwable> onErrorAction) {
|
||||
return until(completable.toObservable(), isCreatedSubject.map(created -> !created), Actions.empty(), onErrorAction, onCompletedAction);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private <T> Subscription until(@NonNull final Observable<T> observable,
|
||||
@NonNull final Observable<Boolean> conditionSubject,
|
||||
@NonNull final Action1<T> onNextAction,
|
||||
@NonNull final Action1<Throwable> onErrorAction,
|
||||
@NonNull final Action0 onCompletedAction) {
|
||||
final Observable<T> actualObservable;
|
||||
if (onNextAction == Actions.empty() && onErrorAction == (Action1) Actions.empty() && onCompletedAction == Actions.empty()) {
|
||||
actualObservable = observable;
|
||||
} else {
|
||||
actualObservable = observable.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnCompleted(onCompletedAction)
|
||||
.doOnNext(onNextAction)
|
||||
.doOnError(onErrorAction);
|
||||
}
|
||||
|
||||
return isCreatedSubject.first()
|
||||
.switchMap(created -> created ? actualObservable : Observable.empty())
|
||||
.takeUntil(conditionSubject.filter(condition -> condition))
|
||||
.onErrorResumeNext(throwable -> {
|
||||
final boolean isRxError = throwable instanceof OnErrorThrowable;
|
||||
if ((!isRxError && throwable instanceof RuntimeException)
|
||||
|| (isRxError && throwable.getCause() instanceof RuntimeException)) {
|
||||
Lc.assertion(throwable);
|
||||
}
|
||||
return Observable.empty();
|
||||
})
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
@SuppressWarnings("CPD-END")
|
||||
//CPD: it is same as in other implementation based on BaseLifecycleBindable
|
||||
@NonNull
|
||||
@Override
|
||||
public IBinder onBind(@NonNull final Intent intent) {
|
||||
|
|
@ -299,15 +59,9 @@ public abstract class TouchinService<TLogic extends Logic> extends Service {
|
|||
@Override
|
||||
public void onDestroy() {
|
||||
UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this));
|
||||
postHandler.removeCallbacksAndMessages(null);
|
||||
isCreatedSubject.onNext(false);
|
||||
destroyable.onDestroy();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Action1<Throwable> getActionThrowableForAssertion(@NonNull final String codePoint) {
|
||||
return throwable -> Lc.assertion(new ShouldNotHappenException("Unexpected error on untilDestroy at " + codePoint, throwable));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ public abstract class CalendarAdapter<TDayViewHolder extends RecyclerView.ViewHo
|
|||
notifyItemRangeChanged(startSelectionPosition, 1);
|
||||
return;
|
||||
}
|
||||
notifyItemRangeChanged(startSelectionPosition, endSelectionPosition - startSelectionPosition);
|
||||
notifyItemRangeChanged(startSelectionPosition, endSelectionPosition - startSelectionPosition + 1);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ public final class CalendarUtils {
|
|||
* @param calendarItems List of {@link CalendarItem} where need to find specific element;
|
||||
* @param position Position of adapter;
|
||||
* @return Position of Header that respond to requested position.
|
||||
* Returns null if Header or related CalendarItem was not found for specified position.
|
||||
* Returns null if Header or related CalendarItem was not found for specified position.
|
||||
*/
|
||||
@Nullable
|
||||
public static Integer findPositionOfSelectedMonth(@NonNull final List<CalendarItem> calendarItems, final long position) {
|
||||
|
|
@ -76,7 +76,7 @@ public final class CalendarUtils {
|
|||
* @param calendarItems List of {@link CalendarItem} where need to find specific element;
|
||||
* @param date Requested date in milliseconds.
|
||||
* @return Position of Calendar cell that that has specific date.
|
||||
* Returns null if CalendarItem was not found for specified position.
|
||||
* Returns null if CalendarItem was not found for specified position.
|
||||
*/
|
||||
@Nullable
|
||||
public static Integer findPositionByDate(@NonNull final List<CalendarItem> calendarItems, final long date) {
|
||||
|
|
|
|||
|
|
@ -1,228 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2016 Touch Instinct
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package ru.touchin.templates.chat;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import ru.touchin.roboswag.core.log.Lc;
|
||||
import ru.touchin.roboswag.core.observables.collections.ObservableCollection;
|
||||
import ru.touchin.roboswag.core.observables.collections.ObservableList;
|
||||
import rx.Completable;
|
||||
import rx.Observable;
|
||||
import rx.Scheduler;
|
||||
import rx.Subscription;
|
||||
import rx.functions.Actions;
|
||||
import rx.schedulers.Schedulers;
|
||||
import rx.subjects.BehaviorSubject;
|
||||
import rx.subjects.PublishSubject;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 12/05/16.
|
||||
* Object which is containing logic of sending messages as queue one-by-one.
|
||||
*
|
||||
* @param <TOutgoingMessage> Type of messages to send.
|
||||
*/
|
||||
public abstract class Chat<TOutgoingMessage> {
|
||||
|
||||
private static final long RETRY_SENDING_DELAY = TimeUnit.SECONDS.toMillis(5);
|
||||
|
||||
@NonNull
|
||||
private final ObservableList<TOutgoingMessage> sendingMessages = new ObservableList<>();
|
||||
@NonNull
|
||||
private final PublishSubject<?> retrySendingRequest = PublishSubject.create();
|
||||
@NonNull
|
||||
private final BehaviorSubject<Boolean> isSendingInError = BehaviorSubject.create(false);
|
||||
@NonNull
|
||||
private final Scheduler sendingScheduler = Schedulers.from(Executors.newSingleThreadExecutor());
|
||||
@NonNull
|
||||
private final Observable<?> messagesToSendObservable;
|
||||
@Nullable
|
||||
private Subscription activationSubscription;
|
||||
|
||||
public Chat(@Nullable final Collection<TOutgoingMessage> messagesToSend) {
|
||||
if (messagesToSend != null) {
|
||||
sendingMessages.addAll(messagesToSend);
|
||||
}
|
||||
|
||||
messagesToSendObservable = sendingMessages.observeItems()
|
||||
.first()
|
||||
.concatMap(initialMessages -> {
|
||||
final List<TOutgoingMessage> reversedMessages = new ArrayList<>(initialMessages);
|
||||
Collections.reverse(reversedMessages);
|
||||
return Observable.from(reversedMessages)
|
||||
.concatWith(sendingMessages.observeChanges().concatMap(changes ->
|
||||
changes.getInsertedItems().isEmpty() ? Observable.empty() : Observable.from(changes.getInsertedItems())))
|
||||
//observe on some scheduler?
|
||||
.flatMap(message -> internalSendMessage(message).toObservable());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link Observable} to check if sending have failed so it is in error state and user have to retry send messages.
|
||||
*
|
||||
* @return {@link Observable} to check if sending have failed.
|
||||
*/
|
||||
@NonNull
|
||||
public Observable<Boolean> observeIsSendingInError() {
|
||||
return isSendingInError.distinctUntilChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link ObservableCollection} of currently sending messages.
|
||||
*
|
||||
* @return Collection of sending messages.
|
||||
*/
|
||||
@NonNull
|
||||
public ObservableCollection<TOutgoingMessage> getSendingMessages() {
|
||||
return sendingMessages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link Observable} to determine if message is in cache stored on disk.
|
||||
* It is needed to not send message which is already loaded from server and cached.
|
||||
*
|
||||
* @param message Message to check if it is in cache;
|
||||
* @return {@link Observable} which is checking if message is in cache.
|
||||
*/
|
||||
@NonNull
|
||||
protected abstract Observable<Boolean> isMessageInCacheObservable(@NonNull final TOutgoingMessage message);
|
||||
|
||||
/**
|
||||
* Returns {@link Observable} to determine if message is in actually loaded messages.
|
||||
* It is needed to not send message which is already loaded from server and showing to user at this moment.
|
||||
*
|
||||
* @param message Message to check if it is in actual data;
|
||||
* @return {@link Observable} which is checking if message is in actual data.
|
||||
*/
|
||||
@NonNull
|
||||
protected abstract Observable<Boolean> isMessageInActualObservable(@NonNull final TOutgoingMessage message);
|
||||
|
||||
/**
|
||||
* Method to create {@link Observable} which is sending message to server.
|
||||
*
|
||||
* @param message Message to send;
|
||||
* @return {@link Observable} to send message.
|
||||
*/
|
||||
@NonNull
|
||||
protected abstract Observable<?> createSendMessageObservable(@NonNull final TOutgoingMessage message);
|
||||
|
||||
/**
|
||||
* Method to start sending message.
|
||||
*
|
||||
* @param message Message to send.
|
||||
*/
|
||||
public void sendMessage(@NonNull final TOutgoingMessage message) {
|
||||
sendingMessages.add(0, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to start sending collection of messages.
|
||||
*
|
||||
* @param messages Messages to send.
|
||||
*/
|
||||
public void sendMessages(@NonNull final Collection<TOutgoingMessage> messages) {
|
||||
sendingMessages.addAll(0, messages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Activates chat so it will start sending messages.
|
||||
*/
|
||||
public void activate() {
|
||||
if (activationSubscription != null) {
|
||||
Lc.assertion("Chat already activated");
|
||||
return;
|
||||
}
|
||||
activationSubscription = messagesToSendObservable.subscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to retry send messages.
|
||||
*/
|
||||
public void retrySend() {
|
||||
retrySendingRequest.onNext(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivates chat so it will stop sending messages.
|
||||
*/
|
||||
public void deactivate() {
|
||||
if (activationSubscription == null) {
|
||||
Lc.assertion("Chat not activated yet");
|
||||
return;
|
||||
}
|
||||
activationSubscription.unsubscribe();
|
||||
activationSubscription = null;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Completable internalSendMessage(@NonNull final TOutgoingMessage message) {
|
||||
final SubscriptionHolder subscriptionHolder = new SubscriptionHolder();
|
||||
return Completable
|
||||
.create(subscriber -> {
|
||||
subscriptionHolder.subscription = sendingScheduler.createWorker().schedule(() -> {
|
||||
final CountDownLatch blocker = new CountDownLatch(1);
|
||||
final Subscription sendSubscription = Observable
|
||||
.combineLatest(isMessageInCacheObservable(message), isMessageInActualObservable(message),
|
||||
(messageInCache, messageInActual) -> !messageInCache && !messageInActual)
|
||||
.subscribeOn(Schedulers.computation())
|
||||
.first()
|
||||
.switchMap(shouldSendMessage -> shouldSendMessage
|
||||
? createSendMessageObservable(message).ignoreElements() : Observable.empty())
|
||||
.retryWhen(attempts -> attempts.switchMap(ignored -> {
|
||||
isSendingInError.onNext(true);
|
||||
return Observable
|
||||
.merge(retrySendingRequest, Observable.timer(RETRY_SENDING_DELAY, TimeUnit.MILLISECONDS))
|
||||
.first()
|
||||
.doOnCompleted(() -> isSendingInError.onNext(false));
|
||||
}))
|
||||
.doOnUnsubscribe(blocker::countDown)
|
||||
.subscribe(Actions.empty(), Lc::assertion, () -> sendingMessages.remove(message));
|
||||
try {
|
||||
blocker.await();
|
||||
} catch (final InterruptedException exception) {
|
||||
sendSubscription.unsubscribe();
|
||||
}
|
||||
subscriber.onCompleted();
|
||||
});
|
||||
})
|
||||
.doOnUnsubscribe(() -> {
|
||||
if (subscriptionHolder.subscription != null && !subscriptionHolder.subscription.isUnsubscribed()) {
|
||||
subscriptionHolder.subscription.unsubscribe();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private class SubscriptionHolder {
|
||||
|
||||
@Nullable
|
||||
private Subscription subscription;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2016 Touch Instinct
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package ru.touchin.templates.googlejson;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.google.api.client.http.json.JsonHttpContent;
|
||||
import com.google.api.client.json.jackson2.JacksonFactory;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.ResponseBody;
|
||||
import retrofit2.Converter;
|
||||
import retrofit2.Retrofit;
|
||||
import ru.touchin.templates.retrofit.JsonRequestBodyConverter;
|
||||
import ru.touchin.templates.retrofit.JsonResponseBodyConverter;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 2/06/2016.
|
||||
* Converter class to use with {@link Retrofit} to parse and generate models based on Google Jackson library {@link JacksonFactory}.
|
||||
*/
|
||||
public class GoogleJsonFactory extends Converter.Factory {
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Converter<ResponseBody, ?> responseBodyConverter(@NonNull final Type type,
|
||||
@NonNull final Annotation[] annotations,
|
||||
@NonNull final Retrofit retrofit) {
|
||||
return new GoogleJsonResponseBodyConverter<>(type);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Converter<?, RequestBody> requestBodyConverter(@NonNull final Type type,
|
||||
@NonNull final Annotation[] parameterAnnotations,
|
||||
@NonNull final Annotation[] methodAnnotations,
|
||||
@NonNull final Retrofit retrofit) {
|
||||
return new GoogleJsonRequestBodyConverter<>();
|
||||
}
|
||||
|
||||
public static class GoogleJsonResponseBodyConverter<T> extends JsonResponseBodyConverter<T> {
|
||||
|
||||
@NonNull
|
||||
private final Type type;
|
||||
|
||||
public GoogleJsonResponseBodyConverter(@NonNull final Type type) {
|
||||
super();
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@NonNull
|
||||
@Override
|
||||
protected T parseResponse(@NonNull final ResponseBody value) throws IOException {
|
||||
return (T) GoogleJsonModel.DEFAULT_JSON_FACTORY.createJsonParser(value.charStream()).parse(type, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class GoogleJsonRequestBodyConverter<T> extends JsonRequestBodyConverter<T> {
|
||||
|
||||
@Override
|
||||
protected void writeValueToByteArray(@NonNull final T value, @NonNull final ByteArrayOutputStream byteArrayOutputStream)
|
||||
throws IOException {
|
||||
new JsonHttpContent(GoogleJsonModel.DEFAULT_JSON_FACTORY, value).writeTo(byteArrayOutputStream);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,117 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2016 Touch Instinct
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package ru.touchin.templates.googlejson;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.google.api.client.json.JsonFactory;
|
||||
import com.google.api.client.json.jackson2.JacksonFactory;
|
||||
import com.google.api.client.util.Data;
|
||||
|
||||
import ru.touchin.roboswag.core.log.Lc;
|
||||
import ru.touchin.templates.ApiModel;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 13/11/2015.
|
||||
* Simple class with helpers inside to work with models generated by {@link GoogleJsonFactory}.
|
||||
* Mostly used to validate models returned from server.
|
||||
*/
|
||||
public abstract class GoogleJsonModel extends ApiModel {
|
||||
|
||||
/**
|
||||
* Just a simple default Google JSON factory to create model parers and generators.
|
||||
*/
|
||||
public static final JsonFactory DEFAULT_JSON_FACTORY = new JacksonFactory();
|
||||
|
||||
/**
|
||||
* Returns if this object not responded from server (no parameter in JSON file).
|
||||
*
|
||||
* @param object Value of field;
|
||||
* @return True if missed.
|
||||
*/
|
||||
protected static boolean isMissed(@Nullable final Object object) {
|
||||
return object == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if this object is responded from server as null (parameter in JSON file equals null).
|
||||
*
|
||||
* @param object Value of field;
|
||||
* @return True if null.
|
||||
*/
|
||||
protected static boolean isNull(@Nullable final Object object) {
|
||||
return Data.isNull(object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if this object is responded from server as null (parameter in JSON file equals null)
|
||||
* or if this object not responded from server (no parameter in JSON file).
|
||||
*
|
||||
* @param object Value of field;
|
||||
* @return True if null or missed.
|
||||
*/
|
||||
protected static boolean isNullOrMissed(@Nullable final Object object) {
|
||||
return isMissed(object) || isNull(object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws exception if object is missed or null.
|
||||
*
|
||||
* @param object Value of field to check;
|
||||
* @throws ValidationException Exception of validation.
|
||||
*/
|
||||
protected static void validateNotNull(@Nullable final Object object) throws ValidationException {
|
||||
if (isNull(object)) {
|
||||
throw new ValidationException("Not nullable object is null at " + Lc.getCodePoint(null, 1));
|
||||
}
|
||||
if (isMissed(object)) {
|
||||
throw new ValidationException("Not nullable object is missed at " + Lc.getCodePoint(null, 1));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws exception if object is missed.
|
||||
*
|
||||
* @param object Value of field to check;
|
||||
* @throws ValidationException Exception of validation.
|
||||
*/
|
||||
protected static void validateNotMissed(@Nullable final Object object) throws ValidationException {
|
||||
if (isMissed(object)) {
|
||||
throw new ValidationException("Object missed at " + Lc.getCodePoint(null, 1));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws exception if object is null.
|
||||
*
|
||||
* @param object Value of field to check;
|
||||
* @throws ValidationException Exception of validation.
|
||||
*/
|
||||
protected static void validateMissedOrNotNull(@Nullable final Object object) throws ValidationException {
|
||||
if (isNull(object)) {
|
||||
throw new ValidationException("Not null or not missed object is null at " + Lc.getCodePoint(null, 1));
|
||||
}
|
||||
}
|
||||
|
||||
protected GoogleJsonModel() {
|
||||
super();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,149 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2016 Touch Instinct
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package ru.touchin.templates.googlejson;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.google.api.client.http.json.JsonHttpContent;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import ru.touchin.roboswag.components.utils.storables.PreferenceStore;
|
||||
import ru.touchin.roboswag.core.observables.storable.Converter;
|
||||
import ru.touchin.roboswag.core.observables.storable.Storable;
|
||||
import ru.touchin.roboswag.core.observables.storable.concrete.NonNullStorable;
|
||||
import ru.touchin.roboswag.core.utils.ShouldNotHappenException;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 23/08/2016.
|
||||
* Utility class to get {@link Storable} that is storing Google Json generated object into preferences.
|
||||
*/
|
||||
public final class GoogleJsonPreferences {
|
||||
|
||||
@NonNull
|
||||
public static <T> Storable<String, T, String> jsonStorable(@NonNull final String name,
|
||||
@NonNull final Class<T> jsonClass,
|
||||
@NonNull final SharedPreferences preferences) {
|
||||
return new Storable.Builder<String, T, String>(name, jsonClass, String.class, new PreferenceStore<>(preferences), new JsonConverter<>())
|
||||
.setObserveStrategy(Storable.ObserveStrategy.CACHE_ACTUAL_VALUE)
|
||||
.build();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static <T> NonNullStorable<String, T, String> jsonStorable(@NonNull final String name,
|
||||
@NonNull final Class<T> jsonClass,
|
||||
@NonNull final SharedPreferences preferences,
|
||||
@NonNull final T defaultValue) {
|
||||
return new Storable.Builder<String, T, String>(name, jsonClass, String.class, new PreferenceStore<>(preferences), new JsonConverter<>())
|
||||
.setObserveStrategy(Storable.ObserveStrategy.CACHE_ACTUAL_VALUE)
|
||||
.setDefaultValue(defaultValue)
|
||||
.build();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static <T> Storable<String, List<T>, String> jsonListStorable(@NonNull final String name,
|
||||
@NonNull final Class<T> jsonListItemClass,
|
||||
@NonNull final SharedPreferences preferences) {
|
||||
return new Storable.Builder<>(name, List.class, String.class, new PreferenceStore<>(preferences), new JsonListConverter<>(jsonListItemClass))
|
||||
.setObserveStrategy(Storable.ObserveStrategy.CACHE_ACTUAL_VALUE)
|
||||
.build();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static <T> NonNullStorable<String, List<T>, String> jsonListStorable(@NonNull final String name,
|
||||
@NonNull final Class<T> jsonListItemClass,
|
||||
@NonNull final SharedPreferences preferences,
|
||||
@NonNull final List<T> defaultValue) {
|
||||
return new Storable.Builder<>(name, List.class, String.class, new PreferenceStore<>(preferences), new JsonListConverter<>(jsonListItemClass))
|
||||
.setObserveStrategy(Storable.ObserveStrategy.CACHE_ACTUAL_VALUE)
|
||||
.setDefaultValue(defaultValue)
|
||||
.build();
|
||||
}
|
||||
|
||||
private GoogleJsonPreferences() {
|
||||
}
|
||||
|
||||
public static class JsonConverter<TJsonObject> implements Converter<TJsonObject, String> {
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String toStoreObject(@NonNull final Type jsonObjectType, @NonNull final Type stringType,
|
||||
@Nullable final TJsonObject object) {
|
||||
if (object == null) {
|
||||
return null;
|
||||
}
|
||||
final JsonHttpContent content = new JsonHttpContent(GoogleJsonModel.DEFAULT_JSON_FACTORY, object);
|
||||
final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
try {
|
||||
content.writeTo(byteArrayOutputStream);
|
||||
} catch (final IOException exception) {
|
||||
throw new ShouldNotHappenException(exception);
|
||||
}
|
||||
return new String(byteArrayOutputStream.toByteArray());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public TJsonObject toObject(@NonNull final Type jsonObjectType, @NonNull final Type stringType, @Nullable final String storeValue) {
|
||||
if (storeValue == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return (TJsonObject) GoogleJsonModel.DEFAULT_JSON_FACTORY.createJsonParser(storeValue).parse(jsonObjectType, true);
|
||||
} catch (final IOException exception) {
|
||||
throw new ShouldNotHappenException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class JsonListConverter<T> extends JsonConverter<List<T>> {
|
||||
|
||||
@NonNull
|
||||
private final Class<T> itemClass;
|
||||
|
||||
public JsonListConverter(@NonNull final Class<T> itemClass) {
|
||||
super();
|
||||
this.itemClass = itemClass;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public List<T> toObject(@NonNull final Type jsonObjectType, @NonNull final Type stringType, @Nullable final String storeValue) {
|
||||
if (storeValue == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return new ArrayList<>(GoogleJsonModel.DEFAULT_JSON_FACTORY.createJsonParser(storeValue).parseArray(ArrayList.class, itemClass));
|
||||
} catch (final IOException exception) {
|
||||
throw new ShouldNotHappenException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package ru.touchin.templates.googlejson;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import ru.touchin.roboswag.core.log.Lc;
|
||||
import ru.touchin.templates.requests.HttpRequest;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 07/14.
|
||||
* Request that responses data in Google JSON format
|
||||
*/
|
||||
public abstract class GoogleJsonRequest<T> extends HttpRequest<T> {
|
||||
|
||||
protected GoogleJsonRequest(@NonNull final Class<T> responseResultType) {
|
||||
super(responseResultType);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected T parse(@NonNull final Class<T> responseResultType, @NonNull final Charset charset, @NonNull final InputStream inputStream)
|
||||
throws IOException {
|
||||
return GoogleJsonModel.DEFAULT_JSON_FACTORY.createJsonObjectParser().parseAndClose(inputStream, charset, responseResultType);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Request.Builder createHttpRequest() throws IOException {
|
||||
switch (getRequestType()) {
|
||||
case POST:
|
||||
if (getBody() == null) {
|
||||
Lc.assertion("Do you forget to implement getBody() class during POST-request?");
|
||||
return super.createHttpRequest().get();
|
||||
}
|
||||
return super.createHttpRequest().post(getBody());
|
||||
case GET:
|
||||
return super.createHttpRequest().get();
|
||||
default:
|
||||
Lc.assertion("Unknown request type " + getRequestType());
|
||||
return super.createHttpRequest().get();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of request. Basically GET or POST.
|
||||
*
|
||||
* @return Request type.
|
||||
*/
|
||||
@NonNull
|
||||
protected abstract RequestType getRequestType();
|
||||
|
||||
@Nullable
|
||||
protected RequestBody getBody() throws IOException {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected enum RequestType {
|
||||
GET,
|
||||
POST
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2016 Touch Instinct
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package ru.touchin.templates.googlejson;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import ru.touchin.templates.socket.SocketEvent;
|
||||
import ru.touchin.templates.socket.SocketMessageHandler;
|
||||
import ru.touchin.templates.ApiModel;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 01/09/2016.
|
||||
* Socket event that response JSON objects and could be parsed by Google Json lib.
|
||||
*
|
||||
* @param <TMessage> Type of message.
|
||||
*/
|
||||
public class GoogleJsonSocketEvent<TMessage> extends SocketEvent<TMessage> {
|
||||
|
||||
public GoogleJsonSocketEvent(@NonNull final String name, @NonNull final Class<TMessage> clz,
|
||||
@Nullable final SocketMessageHandler<TMessage> eventDataHandler) {
|
||||
super(name, clz, eventDataHandler);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public TMessage parse(@NonNull final byte[] data) throws IOException {
|
||||
final TMessage message = GoogleJsonModel.DEFAULT_JSON_FACTORY.createJsonParser(new String(data, "UTF-8")).parseAndClose(getMessageClass());
|
||||
if (message instanceof ApiModel) {
|
||||
((ApiModel) message).validate();
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
package ru.touchin.templates.livedata
|
||||
|
||||
import android.arch.lifecycle.MutableLiveData
|
||||
import io.reactivex.Completable
|
||||
import io.reactivex.Flowable
|
||||
import io.reactivex.Maybe
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.disposables.Disposable
|
||||
import ru.touchin.roboswag.components.utils.destroyable.BaseDestroyable
|
||||
import ru.touchin.roboswag.components.utils.destroyable.Destroyable
|
||||
import ru.touchin.templates.livedata.event.CompletableEvent
|
||||
import ru.touchin.templates.livedata.event.MaybeEvent
|
||||
import ru.touchin.templates.livedata.event.ObservableEvent
|
||||
import ru.touchin.templates.livedata.event.SingleEvent
|
||||
|
||||
class BaseLiveDataDispatcher(private val destroyable: BaseDestroyable = BaseDestroyable()) : LiveDataDispatcher, Destroyable by destroyable {
|
||||
|
||||
override fun <T> Flowable<out T>.dispatchTo(liveData: MutableLiveData<ObservableEvent<T>>): Disposable {
|
||||
liveData.value = ObservableEvent.Loading(liveData.value?.data)
|
||||
return untilDestroy(
|
||||
{ data -> liveData.value = ObservableEvent.Success(data) },
|
||||
{ throwable -> liveData.value = ObservableEvent.Error(throwable, liveData.value?.data) },
|
||||
{ liveData.value = ObservableEvent.Completed(liveData.value?.data) })
|
||||
}
|
||||
|
||||
override fun <T> Observable<out T>.dispatchTo(liveData: MutableLiveData<ObservableEvent<T>>): Disposable {
|
||||
liveData.value = ObservableEvent.Loading(liveData.value?.data)
|
||||
return untilDestroy(
|
||||
{ data -> liveData.value = ObservableEvent.Success(data) },
|
||||
{ throwable -> liveData.value = ObservableEvent.Error(throwable, liveData.value?.data) },
|
||||
{ liveData.value = ObservableEvent.Completed(liveData.value?.data) })
|
||||
}
|
||||
|
||||
override fun <T> Single<out T>.dispatchTo(liveData: MutableLiveData<SingleEvent<T>>): Disposable {
|
||||
liveData.value = SingleEvent.Loading(liveData.value?.data)
|
||||
return untilDestroy(
|
||||
{ data -> liveData.value = SingleEvent.Success(data) },
|
||||
{ throwable -> liveData.value = SingleEvent.Error(throwable, liveData.value?.data) })
|
||||
}
|
||||
|
||||
override fun Completable.dispatchTo(liveData: MutableLiveData<CompletableEvent>): Disposable {
|
||||
liveData.value = CompletableEvent.Loading
|
||||
return untilDestroy(
|
||||
{ liveData.value = CompletableEvent.Completed },
|
||||
{ throwable -> liveData.value = CompletableEvent.Error(throwable) })
|
||||
}
|
||||
|
||||
override fun <T> Maybe<out T>.dispatchTo(liveData: MutableLiveData<MaybeEvent<T>>): Disposable {
|
||||
liveData.value = MaybeEvent.Loading(liveData.value?.data)
|
||||
return untilDestroy(
|
||||
{ data -> liveData.value = MaybeEvent.Success(data) },
|
||||
{ throwable -> liveData.value = MaybeEvent.Error(throwable, liveData.value?.data) },
|
||||
{ liveData.value = MaybeEvent.Completed(liveData.value?.data) })
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package ru.touchin.templates.livedata
|
||||
|
||||
import android.arch.lifecycle.MutableLiveData
|
||||
import io.reactivex.Completable
|
||||
import io.reactivex.Flowable
|
||||
import io.reactivex.Maybe
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.disposables.Disposable
|
||||
import ru.touchin.templates.livedata.event.CompletableEvent
|
||||
import ru.touchin.templates.livedata.event.MaybeEvent
|
||||
import ru.touchin.templates.livedata.event.ObservableEvent
|
||||
import ru.touchin.templates.livedata.event.SingleEvent
|
||||
|
||||
interface LiveDataDispatcher {
|
||||
|
||||
fun <T> Flowable<out T>.dispatchTo(liveData: MutableLiveData<ObservableEvent<T>>): Disposable
|
||||
|
||||
fun <T> Observable<out T>.dispatchTo(liveData: MutableLiveData<ObservableEvent<T>>): Disposable
|
||||
|
||||
fun <T> Single<out T>.dispatchTo(liveData: MutableLiveData<SingleEvent<T>>): Disposable
|
||||
|
||||
fun Completable.dispatchTo(liveData: MutableLiveData<CompletableEvent>): Disposable
|
||||
|
||||
fun <T> Maybe<out T>.dispatchTo(liveData: MutableLiveData<MaybeEvent<T>>): Disposable
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package ru.touchin.templates.livedata
|
||||
|
||||
import android.arch.lifecycle.LifecycleOwner
|
||||
import android.arch.lifecycle.MutableLiveData
|
||||
import android.arch.lifecycle.Observer
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
class SingleLiveEvent<T> : MutableLiveData<T>() {
|
||||
|
||||
private val pending = AtomicBoolean(false)
|
||||
|
||||
override fun observe(owner: LifecycleOwner, observer: Observer<T?>) {
|
||||
super.observe(owner, Observer { value ->
|
||||
if (pending.compareAndSet(true, false)) {
|
||||
observer.onChanged(value)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun setValue(value: T) {
|
||||
pending.set(true)
|
||||
super.setValue(value)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package ru.touchin.templates.livedata.event
|
||||
|
||||
/**
|
||||
* Created by Denis Karmyshakov on 14.03.18.
|
||||
* Event class that emits from [io.reactivex.Completable].
|
||||
*/
|
||||
sealed class CompletableEvent {
|
||||
|
||||
object Loading: CompletableEvent()
|
||||
|
||||
object Completed: CompletableEvent()
|
||||
|
||||
data class Error(val throwable: Throwable): CompletableEvent()
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package ru.touchin.templates.livedata.event
|
||||
|
||||
/**
|
||||
* Created by Denis Karmyshakov on 14.03.18.
|
||||
* Event class that emits from [io.reactivex.Maybe].
|
||||
*/
|
||||
sealed class MaybeEvent<out T>(open val data: T?) {
|
||||
|
||||
class Loading<out T>(data: T?): MaybeEvent<T>(data)
|
||||
|
||||
class Success<out T>(override val data: T): MaybeEvent<T>(data)
|
||||
|
||||
class Error<out T>(val throwable: Throwable, data: T?): MaybeEvent<T>(data)
|
||||
|
||||
class Completed<out T>(data: T?): MaybeEvent<T>(data)
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package ru.touchin.templates.livedata.event
|
||||
|
||||
/**
|
||||
* Created by Denis Karmyshakov on 14.03.18.
|
||||
* Event class that emits from [io.reactivex.Observable].
|
||||
*/
|
||||
sealed class ObservableEvent<out T>(open val data: T?) {
|
||||
|
||||
class Loading<out T>(data: T? = null): ObservableEvent<T>(data)
|
||||
|
||||
class Success<out T>(override val data: T): ObservableEvent<T>(data)
|
||||
|
||||
class Error<out T>(val throwable: Throwable, data: T? = null): ObservableEvent<T>(data)
|
||||
|
||||
class Completed<out T>(data: T? = null): ObservableEvent<T>(data)
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package ru.touchin.templates.livedata.event
|
||||
|
||||
/**
|
||||
* Created by Denis Karmyshakov on 14.03.18.
|
||||
* Event class that emits from [io.reactivex.Single].
|
||||
*/
|
||||
sealed class SingleEvent<out T>(open val data: T?) {
|
||||
|
||||
class Loading<out T>(data: T?): SingleEvent<T>(data)
|
||||
|
||||
class Success<out T>(override val data: T): SingleEvent<T>(data)
|
||||
|
||||
class Error<out T>(val throwable: Throwable, data: T?): SingleEvent<T>(data)
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Copyright (c) 2016 Touch Instinct
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
package ru.touchin.templates.logansquare;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.bluelinelabs.logansquare.typeconverters.TypeConverter;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import ru.touchin.roboswag.core.log.Lc;
|
||||
|
||||
/**
|
||||
* LoganSquare converter for java.math.BigDecimal
|
||||
*/
|
||||
@SuppressWarnings("CPD-START") // similar to LoganSquareJodaTimeConverter
|
||||
public class LoganSquareBigDecimalConverter implements TypeConverter<BigDecimal> {
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public BigDecimal parse(@NonNull final JsonParser jsonParser) throws IOException {
|
||||
final String dateString = jsonParser.getValueAsString();
|
||||
if (dateString == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return new BigDecimal(dateString);
|
||||
} catch (final RuntimeException exception) {
|
||||
Lc.assertion(exception);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(@Nullable final BigDecimal object,
|
||||
@Nullable final String fieldName,
|
||||
final boolean writeFieldNameForObject,
|
||||
@NonNull final JsonGenerator jsonGenerator)
|
||||
throws IOException {
|
||||
if (fieldName != null) {
|
||||
jsonGenerator.writeStringField(fieldName, object != null ? object.toString() : null);
|
||||
} else {
|
||||
jsonGenerator.writeString(object != null ? object.toString() : null);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -22,12 +22,14 @@ package ru.touchin.templates.logansquare;
|
|||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.bluelinelabs.logansquare.typeconverters.TypeConverter;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.format.DateTimeFormatter;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
|
@ -38,11 +40,22 @@ import ru.touchin.roboswag.core.log.Lc;
|
|||
*/
|
||||
public class LoganSquareJodaTimeConverter implements TypeConverter<DateTime> {
|
||||
|
||||
@Nullable
|
||||
private final DateTimeFormatter formatter;
|
||||
|
||||
public LoganSquareJodaTimeConverter() {
|
||||
this.formatter = null;
|
||||
}
|
||||
|
||||
public LoganSquareJodaTimeConverter(@Nullable final DateTimeFormatter formatter) {
|
||||
this.formatter = formatter;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public DateTime parse(@NonNull final JsonParser jsonParser) throws IOException {
|
||||
final String dateString = jsonParser.getValueAsString();
|
||||
if (dateString == null) {
|
||||
if (dateString == null || dateString.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
|
|
@ -54,15 +67,17 @@ public class LoganSquareJodaTimeConverter implements TypeConverter<DateTime> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void serialize(@Nullable final DateTime object,
|
||||
@Nullable final String fieldName,
|
||||
final boolean writeFieldNameForObject,
|
||||
@NonNull final JsonGenerator jsonGenerator)
|
||||
throws IOException {
|
||||
public void serialize(
|
||||
@Nullable final DateTime object,
|
||||
@Nullable final String fieldName,
|
||||
final boolean writeFieldNameForObject,
|
||||
@NonNull final JsonGenerator jsonGenerator
|
||||
) throws IOException {
|
||||
final String serializedValue = object != null ? object.toString(formatter) : null;
|
||||
if (fieldName != null) {
|
||||
jsonGenerator.writeStringField(fieldName, object != null ? object.toString() : null);
|
||||
jsonGenerator.writeStringField(fieldName, !TextUtils.isEmpty(serializedValue) ? serializedValue : null);
|
||||
} else {
|
||||
jsonGenerator.writeString(object != null ? object.toString() : null);
|
||||
jsonGenerator.writeString(!TextUtils.isEmpty(serializedValue) ? serializedValue : null);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,13 +20,15 @@
|
|||
package ru.touchin.templates.logansquare;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.bluelinelabs.logansquare.ConverterUtils;
|
||||
import com.bluelinelabs.logansquare.LoganSquare;
|
||||
import com.google.api.client.json.jackson2.JacksonFactory;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
|
|
@ -42,7 +44,7 @@ import ru.touchin.templates.retrofit.JsonResponseBodyConverter;
|
|||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 2/06/2016.
|
||||
* LoganSquareConverter class to use with {@link Retrofit} to parse and generate models based on Google Jackson library {@link JacksonFactory}.
|
||||
* LoganSquareConverter class to use with {@link Retrofit} to parse and generate models based on Logan Square library.
|
||||
*/
|
||||
public class LoganSquareJsonFactory extends Converter.Factory {
|
||||
|
||||
|
|
@ -63,6 +65,16 @@ public class LoganSquareJsonFactory extends Converter.Factory {
|
|||
return new LoganSquareRequestBodyConverter<>();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Converter<?, String> stringConverter(@NonNull final Type type, @NonNull final Annotation[] annotations, @NonNull final Retrofit retrofit) {
|
||||
if (type instanceof Class && ((Class) type).getSuperclass() == Enum.class) {
|
||||
return new LoganSquareStringEnumConverter<>();
|
||||
} else {
|
||||
return super.stringConverter(type, annotations, retrofit);
|
||||
}
|
||||
}
|
||||
|
||||
public static class LoganSquareJsonResponseBodyConverter<T> extends JsonResponseBodyConverter<T> {
|
||||
|
||||
@NonNull
|
||||
|
|
@ -101,11 +113,29 @@ public class LoganSquareJsonFactory extends Converter.Factory {
|
|||
public static class LoganSquareRequestBodyConverter<T> extends JsonRequestBodyConverter<T> {
|
||||
|
||||
@Override
|
||||
protected void writeValueToByteArray(@NonNull final T value, @NonNull final ByteArrayOutputStream byteArrayOutputStream)
|
||||
throws IOException {
|
||||
protected void writeValueToByteArray(@NonNull final T value, @NonNull final ByteArrayOutputStream byteArrayOutputStream) throws IOException {
|
||||
LoganSquare.serialize(value, byteArrayOutputStream);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
public static class LoganSquareStringEnumConverter<T> implements Converter<T, String> {
|
||||
|
||||
@Nullable
|
||||
@SuppressWarnings({"unchecked", "TryFinallyCanBeTryWithResources"})
|
||||
@Override
|
||||
public String convert(@NonNull final T value) throws IOException {
|
||||
final StringWriter writer = new StringWriter();
|
||||
try {
|
||||
final JsonGenerator generator = LoganSquare.JSON_FACTORY.createGenerator(writer);
|
||||
LoganSquare.typeConverterFor((Class<T>) value.getClass()).serialize(value, null, false, generator);
|
||||
generator.close();
|
||||
return writer.toString().replaceAll("\"", "");
|
||||
} finally {
|
||||
writer.close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ import java.util.List;
|
|||
import ru.touchin.roboswag.components.utils.storables.PreferenceStore;
|
||||
import ru.touchin.roboswag.core.observables.storable.Converter;
|
||||
import ru.touchin.roboswag.core.observables.storable.Storable;
|
||||
import ru.touchin.roboswag.core.observables.storable.concrete.NonNullStorable;
|
||||
import ru.touchin.roboswag.core.observables.storable.NonNullStorable;
|
||||
import ru.touchin.roboswag.core.utils.ShouldNotHappenException;
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,105 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2015 Wasabeef
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ru.touchin.templates.postprocessors;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.os.Build;
|
||||
import android.renderscript.RSRuntimeException;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.facebook.cache.common.CacheKey;
|
||||
import com.facebook.cache.common.SimpleCacheKey;
|
||||
import com.facebook.imagepipeline.request.BasePostprocessor;
|
||||
|
||||
import ru.touchin.roboswag.components.utils.images.BlurUtils;
|
||||
|
||||
public class BlurPostprocessor extends BasePostprocessor {
|
||||
|
||||
private static final int MAX_RADIUS = 25;
|
||||
private static final int DEFAULT_DOWN_SAMPLING = 1;
|
||||
|
||||
@NonNull
|
||||
private final Context context;
|
||||
private final int radius;
|
||||
private final int sampling;
|
||||
|
||||
public BlurPostprocessor(@NonNull final Context context) {
|
||||
this(context, MAX_RADIUS, DEFAULT_DOWN_SAMPLING);
|
||||
}
|
||||
|
||||
public BlurPostprocessor(@NonNull final Context context, final int radius) {
|
||||
this(context, radius, DEFAULT_DOWN_SAMPLING);
|
||||
}
|
||||
|
||||
public BlurPostprocessor(@NonNull final Context context, final int radius, final int sampling) {
|
||||
super();
|
||||
this.context = context.getApplicationContext();
|
||||
this.radius = radius;
|
||||
this.sampling = sampling;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(@NonNull final Bitmap dest, @NonNull final Bitmap source) {
|
||||
|
||||
final int width = source.getWidth();
|
||||
final int height = source.getHeight();
|
||||
final int scaledWidth = width / sampling;
|
||||
final int scaledHeight = height / sampling;
|
||||
|
||||
Bitmap blurredBitmap = Bitmap.createBitmap(scaledWidth, scaledHeight, Bitmap.Config.ARGB_8888);
|
||||
|
||||
final Canvas canvas = new Canvas(blurredBitmap);
|
||||
canvas.scale(1 / (float) sampling, 1 / (float) sampling);
|
||||
final Paint paint = new Paint();
|
||||
paint.setFlags(Paint.FILTER_BITMAP_FLAG);
|
||||
canvas.drawBitmap(source, 0, 0, paint);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||
try {
|
||||
blurredBitmap = BlurUtils.blurRenderscript(context, blurredBitmap, radius);
|
||||
} catch (final RSRuntimeException exception) {
|
||||
blurredBitmap = BlurUtils.blurFast(blurredBitmap, radius, true);
|
||||
}
|
||||
} else {
|
||||
blurredBitmap = BlurUtils.blurFast(blurredBitmap, radius, true);
|
||||
}
|
||||
|
||||
final Bitmap scaledBitmap =
|
||||
Bitmap.createScaledBitmap(blurredBitmap, dest.getWidth(), dest.getHeight(), true);
|
||||
if (blurredBitmap != null) {
|
||||
blurredBitmap.recycle();
|
||||
|
||||
}
|
||||
super.process(dest, scaledBitmap);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public String getName() {
|
||||
return getClass().getSimpleName();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public CacheKey getPostprocessorCacheKey() {
|
||||
return new SimpleCacheKey("radius=" + radius + ",sampling=" + sampling);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -28,6 +28,8 @@ import java.nio.charset.Charset;
|
|||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Headers;
|
||||
import okhttp3.HttpUrl;
|
||||
|
|
@ -40,8 +42,6 @@ import okhttp3.ResponseBody;
|
|||
import okio.Buffer;
|
||||
import ru.touchin.roboswag.core.log.Lc;
|
||||
import ru.touchin.roboswag.core.log.LcLevel;
|
||||
import rx.Observable;
|
||||
import rx.schedulers.Schedulers;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 13/11/2015.
|
||||
|
|
@ -208,7 +208,7 @@ public abstract class HttpRequest<T> {
|
|||
.fromCallable(() -> executeSyncInternal(requestController))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.unsubscribeOn(Schedulers.io())
|
||||
.doOnUnsubscribe(requestController.call::cancel));
|
||||
.doOnDispose(requestController.call::cancel));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ public abstract class JsonResponseBodyConverter<T> implements Converter<Response
|
|||
validateCollection((Collection) result);
|
||||
}
|
||||
if (result instanceof Map) {
|
||||
validateCollection(((Map)result).values());
|
||||
validateCollection(((Map) result).values());
|
||||
}
|
||||
|
||||
return result;
|
||||
|
|
|
|||
|
|
@ -1,207 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package ru.touchin.templates.socket;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.util.Pair;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import io.socket.client.Socket;
|
||||
import io.socket.emitter.Emitter;
|
||||
import ru.touchin.roboswag.core.log.Lc;
|
||||
import ru.touchin.templates.ApiModel;
|
||||
import rx.Observable;
|
||||
import rx.Scheduler;
|
||||
import rx.functions.Action1;
|
||||
import rx.schedulers.Schedulers;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 29/02/16.
|
||||
* Base class to realize socket.io logic into your project.
|
||||
*/
|
||||
public abstract class SocketConnection {
|
||||
|
||||
@NonNull
|
||||
private final Scheduler scheduler = Schedulers.from(Executors.newSingleThreadExecutor());
|
||||
@NonNull
|
||||
private final Map<SocketEvent, Observable> messagesObservableCache = new HashMap<>();
|
||||
@NonNull
|
||||
private final Observable<Pair<Socket, State>> socketObservable = createSocketObservable();
|
||||
private final boolean autoConnectOnAnySubscription;
|
||||
|
||||
public SocketConnection(final boolean autoConnectOnAnySubscription) {
|
||||
this.autoConnectOnAnySubscription = autoConnectOnAnySubscription;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Scheduler getScheduler() {
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link Observable} that creates socket connection and connects/disconnects by subscription state.
|
||||
*
|
||||
* @return Socket {@link Observable}.
|
||||
*/
|
||||
@NonNull
|
||||
protected Observable<Socket> getSocket() {
|
||||
return socketObservable
|
||||
.map(pair -> pair.first)
|
||||
.distinctUntilChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates socket.
|
||||
*
|
||||
* @return New socket.
|
||||
* @throws Exception Exception throwing during socket creation.
|
||||
*/
|
||||
@NonNull
|
||||
protected abstract Socket createSocket() throws Exception;
|
||||
|
||||
@NonNull
|
||||
private Observable<Pair<Socket, State>> createSocketObservable() {
|
||||
return Observable
|
||||
.fromCallable(this::createSocket)
|
||||
.switchMap(socket -> Observable
|
||||
.<Pair<Socket, State>>create(emitter -> {
|
||||
socket.on(Socket.EVENT_CONNECT, args -> emitter.onNext(new Pair<>(socket, State.CONNECTED)));
|
||||
socket.on(Socket.EVENT_CONNECTING, args -> emitter.onNext(new Pair<>(socket, State.CONNECTING)));
|
||||
socket.on(Socket.EVENT_CONNECT_ERROR, args -> emitter.onNext(new Pair<>(socket, State.CONNECTION_ERROR)));
|
||||
socket.on(Socket.EVENT_CONNECT_TIMEOUT, args -> emitter.onNext(new Pair<>(socket, State.CONNECTION_ERROR)));
|
||||
socket.on(Socket.EVENT_DISCONNECT, args -> emitter.onNext(new Pair<>(socket, State.DISCONNECTED)));
|
||||
socket.on(Socket.EVENT_RECONNECT_ATTEMPT, args -> emitter.onNext(new Pair<>(socket, State.CONNECTING)));
|
||||
socket.on(Socket.EVENT_RECONNECTING, args -> emitter.onNext(new Pair<>(socket, State.CONNECTING)));
|
||||
socket.on(Socket.EVENT_RECONNECT, args -> emitter.onNext(new Pair<>(socket, State.CONNECTED)));
|
||||
socket.on(Socket.EVENT_RECONNECT_ERROR, args -> emitter.onNext(new Pair<>(socket, State.CONNECTION_ERROR)));
|
||||
socket.on(Socket.EVENT_RECONNECT_FAILED, args -> emitter.onNext(new Pair<>(socket, State.CONNECTION_ERROR)));
|
||||
emitter.onNext(new Pair<>(socket, State.DISCONNECTED));
|
||||
}, rx.Emitter.BackpressureMode.LATEST)
|
||||
.distinctUntilChanged()
|
||||
.doOnSubscribe(() -> {
|
||||
if (autoConnectOnAnySubscription) {
|
||||
socket.connect();
|
||||
}
|
||||
})
|
||||
.doOnUnsubscribe(() -> {
|
||||
if (autoConnectOnAnySubscription) {
|
||||
socket.disconnect();
|
||||
}
|
||||
}))
|
||||
.subscribeOn(scheduler)
|
||||
.replay(1)
|
||||
.refCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link Observable} to observe socket state.
|
||||
*
|
||||
* @return {@link Observable} to observe socket state.
|
||||
*/
|
||||
@NonNull
|
||||
public Observable<State> observeSocketState() {
|
||||
return socketObservable.map(pair -> pair.second);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@SuppressWarnings("unchecked")
|
||||
//unchecked: it's OK as we are caching raw observables
|
||||
protected <T> Observable<T> observeEvent(@NonNull final SocketEvent<T> socketEvent) {
|
||||
return Observable.switchOnNext(Observable
|
||||
.fromCallable(() -> {
|
||||
Observable<T> result = (Observable<T>) messagesObservableCache.get(socketEvent);
|
||||
if (result == null) {
|
||||
result = getSocket()
|
||||
.switchMap(socket -> Observable
|
||||
.<T>create(emitter -> socket.on(socketEvent.getName(), new SocketListener<>(socketEvent, emitter::onNext)),
|
||||
rx.Emitter.BackpressureMode.BUFFER)
|
||||
.unsubscribeOn(scheduler)
|
||||
.doOnUnsubscribe(() -> {
|
||||
socket.off(socketEvent.getName());
|
||||
messagesObservableCache.remove(socketEvent);
|
||||
}))
|
||||
.publish()
|
||||
.refCount();
|
||||
messagesObservableCache.put(socketEvent, result);
|
||||
}
|
||||
return result;
|
||||
})
|
||||
.subscribeOn(scheduler));
|
||||
}
|
||||
|
||||
/**
|
||||
* State of socket connection.
|
||||
*/
|
||||
public enum State {
|
||||
DISCONNECTED,
|
||||
CONNECTING,
|
||||
CONNECTED,
|
||||
CONNECTION_ERROR
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface to listen socket messages.
|
||||
*
|
||||
* @param <TMessage> Type of socket message.
|
||||
*/
|
||||
public static class SocketListener<TMessage> implements Emitter.Listener {
|
||||
|
||||
@NonNull
|
||||
private final SocketEvent<TMessage> socketEvent;
|
||||
@NonNull
|
||||
private final Action1<TMessage> onMessageAction;
|
||||
|
||||
public SocketListener(@NonNull final SocketEvent<TMessage> socketEvent, @NonNull final Action1<TMessage> onMessageAction) {
|
||||
this.socketEvent = socketEvent;
|
||||
this.onMessageAction = onMessageAction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void call(@Nullable final Object... args) {
|
||||
if (args == null || args[0] == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
final byte[] response = args[0] instanceof byte[] ? (byte[]) args[0] : args[0].toString().getBytes();
|
||||
final TMessage message = socketEvent.parse(response);
|
||||
if (socketEvent.getEventDataHandler() != null) {
|
||||
socketEvent.getEventDataHandler().handleMessage(message);
|
||||
}
|
||||
onMessageAction.call(message);
|
||||
} catch (final RuntimeException throwable) {
|
||||
Lc.assertion(throwable);
|
||||
} catch (final JsonProcessingException exception) {
|
||||
Lc.assertion(exception);
|
||||
} catch (final ApiModel.ValidationException exception) {
|
||||
Lc.assertion(exception);
|
||||
} catch (final Exception exception) {
|
||||
Lc.e(exception, "Socket processing error");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,101 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package ru.touchin.templates.socket;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 29/02/16.
|
||||
* Object that represents event on socket connection by name (e.g. messages/get).
|
||||
*
|
||||
* @param <TMessage> Type of message coming from socket by event.
|
||||
*/
|
||||
public abstract class SocketEvent<TMessage> {
|
||||
|
||||
@NonNull
|
||||
private final String name;
|
||||
@NonNull
|
||||
private final Class<TMessage> messageClass;
|
||||
@Nullable
|
||||
private final SocketMessageHandler<TMessage> eventDataHandler;
|
||||
|
||||
public SocketEvent(@NonNull final String name, @NonNull final Class<TMessage> messageClass,
|
||||
@Nullable final SocketMessageHandler<TMessage> eventDataHandler) {
|
||||
this.name = name;
|
||||
this.messageClass = messageClass;
|
||||
this.eventDataHandler = eventDataHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns name of event.
|
||||
*
|
||||
* @return Name of event.
|
||||
*/
|
||||
@NonNull
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns message class;
|
||||
*
|
||||
* @return message class.
|
||||
*/
|
||||
@NonNull
|
||||
public Class<TMessage> getMessageClass() {
|
||||
return messageClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns handler to handle message after response and parsing.
|
||||
*
|
||||
* @return Message handler.
|
||||
*/
|
||||
@Nullable
|
||||
public SocketMessageHandler<TMessage> getEventDataHandler() {
|
||||
return eventDataHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses input string to message.
|
||||
*
|
||||
* @param data Input bytes;
|
||||
* @return Message object;
|
||||
* @throws IOException Exception during parsing.
|
||||
*/
|
||||
@NonNull
|
||||
public abstract TMessage parse(@NonNull final byte[] data) throws IOException;
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable final Object object) {
|
||||
return object instanceof SocketEvent
|
||||
&& ((SocketEvent) object).name.equals(name)
|
||||
&& ((SocketEvent) object).messageClass.equals(messageClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return name.hashCode() + messageClass.hashCode();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package ru.touchin.templates.socket;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 29/02/16.
|
||||
* Interface to implement for objects which could handle message coming from socket.
|
||||
*
|
||||
* @param <TMessage> Type of message coming from socket.
|
||||
*/
|
||||
public interface SocketMessageHandler<TMessage> {
|
||||
|
||||
/**
|
||||
* Method to handle message
|
||||
*
|
||||
* @param message Message to handle;
|
||||
* @return Result of handling message;
|
||||
* @throws Exception Throws during handling.
|
||||
*/
|
||||
@NonNull
|
||||
TMessage handleMessage(@NonNull TMessage message) throws Exception;
|
||||
|
||||
}
|
||||
|
|
@ -4,11 +4,13 @@ import android.support.annotation.NonNull;
|
|||
|
||||
import java.io.Serializable;
|
||||
|
||||
import io.reactivex.functions.Function;
|
||||
|
||||
/**
|
||||
* Created by Ilia Kurtov on 30/01/2017.
|
||||
* Simple interface that gets one parameter with {@link TInput} type as input and returns other type {@link TReturn} as a result.
|
||||
* Interface extends {@link Serializable} to survive after {@link ru.touchin.roboswag.components.navigation.AbstractState} recreation.
|
||||
* Created as a replace for {@link rx.functions.Func1} because it needed to be {@link Serializable}
|
||||
* Created as a replace for {@link Function} because it needed to be {@link Serializable}
|
||||
*
|
||||
* @param <TInput> input type.
|
||||
* @param <TReturn> return type.
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ public class ValidationState implements Serializable {
|
|||
|
||||
/**
|
||||
* Returns unique code of the {@link ValidationState}.
|
||||
*
|
||||
* @return code or the ValidationState.
|
||||
*/
|
||||
public int getCode() {
|
||||
|
|
@ -67,6 +68,7 @@ public class ValidationState implements Serializable {
|
|||
|
||||
/**
|
||||
* Don't forget to override this method!
|
||||
*
|
||||
* @param object that you want to compare.
|
||||
* @return true if objects equals and false otherwise.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ public interface ViewWithError {
|
|||
* Shows error
|
||||
* Pass here error state.
|
||||
* It is not correct to pass here {@link ValidationState#VALID} or {@link ValidationState#INITIAL}
|
||||
*
|
||||
* @param validationState error state. Can be other than {@link ValidationState} if you have successor of base {@link ValidationState}.
|
||||
*/
|
||||
void showError(@NonNull final ValidationState validationState);
|
||||
|
|
|
|||
|
|
@ -21,9 +21,9 @@ package ru.touchin.templates.validation.validationcontrollers;
|
|||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import io.reactivex.Observable;
|
||||
import ru.touchin.templates.validation.ValidationState;
|
||||
import ru.touchin.templates.validation.validators.SameTypeValidator;
|
||||
import rx.Observable;
|
||||
|
||||
/**
|
||||
* Created by Ilia Kurtov on 24/01/2017.
|
||||
|
|
@ -37,6 +37,7 @@ public class BooleanValidationController extends ValidationController<Boolean, B
|
|||
|
||||
/**
|
||||
* This method validates bounded view.
|
||||
*
|
||||
* @param activatedObservable emits true when we need to show error on empty fields. Eg when user clicks on Done button but he missed some
|
||||
* necessary fields to fill.
|
||||
* @return observable without any concrete type. Simply subscribe to this method to make it works.
|
||||
|
|
@ -45,7 +46,7 @@ public class BooleanValidationController extends ValidationController<Boolean, B
|
|||
public Observable<?> validation(@NonNull final Observable<Boolean> activatedObservable) {
|
||||
return Observable.combineLatest(activatedObservable, getValidator().getWrapperModel().observe(),
|
||||
(activated, flag) -> {
|
||||
final boolean selected = flag == null ? false : flag;
|
||||
final boolean selected = flag.get() == null ? false : flag.get();
|
||||
if (activated && !selected) {
|
||||
return ValidationState.ERROR_NO_DESCRIPTION;
|
||||
} else if (!activated && !selected) {
|
||||
|
|
|
|||
|
|
@ -25,10 +25,11 @@ import android.text.TextUtils;
|
|||
|
||||
import java.io.Serializable;
|
||||
|
||||
import io.reactivex.Observable;
|
||||
import ru.touchin.roboswag.core.utils.Optional;
|
||||
import ru.touchin.roboswag.core.utils.pairs.NonNullPair;
|
||||
import ru.touchin.templates.validation.ValidationState;
|
||||
import ru.touchin.templates.validation.validators.EditTextValidator;
|
||||
import rx.Observable;
|
||||
|
||||
/**
|
||||
* Created by Ilia Kurtov on 24/01/2017.
|
||||
|
|
@ -75,17 +76,17 @@ public class EditTextValidationController<TModel extends Serializable>
|
|||
}
|
||||
|
||||
@Nullable
|
||||
@SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity"})
|
||||
private NonNullPair<Boolean, Observable<ValidationState>> getValidationPair(final boolean activated,
|
||||
@Nullable final String text,
|
||||
@NonNull final Optional<String> optionalText,
|
||||
@Nullable final Boolean focusIn,
|
||||
final boolean showError) {
|
||||
final String text = optionalText.get();
|
||||
if (focusIn == null && TextUtils.isEmpty(text) && !activated && !showError) {
|
||||
return null;
|
||||
}
|
||||
final boolean focus = focusIn == null ? false : focusIn;
|
||||
final boolean focus = focusIn != null && focusIn;
|
||||
if (TextUtils.isEmpty(text)) {
|
||||
return new NonNullPair<>(focus, (activated || showError)
|
||||
return new NonNullPair<>(focus, activated || showError
|
||||
? getValidator().getValidationStateWhenEmpty().observe()
|
||||
: Observable.just(ValidationState.INITIAL));
|
||||
}
|
||||
|
|
@ -98,8 +99,7 @@ public class EditTextValidationController<TModel extends Serializable>
|
|||
/**
|
||||
* If we don't want to show error when focus is lost.
|
||||
*
|
||||
* @param showErrorOnFocusOut show an error or don't show an error.
|
||||
*
|
||||
* @param showErrorOnFocusOut show an error or don't show an error.
|
||||
*/
|
||||
public void setShowErrorOnFocusOut(final boolean showErrorOnFocusOut) {
|
||||
this.showErrorOnFocusOut = showErrorOnFocusOut;
|
||||
|
|
|
|||
|
|
@ -4,15 +4,16 @@ import android.support.annotation.NonNull;
|
|||
|
||||
import java.io.Serializable;
|
||||
|
||||
import io.reactivex.Observable;
|
||||
import ru.touchin.templates.validation.ValidationState;
|
||||
import ru.touchin.templates.validation.validators.Validator;
|
||||
import rx.Observable;
|
||||
|
||||
/**
|
||||
* Created by Ilia Kurtov on 24/01/2017.
|
||||
* {@link ValidationController} for models that have the same modal as wrapper model. You can use it when you simply need to be sure
|
||||
* that user have selected some item and it is not null.
|
||||
* @param <TModel> type of the model.
|
||||
*
|
||||
* @param <TModel> type of the model.
|
||||
* @param <TValidator> corresponding {@link Validator}
|
||||
*/
|
||||
public class SimpleValidationController<TModel extends Serializable, TValidator extends Validator<TModel, TModel>>
|
||||
|
|
@ -24,6 +25,7 @@ public class SimpleValidationController<TModel extends Serializable, TValidator
|
|||
|
||||
/**
|
||||
* This method validates bounded view.
|
||||
*
|
||||
* @param activatedObservable emits true when we need to show error on empty fields. Eg when user clicks on Done button but he missed some
|
||||
* necessary fields to fill.
|
||||
* @return observable without any concrete type. Simply subscribe to this method to make it works.
|
||||
|
|
|
|||
|
|
@ -24,12 +24,13 @@ import android.support.annotation.Nullable;
|
|||
|
||||
import java.io.Serializable;
|
||||
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.functions.Consumer;
|
||||
import ru.touchin.roboswag.core.utils.Optional;
|
||||
import ru.touchin.templates.validation.ValidationState;
|
||||
import ru.touchin.templates.validation.ViewWithError;
|
||||
import ru.touchin.templates.validation.validators.Validator;
|
||||
import rx.Observable;
|
||||
import rx.android.schedulers.AndroidSchedulers;
|
||||
import rx.functions.Action1;
|
||||
|
||||
/**
|
||||
* Created by Ilia Kurtov on 24/01/2017.
|
||||
|
|
@ -53,22 +54,24 @@ public class ValidationController
|
|||
/**
|
||||
* Bind to this observable to connect view and model. If you provide first argument (viewStateObservable) - the connection would be two-way.
|
||||
* If not - one-way. This method changes updates view with current {@link ValidationState}.
|
||||
*
|
||||
* @param viewStateObservable input view state {@link Observable}.
|
||||
* Eg it can be observable with input text from the {@link android.widget.EditText}
|
||||
* @param updateViewAction action that updates current state of the bounded view.
|
||||
* @param viewWithError view that implements {@link ViewWithError} interface and could reacts to the validation errors.
|
||||
* @param updateViewAction action that updates current state of the bounded view.
|
||||
* @param viewWithError view that implements {@link ViewWithError} interface and could reacts to the validation errors.
|
||||
* @return observable without any concrete type. Simply subscribe to this method to make it works.
|
||||
*/
|
||||
@NonNull
|
||||
public Observable<?> modelAndViewUpdating(@Nullable final Observable<TWrapperModel> viewStateObservable,
|
||||
@NonNull final Action1<TWrapperModel> updateViewAction,
|
||||
@NonNull final Consumer<Optional<TWrapperModel>> updateViewAction,
|
||||
@NonNull final ViewWithError viewWithError) {
|
||||
|
||||
final Observable<?> stateObservable = viewStateObservable != null
|
||||
? viewStateObservable.doOnNext(flag -> getValidator().getWrapperModel().set(flag))
|
||||
: Observable.empty();
|
||||
return Observable
|
||||
.merge(getValidator().getWrapperModel().observe()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnNext(updateViewAction),
|
||||
getValidator().getValidationState().observe()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
|
|
@ -84,6 +87,7 @@ public class ValidationController
|
|||
|
||||
/**
|
||||
* Helper function to check if validation state in error state ot not
|
||||
*
|
||||
* @param validationState the state you want to check for the errors.
|
||||
* @return true if validation state is in error and false otherwise.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -24,13 +24,13 @@ import android.support.annotation.Nullable;
|
|||
|
||||
import java.io.Serializable;
|
||||
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import ru.touchin.roboswag.core.observables.Changeable;
|
||||
import ru.touchin.roboswag.core.observables.NonNullChangeable;
|
||||
import ru.touchin.roboswag.core.utils.pairs.HalfNullablePair;
|
||||
import ru.touchin.templates.validation.ValidationFunc;
|
||||
import ru.touchin.templates.validation.ValidationState;
|
||||
import rx.Observable;
|
||||
import rx.schedulers.Schedulers;
|
||||
|
||||
/**
|
||||
* Created by Ilia Kurtov on 24/01/2017.
|
||||
|
|
@ -51,6 +51,7 @@ public abstract class EditTextValidator<TModel extends Serializable> extends Val
|
|||
* This flag needed to force showing errors. You don't want to show final error when you start to enter data in some field at first time.
|
||||
* But if user leaves this view and final check not passed - you need to force to show an error till user not enters correct data and leaves
|
||||
* the view.
|
||||
*
|
||||
* @return {@link NonNullChangeable} with current state of the flag - do we need to show errors from final checks while user types.
|
||||
*/
|
||||
@NonNull
|
||||
|
|
@ -60,6 +61,7 @@ public abstract class EditTextValidator<TModel extends Serializable> extends Val
|
|||
|
||||
/**
|
||||
* Use this method to get or set final check.
|
||||
*
|
||||
* @return final check.
|
||||
*/
|
||||
@NonNull
|
||||
|
|
@ -69,6 +71,7 @@ public abstract class EditTextValidator<TModel extends Serializable> extends Val
|
|||
|
||||
/**
|
||||
* Use this method to get or set primary check.
|
||||
*
|
||||
* @return primary check.
|
||||
*/
|
||||
@NonNull
|
||||
|
|
@ -104,21 +107,16 @@ public abstract class EditTextValidator<TModel extends Serializable> extends Val
|
|||
primaryCheck.observe().observeOn(Schedulers.computation()),
|
||||
(finalCheck, primaryCheck) -> {
|
||||
try {
|
||||
return validateText(finalCheck, primaryCheck, text, fullCheck);
|
||||
return validateText(finalCheck.get(), primaryCheck.get(), text, fullCheck);
|
||||
} catch (final Throwable exception) {
|
||||
return new HalfNullablePair<>(ValidationState.ERROR_CONVERSION, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Observable<ValidationState> processChecks(@NonNull final String text, final boolean fullCheck) {
|
||||
return createValidationObservable(text, fullCheck)
|
||||
.map(HalfNullablePair::getFirst);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates text with primary check.
|
||||
*
|
||||
* @param text - input text.
|
||||
* @return {@link Observable} with the result of the primary check.
|
||||
*/
|
||||
|
|
@ -129,6 +127,7 @@ public abstract class EditTextValidator<TModel extends Serializable> extends Val
|
|||
|
||||
/**
|
||||
* Validates text with final check.
|
||||
*
|
||||
* @param text - input text.
|
||||
* @return {@link Observable} with the result of the final check.
|
||||
*/
|
||||
|
|
@ -140,15 +139,21 @@ public abstract class EditTextValidator<TModel extends Serializable> extends Val
|
|||
/**
|
||||
* Validates text with primary and final check consequentially and returns {@link Observable} with {@link HalfNullablePair} of final state
|
||||
* and resulting model.
|
||||
*
|
||||
* @param text - input text.
|
||||
* @return pair with final {@link ValidationState} that is always not null and a model that we get after converting the text.
|
||||
* Model can be null if validation fails on primary or final checks.
|
||||
* Model can be null if validation fails on primary or final checks.
|
||||
*/
|
||||
@NonNull
|
||||
@Override
|
||||
public Observable<HalfNullablePair<ValidationState, TModel>> fullValidateAndGetModel(@NonNull final String text) {
|
||||
return createValidationObservable(text, true)
|
||||
.first();
|
||||
return createValidationObservable(text, true);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Observable<ValidationState> processChecks(@NonNull final String text, final boolean fullCheck) {
|
||||
return createValidationObservable(text, fullCheck)
|
||||
.map(HalfNullablePair::getFirst);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -5,19 +5,21 @@ import android.support.annotation.NonNull;
|
|||
|
||||
import java.io.Serializable;
|
||||
|
||||
import io.reactivex.Observable;
|
||||
import ru.touchin.roboswag.core.utils.pairs.HalfNullablePair;
|
||||
import ru.touchin.templates.validation.ValidationState;
|
||||
import rx.Observable;
|
||||
|
||||
/**
|
||||
* Created by Ilia Kurtov on 24/01/2017.
|
||||
* Class that simplifies work with {@link Validator}'s that have the same wrapper model and model type.
|
||||
*
|
||||
* @param <TModel> model that should be bounded with a view.
|
||||
*/
|
||||
public class SameTypeValidator<TModel extends Serializable> extends Validator<TModel, TModel> {
|
||||
|
||||
/**
|
||||
* Simply returns the same model without any converting.
|
||||
*
|
||||
* @param wrapperModel input model.
|
||||
* @return the same model as input parameter.
|
||||
* @throws Throwable - in this case no throwable would be thrown.
|
||||
|
|
@ -31,9 +33,10 @@ public class SameTypeValidator<TModel extends Serializable> extends Validator<TM
|
|||
|
||||
/**
|
||||
* Validates {@link TModel} and returns {@link Observable} with {@link HalfNullablePair} of final state and resulting model.
|
||||
*
|
||||
* @param wrapperModel - not null value that should be validated.
|
||||
* @return pair with final {@link ValidationState} that is always not null and a model that we get after converting the {@link TModel}.
|
||||
* Model can be null if validation fails.
|
||||
* Model can be null if validation fails.
|
||||
*/
|
||||
@NonNull
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -23,11 +23,12 @@ import android.support.annotation.NonNull;
|
|||
|
||||
import java.io.Serializable;
|
||||
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.Single;
|
||||
import ru.touchin.roboswag.core.observables.Changeable;
|
||||
import ru.touchin.roboswag.core.observables.NonNullChangeable;
|
||||
import ru.touchin.roboswag.core.utils.pairs.HalfNullablePair;
|
||||
import ru.touchin.templates.validation.ValidationState;
|
||||
import rx.Observable;
|
||||
|
||||
/**
|
||||
* Created by Ilia Kurtov on 24/01/2017.
|
||||
|
|
@ -54,6 +55,7 @@ public abstract class Validator<TWrapperModel extends Serializable, TModel exten
|
|||
|
||||
/**
|
||||
* This method converts {@link TWrapperModel} into a {@link TModel}.
|
||||
*
|
||||
* @param wrapperModel - not null value that should be converted into a {@link TModel} object.
|
||||
* @return converted wrapperModel into a {@link TModel}.
|
||||
* @throws Throwable for the cases when converting cannot be processed.
|
||||
|
|
@ -63,6 +65,7 @@ public abstract class Validator<TWrapperModel extends Serializable, TModel exten
|
|||
|
||||
/**
|
||||
* Call this method to get {@link Changeable} with {@link TWrapperModel} inside it that should be connected to its bounded view.
|
||||
*
|
||||
* @return {@link Changeable} with {@link TWrapperModel}.
|
||||
*/
|
||||
@NonNull
|
||||
|
|
@ -72,6 +75,7 @@ public abstract class Validator<TWrapperModel extends Serializable, TModel exten
|
|||
|
||||
/**
|
||||
* Returns current {@link ValidationState} or its successor. Needed to connect with bounded view and react to this state changes.
|
||||
*
|
||||
* @return current validation state.
|
||||
*/
|
||||
@NonNull
|
||||
|
|
@ -82,6 +86,7 @@ public abstract class Validator<TWrapperModel extends Serializable, TModel exten
|
|||
/**
|
||||
* This method needed to get {@link ValidationState} that needed to be shown when bounded view is empty and you need to show to user reminder,
|
||||
* that he or she needs to fill this view.
|
||||
*
|
||||
* @return {@link ValidationState} that should be shown for an empty field.
|
||||
*/
|
||||
@NonNull
|
||||
|
|
@ -90,10 +95,11 @@ public abstract class Validator<TWrapperModel extends Serializable, TModel exten
|
|||
}
|
||||
|
||||
/**
|
||||
* Validates {@link TWrapperModel} and returns {@link Observable} with {@link HalfNullablePair} of final state and resulting model.
|
||||
* Validates {@link TWrapperModel} and returns {@link Single} with {@link HalfNullablePair} of final state and resulting model.
|
||||
*
|
||||
* @param wrapperModel - not null value that should be validated.
|
||||
* @return pair with final {@link ValidationState} that is always not null and a model that we get after converting the {@link TWrapperModel}.
|
||||
* Model can be null if validation fails.
|
||||
* Model can be null if validation fails.
|
||||
*/
|
||||
@NonNull
|
||||
public abstract Observable<HalfNullablePair<ValidationState, TModel>> fullValidateAndGetModel(@NonNull final TWrapperModel wrapperModel);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
package ru.touchin.templates.viewmodel
|
||||
|
||||
import android.app.Activity
|
||||
import android.arch.lifecycle.LifecycleOwner
|
||||
import android.arch.lifecycle.ViewModelProvider
|
||||
import android.arch.lifecycle.ViewModelProviders
|
||||
import android.support.v4.app.Fragment
|
||||
import android.support.v4.app.FragmentActivity
|
||||
import ru.touchin.roboswag.components.navigation.viewcontrollers.ViewController
|
||||
|
||||
object LifecycleViewModelProviders {
|
||||
|
||||
/**
|
||||
* Creates a {@link ViewModelProvider}, which retains ViewModels while a scope of given
|
||||
* {@code lifecycleOwner} is alive. More detailed explanation is in {@link ViewModel}.
|
||||
* <p>
|
||||
* It uses the given {@link Factory} to instantiate new ViewModels.
|
||||
*
|
||||
* @param lifecycleOwner a lifecycle owner, in whose scope ViewModels should be retained (ViewController, Fragment, Activity)
|
||||
* @param factory a {@code Factory} to instantiate new ViewModels
|
||||
* @return a ViewModelProvider instance
|
||||
*/
|
||||
fun of(lifecycleOwner: LifecycleOwner, factory: ViewModelProvider.Factory = getViewModelFactory(lifecycleOwner)): ViewModelProvider =
|
||||
when (lifecycleOwner) {
|
||||
is ViewController<*, *> -> ViewModelProviders.of(lifecycleOwner.fragment, factory)
|
||||
is Fragment -> ViewModelProviders.of(lifecycleOwner, factory)
|
||||
is FragmentActivity -> ViewModelProviders.of(lifecycleOwner, factory)
|
||||
else -> throw IllegalArgumentException("Not supported LifecycleOwner.")
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns ViewModelProvider.Factory instance from current lifecycleOwner.
|
||||
* Search #ViewModelFactoryProvider are produced according to priorities:
|
||||
* 1. View controller;
|
||||
* 2. Fragment;
|
||||
* 3. Parent fragment recursively;
|
||||
* 4. Activity;
|
||||
* 5. Application.
|
||||
*/
|
||||
fun getViewModelFactory(provider: Any): ViewModelProvider.Factory =
|
||||
when (provider) {
|
||||
is ViewModelFactoryProvider -> provider.viewModelFactory
|
||||
is ViewController<*, *> -> getViewModelFactory(provider.fragment)
|
||||
is Fragment -> getViewModelFactory(provider.parentFragment ?: provider.requireActivity())
|
||||
is Activity -> getViewModelFactory(provider.application)
|
||||
else -> throw IllegalArgumentException("View model factory not found.")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package ru.touchin.templates.viewmodel
|
||||
|
||||
import android.arch.lifecycle.ViewModel
|
||||
import android.support.annotation.CallSuper
|
||||
import ru.touchin.roboswag.components.utils.destroyable.BaseDestroyable
|
||||
import ru.touchin.roboswag.components.utils.destroyable.Destroyable
|
||||
import ru.touchin.templates.livedata.BaseLiveDataDispatcher
|
||||
import ru.touchin.templates.livedata.LiveDataDispatcher
|
||||
|
||||
/**
|
||||
* Created by Denis Karmyshakov on 14.03.18.
|
||||
* Base class of ViewModel with [io.reactivex.disposables.Disposable] handling.
|
||||
*/
|
||||
open class RxViewModel(
|
||||
private val destroyable: BaseDestroyable = BaseDestroyable(),
|
||||
private val liveDataDispatcher: BaseLiveDataDispatcher = BaseLiveDataDispatcher(destroyable)
|
||||
) : ViewModel(), Destroyable by destroyable, LiveDataDispatcher by liveDataDispatcher {
|
||||
|
||||
@CallSuper
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
destroyable.onDestroy()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package ru.touchin.templates.viewmodel
|
||||
|
||||
import android.arch.lifecycle.ViewModel
|
||||
import android.arch.lifecycle.ViewModelProvider
|
||||
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Provider
|
||||
|
||||
class ViewModelFactory @Inject constructor(
|
||||
private val creators: Map<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>
|
||||
) : ViewModelProvider.Factory {
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T
|
||||
= (creators[modelClass]?.get() as? T) ?: throw IllegalArgumentException("Unknown model class $modelClass")
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package ru.touchin.templates.viewmodel
|
||||
|
||||
interface ViewModelFactoryProvider {
|
||||
|
||||
val viewModelFactory: ViewModelFactory
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue