Compare commits

..

82 Commits
master ... mvvm

Author SHA1 Message Date
Anton Domnikov 6a92e73c12
fixed: notify all items in range (#110) 2018-09-14 16:01:45 +03:00
Denis Karmyshakov 90abae925d untilDestroy moved to extension 2018-07-03 15:01:49 +03:00
Denis Karmyshakov 5692f4c407 Joda time converter with custom formatter 2018-06-09 14:52:15 +03:00
maxbach fb0dc47b82
Fix live data generics (#109) 2018-06-06 14:45:57 +03:00
Denis Karmyshakov 33903efadc dispatchTo methods now returns Disposable 2018-05-25 21:09:47 +03:00
Denis Karmyshakov 5c9e255350 Remove Singleton scope from ViewModelFactory 2018-05-17 17:35:58 +03:00
Denis Karmyshakov 7eb1d8bb21 Removed TFragment generic in ViewController 2018-04-21 02:38:51 +03:00
Denis Karmyshakov aaafae44e5 LifecycleViewModelProviders and ViewModelFactoryProvider 2018-03-27 17:45:54 +03:00
Denis Karmyshakov f84fae765d Flowable handling, SingleLiveEvent 2018-03-27 17:43:51 +03:00
Denis Karmyshakov fffda150e5 Renaming base ViewModel class, added constant for retrofit version 2018-03-16 17:55:33 +03:00
Denis Karmyshakov 87fd17084e Added default ViewModelFactory for DI 2018-03-15 18:35:44 +03:00
Denis Karmyshakov 11d2b2de43 Added live data dispatcher from rx sources 2018-03-14 20:00:17 +03:00
Denis Karmyshakov f12c9414eb Migration to AAC lifecycle, removed not common dependencies 2018-03-14 15:52:10 +03:00
Denis Karmyshakov 7250a60f1d LoganSquare update (#104) 2018-02-16 18:15:00 +03:00
Denis Karmyshakov 38e52e5ba9 Enum serialization (#102) 2018-02-09 14:57:21 +03:00
Anton Domnikov 7a3c3f11fd fixed parsing when date is empty (#100) 2018-02-02 17:04:11 +03:00
Denis Karmyshakov 1e3acae437
Merge pull request #98 from TouchInstinct/gradle_update
Gradle update
2017-12-01 16:11:29 +03:00
Denis Karmyshakov 25492e93da Gradle update 2017-11-30 18:02:23 +03:00
Arseniy Borisov 90d88a9e97 LoganSquareBigDecimalConverter (#96) 2017-11-15 17:31:25 +03:00
Ilia Kurtov 3fcf47c4f1 fixed internet observable 2017-10-23 20:20:26 +03:00
Ilia Kurtov e7be976085 Merge pull request #95 from TouchInstinct/feature/network_connection
feature/network_connection
2017-10-06 15:37:36 +03:00
Oleg f0965d3dfa small fix 2017-10-06 15:29:18 +03:00
Oleg cee02ad925 add new method 2017-10-06 14:23:57 +03:00
Oleg d004fb7686 Merge pull request #94 from TouchInstinct/update/gradle
Versions in constants
2017-10-05 15:52:28 +03:00
Oleg 21c368cc39 Versions in constants 2017-10-05 15:51:23 +03:00
Denis Karmyshakov 617ce9494a Merge pull request #92 from TouchInstinct/versions_in_constants
Versions in constants
2017-10-04 12:28:51 +03:00
Denis Karmyshakov 2b3ee88ea7 Versions in constants 2017-10-04 12:25:04 +03:00
Oleg c436d06efd buildToolsVersion 26.0.2 (#90) 2017-09-29 14:52:59 +03:00
Ilia Kurtov 96f91a0da1 Merge pull request #89 from TouchInstinct/update/rxjava
update rxjava
2017-09-22 16:43:05 +03:00
Ilia Kurtov 2790890222 update rxjava 2017-09-22 16:38:02 +03:00
Anton Domnikov 8d77683453 Merge pull request #88 from TouchInstinct/feature/network_connection_observable
Feature/network connection observable
2017-09-20 16:27:21 +03:00
Anton Domnikov b23ee3581c Merge branch 'master-kotlin-rxjava2' into feature/network_connection_observable 2017-09-20 16:26:23 +03:00
Anton Domnikov 1fa0aba9db CR fixes 2017-09-20 16:26:00 +03:00
Ilia Kurtov 164a9eee9c Merge pull request #87 from TouchInstinct/feature/network_connection_observable
added network connection observable
2017-09-20 14:42:59 +03:00
Anton Domnikov e3edb0a7a7 added network connection observable 2017-09-19 18:18:28 +03:00
Arseniy Borisov 46295137a8 format javadoc (#83) 2017-08-17 14:46:17 +03:00
Ilia Kurtov e1b58249b9 rxjava update (#82) 2017-08-15 21:02:36 +03:00
Ilia Kurtov 2a0e33c6e7 libs update (#81) 2017-08-14 17:41:04 +03:00
Ilia Kurtov be01ad5792 Merge branch 'kotlin_migration' into master-kotlin-rxjava2
# Conflicts:
#	build.gradle
2017-08-11 13:51:55 +03:00
Arseniy Borisov 861675674f fresco 1.5.0 2017-08-10 19:57:31 +03:00
Denis Karmyshakov 3aacc52b04 Support lib update (#78) 2017-08-03 18:22:25 +03:00
gorodeckii 22da8c58e8 Merge branch 'master' into master-rx-java-2
# Conflicts:
#	build.gradle
2017-08-01 19:21:42 +03:00
Elena Bobkova bc1ffc1b35 fixed support library version 2017-07-27 17:42:36 +03:00
gorodeckii abaf35300b indents in comments to pass static 2017-07-26 18:13:16 +03:00
Arseniy Borisov dd08a07afc Merge pull request #75 from TouchInstinct/build_tools_update
Build tools update
2017-07-25 19:58:02 +03:00
Denis Karmyshakov ccd4b80011 Build tools update 2017-07-25 19:54:06 +03:00
Arseniy Borisov b5554a6761 Merge branch 'master-rx-java-2' into kotlin_migration 2017-07-24 17:01:40 +03:00
Denis Karmyshakov af8569d147 Merge pull request #74 from TouchInstinct/idea_formatting
idea formatting
2017-07-24 12:44:44 +03:00
Arseniy Borisov 4b0fd5945f idea formatting 2017-07-24 12:28:57 +03:00
Anton Domnikov 488d15a22b Merge branch 'master-rx-java-2' into kotlin_migration
# Conflicts:
#	build.gradle
2017-07-20 13:33:35 +03:00
Alexander Bubnov 2013a7ad56 Merge branch 'lib_upd' into master-rx-java-2 2017-07-17 14:46:12 +03:00
Alexander Bubnov 129bbf5b18 update libs 2017-07-17 14:15:33 +03:00
gorodeckii 3bf46be7e1 Merge branch 'master' into master-rx-java-2 2017-07-13 17:48:47 +03:00
gorodeckii e0f57761e7 Merge branch 'master' into master-rx-java-2
# Conflicts:
#	src/main/java/ru/touchin/templates/chat/Chat.java
2017-07-13 16:33:03 +03:00
Anton Domnikov a73fed8fee Merge branch 'master-rx-java-2' into kotlin_migration 2017-07-11 14:31:22 +03:00
gorodeckii c79a77f52a Merge branch 'master' into master-rx-java-2 2017-07-05 15:04:31 +03:00
Gavriil 435ab8617c Merge pull request #64 from TouchInstinct/master-rx-java-2-maybe
add maybe to bindable
2017-06-30 15:24:55 +03:00
Alexander Bubnov b970b7c673 add maybe to bindable 2017-06-30 15:13:33 +03:00
Anton Domnikov 9d6aa9c445 Merge branch 'master-rx-java-2' into kotlin_migration 2017-06-23 13:31:52 +03:00
Alexander Bubnov e3666b32b3 update rxJava2 to 2.1.1 2017-06-21 17:04:42 +03:00
Gavriil e97bce4061 Merge pull request #63 from TouchInstinct/bugs/network_type
fix getNetworkType
2017-06-21 15:12:44 +03:00
Anton Domnikov a2fe86ec09 revert retrolambda 2017-06-19 18:23:48 +03:00
Anton Domnikov a2f2dc5c88 remove retrolambda 2017-06-14 17:52:44 +03:00
gorodeckii 5ef88a5ccc Merge branch 'master' into master-rx-java-2 2017-06-09 15:51:20 +03:00
Gavriil Sitnikov d9878a8035 Merge branch 'master' into master-rx-java-2 2017-06-08 18:43:42 +03:00
Gavriil Sitnikov bd63ca8570 Merge branch 'calendar_init_fix' into master-rx-java-2 2017-06-08 16:50:27 +03:00
Gavriil Sitnikov 876f634923 Merge branch 'master' into master-rx-java-2 2017-06-08 14:59:48 +03:00
Gavriil Sitnikov 29b5d1b95e Merge branch 'master' into master-rx-java-2 2017-06-08 14:22:13 +03:00
Elena Bobkova 0a9c3d531d Merge branch 'master' into master-rx-java-2 2017-06-01 15:14:59 +03:00
Elena Bobkova 169f4ffec8 Merge branch 'master' into master-rx-java-2
# Conflicts:
#	build.gradle
2017-06-01 14:53:06 +03:00
gorodeckii c664bf7314 retrofit version 2017-05-15 16:36:29 +03:00
Gavriil Sitnikov cfa1c96229 compile error fixed 2017-05-12 20:11:05 +03:00
Gavriil Sitnikov ef2a6efb90 google son removed 2017-05-12 19:45:20 +03:00
Gavriil Sitnikov 7b12898a76 Merge branch 'feature/general_improvements' into master-rx-java-2
# Conflicts:
#	build.gradle
#	src/main/java/ru/touchin/templates/TouchinApp.java
2017-05-12 19:33:14 +03:00
Arhipov 317bcce28e Merge branch 'master' into master-rx-java-2
# Conflicts:
#	src/main/java/ru/touchin/templates/TouchinApp.java
2017-05-04 14:10:06 +03:00
gorodeckii 0b4e2eb14d rxjava version + static fix 2017-05-03 09:37:15 +03:00
Gavriil Sitnikov 6266c8356b Merge branch 'feature/general_improvements' into master-rx-java-2
Conflicts:
	build.gradle
2017-04-28 02:10:09 +03:00
Gavriil Sitnikov 2efd0a5b29 static fixes 2017-04-21 00:10:56 +03:00
Gavriil Sitnikov bf045ba8e2 Merge branch 'feature/general_improvements' into master-rx-java-2 2017-04-20 19:42:30 +03:00
Gavriil cf47237e49 Merge pull request #48 from TouchInstinct/rxjava2/validation
validation
2017-04-18 14:40:52 +03:00
Ilia Kurtov 6e73850d06 validation 2017-04-18 14:04:50 +03:00
Gavriil Sitnikov 468da1f2b9 RxJava2 migration 2017-04-17 03:12:19 +03:00
40 changed files with 514 additions and 1628 deletions

View File

@ -1,4 +1,5 @@
apply plugin: 'com.android.library' apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android { android {
compileSdkVersion compileSdk compileSdkVersion compileSdk
@ -13,42 +14,29 @@ android {
} }
} }
repositories {
maven { url 'https://maven.fabric.io/public' }
maven { url "http://dl.bintray.com/touchin/touchin-tools" }
}
dependencies { dependencies {
api project(path: ':libraries:components') api project(path: ':libraries:components')
api 'javax.inject:javax.inject:1'
api 'net.danlew:android.joda:2.9.9' api 'net.danlew:android.joda:2.9.9'
api 'com.android.support:multidex:1.0.2' api 'com.android.support:multidex:1.0.3'
api "io.reactivex:rxandroid:$rxAndroidVersion"
api "io.reactivex:rxjava:$rxJavaVersion" api "io.reactivex.rxjava2:rxandroid:$rxAndroidVersion"
api "io.reactivex.rxjava2:rxjava:$rxJavaVersion"
compileOnly "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
compileOnly "com.android.support:appcompat-v7:$supportLibraryVersion"
compileOnly "com.android.support:recyclerview-v7:$supportLibraryVersion" compileOnly "com.android.support:recyclerview-v7:$supportLibraryVersion"
compileOnly "com.android.support:appcompat-v7:$supportLibraryVersion"
compileOnly 'com.squareup.retrofit2:retrofit:2.3.0' compileOnly "android.arch.lifecycle:extensions:$lifecycleVersion"
compileOnly('com.google.http-client:google-http-client-jackson2:1.23.0') {
exclude(group: 'org.apache.httpcomponents', module: 'httpclient')
}
compileOnly 'com.facebook.fresco:fresco:1.5.0' compileOnly "com.squareup.retrofit2:retrofit:$retrofitVersion"
compileOnly 'ru.touchin:logansquare:1.4.1'
compileOnly 'com.scottyab:aes-crypto:0.0.4' compileOnly 'ru.touchin:logansquare:1.4.3'
// don't use latest(1.0 and above) because they don't support Socket.IO server 1.x version compileOnly("com.crashlytics.sdk.android:crashlytics:$crashlyticsVersion@aar") {
//noinspection NewerVersionAvailable
compileOnly('io.socket:socket.io-client:0.9.0') {
exclude group: 'org.json', module: 'json'
}
compileOnly('com.crashlytics.sdk.android:crashlytics:2.6.8@aar') {
transitive = true transitive = true
} }
compileOnly 'com.facebook.stetho:stetho:1.5.0'
} }

View File

@ -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() {
}
}

View File

@ -19,7 +19,9 @@
package ru.touchin.templates; package ru.touchin.templates;
import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
@ -27,15 +29,19 @@ import android.net.NetworkInfo;
import android.net.wifi.WifiManager; import android.net.wifi.WifiManager;
import android.provider.Settings; import android.provider.Settings;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.telephony.TelephonyManager; import android.telephony.TelephonyManager;
import android.text.TextUtils; import android.text.TextUtils;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.util.UUID; 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.log.Lc;
import ru.touchin.roboswag.core.observables.RxAndroidUtils; import ru.touchin.roboswag.core.observables.RxAndroidUtils;
import rx.Observable;
/** /**
* Utility class that is providing common methods related to android device. * Utility class that is providing common methods related to android device.
@ -171,6 +177,52 @@ public final class DeviceUtils {
.distinctUntilChanged(); .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() { 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));
}
}
}
} }

View File

@ -21,7 +21,7 @@ package ru.touchin.templates;
import android.app.ActivityManager; import android.app.ActivityManager;
import android.content.Intent; import android.content.Intent;
import android.graphics.BitmapFactory; import android.graphics.drawable.BitmapDrawable;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.ColorRes; import android.support.annotation.ColorRes;
@ -30,17 +30,14 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import ru.touchin.roboswag.components.navigation.activities.ViewControllerActivity; import ru.touchin.roboswag.components.navigation.activities.BaseActivity;
import ru.touchin.roboswag.components.utils.Logic;
import ru.touchin.roboswag.core.log.Lc; import ru.touchin.roboswag.core.log.Lc;
/** /**
* Created by Gavriil Sitnikov on 11/03/16. * Created by Gavriil Sitnikov on 11/03/16.
* Base class of activity to extends for Touch Instinct related projects. * 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 @Override
protected void onCreate(@Nullable final Bundle savedInstanceState) { protected void onCreate(@Nullable final Bundle savedInstanceState) {
@ -62,14 +59,9 @@ public abstract class TouchinActivity<TLogic extends Logic> extends ViewControll
* @param primaryColorRes Color of application to show in task bar. * @param primaryColorRes Color of application to show in task bar.
*/ */
protected void setupTaskDescriptor(@NonNull final String label, @DrawableRes final int iconRes, @ColorRes final int primaryColorRes) { protected void setupTaskDescriptor(@NonNull final String label, @DrawableRes final int iconRes, @ColorRes final int primaryColorRes) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
final ActivityManager.TaskDescription taskDescription = new ActivityManager.TaskDescription(label, final ActivityManager.TaskDescription taskDescription = new ActivityManager.TaskDescription(label,
iconRes, ((BitmapDrawable) ContextCompat.getDrawable(this, iconRes)).getBitmap(),
ContextCompat.getColor(this, primaryColorRes));
setTaskDescription(taskDescription);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
final ActivityManager.TaskDescription taskDescription = new ActivityManager.TaskDescription(label,
BitmapFactory.decodeResource(getResources(), iconRes),
ContextCompat.getColor(this, primaryColorRes)); ContextCompat.getColor(this, primaryColorRes));
setTaskDescription(taskDescription); setTaskDescription(taskDescription);
} }

View File

@ -29,7 +29,6 @@ import android.support.multidex.MultiDex;
import android.util.Log; import android.util.Log;
import com.crashlytics.android.Crashlytics; import com.crashlytics.android.Crashlytics;
import com.facebook.stetho.Stetho;
import net.danlew.android.joda.JodaTimeAndroid; import net.danlew.android.joda.JodaTimeAndroid;
@ -38,7 +37,11 @@ import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import io.fabric.sdk.android.Fabric; 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.navigation.fragments.ViewControllerFragment;
import ru.touchin.roboswag.components.utils.UiUtils; import ru.touchin.roboswag.components.utils.UiUtils;
import ru.touchin.roboswag.components.views.TypefacedEditText; 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.LcLevel;
import ru.touchin.roboswag.core.log.LogProcessor; import ru.touchin.roboswag.core.log.LogProcessor;
import ru.touchin.roboswag.core.utils.ShouldNotHappenException; 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. * Created by Gavriil Sitnikov on 10/03/16.
@ -82,27 +78,15 @@ public abstract class TouchinApp extends Application {
@Override @Override
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
RxAndroidPlugins.getInstance().registerSchedulersHook(new RxAndroidSchedulersHook() { RxAndroidPlugins.setMainThreadSchedulerHandler(schedulerCallable -> MainThreadScheduler.INSTANCE);
@NonNull
@Override
public Scheduler getMainThreadScheduler() {
return new MainThreadScheduler();
}
});
JodaTimeAndroid.init(this); JodaTimeAndroid.init(this);
if (isDebug()) { if (isDebug()) {
enableStrictMode(); enableStrictMode();
ObservableCollectionAdapter.setInDebugMode();
ViewControllerFragment.setInDebugMode(); ViewControllerFragment.setInDebugMode();
TypefacedEditText.setInDebugMode(); TypefacedEditText.setInDebugMode();
TypefacedTextView.setInDebugMode(); TypefacedTextView.setInDebugMode();
Lc.initialize(new ConsoleLogProcessor(LcLevel.VERBOSE), true); Lc.initialize(new ConsoleLogProcessor(LcLevel.VERBOSE), true);
UiUtils.UI_LIFECYCLE_LC_GROUP.disable(); 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 { } else {
try { try {
final Crashlytics crashlytics = new Crashlytics(); final Crashlytics crashlytics = new Crashlytics();
@ -189,6 +173,8 @@ public abstract class TouchinApp extends Application {
*/ */
private static class MainThreadScheduler extends Scheduler { private static class MainThreadScheduler extends Scheduler {
public static final MainThreadScheduler INSTANCE = new MainThreadScheduler();
@NonNull @NonNull
@Override @Override
public Worker createWorker() { public Worker createWorker() {
@ -202,28 +188,28 @@ public abstract class TouchinApp extends Application {
@NonNull @NonNull
@Override @Override
public Subscription schedule(@NonNull final Action0 action) { public Disposable schedule(@NonNull final Runnable action) {
if (Looper.getMainLooper().equals(Looper.myLooper())) { if (Looper.getMainLooper().equals(Looper.myLooper())) {
action.call(); action.run();
return Subscriptions.unsubscribed(); return Disposables.disposed();
} }
return parentWorker.schedule(action); return parentWorker.schedule(action);
} }
@NonNull @NonNull
@Override @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); return parentWorker.schedule(action, delayTime, unit);
} }
@Override @Override
public void unsubscribe() { public void dispose() {
parentWorker.unsubscribe(); parentWorker.dispose();
} }
@Override @Override
public boolean isUnsubscribed() { public boolean isDisposed() {
return parentWorker.isUnsubscribed(); return parentWorker.isDisposed();
} }
} }

View File

@ -21,269 +21,29 @@ package ru.touchin.templates;
import android.app.Service; import android.app.Service;
import android.content.Intent; import android.content.Intent;
import android.os.Handler;
import android.os.IBinder; import android.os.IBinder;
import android.support.annotation.NonNull; 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.UiUtils;
import ru.touchin.roboswag.components.utils.destroyable.BaseDestroyable;
import ru.touchin.roboswag.core.log.Lc; import ru.touchin.roboswag.core.log.Lc;
import ru.touchin.roboswag.core.utils.ServiceBinder; 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. * Created by Gavriil Sitnikov on 10/01/17.
* Base class of service to extends for Touch Instinct related projects. * 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 @NonNull
private final Handler postHandler = new Handler(); protected final BaseDestroyable destroyable = new BaseDestroyable();
@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();
@Override @Override
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this)); 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 @NonNull
@Override @Override
public IBinder onBind(@NonNull final Intent intent) { public IBinder onBind(@NonNull final Intent intent) {
@ -299,15 +59,9 @@ public abstract class TouchinService<TLogic extends Logic> extends Service {
@Override @Override
public void onDestroy() { public void onDestroy() {
UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this)); UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this));
postHandler.removeCallbacksAndMessages(null); destroyable.onDestroy();
isCreatedSubject.onNext(false);
super.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));
}
} }

View File

@ -132,7 +132,7 @@ public abstract class CalendarAdapter<TDayViewHolder extends RecyclerView.ViewHo
notifyItemRangeChanged(startSelectionPosition, 1); notifyItemRangeChanged(startSelectionPosition, 1);
return; return;
} }
notifyItemRangeChanged(startSelectionPosition, endSelectionPosition - startSelectionPosition); notifyItemRangeChanged(startSelectionPosition, endSelectionPosition - startSelectionPosition + 1);
} }
@NonNull @NonNull

View File

@ -1,237 +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);
}
/**
* Method to cancel sending current message.
*/
@NonNull
public Observable<?> observeCancelEvent(@NonNull final TOutgoingMessage message) {
return Observable.never();
}
/**
* 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())
.takeUntil(observeCancelEvent(message))
.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;
}
}

View File

@ -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);
}
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}
}
}

View File

@ -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
}
}

View File

@ -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;
}
}

View File

@ -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) })
}
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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()
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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);
}
}
}

View File

@ -22,12 +22,14 @@ package ru.touchin.templates.logansquare;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.text.TextUtils;
import com.bluelinelabs.logansquare.typeconverters.TypeConverter; import com.bluelinelabs.logansquare.typeconverters.TypeConverter;
import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonParser;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormatter;
import java.io.IOException; import java.io.IOException;
@ -38,6 +40,17 @@ import ru.touchin.roboswag.core.log.Lc;
*/ */
public class LoganSquareJodaTimeConverter implements TypeConverter<DateTime> { 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 @Nullable
@Override @Override
public DateTime parse(@NonNull final JsonParser jsonParser) throws IOException { public DateTime parse(@NonNull final JsonParser jsonParser) throws IOException {
@ -54,15 +67,17 @@ public class LoganSquareJodaTimeConverter implements TypeConverter<DateTime> {
} }
@Override @Override
public void serialize(@Nullable final DateTime object, public void serialize(
@Nullable final String fieldName, @Nullable final DateTime object,
final boolean writeFieldNameForObject, @Nullable final String fieldName,
@NonNull final JsonGenerator jsonGenerator) final boolean writeFieldNameForObject,
throws IOException { @NonNull final JsonGenerator jsonGenerator
) throws IOException {
final String serializedValue = object != null ? object.toString(formatter) : null;
if (fieldName != null) { if (fieldName != null) {
jsonGenerator.writeStringField(fieldName, object != null && !object.toString().isEmpty() ? object.toString() : null); jsonGenerator.writeStringField(fieldName, !TextUtils.isEmpty(serializedValue) ? serializedValue : null);
} else { } else {
jsonGenerator.writeString(object != null && !object.toString().isEmpty() ? object.toString() : null); jsonGenerator.writeString(!TextUtils.isEmpty(serializedValue) ? serializedValue : null);
} }
} }

View File

@ -32,7 +32,7 @@ import java.util.List;
import ru.touchin.roboswag.components.utils.storables.PreferenceStore; import ru.touchin.roboswag.components.utils.storables.PreferenceStore;
import ru.touchin.roboswag.core.observables.storable.Converter; import ru.touchin.roboswag.core.observables.storable.Converter;
import ru.touchin.roboswag.core.observables.storable.Storable; 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; import ru.touchin.roboswag.core.utils.ShouldNotHappenException;
/** /**

View File

@ -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);
}
}

View File

@ -28,6 +28,8 @@ import java.nio.charset.Charset;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import io.reactivex.Observable;
import io.reactivex.schedulers.Schedulers;
import okhttp3.Call; import okhttp3.Call;
import okhttp3.Headers; import okhttp3.Headers;
import okhttp3.HttpUrl; import okhttp3.HttpUrl;
@ -40,8 +42,6 @@ import okhttp3.ResponseBody;
import okio.Buffer; import okio.Buffer;
import ru.touchin.roboswag.core.log.Lc; import ru.touchin.roboswag.core.log.Lc;
import ru.touchin.roboswag.core.log.LcLevel; import ru.touchin.roboswag.core.log.LcLevel;
import rx.Observable;
import rx.schedulers.Schedulers;
/** /**
* Created by Gavriil Sitnikov on 13/11/2015. * Created by Gavriil Sitnikov on 13/11/2015.
@ -208,7 +208,7 @@ public abstract class HttpRequest<T> {
.fromCallable(() -> executeSyncInternal(requestController)) .fromCallable(() -> executeSyncInternal(requestController))
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io()) .unsubscribeOn(Schedulers.io())
.doOnUnsubscribe(requestController.call::cancel)); .doOnDispose(requestController.call::cancel));
} }
/** /**

View File

@ -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");
}
}
}
}

View File

@ -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();
}
}

View File

@ -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;
}

View File

@ -4,11 +4,13 @@ import android.support.annotation.NonNull;
import java.io.Serializable; import java.io.Serializable;
import io.reactivex.functions.Function;
/** /**
* Created by Ilia Kurtov on 30/01/2017. * 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. * 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. * 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 <TInput> input type.
* @param <TReturn> return type. * @param <TReturn> return type.

View File

@ -21,9 +21,9 @@ package ru.touchin.templates.validation.validationcontrollers;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import io.reactivex.Observable;
import ru.touchin.templates.validation.ValidationState; import ru.touchin.templates.validation.ValidationState;
import ru.touchin.templates.validation.validators.SameTypeValidator; import ru.touchin.templates.validation.validators.SameTypeValidator;
import rx.Observable;
/** /**
* Created by Ilia Kurtov on 24/01/2017. * Created by Ilia Kurtov on 24/01/2017.
@ -46,7 +46,7 @@ public class BooleanValidationController extends ValidationController<Boolean, B
public Observable<?> validation(@NonNull final Observable<Boolean> activatedObservable) { public Observable<?> validation(@NonNull final Observable<Boolean> activatedObservable) {
return Observable.combineLatest(activatedObservable, getValidator().getWrapperModel().observe(), return Observable.combineLatest(activatedObservable, getValidator().getWrapperModel().observe(),
(activated, flag) -> { (activated, flag) -> {
final boolean selected = flag == null ? false : flag; final boolean selected = flag.get() == null ? false : flag.get();
if (activated && !selected) { if (activated && !selected) {
return ValidationState.ERROR_NO_DESCRIPTION; return ValidationState.ERROR_NO_DESCRIPTION;
} else if (!activated && !selected) { } else if (!activated && !selected) {

View File

@ -25,10 +25,11 @@ import android.text.TextUtils;
import java.io.Serializable; 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.roboswag.core.utils.pairs.NonNullPair;
import ru.touchin.templates.validation.ValidationState; import ru.touchin.templates.validation.ValidationState;
import ru.touchin.templates.validation.validators.EditTextValidator; import ru.touchin.templates.validation.validators.EditTextValidator;
import rx.Observable;
/** /**
* Created by Ilia Kurtov on 24/01/2017. * Created by Ilia Kurtov on 24/01/2017.
@ -75,17 +76,17 @@ public class EditTextValidationController<TModel extends Serializable>
} }
@Nullable @Nullable
@SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity"})
private NonNullPair<Boolean, Observable<ValidationState>> getValidationPair(final boolean activated, private NonNullPair<Boolean, Observable<ValidationState>> getValidationPair(final boolean activated,
@Nullable final String text, @NonNull final Optional<String> optionalText,
@Nullable final Boolean focusIn, @Nullable final Boolean focusIn,
final boolean showError) { final boolean showError) {
final String text = optionalText.get();
if (focusIn == null && TextUtils.isEmpty(text) && !activated && !showError) { if (focusIn == null && TextUtils.isEmpty(text) && !activated && !showError) {
return null; return null;
} }
final boolean focus = focusIn == null ? false : focusIn; final boolean focus = focusIn != null && focusIn;
if (TextUtils.isEmpty(text)) { if (TextUtils.isEmpty(text)) {
return new NonNullPair<>(focus, (activated || showError) return new NonNullPair<>(focus, activated || showError
? getValidator().getValidationStateWhenEmpty().observe() ? getValidator().getValidationStateWhenEmpty().observe()
: Observable.just(ValidationState.INITIAL)); : Observable.just(ValidationState.INITIAL));
} }

View File

@ -4,9 +4,9 @@ import android.support.annotation.NonNull;
import java.io.Serializable; import java.io.Serializable;
import io.reactivex.Observable;
import ru.touchin.templates.validation.ValidationState; import ru.touchin.templates.validation.ValidationState;
import ru.touchin.templates.validation.validators.Validator; import ru.touchin.templates.validation.validators.Validator;
import rx.Observable;
/** /**
* Created by Ilia Kurtov on 24/01/2017. * Created by Ilia Kurtov on 24/01/2017.

View File

@ -24,12 +24,13 @@ import android.support.annotation.Nullable;
import java.io.Serializable; 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.ValidationState;
import ru.touchin.templates.validation.ViewWithError; import ru.touchin.templates.validation.ViewWithError;
import ru.touchin.templates.validation.validators.Validator; 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. * Created by Ilia Kurtov on 24/01/2017.
@ -62,8 +63,9 @@ public class ValidationController
*/ */
@NonNull @NonNull
public Observable<?> modelAndViewUpdating(@Nullable final Observable<TWrapperModel> viewStateObservable, public Observable<?> modelAndViewUpdating(@Nullable final Observable<TWrapperModel> viewStateObservable,
@NonNull final Action1<TWrapperModel> updateViewAction, @NonNull final Consumer<Optional<TWrapperModel>> updateViewAction,
@NonNull final ViewWithError viewWithError) { @NonNull final ViewWithError viewWithError) {
final Observable<?> stateObservable = viewStateObservable != null final Observable<?> stateObservable = viewStateObservable != null
? viewStateObservable.doOnNext(flag -> getValidator().getWrapperModel().set(flag)) ? viewStateObservable.doOnNext(flag -> getValidator().getWrapperModel().set(flag))
: Observable.empty(); : Observable.empty();

View File

@ -24,13 +24,13 @@ import android.support.annotation.Nullable;
import java.io.Serializable; 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.Changeable;
import ru.touchin.roboswag.core.observables.NonNullChangeable; import ru.touchin.roboswag.core.observables.NonNullChangeable;
import ru.touchin.roboswag.core.utils.pairs.HalfNullablePair; import ru.touchin.roboswag.core.utils.pairs.HalfNullablePair;
import ru.touchin.templates.validation.ValidationFunc; import ru.touchin.templates.validation.ValidationFunc;
import ru.touchin.templates.validation.ValidationState; import ru.touchin.templates.validation.ValidationState;
import rx.Observable;
import rx.schedulers.Schedulers;
/** /**
* Created by Ilia Kurtov on 24/01/2017. * Created by Ilia Kurtov on 24/01/2017.
@ -107,19 +107,13 @@ public abstract class EditTextValidator<TModel extends Serializable> extends Val
primaryCheck.observe().observeOn(Schedulers.computation()), primaryCheck.observe().observeOn(Schedulers.computation()),
(finalCheck, primaryCheck) -> { (finalCheck, primaryCheck) -> {
try { try {
return validateText(finalCheck, primaryCheck, text, fullCheck); return validateText(finalCheck.get(), primaryCheck.get(), text, fullCheck);
} catch (final Throwable exception) { } catch (final Throwable exception) {
return new HalfNullablePair<>(ValidationState.ERROR_CONVERSION, null); 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. * Validates text with primary check.
* *
@ -153,8 +147,13 @@ public abstract class EditTextValidator<TModel extends Serializable> extends Val
@NonNull @NonNull
@Override @Override
public Observable<HalfNullablePair<ValidationState, TModel>> fullValidateAndGetModel(@NonNull final String text) { public Observable<HalfNullablePair<ValidationState, TModel>> fullValidateAndGetModel(@NonNull final String text) {
return createValidationObservable(text, true) return createValidationObservable(text, true);
.first(); }
@NonNull
private Observable<ValidationState> processChecks(@NonNull final String text, final boolean fullCheck) {
return createValidationObservable(text, fullCheck)
.map(HalfNullablePair::getFirst);
} }
} }

View File

@ -5,9 +5,9 @@ import android.support.annotation.NonNull;
import java.io.Serializable; import java.io.Serializable;
import io.reactivex.Observable;
import ru.touchin.roboswag.core.utils.pairs.HalfNullablePair; import ru.touchin.roboswag.core.utils.pairs.HalfNullablePair;
import ru.touchin.templates.validation.ValidationState; import ru.touchin.templates.validation.ValidationState;
import rx.Observable;
/** /**
* Created by Ilia Kurtov on 24/01/2017. * Created by Ilia Kurtov on 24/01/2017.

View File

@ -23,11 +23,12 @@ import android.support.annotation.NonNull;
import java.io.Serializable; 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.Changeable;
import ru.touchin.roboswag.core.observables.NonNullChangeable; import ru.touchin.roboswag.core.observables.NonNullChangeable;
import ru.touchin.roboswag.core.utils.pairs.HalfNullablePair; import ru.touchin.roboswag.core.utils.pairs.HalfNullablePair;
import ru.touchin.templates.validation.ValidationState; import ru.touchin.templates.validation.ValidationState;
import rx.Observable;
/** /**
* Created by Ilia Kurtov on 24/01/2017. * Created by Ilia Kurtov on 24/01/2017.
@ -94,7 +95,7 @@ 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. * @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}. * @return pair with final {@link ValidationState} that is always not null and a model that we get after converting the {@link TWrapperModel}.

View File

@ -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.")
}
}

View File

@ -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()
}
}

View File

@ -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")
}

View File

@ -0,0 +1,7 @@
package ru.touchin.templates.viewmodel
interface ViewModelFactoryProvider {
val viewModelFactory: ViewModelFactory
}