Compare commits

..

60 Commits

Author SHA1 Message Date
Oleg d08b7df5f0
UnusedImports fix (#112) 2018-11-28 17:17:14 +03:00
Oleg 333cb3fa64
TaskDescription fix (#111) 2018-11-28 16:54:29 +03:00
Denis Karmyshakov 098db7d2e3 Enum serialization (#103) 2018-02-09 15:03:30 +03:00
Anton Domnikov e1eed0c37f fixed parsing when date is empty (#101) 2018-02-02 17:13:42 +03:00
Denis Karmyshakov de538e14ba Gradle update (#97)
* Gradle update

* Excluding apache.httpcomponents
2017-11-20 19:19:53 +03:00
Denis Karmyshakov 054b62424c Merge pull request #93 from TouchInstinct/versions_in_constants
Versions in constants
2017-10-04 13:12:12 +03:00
Denis Karmyshakov a863285016 Versions in constants 2017-10-04 13:05:39 +03:00
nbnbbs 8383a41bca upd google-http-client-jackson2 (#91) 2017-10-03 03:32:28 -07:00
Ilia Kurtov b6d90da359 Merge pull request #86 from TouchInstinct/lib_update
rxjava version
2017-09-15 16:18:40 +03:00
gorodeckii f66d85c9c6 rxjava version 2017-09-15 15:18:45 +03:00
Gavriil 4049f4e458 RxJava version update (#85) 2017-09-11 18:51:10 +03:00
Arseniy Borisov dc2b9ef3ff idea formatter (#84) 2017-09-04 17:36:34 +03:00
Ilia Kurtov 20a9597c35 Feature/chat merge (#80) 2017-08-11 12:14:13 +03:00
Ilia Kurtov f7f98f0106 fresco update (#79) 2017-08-10 18:39:05 +03:00
nbnbbs 2dbe8a45e3 update libs (#73) 2017-08-09 16:24:20 +03:00
Gavriil 29aae2df52 Merge pull request #77 from TouchInstinct/fix/converter_fix
logan square enum converter - return default value for null
2017-08-01 19:17:30 +03:00
gorodeckii 688066b30b logan square enum converter - return default value for null 2017-08-01 19:16:58 +03:00
Gavriil 4f3458935f Merge pull request #76 from TouchInstinct/support_library_version_fix
fixed support library version
2017-07-27 17:06:50 +03:00
Elena Bobkova fe29f25fdf fixed support library version 2017-07-27 16:20:46 +03:00
Gavriil 2ce692bfb2 Merge pull request #71 from TouchInstinct/collections_validation
validation for collection responses
2017-07-13 17:21:32 +03:00
gorodeckii a0c2e758cb fix static 2017-07-13 17:19:01 +03:00
gorodeckii 06acc22f11 validation for collection responses 2017-07-13 17:01:26 +03:00
Gavriil e1dfde7b0c Merge pull request #70 from TouchInstinct/feature/enum_exception_description
description for unknown enum values
2017-07-13 15:51:38 +03:00
gorodeckii 796371648c description for unknown enum values 2017-07-13 15:50:44 +03:00
Gavriil dc68c8445a Merge pull request #69 from TouchInstinct/lib_version
socket.io new version
2017-07-12 13:11:43 +03:00
gorodeckii cde6c030f1 socket.io new version 2017-07-12 12:39:37 +03:00
Gavriil 1c17b565c7 Merge pull request #66 from TouchInstinct/changes_fix
Changes fix
2017-07-05 15:26:57 +03:00
Denis Karmyshakov 9051f8074e Changes fix 2017-07-05 15:25:09 +03:00
Gavriil f1bbb5ee6f Merge pull request #65 from TouchInstinct/enum_converter
enum converter with default value
2017-07-05 15:03:40 +03:00
gorodeckii c6fd3f738c implemented into existing class 2017-07-05 14:48:50 +03:00
gorodeckii 3b1e944bf4 enum converter with default value 2017-07-05 14:41:21 +03:00
Gavriil 2abdc20f65 Merge pull request #63 from TouchInstinct/bugs/network_type
fix getNetworkType
2017-06-21 15:10:24 +03:00
Arseniy Borisov af49926976 fix getNetworkType 2017-06-21 14:56:00 +03:00
Gavriil 140f01d28e Merge pull request #62 from TouchInstinct/OldTargetApi
noinspection OldTargetApi
2017-06-09 15:43:15 +03:00
Anton Domnikov ff4167316a remove targetSdkVersion 2017-06-09 15:41:50 +03:00
Anton Domnikov f607946ecb noinspection OldTargetApi 2017-06-09 15:17:10 +03:00
Gavriil 04019efaa0 Merge pull request #61 from TouchInstinct/static_fix
static fix
2017-06-08 17:45:46 +03:00
Gavriil Sitnikov 79ec7fb827 static fix 2017-06-08 17:39:26 +03:00
Gavriil b5f1a6958a Merge pull request #60 from TouchInstinct/calendar_init_fix
fix of layout manager
2017-06-08 16:53:13 +03:00
Gavriil Sitnikov 41ce954ba1 fix of layout manager 2017-06-08 16:49:56 +03:00
Gavriil b5a06328df Merge pull request #59 from TouchInstinct/static_fix
calendar static fix
2017-06-08 14:58:24 +03:00
Gavriil Sitnikov 0c25b19563 calendar static fix 2017-06-08 14:54:36 +03:00
Gavriil d7b4e01537 Merge pull request #58 from TouchInstinct/calendar_fix
fix of unnecessary moves of adapter on it's items changes
2017-06-08 14:21:34 +03:00
Gavriil Sitnikov c8fc0fe745 fix of unnecessary moves of adapter on it's items changes 2017-06-08 14:20:15 +03:00
Gavriil 1951c44ef1 Merge pull request #57 from TouchInstinct/feature/general_improvements
Feature/general improvements
2017-06-06 16:01:37 +03:00
Elena Bobkova 2727d3be55 Merge branch 'master' into feature/general_improvements 2017-06-01 15:14:37 +03:00
Elena Bobkova 3364e67281 Merge branch 'master' into feature/general_improvements 2017-06-01 14:53:17 +03:00
Anton Domnikov e711ff27d3 added method to update calendarItems 2017-05-31 14:19:02 +03:00
Anton Domnikov 3efec74601 static 2017-05-20 16:56:57 +03:00
Anton Domnikov 9205c14efc fixed some calendar logic 2017-05-20 16:43:28 +03:00
Oleg Kuznetcov 09286b3ddd retrofit version 2017-05-15 16:40:05 +03:00
Gavriil Sitnikov 6203b9a7cf observable collection debug enabled 2017-05-12 19:31:41 +03:00
Gavriil Sitnikov 6c5a299bff Merge branch 'master' into feature/general_improvements 2017-05-12 19:31:14 +03:00
Oleg 0ae38bf5f3 rxjava version 2017-05-10 21:15:38 +03:00
Anton Domnikov f3b989cd0a static 2017-05-04 13:31:43 +03:00
Anton Domnikov ccd9b85d04 update build tools version 2017-05-04 13:27:55 +03:00
Gavriil Sitnikov fe29d22933 fixed useless validation of logic on collections validation 2017-04-28 02:09:39 +03:00
Oleg 0eab7853c0 new versions 2017-04-26 15:40:05 +03:00
Gavriil Sitnikov 71d4d60e99 lognasquare fixes added 2017-04-20 19:37:09 +03:00
Gavriil Sitnikov 140eff7458 rxjava 1.2.9 fixes 2017-04-17 00:45:22 +03:00
27 changed files with 301 additions and 258 deletions

View File

@ -1,9 +1,7 @@
apply plugin: 'com.android.library' apply plugin: 'com.android.library'
apply plugin: 'me.tatarka.retrolambda'
android { android {
compileSdkVersion 25 compileSdkVersion compileSdk
buildToolsVersion "25.0.3"
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
@ -17,36 +15,40 @@ android {
repositories { repositories {
maven { url 'https://maven.fabric.io/public' } maven { url 'https://maven.fabric.io/public' }
maven { url "http://dl.bintray.com/touchin/touchin-tools" }
} }
dependencies { dependencies {
compile project(path: ':libraries:components') api project(path: ':libraries:components')
compile 'net.danlew:android.joda:2.9.9' api 'net.danlew:android.joda:2.9.9'
compile 'com.android.support:multidex:1.0.1' api 'com.android.support:multidex:1.0.2'
compile 'io.reactivex:rxandroid:1.2.1' api "io.reactivex:rxandroid:$rxAndroidVersion"
api "io.reactivex:rxjava:$rxJavaVersion"
provided 'com.android.support:appcompat-v7:25.3.1' compileOnly "com.android.support:appcompat-v7:$supportLibraryVersion"
provided 'com.android.support:recyclerview-v7:25.3.1' compileOnly "com.android.support:recyclerview-v7:$supportLibraryVersion"
provided 'com.squareup.retrofit2:retrofit:2.3.0' compileOnly 'com.squareup.retrofit2:retrofit:2.3.0'
provided('com.google.http-client:google-http-client-jackson2:1.22.0') { compileOnly('com.google.http-client:google-http-client-jackson2:1.23.0') {
exclude(group: 'org.apache.httpcomponents', module: 'httpclient') exclude(group: 'org.apache.httpcomponents', module: 'httpclient')
} }
provided 'com.facebook.fresco:fresco:1.4.0' compileOnly 'com.facebook.fresco:fresco:1.5.0'
provided 'com.bluelinelabs:logansquare:1.3.7' compileOnly 'ru.touchin:logansquare:1.4.1'
provided 'com.scottyab:aes-crypto:0.0.4' compileOnly 'com.scottyab:aes-crypto:0.0.4'
provided('io.socket:socket.io-client:1.0.0') { // don't use latest(1.0 and above) because they don't support Socket.IO server 1.x version
//noinspection NewerVersionAvailable
compileOnly('io.socket:socket.io-client:0.9.0') {
exclude group: 'org.json', module: 'json' exclude group: 'org.json', module: 'json'
} }
provided('com.crashlytics.sdk.android:crashlytics:2.6.7@aar') { compileOnly('com.crashlytics.sdk.android:crashlytics:2.6.8@aar') {
transitive = true; transitive = true
} }
provided 'com.facebook.stetho:stetho:1.5.0' compileOnly 'com.facebook.stetho:stetho:1.5.0'
} }

View File

@ -0,0 +1,22 @@
package com.bluelinelabs.logansquare;
import android.support.annotation.NonNull;
import java.lang.reflect.Type;
/**
* Utility class for the {@link ru.touchin.templates.logansquare.LoganSquareJsonFactory}. This resides in LoganSquare's
* main package in order to take advantage of the package-visible ConcreteParameterizedType class, which is essential
* to the support of generic classes in the Retrofit converter.
*/
public final class ConverterUtils {
@NonNull
public static ParameterizedType parameterizedTypeOf(@NonNull final Type type) {
return new ParameterizedType.ConcreteParameterizedType(type);
}
private ConverterUtils() {
}
}

View File

@ -50,9 +50,9 @@ public abstract class ApiModel implements Serializable {
* @param collectionValidationRule Rule explaining what to do if invalid items found; * @param collectionValidationRule Rule explaining what to do if invalid items found;
* @throws ValidationException Exception of validation. * @throws ValidationException Exception of validation.
*/ */
@SuppressWarnings("PMD.PreserveStackTrace") @SuppressWarnings({"PMD.PreserveStackTrace", "PMD.CyclomaticComplexity"})
// PreserveStackTrace: it's ok - we are logging it on Lc.e() // PreserveStackTrace: it's ok - we are logging it on Lc.e()
protected static void validateCollection(@NonNull final Collection collection, @NonNull final CollectionValidationRule collectionValidationRule) public static void validateCollection(@NonNull final Collection collection, @NonNull final CollectionValidationRule collectionValidationRule)
throws ValidationException { throws ValidationException {
boolean haveValidItem = false; boolean haveValidItem = false;
int position = 0; int position = 0;
@ -60,6 +60,10 @@ public abstract class ApiModel implements Serializable {
while (iterator.hasNext()) { while (iterator.hasNext()) {
final Object item = iterator.next(); final Object item = iterator.next();
if (!(item instanceof ApiModel)) { if (!(item instanceof ApiModel)) {
if (item != null) {
// let's just think that all of items are not ApiModels
break;
}
continue; continue;
} }
@ -113,7 +117,7 @@ public abstract class ApiModel implements Serializable {
//do nothing //do nothing
} }
protected enum CollectionValidationRule { public enum CollectionValidationRule {
EXCEPTION_IF_ANY_INVALID, EXCEPTION_IF_ANY_INVALID,
EXCEPTION_IF_ALL_INVALID, EXCEPTION_IF_ALL_INVALID,
REMOVE_INVALID_ITEMS, REMOVE_INVALID_ITEMS,

View File

@ -19,9 +19,7 @@
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;
@ -29,7 +27,6 @@ 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;
@ -37,8 +34,8 @@ import java.io.UnsupportedEncodingException;
import java.util.UUID; import java.util.UUID;
import ru.touchin.roboswag.core.log.Lc; import ru.touchin.roboswag.core.log.Lc;
import ru.touchin.roboswag.core.observables.RxAndroidUtils;
import rx.Observable; import rx.Observable;
import rx.Subscriber;
/** /**
* Utility class that is providing common methods related to android device. * Utility class that is providing common methods related to android device.
@ -109,13 +106,8 @@ public final class DeviceUtils {
*/ */
@NonNull @NonNull
public static NetworkType getNetworkType(@NonNull final Context context) { public static NetworkType getNetworkType(@NonNull final Context context) {
final ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); final ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
return getNetworkType(connectivityManager); final NetworkInfo info = cm.getActiveNetworkInfo();
}
@NonNull
private static NetworkType getNetworkType(@NonNull final ConnectivityManager connectivityManager) {
final NetworkInfo info = connectivityManager.getActiveNetworkInfo();
if (info == null || !info.isConnected()) { if (info == null || !info.isConnected()) {
return NetworkType.NONE; return NetworkType.NONE;
} }
@ -162,6 +154,7 @@ public final class DeviceUtils {
return getNetworkType(context) != NetworkType.NONE; return getNetworkType(context) != NetworkType.NONE;
} }
/** /**
* Returns observable to observe is device connected to Wi-Fi network. * Returns observable to observe is device connected to Wi-Fi network.
* *
@ -170,38 +163,12 @@ public final class DeviceUtils {
*/ */
@NonNull @NonNull
public static Observable<Boolean> observeIsConnectedToWifi(@NonNull final Context context) { public static Observable<Boolean> observeIsConnectedToWifi(@NonNull final Context context) {
return Observable.switchOnNext(Observable.fromCallable(() -> { return RxAndroidUtils.observeBroadcastEvent(context, new IntentFilter(WifiManager.NETWORK_STATE_CHANGED_ACTION))
final WifiStateReceiver wifiStateReceiver = new WifiStateReceiver(); .map(intent -> {
return Observable final NetworkInfo networkInfo = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
.<Boolean>create(subscriber -> { return networkInfo != null && networkInfo.isConnected();
subscriber.onNext(DeviceUtils.getNetworkType(context) == DeviceUtils.NetworkType.WI_FI); })
wifiStateReceiver.setSubscriber(subscriber); .distinctUntilChanged();
context.registerReceiver(wifiStateReceiver, WifiStateReceiver.INTENT_FILTER);
})
.doOnUnsubscribe(() -> context.unregisterReceiver(wifiStateReceiver))
.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 -> {
subscriber.onNext(isNetworkConnected(context));
networkStateReceiver.setSubscriber(subscriber);
context.registerReceiver(networkStateReceiver, NetworkStateReceiver.INTENT_FILTER);
})
.doOnUnsubscribe(() -> context.unregisterReceiver(networkStateReceiver))
.distinctUntilChanged();
}));
} }
private DeviceUtils() { private DeviceUtils() {
@ -253,48 +220,4 @@ public final class DeviceUtils {
} }
private static class WifiStateReceiver extends BroadcastReceiver {
private static final IntentFilter INTENT_FILTER = new IntentFilter(WifiManager.NETWORK_STATE_CHANGED_ACTION);
@Nullable
private Subscriber<? super Boolean> subscriber;
public void setSubscriber(@Nullable final Subscriber<? super Boolean> subscriber) {
this.subscriber = subscriber;
}
@Override
public void onReceive(@NonNull final Context context, @NonNull final Intent intent) {
final NetworkInfo networkInfo = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
if (subscriber != null) {
subscriber.onNext(networkInfo != null && networkInfo.isConnected());
}
}
}
private static class NetworkStateReceiver extends BroadcastReceiver {
private static final IntentFilter INTENT_FILTER = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
@Nullable
private ConnectivityManager connectivityManager;
@Nullable
private Subscriber<? super Boolean> subscriber;
public void setSubscriber(@Nullable final Subscriber<? super Boolean> subscriber) {
this.subscriber = subscriber;
}
public void onReceive(@NonNull final Context context, @NonNull final Intent intent) {
if (connectivityManager == null) {
connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
}
if (subscriber != null) {
subscriber.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.drawable.BitmapDrawable; import android.graphics.BitmapFactory;
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;
@ -62,9 +62,14 @@ 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.LOLLIPOP) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
final ActivityManager.TaskDescription taskDescription = new ActivityManager.TaskDescription(label, final ActivityManager.TaskDescription taskDescription = new ActivityManager.TaskDescription(label,
((BitmapDrawable) ContextCompat.getDrawable(this, iconRes)).getBitmap(), iconRes,
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

@ -38,6 +38,7 @@ 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 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;
@ -91,6 +92,7 @@ public abstract class TouchinApp extends Application {
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();

View File

@ -32,6 +32,7 @@ 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 ru.touchin.roboswag.core.utils.ShouldNotHappenException;
import rx.Completable; import rx.Completable;
import rx.CompletableSubscriber;
import rx.Observable; import rx.Observable;
import rx.Single; import rx.Single;
import rx.SingleSubscriber; import rx.SingleSubscriber;
@ -226,7 +227,7 @@ public abstract class TouchinService<TLogic extends Logic> extends Service {
* Don't forget to process errors if completable can emit them. * Don't forget to process errors if completable can emit them.
* *
* @param completable {@link Completable} to subscribe until onDestroy; * @param completable {@link Completable} to subscribe until onDestroy;
* @param onCompletedAction Action which will raise on every {@link Completable.CompletableSubscriber#onCompleted()} item; * @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. * @return {@link Subscription} which is wrapping source completable to unsubscribe from it onDestroy.
*/ */
@NonNull @NonNull
@ -240,8 +241,8 @@ public abstract class TouchinService<TLogic extends Logic> extends Service {
* Don't forget to process errors if completable can emit them. * Don't forget to process errors if completable can emit them.
* *
* @param completable {@link Single} to subscribe until onDestroy; * @param completable {@link Single} to subscribe until onDestroy;
* @param onCompletedAction Action which will raise on {@link Completable.CompletableSubscriber#onCompleted()} item; * @param onCompletedAction Action which will raise on {@link CompletableSubscriber#onCompleted()} item;
* @param onErrorAction Action which will raise on every {@link Completable.CompletableSubscriber#onError(Throwable)} throwable; * @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. * @return {@link Subscription} which is wrapping source completable to unsubscribe from it onDestroy.
*/ */
@NonNull @NonNull

View File

@ -51,8 +51,7 @@ public abstract class CalendarAdapter<TDayViewHolder extends RecyclerView.ViewHo
public static final int MONTHS_IN_YEAR = 12; public static final int MONTHS_IN_YEAR = 12;
public static final long ONE_DAY_LENGTH = TimeUnit.DAYS.toMillis(1); public static final long ONE_DAY_LENGTH = TimeUnit.DAYS.toMillis(1);
@NonNull private List<CalendarItem> calendarItems;
private final List<CalendarItem> calendarItems;
@Nullable @Nullable
private Integer startSelectionPosition; private Integer startSelectionPosition;
@Nullable @Nullable
@ -72,6 +71,10 @@ public abstract class CalendarAdapter<TDayViewHolder extends RecyclerView.ViewHo
if (monthsNames != null && monthsNames.length == MONTHS_IN_YEAR) { if (monthsNames != null && monthsNames.length == MONTHS_IN_YEAR) {
this.monthsNames = monthsNames; this.monthsNames = monthsNames;
} }
updateCalendarItems(startDate, endDate);
}
public final void updateCalendarItems(@NonNull final DateTime startDate, @NonNull final DateTime endDate) {
calendarItems = CalendarUtils.fillRanges(startDate, endDate); calendarItems = CalendarUtils.fillRanges(startDate, endDate);
if (calendarItems.isEmpty()) { if (calendarItems.isEmpty()) {
throw new ShouldNotHappenException("There is no items in calendar with startDate: " + DateTimeFormat.fullDate().print(startDate) throw new ShouldNotHappenException("There is no items in calendar with startDate: " + DateTimeFormat.fullDate().print(startDate)
@ -86,12 +89,12 @@ public abstract class CalendarAdapter<TDayViewHolder extends RecyclerView.ViewHo
* @param endSelectionDate Last date that should be selected (inclusive). * @param endSelectionDate Last date that should be selected (inclusive).
*/ */
public void setSelectedRange(@Nullable final DateTime startSelectionDate, @Nullable final DateTime endSelectionDate) { public void setSelectedRange(@Nullable final DateTime startSelectionDate, @Nullable final DateTime endSelectionDate) {
if (startSelectionDate != null) { startSelectionPosition = startSelectionDate != null
startSelectionPosition = CalendarUtils.findPositionByDate(calendarItems, startSelectionDate.withTimeAtStartOfDay().getMillis()); ? CalendarUtils.findPositionByDate(calendarItems, startSelectionDate.withTimeAtStartOfDay().getMillis())
} : null;
if (endSelectionDate != null) { endSelectionPosition = endSelectionDate != null
endSelectionPosition = CalendarUtils.findPositionByDate(calendarItems, endSelectionDate.withTimeAtStartOfDay().getMillis()); ? CalendarUtils.findPositionByDate(calendarItems, endSelectionDate.withTimeAtStartOfDay().getMillis())
} : null;
notifySelectedDaysChanged(); notifySelectedDaysChanged();
} }
@ -132,6 +135,16 @@ public abstract class CalendarAdapter<TDayViewHolder extends RecyclerView.ViewHo
notifyItemRangeChanged(startSelectionPosition, endSelectionPosition - startSelectionPosition); notifyItemRangeChanged(startSelectionPosition, endSelectionPosition - startSelectionPosition);
} }
@NonNull
protected List<CalendarItem> getCalendarItems() {
return calendarItems;
}
@NonNull
protected String getMonthsNameByHeaderCalendarItem(@NonNull final CalendarHeaderItem item) {
return monthsNames != null ? monthsNames[item.getMonth()] : String.valueOf(item.getMonth());
}
@NonNull @NonNull
@Override @Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { public RecyclerView.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
@ -237,7 +250,7 @@ public abstract class CalendarAdapter<TDayViewHolder extends RecyclerView.ViewHo
} }
//TODO fix suppress //TODO fix suppress
@SuppressWarnings("PMD.CyclomaticComplexity") @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity"})
private void bindDay(@NonNull final TDayViewHolder holder, final int position, @NonNull final CalendarItem calendarItem) { private void bindDay(@NonNull final TDayViewHolder holder, final int position, @NonNull final CalendarItem calendarItem) {
final String currentDay = String.valueOf(((CalendarDayItem) calendarItem).getPositionOfFirstDay() final String currentDay = String.valueOf(((CalendarDayItem) calendarItem).getPositionOfFirstDay()
+ position - calendarItem.getStartRange()); + position - calendarItem.getStartRange());
@ -245,13 +258,18 @@ public abstract class CalendarAdapter<TDayViewHolder extends RecyclerView.ViewHo
+ (position - calendarItem.getStartRange()) * ONE_DAY_LENGTH); + (position - calendarItem.getStartRange()) * ONE_DAY_LENGTH);
final ComparingToToday dateState = ((CalendarDayItem) calendarItem).getComparingToToday(); final ComparingToToday dateState = ((CalendarDayItem) calendarItem).getComparingToToday();
if (startSelectionPosition != null && position == startSelectionPosition) { if (startSelectionPosition != null && position == startSelectionPosition) {
if (endSelectionPosition == null || endSelectionPosition.equals(startSelectionPosition)) { if (endSelectionPosition == null || endSelectionPosition.equals(startSelectionPosition)
|| startSelectionPosition > endSelectionPosition) {
bindDayItem(holder, currentDay, currentDate, SelectionMode.SELECTED_ONE_ONLY, dateState); bindDayItem(holder, currentDay, currentDate, SelectionMode.SELECTED_ONE_ONLY, dateState);
return; return;
} }
bindDayItem(holder, currentDay, currentDate, SelectionMode.SELECTED_FIRST, dateState); bindDayItem(holder, currentDay, currentDate, SelectionMode.SELECTED_FIRST, dateState);
return; return;
} }
if (startSelectionPosition != null && endSelectionPosition != null && startSelectionPosition > endSelectionPosition) {
bindDayItem(holder, currentDay, currentDate, SelectionMode.NOT_SELECTED, dateState);
return;
}
if (endSelectionPosition != null && position == endSelectionPosition) { if (endSelectionPosition != null && position == endSelectionPosition) {
bindDayItem(holder, currentDay, currentDate, SelectionMode.SELECTED_LAST, dateState); bindDayItem(holder, currentDay, currentDate, SelectionMode.SELECTED_LAST, dateState);
return; return;
@ -285,6 +303,10 @@ public abstract class CalendarAdapter<TDayViewHolder extends RecyclerView.ViewHo
return calendarItems.isEmpty() ? 0 : calendarItems.get(calendarItems.size() - 1).getEndRange(); return calendarItems.isEmpty() ? 0 : calendarItems.get(calendarItems.size() - 1).getEndRange();
} }
protected boolean isEndPositionExist() {
return endSelectionPosition != null;
}
/** /**
* Selection mode that shows the type of selection of a calendar cell. * Selection mode that shows the type of selection of a calendar cell.
*/ */

View File

@ -23,6 +23,7 @@ import android.content.Context;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.util.AttributeSet; import android.util.AttributeSet;
import ru.touchin.roboswag.core.log.Lc; import ru.touchin.roboswag.core.log.Lc;
@ -45,11 +46,31 @@ public class CalendarRecyclerView extends RecyclerView {
this(context, attrs, 0); this(context, attrs, 0);
} }
@SuppressWarnings("PMD.ConstructorCallsOverridableMethod")
//ConstructorCallsOverridableMethod: it's OK
public CalendarRecyclerView(@NonNull final Context context, @Nullable final AttributeSet attrs, final int defStyle) { public CalendarRecyclerView(@NonNull final Context context, @Nullable final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle); super(context, attrs, defStyle);
getRecycledViewPool().setMaxRecycledViews(CalendarAdapter.HEADER_ITEM_TYPE, HEADER_MAX_ELEMENTS_IN_A_ROW * 3); initialize();
getRecycledViewPool().setMaxRecycledViews(CalendarAdapter.EMPTY_ITEM_TYPE, EMPTY_MAX_ELEMENTS_IN_A_ROW * 3); }
getRecycledViewPool().setMaxRecycledViews(CalendarAdapter.DAY_ITEM_TYPE, DAY_MAX_ELEMENTS_IN_A_ROW * 3);
private void initialize() {
setupCacheForMonthsOnScreenCount(3);
final StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(7, StaggeredGridLayoutManager.VERTICAL);
layoutManager.setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_NONE);
setLayoutManager(layoutManager);
setItemAnimator(null);
}
/**
* Setups recycler cache for smooth scroll without lagging based on month that could be displayed on screen.
*
* @param maxMonthOnScreen Maximum months count on screen.
*/
public void setupCacheForMonthsOnScreenCount(final int maxMonthOnScreen) {
getRecycledViewPool().setMaxRecycledViews(CalendarAdapter.HEADER_ITEM_TYPE, HEADER_MAX_ELEMENTS_IN_A_ROW * (maxMonthOnScreen + 1));
getRecycledViewPool().setMaxRecycledViews(CalendarAdapter.EMPTY_ITEM_TYPE, EMPTY_MAX_ELEMENTS_IN_A_ROW * (maxMonthOnScreen * 2 + 1));
// we need such much views to prevent cache/gap animations of StaggeredGridLayoutManager
getRecycledViewPool().setMaxRecycledViews(CalendarAdapter.DAY_ITEM_TYPE, DAY_MAX_ELEMENTS_IN_A_ROW * (maxMonthOnScreen * 5 + 1));
setItemViewCacheSize(0); setItemViewCacheSize(0);
} }

View File

@ -59,7 +59,7 @@ public final class CalendarUtils {
* @param calendarItems List of {@link CalendarItem} where need to find specific element; * @param calendarItems List of {@link CalendarItem} where need to find specific element;
* @param position Position of adapter; * @param position Position of adapter;
* @return Position of Header that respond to requested position. * @return Position of Header that respond to requested position.
* Returns null if Header or related CalendarItem was not found for specified position. * Returns null if Header or related CalendarItem was not found for specified position.
*/ */
@Nullable @Nullable
public static Integer findPositionOfSelectedMonth(@NonNull final List<CalendarItem> calendarItems, final long position) { public static Integer findPositionOfSelectedMonth(@NonNull final List<CalendarItem> calendarItems, final long position) {
@ -76,7 +76,7 @@ public final class CalendarUtils {
* @param calendarItems List of {@link CalendarItem} where need to find specific element; * @param calendarItems List of {@link CalendarItem} where need to find specific element;
* @param date Requested date in milliseconds. * @param date Requested date in milliseconds.
* @return Position of Calendar cell that that has specific date. * @return Position of Calendar cell that that has specific date.
* Returns null if CalendarItem was not found for specified position. * Returns null if CalendarItem was not found for specified position.
*/ */
@Nullable @Nullable
public static Integer findPositionByDate(@NonNull final List<CalendarItem> calendarItems, final long date) { public static Integer findPositionByDate(@NonNull final List<CalendarItem> calendarItems, final long date) {

View File

@ -31,9 +31,9 @@ import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import ru.touchin.roboswag.core.log.Lc; import ru.touchin.roboswag.core.log.Lc;
import ru.touchin.roboswag.core.observables.collections.Change;
import ru.touchin.roboswag.core.observables.collections.ObservableCollection; import ru.touchin.roboswag.core.observables.collections.ObservableCollection;
import ru.touchin.roboswag.core.observables.collections.ObservableList; import ru.touchin.roboswag.core.observables.collections.ObservableList;
import rx.Completable;
import rx.Observable; import rx.Observable;
import rx.Scheduler; import rx.Scheduler;
import rx.Subscription; import rx.Subscription;
@ -76,17 +76,10 @@ public abstract class Chat<TOutgoingMessage> {
final List<TOutgoingMessage> reversedMessages = new ArrayList<>(initialMessages); final List<TOutgoingMessage> reversedMessages = new ArrayList<>(initialMessages);
Collections.reverse(reversedMessages); Collections.reverse(reversedMessages);
return Observable.from(reversedMessages) return Observable.from(reversedMessages)
.concatWith(sendingMessages.observeChanges().concatMap(changes -> { .concatWith(sendingMessages.observeChanges().concatMap(changes ->
final Collection<TOutgoingMessage> insertedMessages = new ArrayList<>(); changes.getInsertedItems().isEmpty() ? Observable.empty() : Observable.from(changes.getInsertedItems())))
for (final Change<TOutgoingMessage> change : changes.getChanges()) {
if (change.getType() == Change.Type.INSERTED) {
insertedMessages.addAll(change.getChangedItems());
}
}
return insertedMessages.isEmpty() ? Observable.empty() : Observable.from(insertedMessages);
}))
//observe on some scheduler? //observe on some scheduler?
.flatMap(this::internalSendMessage); .flatMap(message -> internalSendMessage(message).toObservable());
}); });
} }
@ -175,6 +168,14 @@ public abstract class Chat<TOutgoingMessage> {
retrySendingRequest.onNext(null); 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. * Deactivates chat so it will stop sending messages.
*/ */
@ -188,9 +189,9 @@ public abstract class Chat<TOutgoingMessage> {
} }
@NonNull @NonNull
private Observable<?> internalSendMessage(@NonNull final TOutgoingMessage message) { private Completable internalSendMessage(@NonNull final TOutgoingMessage message) {
final SubscriptionHolder subscriptionHolder = new SubscriptionHolder(); final SubscriptionHolder subscriptionHolder = new SubscriptionHolder();
return Observable return Completable
.create(subscriber -> { .create(subscriber -> {
subscriptionHolder.subscription = sendingScheduler.createWorker().schedule(() -> { subscriptionHolder.subscription = sendingScheduler.createWorker().schedule(() -> {
final CountDownLatch blocker = new CountDownLatch(1); final CountDownLatch blocker = new CountDownLatch(1);
@ -201,6 +202,7 @@ public abstract class Chat<TOutgoingMessage> {
.first() .first()
.switchMap(shouldSendMessage -> shouldSendMessage .switchMap(shouldSendMessage -> shouldSendMessage
? createSendMessageObservable(message).ignoreElements() : Observable.empty()) ? createSendMessageObservable(message).ignoreElements() : Observable.empty())
.takeUntil(observeCancelEvent(message))
.retryWhen(attempts -> attempts.switchMap(ignored -> { .retryWhen(attempts -> attempts.switchMap(ignored -> {
isSendingInError.onNext(true); isSendingInError.onNext(true);
return Observable return Observable

View File

@ -29,9 +29,7 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
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;
@ -85,26 +83,6 @@ public final class GoogleJsonPreferences {
.build(); .build();
} }
@NonNull
public static <T> Storable<String, Set<T>, String> jsonSetStorable(@NonNull final String name,
@NonNull final Class<T> jsonItemClass,
@NonNull final SharedPreferences preferences) {
return new Storable.Builder<>(name, Set.class, String.class, new PreferenceStore<>(preferences), new JsonSetConverter<>(jsonItemClass))
.setObserveStrategy(Storable.ObserveStrategy.CACHE_ACTUAL_VALUE)
.build();
}
@NonNull
public static <T> NonNullStorable<String, Set<T>, String> jsonSetStorable(@NonNull final String name,
@NonNull final Class<T> jsonItemClass,
@NonNull final SharedPreferences preferences,
@NonNull final Set<T> defaultValue) {
return new Storable.Builder<>(name, Set.class, String.class, new PreferenceStore<>(preferences), new JsonSetConverter<>(jsonItemClass))
.setObserveStrategy(Storable.ObserveStrategy.CACHE_ACTUAL_VALUE)
.setDefaultValue(defaultValue)
.build();
}
private GoogleJsonPreferences() { private GoogleJsonPreferences() {
} }
@ -168,29 +146,4 @@ public final class GoogleJsonPreferences {
} }
public static class JsonSetConverter<T> extends JsonConverter<Set<T>> {
@NonNull
private final Class<T> itemClass;
public JsonSetConverter(@NonNull final Class<T> itemClass) {
super();
this.itemClass = itemClass;
}
@Nullable
@Override
public Set<T> toObject(@NonNull final Type jsonObjectType, @NonNull final Type stringType, @Nullable final String storeValue) {
if (storeValue == null) {
return null;
}
try {
return new HashSet<>(GoogleJsonModel.DEFAULT_JSON_FACTORY.createJsonParser(storeValue).parseArray(HashSet.class, itemClass));
} catch (final IOException exception) {
throw new ShouldNotHappenException(exception);
}
}
}
} }

View File

@ -35,24 +35,34 @@ public class LoganSquareEnumConverter<T extends Enum & LoganSquareEnum> extends
@NonNull @NonNull
private final T[] enumValues; private final T[] enumValues;
@Nullable
private final T defaultValue;
public LoganSquareEnumConverter(@NonNull final T[] enumValues) { public LoganSquareEnumConverter(@NonNull final T[] enumValues) {
this(enumValues, null);
}
public LoganSquareEnumConverter(@NonNull final T[] enumValues, @Nullable final T defaultValue) {
super(); super();
this.enumValues = enumValues; this.enumValues = enumValues;
this.defaultValue = defaultValue;
} }
@Nullable @Nullable
@Override @Override
public T getFromString(@Nullable final String string) { public T getFromString(@Nullable final String string) {
if (string == null) { if (string == null) {
return null; return defaultValue;
} }
for (final T value : enumValues) { for (final T value : enumValues) {
if (value.getValueName().equals(string)) { if (value.getValueName().equals(string)) {
return value; return value;
} }
} }
throw new ShouldNotHappenException(); if (defaultValue != null) {
return defaultValue;
}
throw new ShouldNotHappenException("Enum parsing exception for value: " + string);
} }
@Nullable @Nullable

View File

@ -42,7 +42,7 @@ public class LoganSquareJodaTimeConverter implements TypeConverter<DateTime> {
@Override @Override
public DateTime parse(@NonNull final JsonParser jsonParser) throws IOException { public DateTime parse(@NonNull final JsonParser jsonParser) throws IOException {
final String dateString = jsonParser.getValueAsString(); final String dateString = jsonParser.getValueAsString();
if (dateString == null) { if (dateString == null || dateString.isEmpty()) {
return null; return null;
} }
try { try {
@ -60,9 +60,9 @@ public class LoganSquareJodaTimeConverter implements TypeConverter<DateTime> {
@NonNull final JsonGenerator jsonGenerator) @NonNull final JsonGenerator jsonGenerator)
throws IOException { throws IOException {
if (fieldName != null) { if (fieldName != null) {
jsonGenerator.writeStringField(fieldName, object != null ? object.toString() : null); jsonGenerator.writeStringField(fieldName, object != null && !object.toString().isEmpty() ? object.toString() : null);
} else { } else {
jsonGenerator.writeString(object != null ? object.toString() : null); jsonGenerator.writeString(object != null && !object.toString().isEmpty() ? object.toString() : null);
} }
} }

View File

@ -20,14 +20,20 @@
package ru.touchin.templates.logansquare; package ru.touchin.templates.logansquare;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.bluelinelabs.logansquare.ConverterUtils;
import com.bluelinelabs.logansquare.LoganSquare; import com.bluelinelabs.logansquare.LoganSquare;
import com.google.api.client.json.jackson2.JacksonFactory; import com.fasterxml.jackson.core.JsonGenerator;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.StringWriter;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import okhttp3.RequestBody; import okhttp3.RequestBody;
import okhttp3.ResponseBody; import okhttp3.ResponseBody;
@ -38,7 +44,7 @@ import ru.touchin.templates.retrofit.JsonResponseBodyConverter;
/** /**
* Created by Gavriil Sitnikov on 2/06/2016. * Created by Gavriil Sitnikov on 2/06/2016.
* LoganSquareConverter class to use with {@link Retrofit} to parse and generate models based on Google Jackson library {@link JacksonFactory}. * LoganSquareConverter class to use with {@link Retrofit} to parse and generate models based on Logan Square library.
*/ */
public class LoganSquareJsonFactory extends Converter.Factory { public class LoganSquareJsonFactory extends Converter.Factory {
@ -59,6 +65,16 @@ public class LoganSquareJsonFactory extends Converter.Factory {
return new LoganSquareRequestBodyConverter<>(); return new LoganSquareRequestBodyConverter<>();
} }
@Nullable
@Override
public Converter<?, String> stringConverter(@NonNull final Type type, @NonNull final Annotation[] annotations, @NonNull final Retrofit retrofit) {
if (type instanceof Class && ((Class) type).getSuperclass() == Enum.class) {
return new LoganSquareStringEnumConverter<>();
} else {
return super.stringConverter(type, annotations, retrofit);
}
}
public static class LoganSquareJsonResponseBodyConverter<T> extends JsonResponseBodyConverter<T> { public static class LoganSquareJsonResponseBodyConverter<T> extends JsonResponseBodyConverter<T> {
@NonNull @NonNull
@ -73,7 +89,23 @@ public class LoganSquareJsonFactory extends Converter.Factory {
@NonNull @NonNull
@Override @Override
protected T parseResponse(@NonNull final ResponseBody value) throws IOException { protected T parseResponse(@NonNull final ResponseBody value) throws IOException {
return (T) LoganSquare.parse(value.byteStream(), (Class) type); if (type instanceof ParameterizedType) {
final ParameterizedType parameterizedType = (ParameterizedType) type;
final Type[] typeArguments = parameterizedType.getActualTypeArguments();
final Type firstType = typeArguments[0];
final Type rawType = parameterizedType.getRawType();
if (rawType == Map.class) {
return (T) LoganSquare.parseMap(value.byteStream(), (Class<?>) typeArguments[1]);
} else if (rawType == List.class) {
return (T) LoganSquare.parseList(value.byteStream(), (Class<?>) firstType);
} else {
// Generics
return (T) LoganSquare.parse(value.byteStream(), ConverterUtils.parameterizedTypeOf(type));
}
} else {
return (T) LoganSquare.parse(value.byteStream(), (Class) type);
}
} }
} }
@ -81,11 +113,29 @@ public class LoganSquareJsonFactory extends Converter.Factory {
public static class LoganSquareRequestBodyConverter<T> extends JsonRequestBodyConverter<T> { public static class LoganSquareRequestBodyConverter<T> extends JsonRequestBodyConverter<T> {
@Override @Override
protected void writeValueToByteArray(@NonNull final T value, @NonNull final ByteArrayOutputStream byteArrayOutputStream) protected void writeValueToByteArray(@NonNull final T value, @NonNull final ByteArrayOutputStream byteArrayOutputStream) throws IOException {
throws IOException {
LoganSquare.serialize(value, byteArrayOutputStream); LoganSquare.serialize(value, byteArrayOutputStream);
} }
} }
} public static class LoganSquareStringEnumConverter<T> implements Converter<T, String> {
@Nullable
@SuppressWarnings({"unchecked", "TryFinallyCanBeTryWithResources"})
@Override
public String convert(@NonNull final T value) throws IOException {
final StringWriter writer = new StringWriter();
try {
final JsonGenerator generator = LoganSquare.JSON_FACTORY.createGenerator(writer);
LoganSquare.typeConverterFor((Class<T>) value.getClass()).serialize(value, null, false, generator);
generator.close();
return writer.toString().replaceAll("\"", "");
} finally {
writer.close();
}
}
}
}

View File

@ -203,23 +203,9 @@ public abstract class HttpRequest<T> {
@NonNull @NonNull
public Observable<T> execute() { public Observable<T> execute() {
return Observable return Observable
.<RequestController>create(subscriber -> { .fromCallable(RequestController::new)
try {
subscriber.onNext(new RequestController());
} catch (final IOException exception) {
subscriber.onError(exception);
}
subscriber.onCompleted();
})
.switchMap(requestController -> Observable .switchMap(requestController -> Observable
.<T>create(requestSubscriber -> { .fromCallable(() -> executeSyncInternal(requestController))
try {
requestSubscriber.onNext(executeSyncInternal(requestController));
} catch (final IOException exception) {
requestSubscriber.onError(exception);
}
requestSubscriber.onCompleted();
})
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io()) .unsubscribeOn(Schedulers.io())
.doOnUnsubscribe(requestController.call::cancel)); .doOnUnsubscribe(requestController.call::cancel));

View File

@ -24,6 +24,8 @@ import android.support.annotation.NonNull;
import java.io.IOException; import java.io.IOException;
import java.io.InterruptedIOException; import java.io.InterruptedIOException;
import java.net.SocketException; import java.net.SocketException;
import java.util.Collection;
import java.util.Map;
import javax.net.ssl.SSLException; import javax.net.ssl.SSLException;
@ -57,20 +59,46 @@ public abstract class JsonResponseBodyConverter<T> implements Converter<Response
Lc.assertion(exception); Lc.assertion(exception);
} }
throw exception; throw exception;
} finally {
value.close();
} }
if (result instanceof ApiModel) { if (result instanceof ApiModel) {
try { validateModel((ApiModel) result);
((ApiModel) result).validate(); }
} catch (final ApiModel.ValidationException validationException) { if (result instanceof Collection) {
Lc.assertion(validationException); validateCollection((Collection) result);
throw validationException; }
} if (result instanceof Map) {
validateCollection(((Map) result).values());
} }
return result; return result;
} }
private void validateModel(@NonNull final ApiModel result) throws IOException {
try {
result.validate();
} catch (final ApiModel.ValidationException validationException) {
Lc.assertion(validationException);
throw validationException;
}
}
private void validateCollection(@NonNull final Collection result) throws IOException {
try {
ApiModel.validateCollection(result, getValidateCollectionRule());
} catch (final ApiModel.ValidationException validationException) {
Lc.assertion(validationException);
throw validationException;
}
}
@NonNull
protected ApiModel.CollectionValidationRule getValidateCollectionRule() {
return ApiModel.CollectionValidationRule.EXCEPTION_IF_ANY_INVALID;
}
/** /**
* Parses response to specific object. * Parses response to specific object.
* *

View File

@ -85,29 +85,21 @@ public abstract class SocketConnection {
@NonNull @NonNull
private Observable<Pair<Socket, State>> createSocketObservable() { private Observable<Pair<Socket, State>> createSocketObservable() {
return Observable return Observable
.<Socket>create(subscriber -> { .fromCallable(this::createSocket)
try {
final Socket socket = createSocket();
subscriber.onNext(socket);
} catch (final Exception exception) {
Lc.assertion(exception);
}
subscriber.onCompleted();
})
.switchMap(socket -> Observable .switchMap(socket -> Observable
.<Pair<Socket, State>>create(subscriber -> { .<Pair<Socket, State>>create(emitter -> {
socket.on(Socket.EVENT_CONNECT, args -> subscriber.onNext(new Pair<>(socket, State.CONNECTED))); socket.on(Socket.EVENT_CONNECT, args -> emitter.onNext(new Pair<>(socket, State.CONNECTED)));
socket.on(Socket.EVENT_CONNECTING, args -> subscriber.onNext(new Pair<>(socket, State.CONNECTING))); socket.on(Socket.EVENT_CONNECTING, args -> emitter.onNext(new Pair<>(socket, State.CONNECTING)));
socket.on(Socket.EVENT_CONNECT_ERROR, args -> subscriber.onNext(new Pair<>(socket, State.CONNECTION_ERROR))); socket.on(Socket.EVENT_CONNECT_ERROR, args -> emitter.onNext(new Pair<>(socket, State.CONNECTION_ERROR)));
socket.on(Socket.EVENT_CONNECT_TIMEOUT, args -> subscriber.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 -> subscriber.onNext(new Pair<>(socket, State.DISCONNECTED))); socket.on(Socket.EVENT_DISCONNECT, args -> emitter.onNext(new Pair<>(socket, State.DISCONNECTED)));
socket.on(Socket.EVENT_RECONNECT_ATTEMPT, args -> subscriber.onNext(new Pair<>(socket, State.CONNECTING))); socket.on(Socket.EVENT_RECONNECT_ATTEMPT, args -> emitter.onNext(new Pair<>(socket, State.CONNECTING)));
socket.on(Socket.EVENT_RECONNECTING, args -> subscriber.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 -> subscriber.onNext(new Pair<>(socket, State.CONNECTED))); socket.on(Socket.EVENT_RECONNECT, args -> emitter.onNext(new Pair<>(socket, State.CONNECTED)));
socket.on(Socket.EVENT_RECONNECT_ERROR, args -> subscriber.onNext(new Pair<>(socket, State.CONNECTION_ERROR))); socket.on(Socket.EVENT_RECONNECT_ERROR, args -> emitter.onNext(new Pair<>(socket, State.CONNECTION_ERROR)));
socket.on(Socket.EVENT_RECONNECT_FAILED, args -> subscriber.onNext(new Pair<>(socket, State.CONNECTION_ERROR))); socket.on(Socket.EVENT_RECONNECT_FAILED, args -> emitter.onNext(new Pair<>(socket, State.CONNECTION_ERROR)));
subscriber.onNext(new Pair<>(socket, State.DISCONNECTED)); emitter.onNext(new Pair<>(socket, State.DISCONNECTED));
}) }, rx.Emitter.BackpressureMode.LATEST)
.distinctUntilChanged() .distinctUntilChanged()
.doOnSubscribe(() -> { .doOnSubscribe(() -> {
if (autoConnectOnAnySubscription) { if (autoConnectOnAnySubscription) {
@ -139,13 +131,13 @@ public abstract class SocketConnection {
//unchecked: it's OK as we are caching raw observables //unchecked: it's OK as we are caching raw observables
protected <T> Observable<T> observeEvent(@NonNull final SocketEvent<T> socketEvent) { protected <T> Observable<T> observeEvent(@NonNull final SocketEvent<T> socketEvent) {
return Observable.switchOnNext(Observable return Observable.switchOnNext(Observable
.<Observable<T>>create(observableSubscriber -> { .fromCallable(() -> {
Observable<T> result = (Observable<T>) messagesObservableCache.get(socketEvent); Observable<T> result = (Observable<T>) messagesObservableCache.get(socketEvent);
if (result == null) { if (result == null) {
result = getSocket() result = getSocket()
.switchMap(socket -> Observable .switchMap(socket -> Observable
.<T>create(subscriber -> .<T>create(emitter -> socket.on(socketEvent.getName(), new SocketListener<>(socketEvent, emitter::onNext)),
socket.on(socketEvent.getName(), new SocketListener<>(socketEvent, subscriber::onNext))) rx.Emitter.BackpressureMode.BUFFER)
.unsubscribeOn(scheduler) .unsubscribeOn(scheduler)
.doOnUnsubscribe(() -> { .doOnUnsubscribe(() -> {
socket.off(socketEvent.getName()); socket.off(socketEvent.getName());
@ -155,8 +147,7 @@ public abstract class SocketConnection {
.refCount(); .refCount();
messagesObservableCache.put(socketEvent, result); messagesObservableCache.put(socketEvent, result);
} }
observableSubscriber.onNext(result); return result;
observableSubscriber.onCompleted();
}) })
.subscribeOn(scheduler)); .subscribeOn(scheduler));
} }

View File

@ -59,6 +59,7 @@ public class ValidationState implements Serializable {
/** /**
* Returns unique code of the {@link ValidationState}. * Returns unique code of the {@link ValidationState}.
*
* @return code or the ValidationState. * @return code or the ValidationState.
*/ */
public int getCode() { public int getCode() {
@ -67,6 +68,7 @@ public class ValidationState implements Serializable {
/** /**
* Don't forget to override this method! * Don't forget to override this method!
*
* @param object that you want to compare. * @param object that you want to compare.
* @return true if objects equals and false otherwise. * @return true if objects equals and false otherwise.
*/ */

View File

@ -38,6 +38,7 @@ public interface ViewWithError {
* Shows error * Shows error
* Pass here error state. * Pass here error state.
* It is not correct to pass here {@link ValidationState#VALID} or {@link ValidationState#INITIAL} * It is not correct to pass here {@link ValidationState#VALID} or {@link ValidationState#INITIAL}
*
* @param validationState error state. Can be other than {@link ValidationState} if you have successor of base {@link ValidationState}. * @param validationState error state. Can be other than {@link ValidationState} if you have successor of base {@link ValidationState}.
*/ */
void showError(@NonNull final ValidationState validationState); void showError(@NonNull final ValidationState validationState);

View File

@ -37,6 +37,7 @@ public class BooleanValidationController extends ValidationController<Boolean, B
/** /**
* This method validates bounded view. * This method validates bounded view.
*
* @param activatedObservable emits true when we need to show error on empty fields. Eg when user clicks on Done button but he missed some * @param activatedObservable emits true when we need to show error on empty fields. Eg when user clicks on Done button but he missed some
* necessary fields to fill. * necessary fields to fill.
* @return observable without any concrete type. Simply subscribe to this method to make it works. * @return observable without any concrete type. Simply subscribe to this method to make it works.

View File

@ -98,8 +98,7 @@ public class EditTextValidationController<TModel extends Serializable>
/** /**
* If we don't want to show error when focus is lost. * If we don't want to show error when focus is lost.
* *
* @param showErrorOnFocusOut show an error or don't show an error. * @param showErrorOnFocusOut show an error or don't show an error.
*
*/ */
public void setShowErrorOnFocusOut(final boolean showErrorOnFocusOut) { public void setShowErrorOnFocusOut(final boolean showErrorOnFocusOut) {
this.showErrorOnFocusOut = showErrorOnFocusOut; this.showErrorOnFocusOut = showErrorOnFocusOut;

View File

@ -12,7 +12,8 @@ import rx.Observable;
* Created by Ilia Kurtov on 24/01/2017. * Created by Ilia Kurtov on 24/01/2017.
* {@link ValidationController} for models that have the same modal as wrapper model. You can use it when you simply need to be sure * {@link ValidationController} for models that have the same modal as wrapper model. You can use it when you simply need to be sure
* that user have selected some item and it is not null. * that user have selected some item and it is not null.
* @param <TModel> type of the model. *
* @param <TModel> type of the model.
* @param <TValidator> corresponding {@link Validator} * @param <TValidator> corresponding {@link Validator}
*/ */
public class SimpleValidationController<TModel extends Serializable, TValidator extends Validator<TModel, TModel>> public class SimpleValidationController<TModel extends Serializable, TValidator extends Validator<TModel, TModel>>
@ -24,6 +25,7 @@ public class SimpleValidationController<TModel extends Serializable, TValidator
/** /**
* This method validates bounded view. * This method validates bounded view.
*
* @param activatedObservable emits true when we need to show error on empty fields. Eg when user clicks on Done button but he missed some * @param activatedObservable emits true when we need to show error on empty fields. Eg when user clicks on Done button but he missed some
* necessary fields to fill. * necessary fields to fill.
* @return observable without any concrete type. Simply subscribe to this method to make it works. * @return observable without any concrete type. Simply subscribe to this method to make it works.

View File

@ -53,10 +53,11 @@ public class ValidationController
/** /**
* Bind to this observable to connect view and model. If you provide first argument (viewStateObservable) - the connection would be two-way. * Bind to this observable to connect view and model. If you provide first argument (viewStateObservable) - the connection would be two-way.
* If not - one-way. This method changes updates view with current {@link ValidationState}. * If not - one-way. This method changes updates view with current {@link ValidationState}.
*
* @param viewStateObservable input view state {@link Observable}. * @param viewStateObservable input view state {@link Observable}.
* Eg it can be observable with input text from the {@link android.widget.EditText} * Eg it can be observable with input text from the {@link android.widget.EditText}
* @param updateViewAction action that updates current state of the bounded view. * @param updateViewAction action that updates current state of the bounded view.
* @param viewWithError view that implements {@link ViewWithError} interface and could reacts to the validation errors. * @param viewWithError view that implements {@link ViewWithError} interface and could reacts to the validation errors.
* @return observable without any concrete type. Simply subscribe to this method to make it works. * @return observable without any concrete type. Simply subscribe to this method to make it works.
*/ */
@NonNull @NonNull
@ -68,7 +69,7 @@ public class ValidationController
: Observable.empty(); : Observable.empty();
return Observable return Observable
.merge(getValidator().getWrapperModel().observe() .merge(getValidator().getWrapperModel().observe()
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.doOnNext(updateViewAction), .doOnNext(updateViewAction),
getValidator().getValidationState().observe() getValidator().getValidationState().observe()
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
@ -84,6 +85,7 @@ public class ValidationController
/** /**
* Helper function to check if validation state in error state ot not * Helper function to check if validation state in error state ot not
*
* @param validationState the state you want to check for the errors. * @param validationState the state you want to check for the errors.
* @return true if validation state is in error and false otherwise. * @return true if validation state is in error and false otherwise.
*/ */

View File

@ -51,6 +51,7 @@ public abstract class EditTextValidator<TModel extends Serializable> extends Val
* This flag needed to force showing errors. You don't want to show final error when you start to enter data in some field at first time. * This flag needed to force showing errors. You don't want to show final error when you start to enter data in some field at first time.
* But if user leaves this view and final check not passed - you need to force to show an error till user not enters correct data and leaves * But if user leaves this view and final check not passed - you need to force to show an error till user not enters correct data and leaves
* the view. * the view.
*
* @return {@link NonNullChangeable} with current state of the flag - do we need to show errors from final checks while user types. * @return {@link NonNullChangeable} with current state of the flag - do we need to show errors from final checks while user types.
*/ */
@NonNull @NonNull
@ -60,6 +61,7 @@ public abstract class EditTextValidator<TModel extends Serializable> extends Val
/** /**
* Use this method to get or set final check. * Use this method to get or set final check.
*
* @return final check. * @return final check.
*/ */
@NonNull @NonNull
@ -69,6 +71,7 @@ public abstract class EditTextValidator<TModel extends Serializable> extends Val
/** /**
* Use this method to get or set primary check. * Use this method to get or set primary check.
*
* @return primary check. * @return primary check.
*/ */
@NonNull @NonNull
@ -119,6 +122,7 @@ public abstract class EditTextValidator<TModel extends Serializable> extends Val
/** /**
* Validates text with primary check. * Validates text with primary check.
*
* @param text - input text. * @param text - input text.
* @return {@link Observable} with the result of the primary check. * @return {@link Observable} with the result of the primary check.
*/ */
@ -129,6 +133,7 @@ public abstract class EditTextValidator<TModel extends Serializable> extends Val
/** /**
* Validates text with final check. * Validates text with final check.
*
* @param text - input text. * @param text - input text.
* @return {@link Observable} with the result of the final check. * @return {@link Observable} with the result of the final check.
*/ */
@ -140,9 +145,10 @@ public abstract class EditTextValidator<TModel extends Serializable> extends Val
/** /**
* Validates text with primary and final check consequentially and returns {@link Observable} with {@link HalfNullablePair} of final state * Validates text with primary and final check consequentially and returns {@link Observable} with {@link HalfNullablePair} of final state
* and resulting model. * and resulting model.
*
* @param text - input text. * @param text - input text.
* @return pair with final {@link ValidationState} that is always not null and a model that we get after converting the text. * @return pair with final {@link ValidationState} that is always not null and a model that we get after converting the text.
* Model can be null if validation fails on primary or final checks. * Model can be null if validation fails on primary or final checks.
*/ */
@NonNull @NonNull
@Override @Override

View File

@ -12,12 +12,14 @@ import rx.Observable;
/** /**
* Created by Ilia Kurtov on 24/01/2017. * Created by Ilia Kurtov on 24/01/2017.
* Class that simplifies work with {@link Validator}'s that have the same wrapper model and model type. * Class that simplifies work with {@link Validator}'s that have the same wrapper model and model type.
*
* @param <TModel> model that should be bounded with a view. * @param <TModel> model that should be bounded with a view.
*/ */
public class SameTypeValidator<TModel extends Serializable> extends Validator<TModel, TModel> { public class SameTypeValidator<TModel extends Serializable> extends Validator<TModel, TModel> {
/** /**
* Simply returns the same model without any converting. * Simply returns the same model without any converting.
*
* @param wrapperModel input model. * @param wrapperModel input model.
* @return the same model as input parameter. * @return the same model as input parameter.
* @throws Throwable - in this case no throwable would be thrown. * @throws Throwable - in this case no throwable would be thrown.
@ -31,9 +33,10 @@ public class SameTypeValidator<TModel extends Serializable> extends Validator<TM
/** /**
* Validates {@link TModel} and returns {@link Observable} with {@link HalfNullablePair} of final state and resulting model. * Validates {@link TModel} and returns {@link Observable} with {@link HalfNullablePair} of final state and resulting model.
*
* @param wrapperModel - not null value that should be validated. * @param wrapperModel - not null value that should be validated.
* @return pair with final {@link ValidationState} that is always not null and a model that we get after converting the {@link TModel}. * @return pair with final {@link ValidationState} that is always not null and a model that we get after converting the {@link TModel}.
* Model can be null if validation fails. * Model can be null if validation fails.
*/ */
@NonNull @NonNull
@Override @Override

View File

@ -54,6 +54,7 @@ public abstract class Validator<TWrapperModel extends Serializable, TModel exten
/** /**
* This method converts {@link TWrapperModel} into a {@link TModel}. * This method converts {@link TWrapperModel} into a {@link TModel}.
*
* @param wrapperModel - not null value that should be converted into a {@link TModel} object. * @param wrapperModel - not null value that should be converted into a {@link TModel} object.
* @return converted wrapperModel into a {@link TModel}. * @return converted wrapperModel into a {@link TModel}.
* @throws Throwable for the cases when converting cannot be processed. * @throws Throwable for the cases when converting cannot be processed.
@ -63,6 +64,7 @@ public abstract class Validator<TWrapperModel extends Serializable, TModel exten
/** /**
* Call this method to get {@link Changeable} with {@link TWrapperModel} inside it that should be connected to its bounded view. * Call this method to get {@link Changeable} with {@link TWrapperModel} inside it that should be connected to its bounded view.
*
* @return {@link Changeable} with {@link TWrapperModel}. * @return {@link Changeable} with {@link TWrapperModel}.
*/ */
@NonNull @NonNull
@ -72,6 +74,7 @@ public abstract class Validator<TWrapperModel extends Serializable, TModel exten
/** /**
* Returns current {@link ValidationState} or its successor. Needed to connect with bounded view and react to this state changes. * Returns current {@link ValidationState} or its successor. Needed to connect with bounded view and react to this state changes.
*
* @return current validation state. * @return current validation state.
*/ */
@NonNull @NonNull
@ -82,6 +85,7 @@ public abstract class Validator<TWrapperModel extends Serializable, TModel exten
/** /**
* This method needed to get {@link ValidationState} that needed to be shown when bounded view is empty and you need to show to user reminder, * This method needed to get {@link ValidationState} that needed to be shown when bounded view is empty and you need to show to user reminder,
* that he or she needs to fill this view. * that he or she needs to fill this view.
*
* @return {@link ValidationState} that should be shown for an empty field. * @return {@link ValidationState} that should be shown for an empty field.
*/ */
@NonNull @NonNull
@ -91,9 +95,10 @@ 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 Observable} 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}.
* Model can be null if validation fails. * Model can be null if validation fails.
*/ */
@NonNull @NonNull
public abstract Observable<HalfNullablePair<ValidationState, TModel>> fullValidateAndGetModel(@NonNull final TWrapperModel wrapperModel); public abstract Observable<HalfNullablePair<ValidationState, TModel>> fullValidateAndGetModel(@NonNull final TWrapperModel wrapperModel);