From fd68f1ffd426eb585dd11a7fb4e737790f5c7487 Mon Sep 17 00:00:00 2001 From: Gavriil Sitnikov Date: Mon, 17 Apr 2017 03:11:46 +0300 Subject: [PATCH 01/95] RxJava2 migration --- build.gradle | 4 +- .../adapters/BindableViewHolder.java | 94 +++++----- .../adapters/ObservableCollectionAdapter.java | 16 +- .../navigation/FragmentNavigation.java | 46 +++-- .../components/navigation/ViewController.java | 99 +++++------ .../navigation/ViewControllerNavigation.java | 34 ++-- .../navigation/activities/BaseActivity.java | 100 +++++------ .../fragments/ViewControllerFragment.java | 21 +-- .../navigation/fragments/ViewFragment.java | 10 +- .../utils/BaseLifecycleBindable.java | 164 +++++++++--------- .../components/utils/LifecycleBindable.java | 156 ++++++++--------- .../roboswag/components/utils/UiUtils.java | 24 +-- .../utils/audio/HeadsetStateObserver.java | 12 +- .../utils/audio/VolumeController.java | 8 +- .../utils/storables/PreferenceStore.java | 4 +- .../utils/storables/PreferenceUtils.java | 2 +- 16 files changed, 386 insertions(+), 408 deletions(-) diff --git a/build.gradle b/build.gradle index 7ba31be..18085af 100644 --- a/build.gradle +++ b/build.gradle @@ -22,6 +22,6 @@ dependencies { provided 'com.android.support:appcompat-v7:25.3.1' provided 'com.android.support:recyclerview-v7:25.3.1' - provided 'io.reactivex:rxandroid:1.2.1' - provided 'io.reactivex:rxjava:1.2.9' + provided 'io.reactivex.rxjava2:rxandroid:2.0.1' + provided 'io.reactivex.rxjava2:rxjava:2.0.8' } diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/BindableViewHolder.java b/src/main/java/ru/touchin/roboswag/components/adapters/BindableViewHolder.java index 2748721..23a7454 100644 --- a/src/main/java/ru/touchin/roboswag/components/adapters/BindableViewHolder.java +++ b/src/main/java/ru/touchin/roboswag/components/adapters/BindableViewHolder.java @@ -31,14 +31,14 @@ import android.support.v4.content.ContextCompat; import android.support.v7.widget.RecyclerView; import android.view.View; +import io.reactivex.Completable; +import io.reactivex.Observable; +import io.reactivex.Single; +import io.reactivex.disposables.Disposable; +import io.reactivex.functions.Action; +import io.reactivex.functions.Consumer; import ru.touchin.roboswag.components.utils.LifecycleBindable; import ru.touchin.roboswag.core.utils.ShouldNotHappenException; -import rx.Completable; -import rx.Observable; -import rx.Single; -import rx.Subscription; -import rx.functions.Action0; -import rx.functions.Action1; /** * Created by Gavriil Sitnikov on 12/8/2016. @@ -124,145 +124,139 @@ public class BindableViewHolder extends RecyclerView.ViewHolder implements Lifec @NonNull @Override - public Subscription bind(@NonNull final Observable observable, @NonNull final Action1 onNextAction) { - return baseLifecycleBindable.bind(observable, onNextAction); - } - - @NonNull - @Override - public Subscription untilStop(@NonNull final Observable observable) { + public Disposable untilStop(@NonNull final Observable observable) { return baseLifecycleBindable.untilStop(observable); } @NonNull @Override - public Subscription untilStop(@NonNull final Observable observable, @NonNull final Action1 onNextAction) { + public Disposable untilStop(@NonNull final Observable observable, @NonNull final Consumer onNextAction) { return baseLifecycleBindable.untilStop(observable, onNextAction); } @NonNull @Override - public Subscription untilStop(@NonNull final Observable observable, - @NonNull final Action1 onNextAction, - @NonNull final Action1 onErrorAction) { + public Disposable untilStop(@NonNull final Observable observable, + @NonNull final Consumer onNextAction, + @NonNull final Consumer onErrorAction) { return baseLifecycleBindable.untilStop(observable, onNextAction, onErrorAction); } @NonNull @Override - public Subscription untilStop(@NonNull final Observable observable, - @NonNull final Action1 onNextAction, - @NonNull final Action1 onErrorAction, - @NonNull final Action0 onCompletedAction) { + public Disposable untilStop(@NonNull final Observable observable, + @NonNull final Consumer onNextAction, + @NonNull final Consumer onErrorAction, + @NonNull final Action onCompletedAction) { return baseLifecycleBindable.untilStop(observable, onNextAction, onErrorAction, onCompletedAction); } @NonNull @Override - public Subscription untilStop(@NonNull final Single single) { + public Disposable untilStop(@NonNull final Single single) { return baseLifecycleBindable.untilStop(single); } @NonNull @Override - public Subscription untilStop(@NonNull final Single single, @NonNull final Action1 onSuccessAction) { + public Disposable untilStop(@NonNull final Single single, @NonNull final Consumer onSuccessAction) { return baseLifecycleBindable.untilStop(single, onSuccessAction); } @NonNull @Override - public Subscription untilStop(@NonNull final Single single, - @NonNull final Action1 onSuccessAction, - @NonNull final Action1 onErrorAction) { + public Disposable untilStop(@NonNull final Single single, + @NonNull final Consumer onSuccessAction, + @NonNull final Consumer onErrorAction) { return baseLifecycleBindable.untilStop(single, onSuccessAction, onErrorAction); } @NonNull @Override - public Subscription untilStop(@NonNull final Completable completable) { + public Disposable untilStop(@NonNull final Completable completable) { return baseLifecycleBindable.untilStop(completable); } @NonNull @Override - public Subscription untilStop(@NonNull final Completable completable, @NonNull final Action0 onCompletedAction) { + public Disposable untilStop(@NonNull final Completable completable, @NonNull final Action onCompletedAction) { return baseLifecycleBindable.untilStop(completable, onCompletedAction); } @NonNull @Override - public Subscription untilStop(@NonNull final Completable completable, - @NonNull final Action0 onCompletedAction, - @NonNull final Action1 onErrorAction) { + public Disposable untilStop(@NonNull final Completable completable, + @NonNull final Action onCompletedAction, + @NonNull final Consumer onErrorAction) { return baseLifecycleBindable.untilStop(completable, onCompletedAction, onErrorAction); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Observable observable) { + public Disposable untilDestroy(@NonNull final Observable observable) { return baseLifecycleBindable.untilDestroy(observable); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Observable observable, @NonNull final Action1 onNextAction) { + public Disposable untilDestroy(@NonNull final Observable observable, @NonNull final Consumer onNextAction) { return baseLifecycleBindable.untilDestroy(observable, onNextAction); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Observable observable, - @NonNull final Action1 onNextAction, - @NonNull final Action1 onErrorAction) { + public Disposable untilDestroy(@NonNull final Observable observable, + @NonNull final Consumer onNextAction, + @NonNull final Consumer onErrorAction) { return baseLifecycleBindable.untilDestroy(observable, onNextAction, onErrorAction); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Observable observable, - @NonNull final Action1 onNextAction, - @NonNull final Action1 onErrorAction, - @NonNull final Action0 onCompletedAction) { + public Disposable untilDestroy(@NonNull final Observable observable, + @NonNull final Consumer onNextAction, + @NonNull final Consumer onErrorAction, + @NonNull final Action onCompletedAction) { return baseLifecycleBindable.untilDestroy(observable, onNextAction, onErrorAction, onCompletedAction); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Single single) { + public Disposable untilDestroy(@NonNull final Single single) { return baseLifecycleBindable.untilDestroy(single); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Single single, @NonNull final Action1 onSuccessAction) { + public Disposable untilDestroy(@NonNull final Single single, @NonNull final Consumer onSuccessAction) { return baseLifecycleBindable.untilDestroy(single, onSuccessAction); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Single single, - @NonNull final Action1 onSuccessAction, - @NonNull final Action1 onErrorAction) { + public Disposable untilDestroy(@NonNull final Single single, + @NonNull final Consumer onSuccessAction, + @NonNull final Consumer onErrorAction) { return baseLifecycleBindable.untilDestroy(single, onSuccessAction, onErrorAction); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Completable completable) { + public Disposable untilDestroy(@NonNull final Completable completable) { return baseLifecycleBindable.untilDestroy(completable); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Completable completable, @NonNull final Action0 onCompletedAction) { + public Disposable untilDestroy(@NonNull final Completable completable, @NonNull final Action onCompletedAction) { return baseLifecycleBindable.untilDestroy(completable, onCompletedAction); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Completable completable, - @NonNull final Action0 onCompletedAction, - @NonNull final Action1 onErrorAction) { + public Disposable untilDestroy(@NonNull final Completable completable, + @NonNull final Action onCompletedAction, + @NonNull final Consumer onErrorAction) { return baseLifecycleBindable.untilDestroy(completable, onCompletedAction, onErrorAction); } diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/ObservableCollectionAdapter.java b/src/main/java/ru/touchin/roboswag/components/adapters/ObservableCollectionAdapter.java index 9e25e55..8a877bf 100644 --- a/src/main/java/ru/touchin/roboswag/components/adapters/ObservableCollectionAdapter.java +++ b/src/main/java/ru/touchin/roboswag/components/adapters/ObservableCollectionAdapter.java @@ -32,6 +32,10 @@ import java.util.LinkedList; import java.util.List; import java.util.concurrent.TimeUnit; +import io.reactivex.Observable; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; +import io.reactivex.subjects.BehaviorSubject; import ru.touchin.roboswag.components.utils.LifecycleBindable; import ru.touchin.roboswag.components.utils.UiUtils; import ru.touchin.roboswag.core.log.Lc; @@ -40,10 +44,6 @@ import ru.touchin.roboswag.core.observables.collections.ObservableCollection; import ru.touchin.roboswag.core.observables.collections.ObservableList; import ru.touchin.roboswag.core.observables.collections.loadable.LoadingMoreList; import ru.touchin.roboswag.core.utils.Optional; -import rx.Observable; -import rx.Subscription; -import rx.android.schedulers.AndroidSchedulers; -import rx.subjects.BehaviorSubject; /** * Created by Gavriil Sitnikov on 20/11/2015. @@ -63,7 +63,7 @@ public abstract class ObservableCollectionAdapter>> observableCollectionSubject - = BehaviorSubject.create(new Optional<>(null)); + = BehaviorSubject.createDefault(new Optional<>(null)); @NonNull private final LifecycleBindable lifecycleBindable; @Nullable @@ -98,7 +98,7 @@ public abstract class ObservableCollectionAdapter adapter.innerCollection.size() - PRE_LOADING_COUNT) { diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.java b/src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.java index db4346a..3909605 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.java +++ b/src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.java @@ -30,8 +30,8 @@ import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.view.MenuItem; +import io.reactivex.functions.Function; import ru.touchin.roboswag.core.log.Lc; -import rx.functions.Func1; /** * Created by Gavriil Sitnikov on 07/03/2016. @@ -44,7 +44,7 @@ import rx.functions.Func1; * If {@link #back()} method will be called then stack will go to previous fragment. * Usually such logic using to set as top fragments from sidebar and show hamburger when some of them appeared; * 4) {@link #pushForResult} means to push fragment with target fragment. It is also adding {@link #WITH_TARGET_FRAGMENT_TAG_MARK} tag. - * Also if such up/back navigation logic is not OK then {@link #backTo(Func1)} method could be used with any condition to back to. + * Also if such up/back navigation logic is not OK then {@link #backTo(Function)} method could be used with any condition to back to. * In that case in any stack-change method it is allowed to setup fragment transactions. */ public class FragmentNavigation { @@ -128,7 +128,7 @@ public class FragmentNavigation { @Nullable final Fragment targetFragment, @Nullable final Bundle args, @Nullable final String backStackTag, - @Nullable final Func1 transactionSetup) { + @Nullable final Function transactionSetup) { if (fragmentManager.isDestroyed()) { Lc.assertion("FragmentManager is destroyed"); return; @@ -150,7 +150,12 @@ public class FragmentNavigation { fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN); } if (transactionSetup != null) { - transactionSetup.call(fragmentTransaction).commit(); + try { + transactionSetup.apply(fragmentTransaction).commit(); + } catch (final Exception exception) { + Lc.assertion(exception); + fragmentTransaction.commit(); + } } else { fragmentTransaction.commit(); } @@ -175,15 +180,20 @@ public class FragmentNavigation { * @param condition Condition of back stack entry to be satisfied; * @return True if it have back to some entry in stack. */ - public boolean backTo(@NonNull final Func1 condition) { + public boolean backTo(@NonNull final Function condition) { final int stackSize = fragmentManager.getBackStackEntryCount(); Integer id = null; - for (int i = stackSize - 2; i >= 0; i--) { - final FragmentManager.BackStackEntry backStackEntry = fragmentManager.getBackStackEntryAt(i); - id = backStackEntry.getId(); - if (condition.call(backStackEntry)) { - break; + try { + for (int i = stackSize - 2; i >= 0; i--) { + final FragmentManager.BackStackEntry backStackEntry = fragmentManager.getBackStackEntryAt(i); + id = backStackEntry.getId(); + if (condition.apply(backStackEntry)) { + break; + } } + } catch (final Exception exception) { + Lc.assertion(exception); + return false; } if (id != null) { fragmentManager.popBackStack(id, 0); @@ -233,7 +243,7 @@ public class FragmentNavigation { * @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info. */ public void push(@NonNull final Class fragmentClass, - @NonNull final Func1 transactionSetup) { + @NonNull final Function transactionSetup) { addToStack(fragmentClass, null, null, null, transactionSetup); } @@ -246,7 +256,7 @@ public class FragmentNavigation { */ public void push(@NonNull final Class fragmentClass, @Nullable final Bundle args, - @Nullable final Func1 transactionSetup) { + @Nullable final Function transactionSetup) { addToStack(fragmentClass, null, args, null, transactionSetup); } @@ -283,7 +293,7 @@ public class FragmentNavigation { */ public void pushForResult(@NonNull final Class fragmentClass, @NonNull final Fragment targetFragment, - @NonNull final Func1 transactionSetup) { + @NonNull final Function transactionSetup) { addToStack(fragmentClass, targetFragment, null, fragmentClass.getName() + ';' + WITH_TARGET_FRAGMENT_TAG_MARK, transactionSetup); } @@ -298,7 +308,7 @@ public class FragmentNavigation { public void pushForResult(@NonNull final Class fragmentClass, @NonNull final Fragment targetFragment, @Nullable final Bundle args, - @Nullable final Func1 transactionSetup) { + @Nullable final Function transactionSetup) { addToStack(fragmentClass, targetFragment, args, fragmentClass.getName() + ';' + WITH_TARGET_FRAGMENT_TAG_MARK, transactionSetup); } @@ -330,7 +340,7 @@ public class FragmentNavigation { * @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info. */ public void setAsTop(@NonNull final Class fragmentClass, - @NonNull final Func1 transactionSetup) { + @NonNull final Function transactionSetup) { addToStack(fragmentClass, null, null, fragmentClass.getName() + ';' + TOP_FRAGMENT_TAG_MARK, transactionSetup); } @@ -344,7 +354,7 @@ public class FragmentNavigation { */ public void setAsTop(@NonNull final Class fragmentClass, @Nullable final Bundle args, - @Nullable final Func1 transactionSetup) { + @Nullable final Function transactionSetup) { addToStack(fragmentClass, null, args, fragmentClass.getName() + ';' + TOP_FRAGMENT_TAG_MARK, transactionSetup); } @@ -375,7 +385,7 @@ public class FragmentNavigation { * @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info. */ public void setInitial(@NonNull final Class fragmentClass, - @NonNull final Func1 transactionSetup) { + @NonNull final Function transactionSetup) { setInitial(fragmentClass, null, transactionSetup); } @@ -388,7 +398,7 @@ public class FragmentNavigation { */ public void setInitial(@NonNull final Class fragmentClass, @Nullable final Bundle args, - @Nullable final Func1 transactionSetup) { + @Nullable final Function transactionSetup) { beforeSetInitialActions(); setAsTop(fragmentClass, args, transactionSetup); } diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/ViewController.java b/src/main/java/ru/touchin/roboswag/components/navigation/ViewController.java index 8d70cde..3fa2fc4 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/ViewController.java +++ b/src/main/java/ru/touchin/roboswag/components/navigation/ViewController.java @@ -37,6 +37,12 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import io.reactivex.Completable; +import io.reactivex.Observable; +import io.reactivex.Single; +import io.reactivex.disposables.Disposable; +import io.reactivex.functions.Action; +import io.reactivex.functions.Consumer; import ru.touchin.roboswag.components.navigation.activities.ViewControllerActivity; import ru.touchin.roboswag.components.navigation.fragments.ViewControllerFragment; import ru.touchin.roboswag.components.utils.BaseLifecycleBindable; @@ -44,12 +50,6 @@ import ru.touchin.roboswag.components.utils.LifecycleBindable; import ru.touchin.roboswag.components.utils.UiUtils; import ru.touchin.roboswag.core.log.Lc; import ru.touchin.roboswag.core.utils.ShouldNotHappenException; -import rx.Completable; -import rx.Observable; -import rx.Single; -import rx.Subscription; -import rx.functions.Action0; -import rx.functions.Action1; /** * Created by Gavriil Sitnikov on 21/10/2015. @@ -337,155 +337,146 @@ public class ViewController, return false; } - @SuppressWarnings("CPD-START") - //CPD: it is same as in other implementation based on BaseLifecycleBindable @NonNull @Override - public Subscription bind(@NonNull final Observable observable, @NonNull final Action1 onNextAction) { - return baseLifecycleBindable.bind(observable, onNextAction); - } - - @NonNull - @Override - public Subscription untilStop(@NonNull final Observable observable) { + public Disposable untilStop(@NonNull final Observable observable) { return baseLifecycleBindable.untilStop(observable); } @NonNull @Override - public Subscription untilStop(@NonNull final Observable observable, @NonNull final Action1 onNextAction) { + public Disposable untilStop(@NonNull final Observable observable, @NonNull final Consumer onNextAction) { return baseLifecycleBindable.untilStop(observable, onNextAction); } @NonNull @Override - public Subscription untilStop(@NonNull final Observable observable, - @NonNull final Action1 onNextAction, - @NonNull final Action1 onErrorAction) { + public Disposable untilStop(@NonNull final Observable observable, + @NonNull final Consumer onNextAction, + @NonNull final Consumer onErrorAction) { return baseLifecycleBindable.untilStop(observable, onNextAction, onErrorAction); } @NonNull @Override - public Subscription untilStop(@NonNull final Observable observable, - @NonNull final Action1 onNextAction, - @NonNull final Action1 onErrorAction, - @NonNull final Action0 onCompletedAction) { + public Disposable untilStop(@NonNull final Observable observable, + @NonNull final Consumer onNextAction, + @NonNull final Consumer onErrorAction, + @NonNull final Action onCompletedAction) { return baseLifecycleBindable.untilStop(observable, onNextAction, onErrorAction, onCompletedAction); } @NonNull @Override - public Subscription untilStop(@NonNull final Single single) { + public Disposable untilStop(@NonNull final Single single) { return baseLifecycleBindable.untilStop(single); } @NonNull @Override - public Subscription untilStop(@NonNull final Single single, @NonNull final Action1 onSuccessAction) { + public Disposable untilStop(@NonNull final Single single, @NonNull final Consumer onSuccessAction) { return baseLifecycleBindable.untilStop(single, onSuccessAction); } @NonNull @Override - public Subscription untilStop(@NonNull final Single single, - @NonNull final Action1 onSuccessAction, - @NonNull final Action1 onErrorAction) { + public Disposable untilStop(@NonNull final Single single, + @NonNull final Consumer onSuccessAction, + @NonNull final Consumer onErrorAction) { return baseLifecycleBindable.untilStop(single, onSuccessAction, onErrorAction); } @NonNull @Override - public Subscription untilStop(@NonNull final Completable completable) { + public Disposable untilStop(@NonNull final Completable completable) { return baseLifecycleBindable.untilStop(completable); } @NonNull @Override - public Subscription untilStop(@NonNull final Completable completable, @NonNull final Action0 onCompletedAction) { + public Disposable untilStop(@NonNull final Completable completable, @NonNull final Action onCompletedAction) { return baseLifecycleBindable.untilStop(completable, onCompletedAction); } @NonNull @Override - public Subscription untilStop(@NonNull final Completable completable, - @NonNull final Action0 onCompletedAction, - @NonNull final Action1 onErrorAction) { + public Disposable untilStop(@NonNull final Completable completable, + @NonNull final Action onCompletedAction, + @NonNull final Consumer onErrorAction) { return baseLifecycleBindable.untilStop(completable, onCompletedAction, onErrorAction); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Observable observable) { + public Disposable untilDestroy(@NonNull final Observable observable) { return baseLifecycleBindable.untilDestroy(observable); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Observable observable, @NonNull final Action1 onNextAction) { + public Disposable untilDestroy(@NonNull final Observable observable, @NonNull final Consumer onNextAction) { return baseLifecycleBindable.untilDestroy(observable, onNextAction); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Observable observable, - @NonNull final Action1 onNextAction, - @NonNull final Action1 onErrorAction) { + public Disposable untilDestroy(@NonNull final Observable observable, + @NonNull final Consumer onNextAction, + @NonNull final Consumer onErrorAction) { return baseLifecycleBindable.untilDestroy(observable, onNextAction, onErrorAction); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Observable observable, - @NonNull final Action1 onNextAction, - @NonNull final Action1 onErrorAction, - @NonNull final Action0 onCompletedAction) { + public Disposable untilDestroy(@NonNull final Observable observable, + @NonNull final Consumer onNextAction, + @NonNull final Consumer onErrorAction, + @NonNull final Action onCompletedAction) { return baseLifecycleBindable.untilDestroy(observable, onNextAction, onErrorAction, onCompletedAction); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Single single) { + public Disposable untilDestroy(@NonNull final Single single) { return baseLifecycleBindable.untilDestroy(single); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Single single, @NonNull final Action1 onSuccessAction) { + public Disposable untilDestroy(@NonNull final Single single, @NonNull final Consumer onSuccessAction) { return baseLifecycleBindable.untilDestroy(single, onSuccessAction); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Single single, - @NonNull final Action1 onSuccessAction, - @NonNull final Action1 onErrorAction) { + public Disposable untilDestroy(@NonNull final Single single, + @NonNull final Consumer onSuccessAction, + @NonNull final Consumer onErrorAction) { return baseLifecycleBindable.untilDestroy(single, onSuccessAction, onErrorAction); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Completable completable) { + public Disposable untilDestroy(@NonNull final Completable completable) { return baseLifecycleBindable.untilDestroy(completable); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Completable completable, @NonNull final Action0 onCompletedAction) { + public Disposable untilDestroy(@NonNull final Completable completable, @NonNull final Action onCompletedAction) { return baseLifecycleBindable.untilDestroy(completable, onCompletedAction); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Completable completable, - @NonNull final Action0 onCompletedAction, - @NonNull final Action1 onErrorAction) { + public Disposable untilDestroy(@NonNull final Completable completable, + @NonNull final Action onCompletedAction, + @NonNull final Consumer onErrorAction) { return baseLifecycleBindable.untilDestroy(completable, onCompletedAction, onErrorAction); } @SuppressWarnings("CPD-END") - //CPD: it is same as in other implementation based on BaseLifecycleBindable - /** + /* * Helper class to simplify constructor override. */ public static class CreationContext { diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/ViewControllerNavigation.java b/src/main/java/ru/touchin/roboswag/components/navigation/ViewControllerNavigation.java index b4196e3..58fad74 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/ViewControllerNavigation.java +++ b/src/main/java/ru/touchin/roboswag/components/navigation/ViewControllerNavigation.java @@ -27,13 +27,13 @@ import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; +import io.reactivex.functions.Function; import ru.touchin.roboswag.components.navigation.activities.ViewControllerActivity; import ru.touchin.roboswag.components.navigation.fragments.SimpleViewControllerFragment; import ru.touchin.roboswag.components.navigation.fragments.StatelessTargetedViewControllerFragment; import ru.touchin.roboswag.components.navigation.fragments.StatelessViewControllerFragment; import ru.touchin.roboswag.components.navigation.fragments.TargetedViewControllerFragment; import ru.touchin.roboswag.components.navigation.fragments.ViewControllerFragment; -import rx.functions.Func1; /** * Created by Gavriil Sitnikov on 07/03/2016. @@ -72,7 +72,7 @@ public class ViewControllerNavigation void push(@NonNull final Class> fragmentClass, @Nullable final TState state, - @Nullable final Func1 transactionSetup) { + @Nullable final Function transactionSetup) { addToStack(fragmentClass, null, ViewControllerFragment.createState(state), null, transactionSetup); } @@ -103,7 +103,7 @@ public class ViewControllerNavigation void pushForResult(@NonNull final Class> fragmentClass, @NonNull final Fragment targetFragment, @Nullable final TState state, - @Nullable final Func1 transactionSetup) { + @Nullable final Function transactionSetup) { addToStack(fragmentClass, targetFragment, ViewControllerFragment.createState(state), fragmentClass.getName() + ';' + WITH_TARGET_FRAGMENT_TAG_MARK, transactionSetup); } @@ -131,7 +131,7 @@ public class ViewControllerNavigation void setAsTop(@NonNull final Class> fragmentClass, @Nullable final TState state, - @Nullable final Func1 transactionSetup) { + @Nullable final Function transactionSetup) { setAsTop(fragmentClass, ViewControllerFragment.createState(state), transactionSetup); } @@ -157,7 +157,7 @@ public class ViewControllerNavigation void setInitial(@NonNull final Class> fragmentClass, @Nullable final TState state, - @Nullable final Func1 transactionSetup) { + @Nullable final Function transactionSetup) { setInitial(fragmentClass, ViewControllerFragment.createState(state), transactionSetup); } @@ -192,7 +192,7 @@ public class ViewControllerNavigation>> viewControllerClass, - @Nullable final Func1 transactionSetup) { + @Nullable final Function transactionSetup) { addStatelessViewControllerToStack(viewControllerClass, null, null, transactionSetup); } @@ -207,7 +207,7 @@ public class ViewControllerNavigation void pushViewController( @NonNull final Class>> viewControllerClass, @NonNull final TState state, - @Nullable final Func1 transactionSetup) { + @Nullable final Function transactionSetup) { addViewControllerToStack(viewControllerClass, null, state, null, transactionSetup); } @@ -242,7 +242,7 @@ public class ViewControllerNavigation>> viewControllerClass, @NonNull final TTargetFragment targetFragment, - @Nullable final Func1 transactionSetup) { + @Nullable final Function transactionSetup) { addTargetedStatelessViewControllerToStack(viewControllerClass, targetFragment, viewControllerClass.getName() + ';' + WITH_TARGET_FRAGMENT_TAG_MARK, transactionSetup); } @@ -288,7 +288,7 @@ public class ViewControllerNavigation>> viewControllerClass, @NonNull final TTargetFragment targetFragment, @NonNull final TState state, - @Nullable final Func1 transactionSetup) { + @Nullable final Function transactionSetup) { addTargetedViewControllerToStack(viewControllerClass, targetFragment, state, viewControllerClass.getName() + ';' + WITH_TARGET_FRAGMENT_TAG_MARK, transactionSetup); } @@ -326,7 +326,7 @@ public class ViewControllerNavigation>> viewControllerClass, - @Nullable final Func1 transactionSetup) { + @Nullable final Function transactionSetup) { addStatelessViewControllerToStack(viewControllerClass, null, viewControllerClass.getName() + ' ' + TOP_FRAGMENT_TAG_MARK, transactionSetup); } @@ -342,7 +342,7 @@ public class ViewControllerNavigation void setViewControllerAsTop( @NonNull final Class>> viewControllerClass, @NonNull final TState state, - @Nullable final Func1 transactionSetup) { + @Nullable final Function transactionSetup) { addViewControllerToStack(viewControllerClass, null, state, viewControllerClass.getName() + ' ' + TOP_FRAGMENT_TAG_MARK, transactionSetup); } @@ -379,7 +379,7 @@ public class ViewControllerNavigation>> viewControllerClass, - @Nullable final Func1 transactionSetup) { + @Nullable final Function transactionSetup) { beforeSetInitialActions(); setViewControllerAsTop(viewControllerClass, transactionSetup); } @@ -396,7 +396,7 @@ public class ViewControllerNavigation void setInitialViewController( @NonNull final Class>> viewControllerClass, @NonNull final TState state, - @Nullable final Func1 transactionSetup) { + @Nullable final Function transactionSetup) { beforeSetInitialActions(); setViewControllerAsTop(viewControllerClass, state, transactionSetup); } @@ -413,7 +413,7 @@ public class ViewControllerNavigation>> viewControllerClass, @Nullable final Fragment targetFragment, @Nullable final String backStackTag, - @Nullable final Func1 transactionSetup) { + @Nullable final Function transactionSetup) { addToStack(StatelessViewControllerFragment.class, targetFragment, StatelessViewControllerFragment.createState(viewControllerClass), backStackTag, transactionSetup); } @@ -435,7 +435,7 @@ public class ViewControllerNavigation transactionSetup) { + @Nullable final Function transactionSetup) { addToStack(TargetedViewControllerFragment.class, targetFragment, TargetedViewControllerFragment.createState(viewControllerClass, state), backStackTag, transactionSetup); } @@ -454,7 +454,7 @@ public class ViewControllerNavigation>> viewControllerClass, @NonNull final Fragment targetFragment, @Nullable final String backStackTag, - @Nullable final Func1 transactionSetup) { + @Nullable final Function transactionSetup) { addToStack(StatelessTargetedViewControllerFragment.class, targetFragment, StatelessTargetedViewControllerFragment.createState(viewControllerClass), backStackTag, transactionSetup); } @@ -474,7 +474,7 @@ public class ViewControllerNavigation transactionSetup) { + @Nullable final Function transactionSetup) { addToStack(SimpleViewControllerFragment.class, targetFragment, SimpleViewControllerFragment.createState(viewControllerClass, state), backStackTag, transactionSetup); } diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java b/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java index 885e46b..b98fc23 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java +++ b/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java @@ -35,19 +35,19 @@ import android.view.inputmethod.InputMethodManager; import java.util.ArrayList; +import io.reactivex.Completable; +import io.reactivex.Observable; +import io.reactivex.Single; +import io.reactivex.disposables.Disposable; +import io.reactivex.functions.Action; +import io.reactivex.functions.Consumer; +import io.reactivex.subjects.BehaviorSubject; import ru.touchin.roboswag.components.utils.BaseLifecycleBindable; import ru.touchin.roboswag.components.utils.LifecycleBindable; import ru.touchin.roboswag.components.utils.UiUtils; import ru.touchin.roboswag.core.log.Lc; import ru.touchin.roboswag.core.utils.Optional; import ru.touchin.roboswag.core.utils.pairs.HalfNullablePair; -import rx.Completable; -import rx.Observable; -import rx.Single; -import rx.Subscription; -import rx.functions.Action0; -import rx.functions.Action1; -import rx.subjects.BehaviorSubject; /** * Created by Gavriil Sitnikov on 08/03/2016. @@ -68,7 +68,7 @@ public abstract class BaseActivity extends AppCompatActivity @NonNull private final BehaviorSubject>> lastActivityResult - = BehaviorSubject.create(new Optional>(null)); + = BehaviorSubject.createDefault(new Optional>(null)); /** * Returns if activity resumed. @@ -254,149 +254,141 @@ public abstract class BaseActivity extends AppCompatActivity } } - @SuppressWarnings("CPD-START") - //CPD: it is same as in other implementation based on BaseLifecycleBindable @NonNull @Override - public Subscription bind(@NonNull final Observable observable, @NonNull final Action1 onNextAction) { - return baseLifecycleBindable.bind(observable, onNextAction); - } - - @NonNull - @Override - public Subscription untilStop(@NonNull final Observable observable, - @NonNull final Action1 onNextAction, - @NonNull final Action1 onErrorAction) { + public Disposable untilStop(@NonNull final Observable observable, + @NonNull final Consumer onNextAction, + @NonNull final Consumer onErrorAction) { return baseLifecycleBindable.untilStop(observable, onNextAction, onErrorAction); } @NonNull @Override - public Subscription untilStop(@NonNull final Observable observable, - @NonNull final Action1 onNextAction, - @NonNull final Action1 onErrorAction, - @NonNull final Action0 onCompletedAction) { + public Disposable untilStop(@NonNull final Observable observable, + @NonNull final Consumer onNextAction, + @NonNull final Consumer onErrorAction, + @NonNull final Action onCompletedAction) { return baseLifecycleBindable.untilStop(observable, onNextAction, onErrorAction, onCompletedAction); } @NonNull @Override - public Subscription untilStop(@NonNull final Single single) { + public Disposable untilStop(@NonNull final Single single) { return baseLifecycleBindable.untilStop(single); } @NonNull @Override - public Subscription untilStop(@NonNull final Single single, @NonNull final Action1 onSuccessAction) { + public Disposable untilStop(@NonNull final Single single, @NonNull final Consumer onSuccessAction) { return baseLifecycleBindable.untilStop(single, onSuccessAction); } @NonNull @Override - public Subscription untilStop(@NonNull final Single single, - @NonNull final Action1 onSuccessAction, - @NonNull final Action1 onErrorAction) { + public Disposable untilStop(@NonNull final Single single, + @NonNull final Consumer onSuccessAction, + @NonNull final Consumer onErrorAction) { return baseLifecycleBindable.untilStop(single, onSuccessAction, onErrorAction); } @NonNull @Override - public Subscription untilStop(@NonNull final Completable completable) { + public Disposable untilStop(@NonNull final Completable completable) { return baseLifecycleBindable.untilStop(completable); } @NonNull @Override - public Subscription untilStop(@NonNull final Completable completable, @NonNull final Action0 onCompletedAction) { + public Disposable untilStop(@NonNull final Completable completable, @NonNull final Action onCompletedAction) { return baseLifecycleBindable.untilStop(completable, onCompletedAction); } @NonNull @Override - public Subscription untilStop(@NonNull final Completable completable, - @NonNull final Action0 onCompletedAction, - @NonNull final Action1 onErrorAction) { + public Disposable untilStop(@NonNull final Completable completable, + @NonNull final Action onCompletedAction, + @NonNull final Consumer onErrorAction) { return baseLifecycleBindable.untilStop(completable, onCompletedAction, onErrorAction); } @NonNull @Override - public Subscription untilStop(@NonNull final Observable observable) { + public Disposable untilStop(@NonNull final Observable observable) { return baseLifecycleBindable.untilStop(observable); } @NonNull @Override - public Subscription untilStop(@NonNull final Observable observable, @NonNull final Action1 onNextAction) { + public Disposable untilStop(@NonNull final Observable observable, @NonNull final Consumer onNextAction) { return baseLifecycleBindable.untilStop(observable, onNextAction); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Observable observable) { + public Disposable untilDestroy(@NonNull final Observable observable) { return baseLifecycleBindable.untilDestroy(observable); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Observable observable, @NonNull final Action1 onNextAction) { + public Disposable untilDestroy(@NonNull final Observable observable, @NonNull final Consumer onNextAction) { return baseLifecycleBindable.untilDestroy(observable, onNextAction); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Observable observable, - @NonNull final Action1 onNextAction, - @NonNull final Action1 onErrorAction) { + public Disposable untilDestroy(@NonNull final Observable observable, + @NonNull final Consumer onNextAction, + @NonNull final Consumer onErrorAction) { return baseLifecycleBindable.untilDestroy(observable, onNextAction, onErrorAction); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Observable observable, - @NonNull final Action1 onNextAction, - @NonNull final Action1 onErrorAction, - @NonNull final Action0 onCompletedAction) { + public Disposable untilDestroy(@NonNull final Observable observable, + @NonNull final Consumer onNextAction, + @NonNull final Consumer onErrorAction, + @NonNull final Action onCompletedAction) { return baseLifecycleBindable.untilDestroy(observable, onNextAction, onErrorAction, onCompletedAction); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Single single) { + public Disposable untilDestroy(@NonNull final Single single) { return baseLifecycleBindable.untilDestroy(single); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Single single, @NonNull final Action1 onSuccessAction) { + public Disposable untilDestroy(@NonNull final Single single, @NonNull final Consumer onSuccessAction) { return baseLifecycleBindable.untilDestroy(single, onSuccessAction); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Single single, - @NonNull final Action1 onSuccessAction, - @NonNull final Action1 onErrorAction) { + public Disposable untilDestroy(@NonNull final Single single, + @NonNull final Consumer onSuccessAction, + @NonNull final Consumer onErrorAction) { return baseLifecycleBindable.untilDestroy(single, onSuccessAction, onErrorAction); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Completable completable) { + public Disposable untilDestroy(@NonNull final Completable completable) { return baseLifecycleBindable.untilDestroy(completable); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Completable completable, @NonNull final Action0 onCompletedAction) { + public Disposable untilDestroy(@NonNull final Completable completable, @NonNull final Action onCompletedAction) { return baseLifecycleBindable.untilDestroy(completable, onCompletedAction); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Completable completable, - @NonNull final Action0 onCompletedAction, - @NonNull final Action1 onErrorAction) { + public Disposable untilDestroy(@NonNull final Completable completable, + @NonNull final Action onCompletedAction, + @NonNull final Consumer onErrorAction) { return baseLifecycleBindable.untilDestroy(completable, onCompletedAction, onErrorAction); } diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java b/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java index c86573f..561d912 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java +++ b/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java @@ -39,6 +39,9 @@ import java.io.Serializable; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; +import io.reactivex.Observable; +import io.reactivex.disposables.Disposable; +import io.reactivex.subjects.BehaviorSubject; import ru.touchin.roboswag.components.navigation.AbstractState; import ru.touchin.roboswag.components.navigation.ViewController; import ru.touchin.roboswag.components.navigation.activities.ViewControllerActivity; @@ -47,10 +50,6 @@ import ru.touchin.roboswag.core.log.Lc; import ru.touchin.roboswag.core.utils.Optional; import ru.touchin.roboswag.core.utils.ShouldNotHappenException; import ru.touchin.roboswag.core.utils.pairs.NullablePair; -import rx.Observable; -import rx.Subscription; -import rx.exceptions.OnErrorThrowable; -import rx.subjects.BehaviorSubject; /** * Created by Gavriil Sitnikov on 21/10/2015. @@ -118,7 +117,7 @@ public abstract class ViewControllerFragment> viewSubject = BehaviorSubject.create(); @Nullable private ViewController viewController; - private Subscription viewControllerSubscription; + private Disposable viewControllerSubscription; private TState state; private boolean started; private boolean stateCreated; @@ -187,8 +186,7 @@ public abstract class ViewControllerFragment Lc.cutAssertion(throwable, - OnErrorThrowable.class, InvocationTargetException.class, InflateException.class)); + throwable -> Lc.cutAssertion(throwable, InvocationTargetException.class, InflateException.class)); } @NonNull @@ -196,7 +194,7 @@ public abstract class ViewControllerFragment constructor = getViewControllerClass().getConstructors()[0]; final ViewController.CreationContext creationContext = new ViewController.CreationContext(activity, this, view); @@ -208,11 +206,10 @@ public abstract class ViewControllerFragment extends //do nothing } - private void callMethodAfterInstantiation(@NonNull final Action2 action) { + private void callMethodAfterInstantiation(@NonNull final BiConsumer action) { if (getView() == null || getBaseActivity() == null) { Lc.assertion("View and activity shouldn't be null"); return; } - action.call(getView(), getBaseActivity()); + try { + action.accept(getView(), getBaseActivity()); + } catch (final Exception exception) { + Lc.assertion(exception); + } } @Deprecated diff --git a/src/main/java/ru/touchin/roboswag/components/utils/BaseLifecycleBindable.java b/src/main/java/ru/touchin/roboswag/components/utils/BaseLifecycleBindable.java index 567ef88..1858347 100644 --- a/src/main/java/ru/touchin/roboswag/components/utils/BaseLifecycleBindable.java +++ b/src/main/java/ru/touchin/roboswag/components/utils/BaseLifecycleBindable.java @@ -21,18 +21,17 @@ package ru.touchin.roboswag.components.utils; import android.support.annotation.NonNull; +import io.reactivex.Completable; +import io.reactivex.Observable; +import io.reactivex.Single; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; +import io.reactivex.functions.Action; +import io.reactivex.functions.Consumer; +import io.reactivex.internal.functions.Functions; +import io.reactivex.subjects.BehaviorSubject; import ru.touchin.roboswag.core.log.Lc; import ru.touchin.roboswag.core.utils.ShouldNotHappenException; -import rx.Completable; -import rx.Observable; -import rx.Single; -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 18/04/16. @@ -103,188 +102,181 @@ public class BaseLifecycleBindable implements LifecycleBindable { @NonNull @Override - public Subscription bind(@NonNull final Observable observable, @NonNull final Action1 onNextAction) { + public Disposable untilStop(@NonNull final Observable observable) { final String codePoint = Lc.getCodePoint(this, 2); - return isStartedSubject.switchMap(started -> started ? observable.observeOn(AndroidSchedulers.mainThread()) : Observable.never()) - .takeUntil(isCreatedSubject.filter(created -> !created)) - .subscribe(onNextAction, getActionThrowableForAssertion(codePoint, UNTIL_STOP_METHOD)); + return untilStop(observable, Functions.emptyConsumer(), getActionThrowableForAssertion(codePoint, UNTIL_STOP_METHOD), Functions.EMPTY_ACTION); } @NonNull @Override - public Subscription untilStop(@NonNull final Observable observable) { + public Disposable untilStop(@NonNull final Observable observable, @NonNull final Consumer onNextAction) { final String codePoint = Lc.getCodePoint(this, 2); - return untilStop(observable, Actions.empty(), getActionThrowableForAssertion(codePoint, UNTIL_STOP_METHOD), Actions.empty()); + return untilStop(observable, onNextAction, getActionThrowableForAssertion(codePoint, UNTIL_STOP_METHOD), Functions.EMPTY_ACTION); } @NonNull @Override - public Subscription untilStop(@NonNull final Observable observable, @NonNull final Action1 onNextAction) { - final String codePoint = Lc.getCodePoint(this, 2); - return untilStop(observable, onNextAction, getActionThrowableForAssertion(codePoint, UNTIL_STOP_METHOD), Actions.empty()); + public Disposable untilStop(@NonNull final Observable observable, + @NonNull final Consumer onNextAction, + @NonNull final Consumer onErrorAction) { + return untilStop(observable, onNextAction, onErrorAction, Functions.EMPTY_ACTION); } @NonNull @Override - public Subscription untilStop(@NonNull final Observable observable, - @NonNull final Action1 onNextAction, - @NonNull final Action1 onErrorAction) { - return untilStop(observable, onNextAction, onErrorAction, Actions.empty()); - } - - @NonNull - @Override - public Subscription untilStop(@NonNull final Observable observable, - @NonNull final Action1 onNextAction, - @NonNull final Action1 onErrorAction, - @NonNull final Action0 onCompletedAction) { + public Disposable untilStop(@NonNull final Observable observable, + @NonNull final Consumer onNextAction, + @NonNull final Consumer onErrorAction, + @NonNull final Action onCompletedAction) { return until(observable, isStartedSubject.map(started -> !started), onNextAction, onErrorAction, onCompletedAction); } @NonNull @Override - public Subscription untilStop(@NonNull final Single single) { + public Disposable untilStop(@NonNull final Single single) { final String codePoint = Lc.getCodePoint(this, 2); - return untilStop(single, Actions.empty(), getActionThrowableForAssertion(codePoint, UNTIL_STOP_METHOD)); + return untilStop(single, Functions.emptyConsumer(), getActionThrowableForAssertion(codePoint, UNTIL_STOP_METHOD)); } @NonNull @Override - public Subscription untilStop(@NonNull final Single single, @NonNull final Action1 onSuccessAction) { + public Disposable untilStop(@NonNull final Single single, @NonNull final Consumer onSuccessAction) { final String codePoint = Lc.getCodePoint(this, 2); return untilStop(single, onSuccessAction, getActionThrowableForAssertion(codePoint, UNTIL_STOP_METHOD)); } @NonNull @Override - public Subscription untilStop(@NonNull final Single single, - @NonNull final Action1 onSuccessAction, - @NonNull final Action1 onErrorAction) { - return until(single.toObservable(), isStartedSubject.map(started -> !started), onSuccessAction, onErrorAction, Actions.empty()); + public Disposable untilStop(@NonNull final Single single, + @NonNull final Consumer onSuccessAction, + @NonNull final Consumer onErrorAction) { + return until(single.toObservable(), isStartedSubject.map(started -> !started), onSuccessAction, onErrorAction, Functions.EMPTY_ACTION); } @NonNull @Override - public Subscription untilStop(@NonNull final Completable completable) { + public Disposable untilStop(@NonNull final Completable completable) { final String codePoint = Lc.getCodePoint(this, 2); - return untilStop(completable, Actions.empty(), getActionThrowableForAssertion(codePoint, UNTIL_STOP_METHOD)); + return untilStop(completable, Functions.EMPTY_ACTION, getActionThrowableForAssertion(codePoint, UNTIL_STOP_METHOD)); } @NonNull @Override - public Subscription untilStop(@NonNull final Completable completable, - @NonNull final Action0 onCompletedAction) { + public Disposable untilStop(@NonNull final Completable completable, + @NonNull final Action onCompletedAction) { final String codePoint = Lc.getCodePoint(this, 2); return untilStop(completable, onCompletedAction, getActionThrowableForAssertion(codePoint, UNTIL_STOP_METHOD)); } @NonNull @Override - public Subscription untilStop(@NonNull final Completable completable, - @NonNull final Action0 onCompletedAction, - @NonNull final Action1 onErrorAction) { - return until(completable.toObservable(), isStartedSubject.map(started -> !started), Actions.empty(), onErrorAction, onCompletedAction); + public Disposable untilStop(@NonNull final Completable completable, + @NonNull final Action onCompletedAction, + @NonNull final Consumer onErrorAction) { + return until(completable.toObservable(), isStartedSubject.map(started -> !started), + Functions.emptyConsumer(), onErrorAction, onCompletedAction); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Observable observable) { + public Disposable untilDestroy(@NonNull final Observable observable) { final String codePoint = Lc.getCodePoint(this, 2); - return untilDestroy(observable, Actions.empty(), getActionThrowableForAssertion(codePoint, UNTIL_DESTROY_METHOD), Actions.empty()); + return untilDestroy(observable, Functions.emptyConsumer(), + getActionThrowableForAssertion(codePoint, UNTIL_DESTROY_METHOD), Functions.EMPTY_ACTION); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Observable observable, - @NonNull final Action1 onNextAction) { + public Disposable untilDestroy(@NonNull final Observable observable, + @NonNull final Consumer onNextAction) { final String codePoint = Lc.getCodePoint(this, 2); - return untilDestroy(observable, onNextAction, getActionThrowableForAssertion(codePoint, UNTIL_DESTROY_METHOD), Actions.empty()); + return untilDestroy(observable, onNextAction, getActionThrowableForAssertion(codePoint, UNTIL_DESTROY_METHOD), Functions.EMPTY_ACTION); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Observable observable, - @NonNull final Action1 onNextAction, - @NonNull final Action1 onErrorAction) { - return untilDestroy(observable, onNextAction, onErrorAction, Actions.empty()); + public Disposable untilDestroy(@NonNull final Observable observable, + @NonNull final Consumer onNextAction, + @NonNull final Consumer onErrorAction) { + return untilDestroy(observable, onNextAction, onErrorAction, Functions.EMPTY_ACTION); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Observable observable, - @NonNull final Action1 onNextAction, - @NonNull final Action1 onErrorAction, - @NonNull final Action0 onCompletedAction) { + public Disposable untilDestroy(@NonNull final Observable observable, + @NonNull final Consumer onNextAction, + @NonNull final Consumer onErrorAction, + @NonNull final Action onCompletedAction) { return until(observable, isCreatedSubject.map(created -> !created), onNextAction, onErrorAction, onCompletedAction); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Single single) { + public Disposable untilDestroy(@NonNull final Single single) { final String codePoint = Lc.getCodePoint(this, 2); - return untilDestroy(single, Actions.empty(), getActionThrowableForAssertion(codePoint, UNTIL_DESTROY_METHOD)); + return untilDestroy(single, Functions.emptyConsumer(), getActionThrowableForAssertion(codePoint, UNTIL_DESTROY_METHOD)); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Single single, @NonNull final Action1 onSuccessAction) { + public Disposable untilDestroy(@NonNull final Single single, @NonNull final Consumer onSuccessAction) { final String codePoint = Lc.getCodePoint(this, 2); return untilDestroy(single, onSuccessAction, getActionThrowableForAssertion(codePoint, UNTIL_DESTROY_METHOD)); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Single single, - @NonNull final Action1 onSuccessAction, - @NonNull final Action1 onErrorAction) { - return until(single.toObservable(), isCreatedSubject.map(created -> !created), onSuccessAction, onErrorAction, Actions.empty()); + public Disposable untilDestroy(@NonNull final Single single, + @NonNull final Consumer onSuccessAction, + @NonNull final Consumer onErrorAction) { + return until(single.toObservable(), isCreatedSubject.map(created -> !created), onSuccessAction, onErrorAction, Functions.EMPTY_ACTION); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Completable completable) { + public Disposable untilDestroy(@NonNull final Completable completable) { final String codePoint = Lc.getCodePoint(this, 2); - return untilDestroy(completable, Actions.empty(), getActionThrowableForAssertion(codePoint, UNTIL_DESTROY_METHOD)); + return untilDestroy(completable, Functions.EMPTY_ACTION, getActionThrowableForAssertion(codePoint, UNTIL_DESTROY_METHOD)); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Completable completable, @NonNull final Action0 onCompletedAction) { + public Disposable untilDestroy(@NonNull final Completable completable, @NonNull final Action onCompletedAction) { final String codePoint = Lc.getCodePoint(this, 2); return untilDestroy(completable, onCompletedAction, getActionThrowableForAssertion(codePoint, UNTIL_DESTROY_METHOD)); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Completable completable, - @NonNull final Action0 onCompletedAction, - @NonNull final Action1 onErrorAction) { - return until(completable.toObservable(), isCreatedSubject.map(created -> !created), Actions.empty(), onErrorAction, onCompletedAction); + public Disposable untilDestroy(@NonNull final Completable completable, + @NonNull final Action onCompletedAction, + @NonNull final Consumer onErrorAction) { + return until(completable.toObservable(), isCreatedSubject.map(created -> !created), + Functions.emptyConsumer(), onErrorAction, onCompletedAction); } @NonNull - private Subscription until(@NonNull final Observable observable, - @NonNull final Observable conditionSubject, - @NonNull final Action1 onNextAction, - @NonNull final Action1 onErrorAction, - @NonNull final Action0 onCompletedAction) { + private Disposable until(@NonNull final Observable observable, + @NonNull final Observable conditionSubject, + @NonNull final Consumer onNextAction, + @NonNull final Consumer onErrorAction, + @NonNull final Action onCompletedAction) { final Observable actualObservable; - if (onNextAction == Actions.empty() && onErrorAction == (Action1) Actions.empty() && onCompletedAction == Actions.empty()) { + if (onNextAction == Functions.emptyConsumer() && onErrorAction == (Consumer) Functions.emptyConsumer() + && onCompletedAction == Functions.EMPTY_ACTION) { actualObservable = observable; } else { actualObservable = observable.observeOn(AndroidSchedulers.mainThread()) - .doOnCompleted(onCompletedAction) + .doOnComplete(onCompletedAction) .doOnNext(onNextAction) .doOnError(onErrorAction); } - return isCreatedSubject.first() - .switchMap(created -> created ? actualObservable : Observable.empty()) + return isCreatedSubject.firstOrError() + .flatMapObservable(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)) { + if (throwable instanceof RuntimeException) { Lc.assertion(throwable); } return Observable.empty(); @@ -293,7 +285,7 @@ public class BaseLifecycleBindable implements LifecycleBindable { } @NonNull - private Action1 getActionThrowableForAssertion(@NonNull final String codePoint, @NonNull final String method) { + private Consumer getActionThrowableForAssertion(@NonNull final String codePoint, @NonNull final String method) { return throwable -> Lc.assertion(new ShouldNotHappenException("Unexpected error on " + method + " at " + codePoint, throwable)); } diff --git a/src/main/java/ru/touchin/roboswag/components/utils/LifecycleBindable.java b/src/main/java/ru/touchin/roboswag/components/utils/LifecycleBindable.java index 95d7b2f..395fb3a 100644 --- a/src/main/java/ru/touchin/roboswag/components/utils/LifecycleBindable.java +++ b/src/main/java/ru/touchin/roboswag/components/utils/LifecycleBindable.java @@ -21,32 +21,26 @@ package ru.touchin.roboswag.components.utils; import android.support.annotation.NonNull; -import rx.Completable; -import rx.CompletableSubscriber; -import rx.Observable; -import rx.Single; -import rx.SingleSubscriber; -import rx.Subscriber; -import rx.Subscription; -import rx.functions.Action0; -import rx.functions.Action1; +import io.reactivex.Completable; +import io.reactivex.CompletableEmitter; +import io.reactivex.Emitter; +import io.reactivex.Observable; +import io.reactivex.Single; +import io.reactivex.SingleEmitter; +import io.reactivex.disposables.Disposable; +import io.reactivex.functions.Action; +import io.reactivex.functions.Consumer; /** * Created by Gavriil Sitnikov on 15/04/16. * Interface that should be implemented by lifecycle-based elements ({@link android.app.Activity}, {@link android.support.v4.app.Fragment} etc.) * to not manually manage subscriptions. - * Use {@link #bind(Observable, Action1)} method to subscribe to observable onStart and unsubscribe onStop automatically. * Use {@link #untilStop(Observable)} method to subscribe to observable where you want and unsubscribe onStop. * Use {@link #untilDestroy(Observable)} method to subscribe to observable where you want and unsubscribe onDestroy. */ @SuppressWarnings("PMD.TooManyMethods") public interface LifecycleBindable { - @NonNull - //do not use this method - it's not obvious method - @Deprecated - Subscription bind(@NonNull Observable observable, @NonNull Action1 onNextAction); - /** * Method should be used to guarantee that observable won't be subscribed after onStop. * It is automatically subscribing to the observable. @@ -55,10 +49,10 @@ public interface LifecycleBindable { * * @param observable {@link Observable} to subscribe until onStop; * @param Type of emitted by observable items; - * @return {@link Subscription} which will unsubscribes from observable onStop. + * @return {@link Disposable} which will unsubscribes from observable onStop. */ @NonNull - Subscription untilStop(@NonNull Observable observable); + Disposable untilStop(@NonNull Observable observable); /** * Method should be used to guarantee that observable won't be subscribed after onStop. @@ -67,12 +61,12 @@ public interface LifecycleBindable { * Don't forget to process errors if observable can emit them. * * @param observable {@link Observable} to subscribe until onStop; - * @param onNextAction Action which will raise on every {@link Subscriber#onNext(Object)} item; + * @param onNextAction Action which will raise on every {@link Emitter#onNext(Object)} item; * @param Type of emitted by observable items; - * @return {@link Subscription} which will unsubscribes from observable onStop. + * @return {@link Disposable} which will unsubscribes from observable onStop. */ @NonNull - Subscription untilStop(@NonNull Observable observable, @NonNull Action1 onNextAction); + Disposable untilStop(@NonNull Observable observable, @NonNull Consumer onNextAction); /** * Method should be used to guarantee that observable won't be subscribed after onStop. @@ -81,13 +75,13 @@ public interface LifecycleBindable { * Don't forget to process errors if observable can emit them. * * @param observable {@link Observable} to subscribe until onStop; - * @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 onNextAction Action which will raise on every {@link Emitter#onNext(Object)} item; + * @param onErrorAction Action which will raise on every {@link Emitter#onError(Throwable)} throwable; * @param Type of emitted by observable items; - * @return {@link Subscription} which will unsubscribes from observable onStop. + * @return {@link Disposable} which will unsubscribes from observable onStop. */ @NonNull - Subscription untilStop(@NonNull Observable observable, @NonNull Action1 onNextAction, @NonNull Action1 onErrorAction); + Disposable untilStop(@NonNull Observable observable, @NonNull Consumer onNextAction, @NonNull Consumer onErrorAction); /** * Method should be used to guarantee that observable won't be subscribed after onStop. @@ -96,15 +90,15 @@ public interface LifecycleBindable { * Don't forget to process errors if observable can emit them. * * @param observable {@link Observable} to subscribe until onStop; - * @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 onNextAction Action which will raise on every {@link Emitter#onNext(Object)} item; + * @param onErrorAction Action which will raise on every {@link Emitter#onError(Throwable)} throwable; + * @param onCompletedAction Action which will raise at {@link Emitter#onComplete()} on completion of observable; * @param Type of emitted by observable items; - * @return {@link Subscription} which is wrapping source observable to unsubscribe from it onStop. + * @return {@link Disposable} which is wrapping source observable to unsubscribe from it onStop. */ @NonNull - Subscription untilStop(@NonNull Observable observable, - @NonNull Action1 onNextAction, @NonNull Action1 onErrorAction, @NonNull Action0 onCompletedAction); + Disposable untilStop(@NonNull Observable observable, + @NonNull Consumer onNextAction, @NonNull Consumer onErrorAction, @NonNull Action onCompletedAction); /** * Method should be used to guarantee that single won't be subscribed after onStop. @@ -114,10 +108,10 @@ public interface LifecycleBindable { * * @param single {@link Single} to subscribe until onStop; * @param Type of emitted by single item; - * @return {@link Subscription} which will unsubscribes from single onStop. + * @return {@link Disposable} which will unsubscribes from single onStop. */ @NonNull - Subscription untilStop(@NonNull Single single); + Disposable untilStop(@NonNull Single single); /** * Method should be used to guarantee that single won't be subscribed after onStop. @@ -126,12 +120,12 @@ public interface LifecycleBindable { * Don't forget to process errors if single can emit them. * * @param single {@link Single} to subscribe until onStop; - * @param onSuccessAction Action which will raise on every {@link SingleSubscriber#onSuccess(Object)} item; + * @param onSuccessAction Action which will raise on every {@link SingleEmitter#onSuccess(Object)} item; * @param Type of emitted by single item; - * @return {@link Subscription} which will unsubscribes from single onStop. + * @return {@link Disposable} which will unsubscribes from single onStop. */ @NonNull - Subscription untilStop(@NonNull Single single, @NonNull Action1 onSuccessAction); + Disposable untilStop(@NonNull Single single, @NonNull Consumer onSuccessAction); /** * Method should be used to guarantee that single won't be subscribed after onStop. @@ -140,13 +134,13 @@ public interface LifecycleBindable { * Don't forget to process errors if single can emit them. * * @param single {@link Single} to subscribe until onStop; - * @param onSuccessAction Action which will raise on every {@link SingleSubscriber#onSuccess(Object)} item; - * @param onErrorAction Action which will raise on every {@link SingleSubscriber#onError(Throwable)} throwable; + * @param onSuccessAction Action which will raise on every {@link SingleEmitter#onSuccess(Object)} item; + * @param onErrorAction Action which will raise on every {@link SingleEmitter#onError(Throwable)} throwable; * @param Type of emitted by observable items; - * @return {@link Subscription} which is wrapping source single to unsubscribe from it onStop. + * @return {@link Disposable} which is wrapping source single to unsubscribe from it onStop. */ @NonNull - Subscription untilStop(@NonNull Single single, @NonNull Action1 onSuccessAction, @NonNull Action1 onErrorAction); + Disposable untilStop(@NonNull Single single, @NonNull Consumer onSuccessAction, @NonNull Consumer onErrorAction); /** * Method should be used to guarantee that completable won't be subscribed after onStop. @@ -155,10 +149,10 @@ public interface LifecycleBindable { * Don't forget to process errors if completable can emit them. * * @param completable {@link Completable} to subscribe until onStop; - * @return {@link Subscription} which will unsubscribes from completable onStop. + * @return {@link Disposable} which will unsubscribes from completable onStop. */ @NonNull - Subscription untilStop(@NonNull Completable completable); + Disposable untilStop(@NonNull Completable completable); /** * Method should be used to guarantee that completable won't be subscribed after onStop. @@ -167,11 +161,11 @@ public interface LifecycleBindable { * Don't forget to process errors if completable can emit them. * * @param completable {@link Completable} to subscribe until onStop; - * @param onCompletedAction Action which will raise at {@link CompletableSubscriber#onCompleted()} on completion of observable; - * @return {@link Subscription} which is wrapping source completable to unsubscribe from it onStop. + * @param onCompletedAction Action which will raise at {@link CompletableEmitter#onComplete()} on completion of observable; + * @return {@link Disposable} which is wrapping source completable to unsubscribe from it onStop. */ @NonNull - Subscription untilStop(@NonNull Completable completable, @NonNull Action0 onCompletedAction); + Disposable untilStop(@NonNull Completable completable, @NonNull Action onCompletedAction); /** * Method should be used to guarantee that completable won't be subscribed after onStop. @@ -180,12 +174,12 @@ public interface LifecycleBindable { * Don't forget to process errors if completable can emit them. * * @param completable {@link Completable} to subscribe until onStop; - * @param onCompletedAction Action which will raise at {@link CompletableSubscriber#onCompleted()} on completion of observable; - * @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 onStop. + * @param onCompletedAction Action which will raise at {@link CompletableEmitter#onComplete()} on completion of observable; + * @param onErrorAction Action which will raise on every {@link CompletableEmitter#onError(Throwable)} throwable; + * @return {@link Disposable} which is wrapping source completable to unsubscribe from it onStop. */ @NonNull - Subscription untilStop(@NonNull Completable completable, @NonNull Action0 onCompletedAction, @NonNull Action1 onErrorAction); + Disposable untilStop(@NonNull Completable completable, @NonNull Action onCompletedAction, @NonNull Consumer onErrorAction); /** * Method should be used to guarantee that observable won't be subscribed after onDestroy. @@ -194,10 +188,10 @@ public interface LifecycleBindable { * * @param observable {@link Observable} to subscribe until onDestroy; * @param Type of emitted by observable items; - * @return {@link Subscription} which is wrapping source observable to unsubscribe from it onDestroy. + * @return {@link Disposable} which is wrapping source observable to unsubscribe from it onDestroy. */ @NonNull - Subscription untilDestroy(@NonNull Observable observable); + Disposable untilDestroy(@NonNull Observable observable); /** * Method should be used to guarantee that observable won't be subscribed after onDestroy. @@ -205,12 +199,12 @@ public interface LifecycleBindable { * 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 onNextAction Action which will raise on every {@link Emitter#onNext(Object)} item; * @param Type of emitted by observable items; - * @return {@link Subscription} which is wrapping source observable to unsubscribe from it onDestroy. + * @return {@link Disposable} which is wrapping source observable to unsubscribe from it onDestroy. */ @NonNull - Subscription untilDestroy(@NonNull Observable observable, @NonNull Action1 onNextAction); + Disposable untilDestroy(@NonNull Observable observable, @NonNull Consumer onNextAction); /** * Method should be used to guarantee that observable won't be subscribed after onDestroy. @@ -218,13 +212,13 @@ public interface LifecycleBindable { * 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 onNextAction Action which will raise on every {@link Emitter#onNext(Object)} item; + * @param onErrorAction Action which will raise on every {@link Emitter#onError(Throwable)} throwable; * @param Type of emitted by observable items; - * @return {@link Subscription} which is wrapping source observable to unsubscribe from it onDestroy. + * @return {@link Disposable} which is wrapping source observable to unsubscribe from it onDestroy. */ @NonNull - Subscription untilDestroy(@NonNull Observable observable, @NonNull Action1 onNextAction, @NonNull Action1 onErrorAction); + Disposable untilDestroy(@NonNull Observable observable, @NonNull Consumer onNextAction, @NonNull Consumer onErrorAction); /** * Method should be used to guarantee that observable won't be subscribed after onDestroy. @@ -232,15 +226,15 @@ public interface LifecycleBindable { * 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 onNextAction Action which will raise on every {@link Emitter#onNext(Object)} item; + * @param onErrorAction Action which will raise on every {@link Emitter#onError(Throwable)} throwable; + * @param onCompletedAction Action which will raise at {@link Emitter#onComplete()} on completion of observable; * @param Type of emitted by observable items; - * @return {@link Subscription} which is wrapping source observable to unsubscribe from it onDestroy. + * @return {@link Disposable} which is wrapping source observable to unsubscribe from it onDestroy. */ @NonNull - Subscription untilDestroy(@NonNull Observable observable, - @NonNull Action1 onNextAction, @NonNull Action1 onErrorAction, @NonNull Action0 onCompletedAction); + Disposable untilDestroy(@NonNull Observable observable, + @NonNull Consumer onNextAction, @NonNull Consumer onErrorAction, @NonNull Action onCompletedAction); /** * Method should be used to guarantee that single won't be subscribed after onDestroy. @@ -249,10 +243,10 @@ public interface LifecycleBindable { * * @param single {@link Single} to subscribe until onDestroy; * @param Type of emitted by single items; - * @return {@link Subscription} which is wrapping source single to unsubscribe from it onDestroy. + * @return {@link Disposable} which is wrapping source single to unsubscribe from it onDestroy. */ @NonNull - Subscription untilDestroy(@NonNull Single single); + Disposable untilDestroy(@NonNull Single single); /** * Method should be used to guarantee that single won't be subscribed after onDestroy. @@ -260,12 +254,12 @@ public interface LifecycleBindable { * 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 every {@link SingleSubscriber#onSuccess(Object)} item; + * @param onSuccessAction Action which will raise on every {@link SingleEmitter#onSuccess(Object)} item; * @param Type of emitted by single items; - * @return {@link Subscription} which is wrapping source single to unsubscribe from it onDestroy. + * @return {@link Disposable} which is wrapping source single to unsubscribe from it onDestroy. */ @NonNull - Subscription untilDestroy(@NonNull Single single, @NonNull Action1 onSuccessAction); + Disposable untilDestroy(@NonNull Single single, @NonNull Consumer onSuccessAction); /** * Method should be used to guarantee that single won't be subscribed after onDestroy. @@ -273,13 +267,13 @@ public interface LifecycleBindable { * 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 every {@link SingleSubscriber#onSuccess(Object)} item; - * @param onErrorAction Action which will raise on every {@link SingleSubscriber#onError(Throwable)} throwable; + * @param onSuccessAction Action which will raise on every {@link SingleEmitter#onSuccess(Object)} item; + * @param onErrorAction Action which will raise on every {@link SingleEmitter#onError(Throwable)} throwable; * @param Type of emitted by single items; - * @return {@link Subscription} which is wrapping source single to unsubscribe from it onDestroy. + * @return {@link Disposable} which is wrapping source single to unsubscribe from it onDestroy. */ @NonNull - Subscription untilDestroy(@NonNull Single single, @NonNull Action1 onSuccessAction, @NonNull Action1 onErrorAction); + Disposable untilDestroy(@NonNull Single single, @NonNull Consumer onSuccessAction, @NonNull Consumer onErrorAction); /** * Method should be used to guarantee that completable won't be subscribed after onDestroy. @@ -287,10 +281,10 @@ public interface LifecycleBindable { * 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. + * @return {@link Disposable} which is wrapping source completable to unsubscribe from it onDestroy. */ @NonNull - Subscription untilDestroy(@NonNull Completable completable); + Disposable untilDestroy(@NonNull Completable completable); /** * Method should be used to guarantee that completable won't be subscribed after onDestroy. @@ -298,11 +292,11 @@ public interface LifecycleBindable { * Don't forget to process errors if single 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 single to unsubscribe from it onDestroy. + * @param onCompletedAction Action which will raise on every {@link CompletableEmitter#onComplete()} item; + * @return {@link Disposable} which is wrapping source single to unsubscribe from it onDestroy. */ @NonNull - Subscription untilDestroy(@NonNull Completable completable, @NonNull Action0 onCompletedAction); + Disposable untilDestroy(@NonNull Completable completable, @NonNull Action onCompletedAction); /** * Method should be used to guarantee that completable won't be subscribed after onDestroy. @@ -310,11 +304,11 @@ public interface LifecycleBindable { * 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; - * @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. + * @param onCompletedAction Action which will raise on every {@link CompletableEmitter#onComplete()} item; + * @param onErrorAction Action which will raise on every {@link CompletableEmitter#onError(Throwable)} throwable; + * @return {@link Disposable} which is wrapping source completable to unsubscribe from it onDestroy. */ @NonNull - Subscription untilDestroy(@NonNull Completable completable, @NonNull Action0 onCompletedAction, @NonNull Action1 onErrorAction); + Disposable untilDestroy(@NonNull Completable completable, @NonNull Action onCompletedAction, @NonNull Consumer onErrorAction); } \ No newline at end of file diff --git a/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.java b/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.java index 0bd06c8..5ad9f3d 100644 --- a/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.java +++ b/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.java @@ -43,9 +43,11 @@ import android.view.ViewGroup; import java.util.concurrent.atomic.AtomicInteger; +import io.reactivex.functions.Action; +import io.reactivex.functions.Consumer; import ru.touchin.roboswag.components.navigation.activities.BaseActivity; +import ru.touchin.roboswag.core.log.Lc; import ru.touchin.roboswag.core.log.LcGroup; -import rx.functions.Action0; /** * Created by Gavriil Sitnikov on 13/11/2015. @@ -100,8 +102,8 @@ public final class UiUtils { * @param onClickListener Click listener; * @param delay Delay after which click listener will be called. */ - public static void setOnRippleClickListener(@NonNull final View targetView, @Nullable final Action0 onClickListener, final long delay) { - setOnRippleClickListener(targetView, onClickListener != null ? v -> onClickListener.call() : null, delay); + public static void setOnRippleClickListener(@NonNull final View targetView, @Nullable final Action onClickListener, final long delay) { + setOnRippleClickListener(targetView, onClickListener != null ? view -> onClickListener.run() : null, delay); } /** @@ -110,8 +112,8 @@ public final class UiUtils { * @param targetView View to set click listener to; * @param onClickListener Click listener. */ - public static void setOnRippleClickListener(@NonNull final View targetView, @Nullable final Action0 onClickListener) { - setOnRippleClickListener(targetView, onClickListener != null ? v -> onClickListener.call() : null, RIPPLE_EFFECT_DELAY); + public static void setOnRippleClickListener(@NonNull final View targetView, @Nullable final Action onClickListener) { + setOnRippleClickListener(targetView, onClickListener != null ? view -> onClickListener.run() : null, RIPPLE_EFFECT_DELAY); } /** @@ -120,7 +122,7 @@ public final class UiUtils { * @param targetView View to set click listener to; * @param onClickListener Click listener. */ - public static void setOnRippleClickListener(@NonNull final View targetView, @Nullable final View.OnClickListener onClickListener) { + public static void setOnRippleClickListener(@NonNull final View targetView, @Nullable final Consumer onClickListener) { setOnRippleClickListener(targetView, onClickListener, RIPPLE_EFFECT_DELAY); } @@ -131,9 +133,7 @@ public final class UiUtils { * @param onClickListener Click listener; * @param delay Delay after which click listener will be called. */ - public static void setOnRippleClickListener(@NonNull final View targetView, - @Nullable final View.OnClickListener onClickListener, - final long delay) { + public static void setOnRippleClickListener(@NonNull final View targetView, @Nullable final Consumer onClickListener, final long delay) { if (onClickListener == null) { targetView.setOnClickListener(null); return; @@ -145,7 +145,11 @@ public final class UiUtils { || (targetView.getContext() instanceof BaseActivity && !((BaseActivity) targetView.getContext()).isActuallyResumed())) { return; } - onClickListener.onClick(targetView); + try { + onClickListener.accept(targetView); + } catch (final Exception exception) { + Lc.assertion(exception); + } }; targetView.setOnClickListener(v -> { diff --git a/src/main/java/ru/touchin/roboswag/components/utils/audio/HeadsetStateObserver.java b/src/main/java/ru/touchin/roboswag/components/utils/audio/HeadsetStateObserver.java index eea069f..346e52b 100644 --- a/src/main/java/ru/touchin/roboswag/components/utils/audio/HeadsetStateObserver.java +++ b/src/main/java/ru/touchin/roboswag/components/utils/audio/HeadsetStateObserver.java @@ -27,8 +27,8 @@ import android.content.IntentFilter; import android.media.AudioManager; import android.support.annotation.NonNull; -import rx.Observable; -import rx.subjects.BehaviorSubject; +import io.reactivex.Observable; +import io.reactivex.subjects.BehaviorSubject; /** * Created by Gavriil Sitnikov on 02/11/2015. @@ -50,12 +50,12 @@ public final class HeadsetStateObserver { isConnectedReceiver.isWirelessConnectedChangedEvent, (isWiredConnected, isWirelessConnected) -> isWiredConnected || isWirelessConnected) .distinctUntilChanged() - .doOnSubscribe(() -> { + .doOnSubscribe(disposable -> { final IntentFilter headsetStateIntentFilter = new IntentFilter(Intent.ACTION_HEADSET_PLUG); headsetStateIntentFilter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); context.registerReceiver(isConnectedReceiver, headsetStateIntentFilter); }) - .doOnUnsubscribe(() -> context.unregisterReceiver(isConnectedReceiver))) + .doOnDispose(() -> context.unregisterReceiver(isConnectedReceiver))) .replay(1) .refCount(); } @@ -90,8 +90,8 @@ public final class HeadsetStateObserver { @SuppressWarnings("deprecation") public IsConnectedReceiver(@NonNull final AudioManager audioManager) { super(); - isWiredConnectedChangedEvent = BehaviorSubject.create(audioManager.isWiredHeadsetOn()); - isWirelessConnectedChangedEvent = BehaviorSubject.create(audioManager.isBluetoothA2dpOn()); + isWiredConnectedChangedEvent = BehaviorSubject.createDefault(audioManager.isWiredHeadsetOn()); + isWirelessConnectedChangedEvent = BehaviorSubject.createDefault(audioManager.isBluetoothA2dpOn()); } @Override diff --git a/src/main/java/ru/touchin/roboswag/components/utils/audio/VolumeController.java b/src/main/java/ru/touchin/roboswag/components/utils/audio/VolumeController.java index 5a70c4d..c0853de 100644 --- a/src/main/java/ru/touchin/roboswag/components/utils/audio/VolumeController.java +++ b/src/main/java/ru/touchin/roboswag/components/utils/audio/VolumeController.java @@ -27,10 +27,10 @@ import android.os.Looper; import android.provider.Settings; import android.support.annotation.NonNull; +import io.reactivex.Observable; +import io.reactivex.subjects.PublishSubject; import ru.touchin.roboswag.core.log.Lc; import ru.touchin.roboswag.core.utils.ShouldNotHappenException; -import rx.Observable; -import rx.subjects.PublishSubject; /** * Created by Gavriil Sitnikov on 02/11/2015. @@ -58,9 +58,9 @@ public final class VolumeController { .switchMap(volumeObserver -> selfVolumeChangedEvent .mergeWith(volumeObserver.systemVolumeChangedEvent .map(event -> getVolume()) - .doOnSubscribe(() -> context.getContentResolver() + .doOnSubscribe(disposable -> context.getContentResolver() .registerContentObserver(Settings.System.CONTENT_URI, true, volumeObserver)) - .doOnUnsubscribe(() -> context.getContentResolver() + .doOnDispose(() -> context.getContentResolver() .unregisterContentObserver(volumeObserver))) .startWith(getVolume())) .distinctUntilChanged() diff --git a/src/main/java/ru/touchin/roboswag/components/utils/storables/PreferenceStore.java b/src/main/java/ru/touchin/roboswag/components/utils/storables/PreferenceStore.java index d7e33a2..caa718a 100644 --- a/src/main/java/ru/touchin/roboswag/components/utils/storables/PreferenceStore.java +++ b/src/main/java/ru/touchin/roboswag/components/utils/storables/PreferenceStore.java @@ -28,8 +28,8 @@ import java.lang.reflect.Type; import ru.touchin.roboswag.core.log.Lc; import ru.touchin.roboswag.core.observables.storable.Store; import ru.touchin.roboswag.core.utils.Optional; -import rx.Completable; -import rx.Single; +import io.reactivex.Completable; +import io.reactivex.Single; /** diff --git a/src/main/java/ru/touchin/roboswag/components/utils/storables/PreferenceUtils.java b/src/main/java/ru/touchin/roboswag/components/utils/storables/PreferenceUtils.java index 6d710b5..2ee1db9 100644 --- a/src/main/java/ru/touchin/roboswag/components/utils/storables/PreferenceUtils.java +++ b/src/main/java/ru/touchin/roboswag/components/utils/storables/PreferenceUtils.java @@ -28,7 +28,7 @@ import java.lang.reflect.Type; import ru.touchin.roboswag.core.observables.storable.Converter; import ru.touchin.roboswag.core.observables.storable.SameTypesConverter; 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; /** * Created by Gavriil Sitnikov on 01/09/2016. From 68f7c73ff9c8a18af13c3f7885c4d52d1dd4fadb Mon Sep 17 00:00:00 2001 From: Gavriil Sitnikov Date: Wed, 19 Apr 2017 19:09:34 +0300 Subject: [PATCH 02/95] started empty value crash fix --- .../roboswag/components/utils/BaseLifecycleBindable.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/ru/touchin/roboswag/components/utils/BaseLifecycleBindable.java b/src/main/java/ru/touchin/roboswag/components/utils/BaseLifecycleBindable.java index 1858347..a44f9fa 100644 --- a/src/main/java/ru/touchin/roboswag/components/utils/BaseLifecycleBindable.java +++ b/src/main/java/ru/touchin/roboswag/components/utils/BaseLifecycleBindable.java @@ -59,7 +59,7 @@ public class BaseLifecycleBindable implements LifecycleBindable { * Call it on parent's onStart method. */ public void onStart() { - if (!isStartedSubject.getValue()) { + if (!isStartedSubject.hasValue() || !isStartedSubject.getValue()) { isStartedSubject.onNext(true); } } @@ -70,7 +70,7 @@ public class BaseLifecycleBindable implements LifecycleBindable { * In that case onResume will be called after onSaveInstanceState so lifecycle object is becoming started. */ public void onResume() { - if (!isStartedSubject.getValue()) { + if (!isStartedSubject.hasValue() || !isStartedSubject.getValue()) { isStartedSubject.onNext(true); } } @@ -79,7 +79,7 @@ public class BaseLifecycleBindable implements LifecycleBindable { * Call it on parent's onSaveInstanceState method. */ public void onSaveInstanceState() { - if (isStartedSubject.getValue()) { + if (!isStartedSubject.hasValue() || isStartedSubject.getValue()) { isStartedSubject.onNext(false); } } @@ -88,7 +88,7 @@ public class BaseLifecycleBindable implements LifecycleBindable { * Call it on parent's onStop method. */ public void onStop() { - if (isStartedSubject.getValue()) { + if (!isStartedSubject.hasValue() || isStartedSubject.getValue()) { isStartedSubject.onNext(false); } } From 949025a1061a8a1f88c6631a74317d2b8ca02a5a Mon Sep 17 00:00:00 2001 From: Gavriil Sitnikov Date: Fri, 21 Apr 2017 00:10:41 +0300 Subject: [PATCH 03/95] static fixes --- .../components/adapters/AdapterDelegate.java | 88 +++++++++---------- .../adapters/BindableViewHolder.java | 3 + .../navigation/activities/BaseActivity.java | 2 + .../roboswag/components/utils/Logic.java | 2 +- 4 files changed, 50 insertions(+), 45 deletions(-) diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/AdapterDelegate.java b/src/main/java/ru/touchin/roboswag/components/adapters/AdapterDelegate.java index f3e29fd..99ee943 100644 --- a/src/main/java/ru/touchin/roboswag/components/adapters/AdapterDelegate.java +++ b/src/main/java/ru/touchin/roboswag/components/adapters/AdapterDelegate.java @@ -5,14 +5,14 @@ import android.view.ViewGroup; import java.util.List; +import io.reactivex.Completable; +import io.reactivex.Observable; +import io.reactivex.Single; +import io.reactivex.disposables.Disposable; +import io.reactivex.functions.Action; +import io.reactivex.functions.Consumer; import ru.touchin.roboswag.components.utils.LifecycleBindable; import ru.touchin.roboswag.components.utils.UiUtils; -import rx.Completable; -import rx.Observable; -import rx.Single; -import rx.Subscription; -import rx.functions.Action0; -import rx.functions.Action1; /** * Objects of such class controls creation and binding of specific type of RecyclerView's ViewHolders. @@ -114,139 +114,139 @@ public abstract class AdapterDelegate Subscription untilStop(@NonNull final Observable observable) { + public Disposable untilStop(@NonNull final Observable observable) { return parentLifecycleBindable.untilStop(observable); } @NonNull @Override - public Subscription untilStop(@NonNull final Observable observable, @NonNull final Action1 onNextAction) { + public Disposable untilStop(@NonNull final Observable observable, @NonNull final Consumer onNextAction) { return parentLifecycleBindable.untilStop(observable, onNextAction); } @NonNull @Override - public Subscription untilStop(@NonNull final Observable observable, - @NonNull final Action1 onNextAction, - @NonNull final Action1 onErrorAction) { + public Disposable untilStop(@NonNull final Observable observable, + @NonNull final Consumer onNextAction, + @NonNull final Consumer onErrorAction) { return parentLifecycleBindable.untilStop(observable, onNextAction, onErrorAction); } @NonNull @Override - public Subscription untilStop(@NonNull final Observable observable, - @NonNull final Action1 onNextAction, - @NonNull final Action1 onErrorAction, - @NonNull final Action0 onCompletedAction) { + public Disposable untilStop(@NonNull final Observable observable, + @NonNull final Consumer onNextAction, + @NonNull final Consumer onErrorAction, + @NonNull final Action onCompletedAction) { return parentLifecycleBindable.untilStop(observable, onNextAction, onErrorAction, onCompletedAction); } @NonNull @Override - public Subscription untilStop(@NonNull final Single single) { + public Disposable untilStop(@NonNull final Single single) { return parentLifecycleBindable.untilStop(single); } @NonNull @Override - public Subscription untilStop(@NonNull final Single single, @NonNull final Action1 onSuccessAction) { + public Disposable untilStop(@NonNull final Single single, @NonNull final Consumer onSuccessAction) { return parentLifecycleBindable.untilStop(single, onSuccessAction); } @NonNull @Override - public Subscription untilStop(@NonNull final Single single, - @NonNull final Action1 onSuccessAction, - @NonNull final Action1 onErrorAction) { + public Disposable untilStop(@NonNull final Single single, + @NonNull final Consumer onSuccessAction, + @NonNull final Consumer onErrorAction) { return parentLifecycleBindable.untilStop(single, onSuccessAction, onErrorAction); } @NonNull @Override - public Subscription untilStop(@NonNull final Completable completable) { + public Disposable untilStop(@NonNull final Completable completable) { return parentLifecycleBindable.untilStop(completable); } @NonNull @Override - public Subscription untilStop(@NonNull final Completable completable, @NonNull final Action0 onCompletedAction) { + public Disposable untilStop(@NonNull final Completable completable, @NonNull final Action onCompletedAction) { return parentLifecycleBindable.untilStop(completable, onCompletedAction); } @NonNull @Override - public Subscription untilStop(@NonNull final Completable completable, - @NonNull final Action0 onCompletedAction, - @NonNull final Action1 onErrorAction) { + public Disposable untilStop(@NonNull final Completable completable, + @NonNull final Action onCompletedAction, + @NonNull final Consumer onErrorAction) { return parentLifecycleBindable.untilStop(completable, onCompletedAction, onErrorAction); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Observable observable) { + public Disposable untilDestroy(@NonNull final Observable observable) { return parentLifecycleBindable.untilDestroy(observable); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Observable observable, @NonNull final Action1 onNextAction) { + public Disposable untilDestroy(@NonNull final Observable observable, @NonNull final Consumer onNextAction) { return parentLifecycleBindable.untilDestroy(observable, onNextAction); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Observable observable, - @NonNull final Action1 onNextAction, - @NonNull final Action1 onErrorAction) { + public Disposable untilDestroy(@NonNull final Observable observable, + @NonNull final Consumer onNextAction, + @NonNull final Consumer onErrorAction) { return parentLifecycleBindable.untilDestroy(observable, onNextAction, onErrorAction); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Observable observable, - @NonNull final Action1 onNextAction, - @NonNull final Action1 onErrorAction, - @NonNull final Action0 onCompletedAction) { + public Disposable untilDestroy(@NonNull final Observable observable, + @NonNull final Consumer onNextAction, + @NonNull final Consumer onErrorAction, + @NonNull final Action onCompletedAction) { return parentLifecycleBindable.untilDestroy(observable, onNextAction, onErrorAction, onCompletedAction); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Single single) { + public Disposable untilDestroy(@NonNull final Single single) { return parentLifecycleBindable.untilDestroy(single); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Single single, @NonNull final Action1 onSuccessAction) { + public Disposable untilDestroy(@NonNull final Single single, @NonNull final Consumer onSuccessAction) { return parentLifecycleBindable.untilDestroy(single, onSuccessAction); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Single single, - @NonNull final Action1 onSuccessAction, - @NonNull final Action1 onErrorAction) { + public Disposable untilDestroy(@NonNull final Single single, + @NonNull final Consumer onSuccessAction, + @NonNull final Consumer onErrorAction) { return parentLifecycleBindable.untilDestroy(single, onSuccessAction, onErrorAction); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Completable completable) { + public Disposable untilDestroy(@NonNull final Completable completable) { return parentLifecycleBindable.untilDestroy(completable); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Completable completable, @NonNull final Action0 onCompletedAction) { + public Disposable untilDestroy(@NonNull final Completable completable, @NonNull final Action onCompletedAction) { return parentLifecycleBindable.untilDestroy(completable, onCompletedAction); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Completable completable, - @NonNull final Action0 onCompletedAction, - @NonNull final Action1 onErrorAction) { + public Disposable untilDestroy(@NonNull final Completable completable, + @NonNull final Action onCompletedAction, + @NonNull final Consumer onErrorAction) { return parentLifecycleBindable.untilDestroy(completable, onCompletedAction, onErrorAction); } diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/BindableViewHolder.java b/src/main/java/ru/touchin/roboswag/components/adapters/BindableViewHolder.java index 23a7454..7b81e8a 100644 --- a/src/main/java/ru/touchin/roboswag/components/adapters/BindableViewHolder.java +++ b/src/main/java/ru/touchin/roboswag/components/adapters/BindableViewHolder.java @@ -122,6 +122,8 @@ public class BindableViewHolder extends RecyclerView.ViewHolder implements Lifec return ContextCompat.getDrawable(itemView.getContext(), resId); } + @SuppressWarnings("CPD-START") + //CPD: it's ok as it's LifecycleBindable @NonNull @Override public Disposable untilStop(@NonNull final Observable observable) { @@ -252,6 +254,7 @@ public class BindableViewHolder extends RecyclerView.ViewHolder implements Lifec return baseLifecycleBindable.untilDestroy(completable, onCompletedAction); } + @SuppressWarnings("CPD-END") @NonNull @Override public Disposable untilDestroy(@NonNull final Completable completable, diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java b/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java index b98fc23..6fed556 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java +++ b/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java @@ -254,6 +254,8 @@ public abstract class BaseActivity extends AppCompatActivity } } + @SuppressWarnings("CPD-START") + //CPD: it's ok as it's LifecycleBindable @NonNull @Override public Disposable untilStop(@NonNull final Observable observable, diff --git a/src/main/java/ru/touchin/roboswag/components/utils/Logic.java b/src/main/java/ru/touchin/roboswag/components/utils/Logic.java index cdce5c0..84c2e87 100644 --- a/src/main/java/ru/touchin/roboswag/components/utils/Logic.java +++ b/src/main/java/ru/touchin/roboswag/components/utils/Logic.java @@ -27,8 +27,8 @@ import java.lang.reflect.Constructor; import java.util.HashMap; import java.util.Map; +import io.reactivex.Observable; import ru.touchin.roboswag.core.utils.ShouldNotHappenException; -import rx.Observable; /** * Created by Gavriil Sitnikov on 24/03/16. From 5f13aefa844c52e0bc5dd24f8609fe973efb9a90 Mon Sep 17 00:00:00 2001 From: Gavriil Sitnikov Date: Fri, 21 Apr 2017 00:31:26 +0300 Subject: [PATCH 04/95] viewcontroller null fixed --- .../navigation/fragments/ViewControllerFragment.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java b/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java index 561d912..0a80e5f 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java +++ b/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java @@ -179,11 +179,11 @@ public abstract class ViewControllerFragment(null); } final ViewController newViewController = createViewController(activity, container, viewInfo.getSecond()); newViewController.onCreate(); - return newViewController; + return new Optional<>(newViewController); }) .subscribe(this::onViewControllerChanged, throwable -> Lc.cutAssertion(throwable, InvocationTargetException.class, InflateException.class)); @@ -312,11 +312,11 @@ public abstract class ViewControllerFragment viewControllerOptional) { if (this.viewController != null) { this.viewController.onDestroy(); } - this.viewController = viewController; + this.viewController = viewControllerOptional.get(); if (this.viewController != null) { if (started) { this.viewController.onStart(); From d8ca0e610baa72288707bf7d1462c46ca7e215d0 Mon Sep 17 00:00:00 2001 From: Arhipov Date: Wed, 26 Apr 2017 16:27:30 +0300 Subject: [PATCH 05/95] merge continued --- .../components/adapters/ObservableCollectionAdapter.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/ObservableCollectionAdapter.java b/src/main/java/ru/touchin/roboswag/components/adapters/ObservableCollectionAdapter.java index c53a27c..68fb759 100644 --- a/src/main/java/ru/touchin/roboswag/components/adapters/ObservableCollectionAdapter.java +++ b/src/main/java/ru/touchin/roboswag/components/adapters/ObservableCollectionAdapter.java @@ -466,7 +466,9 @@ public abstract class ObservableCollectionAdapter payloads) { // do nothing by default -======= - protected void onBindItemToViewHolder(@NonNull final TItemViewHolder holder, final int position, @NonNull final TItem item) { ->>>>>>> master } @Nullable From 5d0847370dbdce96f8e632a059d0b05101349681 Mon Sep 17 00:00:00 2001 From: gorodeckii Date: Wed, 3 May 2017 09:26:52 +0300 Subject: [PATCH 06/95] rxjava version --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 18085af..50d5575 100644 --- a/build.gradle +++ b/build.gradle @@ -23,5 +23,5 @@ dependencies { provided 'com.android.support:recyclerview-v7:25.3.1' provided 'io.reactivex.rxjava2:rxandroid:2.0.1' - provided 'io.reactivex.rxjava2:rxjava:2.0.8' + provided 'io.reactivex.rxjava2:rxjava:2.1.0' } From e847fcedc5277a8fc2265a666afa23e1fc971eea Mon Sep 17 00:00:00 2001 From: gorodeckii Date: Fri, 12 May 2017 20:30:35 +0300 Subject: [PATCH 07/95] unused import --- .../touchin/roboswag/components/adapters/AdapterDelegate.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/AdapterDelegate.java b/src/main/java/ru/touchin/roboswag/components/adapters/AdapterDelegate.java index 6df5095..020912a 100644 --- a/src/main/java/ru/touchin/roboswag/components/adapters/AdapterDelegate.java +++ b/src/main/java/ru/touchin/roboswag/components/adapters/AdapterDelegate.java @@ -3,8 +3,6 @@ package ru.touchin.roboswag.components.adapters; import android.support.annotation.NonNull; import android.view.ViewGroup; -import java.util.List; - import io.reactivex.Completable; import io.reactivex.Observable; import io.reactivex.Single; From c57f30aca150ea3c4764fd74f6a1fbc886e5db87 Mon Sep 17 00:00:00 2001 From: Alexander Bubnov Date: Wed, 17 May 2017 19:00:46 +0300 Subject: [PATCH 08/95] fix wrong import --- .../adapters/ObservableCollectionAdapter.java | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/ObservableCollectionAdapter.java b/src/main/java/ru/touchin/roboswag/components/adapters/ObservableCollectionAdapter.java index bc4a4e3..4aaae19 100644 --- a/src/main/java/ru/touchin/roboswag/components/adapters/ObservableCollectionAdapter.java +++ b/src/main/java/ru/touchin/roboswag/components/adapters/ObservableCollectionAdapter.java @@ -30,8 +30,9 @@ import java.util.Collection; import java.util.Collections; import java.util.LinkedList; import java.util.List; -import java.util.function.BiConsumer; -import java.util.function.Consumer; + +import io.reactivex.functions.BiConsumer; +import io.reactivex.functions.Consumer; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; @@ -423,19 +424,31 @@ public abstract class ObservableCollectionAdapter delegate : delegates) { if (delegate instanceof ItemAdapterDelegate) { if (positionInCollection >= 0 && viewType == delegate.getItemViewType()) { - itemAdapterDelegateAction.accept((ItemAdapterDelegate) delegate, positionInCollection); + try { + itemAdapterDelegateAction.accept((ItemAdapterDelegate) delegate, positionInCollection); + } catch (final Exception exception) { + Lc.assertion(exception); + } return; } } else if (delegate instanceof PositionAdapterDelegate) { if (viewType == delegate.getItemViewType()) { - positionAdapterDelegateAction.accept((PositionAdapterDelegate) delegate); + try { + positionAdapterDelegateAction.accept((PositionAdapterDelegate) delegate); + } catch (final Exception exception) { + Lc.assertion(exception); + } return; } } else { Lc.assertion("Delegate of type " + delegate.getClass()); } } - defaultAction.accept(positionInCollection); + try { + defaultAction.accept(positionInCollection); + } catch (final Exception exception) { + Lc.assertion(exception); + } } @Override From 88113ef0ee782f65199e4e10d5dc66b244979318 Mon Sep 17 00:00:00 2001 From: Alexander Bubnov Date: Wed, 17 May 2017 19:06:19 +0300 Subject: [PATCH 09/95] add supress --- .../components/adapters/ObservableCollectionAdapter.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/ObservableCollectionAdapter.java b/src/main/java/ru/touchin/roboswag/components/adapters/ObservableCollectionAdapter.java index 4aaae19..9af94e8 100644 --- a/src/main/java/ru/touchin/roboswag/components/adapters/ObservableCollectionAdapter.java +++ b/src/main/java/ru/touchin/roboswag/components/adapters/ObservableCollectionAdapter.java @@ -415,6 +415,7 @@ public abstract class ObservableCollectionAdapter itemAdapterDelegateAction, @NonNull final Consumer positionAdapterDelegateAction, From e9f7815ca33f11aad3ef717c540e097c69bc368c Mon Sep 17 00:00:00 2001 From: gorodeckii Date: Mon, 29 May 2017 12:34:02 +0300 Subject: [PATCH 10/95] fixed imports to rxjava-2 style --- .../components/views/LifecycleView.java | 88 +++++++++---------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/src/main/java/ru/touchin/roboswag/components/views/LifecycleView.java b/src/main/java/ru/touchin/roboswag/components/views/LifecycleView.java index eafcc48..d0b6d39 100644 --- a/src/main/java/ru/touchin/roboswag/components/views/LifecycleView.java +++ b/src/main/java/ru/touchin/roboswag/components/views/LifecycleView.java @@ -30,12 +30,12 @@ import android.widget.FrameLayout; import ru.touchin.roboswag.components.utils.BaseLifecycleBindable; import ru.touchin.roboswag.components.utils.LifecycleBindable; -import rx.Completable; -import rx.Observable; -import rx.Single; -import rx.Subscription; -import rx.functions.Action0; -import rx.functions.Action1; +import io.reactivex.Completable; +import io.reactivex.Observable; +import io.reactivex.Single; +import io.reactivex.disposables.Disposable; +import io.reactivex.functions.Action; +import io.reactivex.functions.Consumer; /** @@ -145,139 +145,139 @@ public class LifecycleView extends FrameLayout implements LifecycleBindable { @NonNull @Override - public Subscription untilStop(@NonNull final Observable observable) { + public Disposable untilStop(@NonNull final Observable observable) { return baseLifecycleBindable.untilStop(observable); } @NonNull @Override - public Subscription untilStop(@NonNull final Observable observable, @NonNull final Action1 onNextAction) { + public Disposable untilStop(@NonNull final Observable observable, @NonNull final Consumer onNextAction) { return baseLifecycleBindable.untilStop(observable, onNextAction); } @NonNull @Override - public Subscription untilStop(@NonNull final Observable observable, - @NonNull final Action1 onNextAction, - @NonNull final Action1 onErrorAction) { + public Disposable untilStop(@NonNull final Observable observable, + @NonNull final Consumer onNextAction, + @NonNull final Consumer onErrorAction) { return baseLifecycleBindable.untilStop(observable, onNextAction, onErrorAction); } @NonNull @Override - public Subscription untilStop(@NonNull final Observable observable, - @NonNull final Action1 onNextAction, - @NonNull final Action1 onErrorAction, - @NonNull final Action0 onCompletedAction) { + public Disposable untilStop(@NonNull final Observable observable, + @NonNull final Consumer onNextAction, + @NonNull final Consumer onErrorAction, + @NonNull final Action onCompletedAction) { return baseLifecycleBindable.untilStop(observable, onNextAction, onErrorAction, onCompletedAction); } @NonNull @Override - public Subscription untilStop(@NonNull final Single single) { + public Disposable untilStop(@NonNull final Single single) { return baseLifecycleBindable.untilStop(single); } @NonNull @Override - public Subscription untilStop(@NonNull final Single single, @NonNull final Action1 onSuccessAction) { + public Disposable untilStop(@NonNull final Single single, @NonNull final Consumer onSuccessAction) { return baseLifecycleBindable.untilStop(single, onSuccessAction); } @NonNull @Override - public Subscription untilStop(@NonNull final Single single, - @NonNull final Action1 onSuccessAction, - @NonNull final Action1 onErrorAction) { + public Disposable untilStop(@NonNull final Single single, + @NonNull final Consumer onSuccessAction, + @NonNull final Consumer onErrorAction) { return baseLifecycleBindable.untilStop(single, onSuccessAction, onErrorAction); } @NonNull @Override - public Subscription untilStop(@NonNull final Completable completable) { + public Disposable untilStop(@NonNull final Completable completable) { return baseLifecycleBindable.untilStop(completable); } @NonNull @Override - public Subscription untilStop(@NonNull final Completable completable, @NonNull final Action0 onCompletedAction) { + public Disposable untilStop(@NonNull final Completable completable, @NonNull final Action onCompletedAction) { return baseLifecycleBindable.untilStop(completable, onCompletedAction); } @NonNull @Override - public Subscription untilStop(@NonNull final Completable completable, - @NonNull final Action0 onCompletedAction, - @NonNull final Action1 onErrorAction) { + public Disposable untilStop(@NonNull final Completable completable, + @NonNull final Action onCompletedAction, + @NonNull final Consumer onErrorAction) { return baseLifecycleBindable.untilStop(completable, onCompletedAction, onErrorAction); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Observable observable) { + public Disposable untilDestroy(@NonNull final Observable observable) { return baseLifecycleBindable.untilDestroy(observable); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Observable observable, @NonNull final Action1 onNextAction) { + public Disposable untilDestroy(@NonNull final Observable observable, @NonNull final Consumer onNextAction) { return baseLifecycleBindable.untilDestroy(observable, onNextAction); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Observable observable, - @NonNull final Action1 onNextAction, - @NonNull final Action1 onErrorAction) { + public Disposable untilDestroy(@NonNull final Observable observable, + @NonNull final Consumer onNextAction, + @NonNull final Consumer onErrorAction) { return baseLifecycleBindable.untilDestroy(observable, onNextAction, onErrorAction); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Observable observable, - @NonNull final Action1 onNextAction, - @NonNull final Action1 onErrorAction, - @NonNull final Action0 onCompletedAction) { + public Disposable untilDestroy(@NonNull final Observable observable, + @NonNull final Consumer onNextAction, + @NonNull final Consumer onErrorAction, + @NonNull final Action onCompletedAction) { return baseLifecycleBindable.untilDestroy(observable, onNextAction, onErrorAction, onCompletedAction); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Single single) { + public Disposable untilDestroy(@NonNull final Single single) { return baseLifecycleBindable.untilDestroy(single); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Single single, @NonNull final Action1 onSuccessAction) { + public Disposable untilDestroy(@NonNull final Single single, @NonNull final Consumer onSuccessAction) { return baseLifecycleBindable.untilDestroy(single, onSuccessAction); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Single single, - @NonNull final Action1 onSuccessAction, - @NonNull final Action1 onErrorAction) { + public Disposable untilDestroy(@NonNull final Single single, + @NonNull final Consumer onSuccessAction, + @NonNull final Consumer onErrorAction) { return baseLifecycleBindable.untilDestroy(single, onSuccessAction, onErrorAction); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Completable completable) { + public Disposable untilDestroy(@NonNull final Completable completable) { return baseLifecycleBindable.untilDestroy(completable); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Completable completable, @NonNull final Action0 onCompletedAction) { + public Disposable untilDestroy(@NonNull final Completable completable, @NonNull final Action onCompletedAction) { return baseLifecycleBindable.untilDestroy(completable, onCompletedAction); } @NonNull @Override - public Subscription untilDestroy(@NonNull final Completable completable, - @NonNull final Action0 onCompletedAction, - @NonNull final Action1 onErrorAction) { + public Disposable untilDestroy(@NonNull final Completable completable, + @NonNull final Action onCompletedAction, + @NonNull final Consumer onErrorAction) { return baseLifecycleBindable.untilDestroy(completable, onCompletedAction, onErrorAction); } From 06f9b1d2cc9bfe726caa6617f2ac1647eb1e70e7 Mon Sep 17 00:00:00 2001 From: Anton Domnikov Date: Wed, 14 Jun 2017 17:51:06 +0300 Subject: [PATCH 11/95] remove retrolambda --- build.gradle | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index e34e77e..4a85fab 100644 --- a/build.gradle +++ b/build.gradle @@ -1,9 +1,8 @@ apply plugin: 'com.android.library' -apply plugin: 'me.tatarka.retrolambda' android { - compileSdkVersion 25 - buildToolsVersion '25.0.3' + compileSdkVersion 26 + buildToolsVersion '26.0.0' compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 @@ -18,8 +17,8 @@ android { dependencies { compile project(':libraries:core') - provided 'com.android.support:appcompat-v7:25.3.1' - provided 'com.android.support:recyclerview-v7:25.3.1' + provided 'com.android.support:appcompat-v7:26.0.0-beta2' + provided 'com.android.support:recyclerview-v7:26.0.0-beta2' provided 'io.reactivex.rxjava2:rxandroid:2.0.1' provided 'io.reactivex.rxjava2:rxjava:2.1.0' From 540e573888836ee5266e5fb606b109935d5b94b1 Mon Sep 17 00:00:00 2001 From: Anton Domnikov Date: Mon, 19 Jun 2017 18:23:27 +0300 Subject: [PATCH 12/95] revert retrolambda --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index 4a85fab..7ae5a16 100644 --- a/build.gradle +++ b/build.gradle @@ -1,4 +1,5 @@ apply plugin: 'com.android.library' +apply plugin: 'me.tatarka.retrolambda' android { compileSdkVersion 26 From d3bcd8974341d7b74c390abab25807ed8e7b66cb Mon Sep 17 00:00:00 2001 From: Alexander Bubnov Date: Wed, 21 Jun 2017 17:05:02 +0300 Subject: [PATCH 13/95] update rxJava2 to 2.1.1 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index e34e77e..7801a3e 100644 --- a/build.gradle +++ b/build.gradle @@ -22,5 +22,5 @@ dependencies { provided 'com.android.support:recyclerview-v7:25.3.1' provided 'io.reactivex.rxjava2:rxandroid:2.0.1' - provided 'io.reactivex.rxjava2:rxjava:2.1.0' + provided 'io.reactivex.rxjava2:rxjava:2.1.1' } From 2398b4fc10af0b6b3c6146cce9d8f7d780674a53 Mon Sep 17 00:00:00 2001 From: Alexander Bubnov Date: Thu, 29 Jun 2017 13:17:58 +0300 Subject: [PATCH 14/95] add maybe support to bindable lifecycle --- .../components/adapters/AdapterDelegate.java | 41 ++++++++++ .../adapters/BindableViewHolder.java | 41 ++++++++++ .../components/navigation/ViewController.java | 41 ++++++++++ .../navigation/activities/BaseActivity.java | 49 ++++++++++- .../utils/BaseLifecycleBindable.java | 45 +++++++++++ .../components/utils/LifecycleBindable.java | 81 ++++++++++++++++++- .../components/views/LifecycleView.java | 37 +++++++++ 7 files changed, 329 insertions(+), 6 deletions(-) diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/AdapterDelegate.java b/src/main/java/ru/touchin/roboswag/components/adapters/AdapterDelegate.java index 6bcd61c..b9d3a0d 100644 --- a/src/main/java/ru/touchin/roboswag/components/adapters/AdapterDelegate.java +++ b/src/main/java/ru/touchin/roboswag/components/adapters/AdapterDelegate.java @@ -23,6 +23,7 @@ import android.support.annotation.NonNull; import android.view.ViewGroup; import io.reactivex.Completable; +import io.reactivex.Maybe; import io.reactivex.Observable; import io.reactivex.Single; import io.reactivex.disposables.Disposable; @@ -149,6 +150,26 @@ public abstract class AdapterDelegate im return parentLifecycleBindable.untilStop(completable, onCompletedAction, onErrorAction); } + @NonNull + @Override + public Disposable untilStop(@NonNull final Maybe maybe) { + return parentLifecycleBindable.untilStop(maybe); + } + + @NonNull + @Override + public Disposable untilStop(@NonNull final Maybe maybe, @NonNull final Consumer onSuccessAction) { + return parentLifecycleBindable.untilStop(maybe, onSuccessAction); + } + + @NonNull + @Override + public Disposable untilStop(@NonNull final Maybe maybe, + @NonNull final Consumer onSuccessAction, + @NonNull final Consumer onErrorAction) { + return parentLifecycleBindable.untilStop(maybe, onSuccessAction, onErrorAction); + } + @NonNull @Override public Disposable untilDestroy(@NonNull final Observable observable) { @@ -218,4 +239,24 @@ public abstract class AdapterDelegate im return parentLifecycleBindable.untilDestroy(completable, onCompletedAction, onErrorAction); } + @NonNull + @Override + public Disposable untilDestroy(@NonNull final Maybe maybe) { + return parentLifecycleBindable.untilDestroy(maybe); + } + + @NonNull + @Override + public Disposable untilDestroy(@NonNull final Maybe maybe, @NonNull final Consumer onSuccessAction) { + return parentLifecycleBindable.untilDestroy(maybe, onSuccessAction); + } + + @NonNull + @Override + public Disposable untilDestroy(@NonNull final Maybe maybe, + @NonNull final Consumer onSuccessAction, + @NonNull final Consumer onErrorAction) { + return parentLifecycleBindable.untilDestroy(maybe, onSuccessAction, onErrorAction); + } + } diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/BindableViewHolder.java b/src/main/java/ru/touchin/roboswag/components/adapters/BindableViewHolder.java index 7b81e8a..b57f290 100644 --- a/src/main/java/ru/touchin/roboswag/components/adapters/BindableViewHolder.java +++ b/src/main/java/ru/touchin/roboswag/components/adapters/BindableViewHolder.java @@ -32,6 +32,7 @@ import android.support.v7.widget.RecyclerView; import android.view.View; import io.reactivex.Completable; +import io.reactivex.Maybe; import io.reactivex.Observable; import io.reactivex.Single; import io.reactivex.disposables.Disposable; @@ -193,6 +194,26 @@ public class BindableViewHolder extends RecyclerView.ViewHolder implements Lifec return baseLifecycleBindable.untilStop(completable, onCompletedAction, onErrorAction); } + @NonNull + @Override + public Disposable untilStop(@NonNull final Maybe maybe) { + return baseLifecycleBindable.untilStop(maybe); + } + + @NonNull + @Override + public Disposable untilStop(@NonNull final Maybe maybe, @NonNull final Consumer onSuccessAction) { + return baseLifecycleBindable.untilStop(maybe, onSuccessAction); + } + + @NonNull + @Override + public Disposable untilStop(@NonNull final Maybe maybe, + @NonNull final Consumer onSuccessAction, + @NonNull final Consumer onErrorAction) { + return baseLifecycleBindable.untilStop(maybe, onSuccessAction, onErrorAction); + } + @NonNull @Override public Disposable untilDestroy(@NonNull final Observable observable) { @@ -263,4 +284,24 @@ public class BindableViewHolder extends RecyclerView.ViewHolder implements Lifec return baseLifecycleBindable.untilDestroy(completable, onCompletedAction, onErrorAction); } + @NonNull + @Override + public Disposable untilDestroy(@NonNull final Maybe maybe) { + return baseLifecycleBindable.untilDestroy(maybe); + } + + @NonNull + @Override + public Disposable untilDestroy(@NonNull final Maybe maybe, @NonNull final Consumer onSuccessAction) { + return baseLifecycleBindable.untilDestroy(maybe, onSuccessAction); + } + + @NonNull + @Override + public Disposable untilDestroy(@NonNull final Maybe maybe, + @NonNull final Consumer onSuccessAction, + @NonNull final Consumer onErrorAction) { + return baseLifecycleBindable.untilDestroy(maybe, onSuccessAction, onErrorAction); + } + } diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/ViewController.java b/src/main/java/ru/touchin/roboswag/components/navigation/ViewController.java index 3fa2fc4..7ead71e 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/ViewController.java +++ b/src/main/java/ru/touchin/roboswag/components/navigation/ViewController.java @@ -38,6 +38,7 @@ import android.view.View; import android.view.ViewGroup; import io.reactivex.Completable; +import io.reactivex.Maybe; import io.reactivex.Observable; import io.reactivex.Single; import io.reactivex.disposables.Disposable; @@ -406,6 +407,26 @@ public class ViewController, return baseLifecycleBindable.untilStop(completable, onCompletedAction, onErrorAction); } + @NonNull + @Override + public Disposable untilStop(@NonNull final Maybe maybe) { + return baseLifecycleBindable.untilStop(maybe); + } + + @NonNull + @Override + public Disposable untilStop(@NonNull final Maybe maybe, @NonNull final Consumer onSuccessAction) { + return baseLifecycleBindable.untilStop(maybe, onSuccessAction); + } + + @NonNull + @Override + public Disposable untilStop(@NonNull final Maybe maybe, + @NonNull final Consumer onSuccessAction, + @NonNull final Consumer onErrorAction) { + return baseLifecycleBindable.untilStop(maybe, onSuccessAction, onErrorAction); + } + @NonNull @Override public Disposable untilDestroy(@NonNull final Observable observable) { @@ -475,6 +496,26 @@ public class ViewController, return baseLifecycleBindable.untilDestroy(completable, onCompletedAction, onErrorAction); } + @NonNull + @Override + public Disposable untilDestroy(@NonNull final Maybe maybe) { + return baseLifecycleBindable.untilDestroy(maybe); + } + + @NonNull + @Override + public Disposable untilDestroy(@NonNull final Maybe maybe, @NonNull final Consumer onCompletedAction) { + return baseLifecycleBindable.untilDestroy(maybe, onCompletedAction); + } + + @NonNull + @Override + public Disposable untilDestroy(@NonNull final Maybe maybe, + @NonNull final Consumer onCompletedAction, + @NonNull final Consumer onErrorAction) { + return baseLifecycleBindable.untilDestroy(maybe, onCompletedAction, onErrorAction); + } + @SuppressWarnings("CPD-END") /* * Helper class to simplify constructor override. diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java b/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java index 6fed556..d444633 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java +++ b/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java @@ -36,6 +36,7 @@ import android.view.inputmethod.InputMethodManager; import java.util.ArrayList; import io.reactivex.Completable; +import io.reactivex.Maybe; import io.reactivex.Observable; import io.reactivex.Single; import io.reactivex.disposables.Disposable; @@ -256,6 +257,18 @@ public abstract class BaseActivity extends AppCompatActivity @SuppressWarnings("CPD-START") //CPD: it's ok as it's LifecycleBindable + @NonNull + @Override + public Disposable untilStop(@NonNull final Observable observable) { + return baseLifecycleBindable.untilStop(observable); + } + + @NonNull + @Override + public Disposable untilStop(@NonNull final Observable observable, @NonNull final Consumer onNextAction) { + return baseLifecycleBindable.untilStop(observable, onNextAction); + } + @NonNull @Override public Disposable untilStop(@NonNull final Observable observable, @@ -315,14 +328,22 @@ public abstract class BaseActivity extends AppCompatActivity @NonNull @Override - public Disposable untilStop(@NonNull final Observable observable) { - return baseLifecycleBindable.untilStop(observable); + public Disposable untilStop(@NonNull final Maybe maybe) { + return baseLifecycleBindable.untilStop(maybe); } @NonNull @Override - public Disposable untilStop(@NonNull final Observable observable, @NonNull final Consumer onNextAction) { - return baseLifecycleBindable.untilStop(observable, onNextAction); + public Disposable untilStop(@NonNull final Maybe maybe, @NonNull final Consumer onSuccessAction) { + return baseLifecycleBindable.untilStop(maybe, onSuccessAction); + } + + @NonNull + @Override + public Disposable untilStop(@NonNull final Maybe maybe, + @NonNull final Consumer onSuccessAction, + @NonNull final Consumer onErrorAction) { + return baseLifecycleBindable.untilStop(maybe, onSuccessAction, onErrorAction); } @NonNull @@ -394,6 +415,26 @@ public abstract class BaseActivity extends AppCompatActivity return baseLifecycleBindable.untilDestroy(completable, onCompletedAction, onErrorAction); } + @NonNull + @Override + public Disposable untilDestroy(@NonNull final Maybe maybe) { + return baseLifecycleBindable.untilDestroy(maybe); + } + + @NonNull + @Override + public Disposable untilDestroy(@NonNull final Maybe maybe, @NonNull final Consumer onCompletedAction) { + return baseLifecycleBindable.untilDestroy(maybe, onCompletedAction); + } + + @NonNull + @Override + public Disposable untilDestroy(@NonNull final Maybe maybe, + @NonNull final Consumer onCompletedAction, + @NonNull final Consumer onErrorAction) { + return baseLifecycleBindable.untilDestroy(maybe, onCompletedAction, onErrorAction); + } + @SuppressWarnings("CPD-END") /* * Interface to be implemented for someone who want to intercept device back button pressing event. diff --git a/src/main/java/ru/touchin/roboswag/components/utils/BaseLifecycleBindable.java b/src/main/java/ru/touchin/roboswag/components/utils/BaseLifecycleBindable.java index a44f9fa..cfbd3fe 100644 --- a/src/main/java/ru/touchin/roboswag/components/utils/BaseLifecycleBindable.java +++ b/src/main/java/ru/touchin/roboswag/components/utils/BaseLifecycleBindable.java @@ -22,6 +22,7 @@ package ru.touchin.roboswag.components.utils; import android.support.annotation.NonNull; import io.reactivex.Completable; +import io.reactivex.Maybe; import io.reactivex.Observable; import io.reactivex.Single; import io.reactivex.android.schedulers.AndroidSchedulers; @@ -177,6 +178,28 @@ public class BaseLifecycleBindable implements LifecycleBindable { Functions.emptyConsumer(), onErrorAction, onCompletedAction); } + @NonNull + @Override + public Disposable untilStop(@NonNull final Maybe maybe) { + final String codePoint = Lc.getCodePoint(this, 2); + return untilStop(maybe, Functions.emptyConsumer(), getActionThrowableForAssertion(codePoint, UNTIL_STOP_METHOD)); + } + + @NonNull + @Override + public Disposable untilStop(@NonNull final Maybe maybe, @NonNull final Consumer onSuccessAction) { + final String codePoint = Lc.getCodePoint(this, 2); + return untilStop(maybe, onSuccessAction, getActionThrowableForAssertion(codePoint, UNTIL_STOP_METHOD)); + } + + @NonNull + @Override + public Disposable untilStop(@NonNull final Maybe maybe, + @NonNull final Consumer onSuccessAction, + @NonNull final Consumer onErrorAction) { + return until(maybe.toObservable(), isStartedSubject.map(started -> !started), onSuccessAction, onErrorAction, Functions.EMPTY_ACTION); + } + @NonNull @Override public Disposable untilDestroy(@NonNull final Observable observable) { @@ -255,6 +278,28 @@ public class BaseLifecycleBindable implements LifecycleBindable { Functions.emptyConsumer(), onErrorAction, onCompletedAction); } + @NonNull + @Override + public Disposable untilDestroy(@NonNull final Maybe maybe) { + final String codePoint = Lc.getCodePoint(this, 2); + return untilDestroy(maybe, Functions.emptyConsumer(), getActionThrowableForAssertion(codePoint, UNTIL_DESTROY_METHOD)); + } + + @NonNull + @Override + public Disposable untilDestroy(@NonNull final Maybe maybe, @NonNull final Consumer onSuccessAction) { + final String codePoint = Lc.getCodePoint(this, 2); + return untilDestroy(maybe, onSuccessAction, getActionThrowableForAssertion(codePoint, UNTIL_DESTROY_METHOD)); + } + + @NonNull + @Override + public Disposable untilDestroy(@NonNull final Maybe maybe, + @NonNull final Consumer onSuccessAction, + @NonNull final Consumer onErrorAction) { + return until(maybe.toObservable(), isCreatedSubject.map(created -> !created), onSuccessAction, onErrorAction, Functions.EMPTY_ACTION); + } + @NonNull private Disposable until(@NonNull final Observable observable, @NonNull final Observable conditionSubject, diff --git a/src/main/java/ru/touchin/roboswag/components/utils/LifecycleBindable.java b/src/main/java/ru/touchin/roboswag/components/utils/LifecycleBindable.java index 395fb3a..e49ddc9 100644 --- a/src/main/java/ru/touchin/roboswag/components/utils/LifecycleBindable.java +++ b/src/main/java/ru/touchin/roboswag/components/utils/LifecycleBindable.java @@ -24,6 +24,8 @@ import android.support.annotation.NonNull; import io.reactivex.Completable; import io.reactivex.CompletableEmitter; import io.reactivex.Emitter; +import io.reactivex.Maybe; +import io.reactivex.MaybeEmitter; import io.reactivex.Observable; import io.reactivex.Single; import io.reactivex.SingleEmitter; @@ -181,6 +183,45 @@ public interface LifecycleBindable { @NonNull Disposable untilStop(@NonNull Completable completable, @NonNull Action onCompletedAction, @NonNull Consumer onErrorAction); + /** + * Method should be used to guarantee that maybe won't be subscribed after onStop. + * It is automatically subscribing to the maybe. + * Usually it is using to stop requests/execution while element is off or to not do illegal actions after onStop like fragment's stack changing. + * Don't forget to process errors if completable can emit them. + * + * @param maybe {@link Maybe} to subscribe until onStop; + * @return {@link Disposable} which will unsubscribes from completable onStop. + */ + @NonNull + Disposable untilStop(@NonNull Maybe maybe); + + /** + * Method should be used to guarantee that maybe won't be subscribed after onStop. + * It is automatically subscribing to the maybe and calls onCompletedAction on maybe item. + * Usually it is using to stop requests/execution while element is off or to not do illegal actions after onStop like fragment's stack changing. + * Don't forget to process errors if completable can emit them. + * + * @param maybe {@link Maybe} to subscribe until onStop; + * @param onSuccessAction Action which will raise at {@link MaybeEmitter#onSuccess(Object)} ()} on completion of observable; + * @return {@link Disposable} which is wrapping source maybe to unsubscribe from it onStop. + */ + @NonNull + Disposable untilStop(@NonNull Maybe maybe, @NonNull Consumer onSuccessAction); + + /** + * Method should be used to guarantee that maybe won't be subscribed after onStop. + * It is automatically subscribing to the maybe and calls onCompletedAction and onErrorAction on maybe item. + * Usually it is using to stop requests/execution while element is off or to not do illegal actions after onStop like fragment's stack changing. + * Don't forget to process errors if completable can emit them. + * + * @param maybe {@link Maybe} to subscribe until onStop; + * @param onSuccessAction Action which will raise at {@link MaybeEmitter#onSuccess(Object)} ()} on completion of observable; + * @param onErrorAction Action which will raise on every {@link MaybeEmitter#onError(Throwable)} throwable; + * @return {@link Disposable} which is wrapping source maybe to unsubscribe from it onStop. + */ + @NonNull + Disposable untilStop(@NonNull Maybe maybe, @NonNull Consumer onSuccessAction, @NonNull Consumer onErrorAction); + /** * Method should be used to guarantee that observable won't be subscribed after onDestroy. * It is automatically subscribing to the observable. @@ -188,7 +229,7 @@ public interface LifecycleBindable { * * @param observable {@link Observable} to subscribe until onDestroy; * @param Type of emitted by observable items; - * @return {@link Disposable} which is wrapping source observable to unsubscribe from it onDestroy. + * @return {@link Disposable} which is wrapping source maybe to unsubscribe from it onDestroy. */ @NonNull Disposable untilDestroy(@NonNull Observable observable); @@ -289,7 +330,7 @@ public interface LifecycleBindable { /** * Method should be used to guarantee that completable won't be subscribed after onDestroy. * It is automatically subscribing to the completable and calls onCompletedAction on completable item. - * Don't forget to process errors if single can emit them. + * 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 CompletableEmitter#onComplete()} item; @@ -311,4 +352,40 @@ public interface LifecycleBindable { @NonNull Disposable untilDestroy(@NonNull Completable completable, @NonNull Action onCompletedAction, @NonNull Consumer onErrorAction); + /** + * Method should be used to guarantee that maybe won't be subscribed after onDestroy. + * It is automatically subscribing to the maybe. + * Don't forget to process errors if maybe can emit them. + * + * @param maybe {@link Maybe} to subscribe until onDestroy; + * @return {@link Disposable} which is wrapping source maybe to unsubscribe from it onDestroy. + */ + @NonNull + Disposable untilDestroy(@NonNull Maybe maybe); + + /** + * Method should be used to guarantee that maybe won't be subscribed after onDestroy. + * It is automatically subscribing to the maybe and calls onCompletedAction on maybe item. + * Don't forget to process errors if maybe can emit them. + * + * @param maybe {@link Maybe} to subscribe until onDestroy; + * @param onSuccessAction Action which will raise on every {@link MaybeEmitter#onSuccess(Object)} ()} item; + * @return {@link Disposable} which is wrapping source maybe to unsubscribe from it onDestroy. + */ + @NonNull + Disposable untilDestroy(@NonNull Maybe maybe, @NonNull Consumer onSuccessAction); + + /** + * Method should be used to guarantee that maybe won't be subscribed after onDestroy. + * It is automatically subscribing to the maybe and calls onSuccessAction and onErrorAction on maybe events. + * Don't forget to process errors if completable can emit them. + * + * @param maybe {@link Maybe} to subscribe until onDestroy; + * @param onSuccessAction Action which will raise on every {@link MaybeEmitter#onSuccess(Object)} ()} item; + * @param onErrorAction Action which will raise on every {@link MaybeEmitter#onError(Throwable)} throwable; + * @return {@link Disposable} which is wrapping source maybe to unsubscribe from it onDestroy. + */ + @NonNull + Disposable untilDestroy(@NonNull Maybe maybe, @NonNull Consumer onSuccessAction, @NonNull Consumer onErrorAction); + } \ No newline at end of file diff --git a/src/main/java/ru/touchin/roboswag/components/views/LifecycleView.java b/src/main/java/ru/touchin/roboswag/components/views/LifecycleView.java index d0b6d39..df91acf 100644 --- a/src/main/java/ru/touchin/roboswag/components/views/LifecycleView.java +++ b/src/main/java/ru/touchin/roboswag/components/views/LifecycleView.java @@ -28,6 +28,7 @@ import android.support.annotation.Nullable; import android.util.AttributeSet; import android.widget.FrameLayout; +import io.reactivex.Maybe; import ru.touchin.roboswag.components.utils.BaseLifecycleBindable; import ru.touchin.roboswag.components.utils.LifecycleBindable; import io.reactivex.Completable; @@ -212,6 +213,24 @@ public class LifecycleView extends FrameLayout implements LifecycleBindable { return baseLifecycleBindable.untilStop(completable, onCompletedAction, onErrorAction); } + @NonNull + @Override + public Disposable untilStop(@NonNull final Maybe maybe) { + return baseLifecycleBindable.untilStop(maybe); + } + + @NonNull + @Override + public Disposable untilStop(@NonNull final Maybe maybe, @NonNull final Consumer onSuccessAction) { + return baseLifecycleBindable.untilStop(maybe, onSuccessAction); + } + + @NonNull + @Override + public Disposable untilStop(@NonNull final Maybe maybe, @NonNull final Consumer onSuccessAction, @NonNull final Consumer onErrorAction) { + return baseLifecycleBindable.untilStop(maybe, onSuccessAction, onErrorAction); + } + @NonNull @Override public Disposable untilDestroy(@NonNull final Observable observable) { @@ -281,4 +300,22 @@ public class LifecycleView extends FrameLayout implements LifecycleBindable { return baseLifecycleBindable.untilDestroy(completable, onCompletedAction, onErrorAction); } + @NonNull + @Override + public Disposable untilDestroy(@NonNull final Maybe maybe) { + return baseLifecycleBindable.untilDestroy(maybe); + } + + @NonNull + @Override + public Disposable untilDestroy(@NonNull final Maybe maybe, @NonNull final Consumer onSuccessAction) { + return baseLifecycleBindable.untilDestroy(maybe, onSuccessAction); + } + + @NonNull + @Override + public Disposable untilDestroy(@NonNull final Maybe maybe, @NonNull final Consumer onSuccessAction, @NonNull final Consumer onErrorAction) { + return baseLifecycleBindable.untilDestroy(maybe, onSuccessAction, onErrorAction); + } + } From bb7473e839a8dbd102f48441fe067767e0ddb8c4 Mon Sep 17 00:00:00 2001 From: Alexander Bubnov Date: Thu, 29 Jun 2017 13:22:29 +0300 Subject: [PATCH 15/95] fix sA --- .../roboswag/components/adapters/BindableViewHolder.java | 2 +- .../touchin/roboswag/components/views/LifecycleView.java | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/BindableViewHolder.java b/src/main/java/ru/touchin/roboswag/components/adapters/BindableViewHolder.java index b57f290..3ca1fe0 100644 --- a/src/main/java/ru/touchin/roboswag/components/adapters/BindableViewHolder.java +++ b/src/main/java/ru/touchin/roboswag/components/adapters/BindableViewHolder.java @@ -275,7 +275,6 @@ public class BindableViewHolder extends RecyclerView.ViewHolder implements Lifec return baseLifecycleBindable.untilDestroy(completable, onCompletedAction); } - @SuppressWarnings("CPD-END") @NonNull @Override public Disposable untilDestroy(@NonNull final Completable completable, @@ -296,6 +295,7 @@ public class BindableViewHolder extends RecyclerView.ViewHolder implements Lifec return baseLifecycleBindable.untilDestroy(maybe, onSuccessAction); } + @SuppressWarnings("CPD-END") @NonNull @Override public Disposable untilDestroy(@NonNull final Maybe maybe, diff --git a/src/main/java/ru/touchin/roboswag/components/views/LifecycleView.java b/src/main/java/ru/touchin/roboswag/components/views/LifecycleView.java index df91acf..a644b3e 100644 --- a/src/main/java/ru/touchin/roboswag/components/views/LifecycleView.java +++ b/src/main/java/ru/touchin/roboswag/components/views/LifecycleView.java @@ -227,7 +227,9 @@ public class LifecycleView extends FrameLayout implements LifecycleBindable { @NonNull @Override - public Disposable untilStop(@NonNull final Maybe maybe, @NonNull final Consumer onSuccessAction, @NonNull final Consumer onErrorAction) { + public Disposable untilStop(@NonNull final Maybe maybe, + @NonNull final Consumer onSuccessAction, + @NonNull final Consumer onErrorAction) { return baseLifecycleBindable.untilStop(maybe, onSuccessAction, onErrorAction); } @@ -314,7 +316,9 @@ public class LifecycleView extends FrameLayout implements LifecycleBindable { @NonNull @Override - public Disposable untilDestroy(@NonNull final Maybe maybe, @NonNull final Consumer onSuccessAction, @NonNull final Consumer onErrorAction) { + public Disposable untilDestroy(@NonNull final Maybe maybe, + @NonNull final Consumer onSuccessAction, + @NonNull final Consumer onErrorAction) { return baseLifecycleBindable.untilDestroy(maybe, onSuccessAction, onErrorAction); } From c32eb4b83560b2ccc8f185aa9ba4d48a8d52e952 Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Fri, 30 Jun 2017 19:32:45 +0300 Subject: [PATCH 16/95] Merge fix --- .../components/adapters/ObservableCollectionAdapter.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/ObservableCollectionAdapter.java b/src/main/java/ru/touchin/roboswag/components/adapters/ObservableCollectionAdapter.java index 42e24b3..8de74b1 100644 --- a/src/main/java/ru/touchin/roboswag/components/adapters/ObservableCollectionAdapter.java +++ b/src/main/java/ru/touchin/roboswag/components/adapters/ObservableCollectionAdapter.java @@ -35,7 +35,6 @@ import io.reactivex.functions.BiConsumer; import io.reactivex.functions.Consumer; import io.reactivex.Observable; -import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.subjects.BehaviorSubject; import ru.touchin.roboswag.components.utils.LifecycleBindable; import ru.touchin.roboswag.components.utils.UiUtils; From a517b16334d475a28b9e5573e85a217d8a8d3042 Mon Sep 17 00:00:00 2001 From: Anton Domnikov Date: Fri, 30 Jun 2017 20:39:49 +0300 Subject: [PATCH 17/95] added ability to push viewcontrollers without adding to back stack --- .../navigation/FragmentNavigation.java | 32 +++++++++------- .../navigation/ViewControllerNavigation.java | 38 +++++++++++++++---- 2 files changed, 48 insertions(+), 22 deletions(-) diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.java b/src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.java index fbcf35d..dad6ee8 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.java +++ b/src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.java @@ -118,6 +118,7 @@ public class FragmentNavigation { * * @param fragmentClass Class of {@link Fragment} to instantiate; * @param targetFragment Target fragment to be set as {@link Fragment#getTargetFragment()} of instantiated {@link Fragment}; + * @param addToStack Flag to add this transaction to the back stack; * @param args Bundle to be set as {@link Fragment#getArguments()} of instantiated {@link Fragment}; * @param backStackTag Tag of {@link Fragment} in back stack; * @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info. @@ -126,6 +127,7 @@ public class FragmentNavigation { //CommitTransaction: it is ok as we could setup transaction before commit protected void addToStack(@NonNull final Class fragmentClass, @Nullable final Fragment targetFragment, + final boolean addToStack, @Nullable final Bundle args, @Nullable final String backStackTag, @Nullable final Function transactionSetup) { @@ -144,8 +146,10 @@ public class FragmentNavigation { } final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction() - .replace(containerViewId, fragment, null) - .addToBackStack(backStackTag); + .replace(containerViewId, fragment, null); + if (addToStack) { + fragmentTransaction.addToBackStack(backStackTag); + } if (fragmentManager.getBackStackEntryCount() != 0) { fragmentTransaction.setTransition(getDefaultTransition()); } @@ -231,7 +235,7 @@ public class FragmentNavigation { * @param fragmentClass Class of {@link Fragment} to instantiate. */ public void push(@NonNull final Class fragmentClass) { - addToStack(fragmentClass, null, null, null, null); + addToStack(fragmentClass, null, true, null, null, null); } /** @@ -242,7 +246,7 @@ public class FragmentNavigation { */ public void push(@NonNull final Class fragmentClass, @NonNull final Bundle args) { - addToStack(fragmentClass, null, args, null, null); + addToStack(fragmentClass, null, true, args, null, null); } /** @@ -253,7 +257,7 @@ public class FragmentNavigation { */ public void push(@NonNull final Class fragmentClass, @NonNull final Function transactionSetup) { - addToStack(fragmentClass, null, null, null, transactionSetup); + addToStack(fragmentClass, null, true, null, null, transactionSetup); } /** @@ -266,7 +270,7 @@ public class FragmentNavigation { public void push(@NonNull final Class fragmentClass, @Nullable final Bundle args, @Nullable final Function transactionSetup) { - addToStack(fragmentClass, null, args, null, transactionSetup); + addToStack(fragmentClass, null, true, args, null, transactionSetup); } /** @@ -277,7 +281,7 @@ public class FragmentNavigation { */ public void pushForResult(@NonNull final Class fragmentClass, @NonNull final Fragment targetFragment) { - addToStack(fragmentClass, targetFragment, null, fragmentClass.getName() + ';' + WITH_TARGET_FRAGMENT_TAG_MARK, null); + addToStack(fragmentClass, targetFragment, true, null, fragmentClass.getName() + ';' + WITH_TARGET_FRAGMENT_TAG_MARK, null); } /** @@ -290,7 +294,7 @@ public class FragmentNavigation { public void pushForResult(@NonNull final Class fragmentClass, @NonNull final Fragment targetFragment, @NonNull final Bundle args) { - addToStack(fragmentClass, targetFragment, args, fragmentClass.getName() + ';' + WITH_TARGET_FRAGMENT_TAG_MARK, null); + addToStack(fragmentClass, targetFragment, true, args, fragmentClass.getName() + ';' + WITH_TARGET_FRAGMENT_TAG_MARK, null); } /** @@ -303,7 +307,7 @@ public class FragmentNavigation { public void pushForResult(@NonNull final Class fragmentClass, @NonNull final Fragment targetFragment, @NonNull final Function transactionSetup) { - addToStack(fragmentClass, targetFragment, null, fragmentClass.getName() + ';' + WITH_TARGET_FRAGMENT_TAG_MARK, transactionSetup); + addToStack(fragmentClass, targetFragment, true, null, fragmentClass.getName() + ';' + WITH_TARGET_FRAGMENT_TAG_MARK, transactionSetup); } /** @@ -318,7 +322,7 @@ public class FragmentNavigation { @NonNull final Fragment targetFragment, @Nullable final Bundle args, @Nullable final Function transactionSetup) { - addToStack(fragmentClass, targetFragment, args, fragmentClass.getName() + ';' + WITH_TARGET_FRAGMENT_TAG_MARK, transactionSetup); + addToStack(fragmentClass, targetFragment, true, args, fragmentClass.getName() + ';' + WITH_TARGET_FRAGMENT_TAG_MARK, transactionSetup); } /** @@ -327,7 +331,7 @@ public class FragmentNavigation { * @param fragmentClass Class of {@link Fragment} to instantiate. */ public void setAsTop(@NonNull final Class fragmentClass) { - addToStack(fragmentClass, null, null, fragmentClass.getName() + ';' + TOP_FRAGMENT_TAG_MARK, null); + addToStack(fragmentClass, null, true, null, fragmentClass.getName() + ';' + TOP_FRAGMENT_TAG_MARK, null); } /** @@ -338,7 +342,7 @@ public class FragmentNavigation { */ public void setAsTop(@NonNull final Class fragmentClass, @NonNull final Bundle args) { - addToStack(fragmentClass, null, args, fragmentClass.getName() + ';' + TOP_FRAGMENT_TAG_MARK, null); + addToStack(fragmentClass, null, true, args, fragmentClass.getName() + ';' + TOP_FRAGMENT_TAG_MARK, null); } /** @@ -350,7 +354,7 @@ public class FragmentNavigation { */ public void setAsTop(@NonNull final Class fragmentClass, @NonNull final Function transactionSetup) { - addToStack(fragmentClass, null, null, fragmentClass.getName() + ';' + TOP_FRAGMENT_TAG_MARK, transactionSetup); + addToStack(fragmentClass, null, true, null, fragmentClass.getName() + ';' + TOP_FRAGMENT_TAG_MARK, transactionSetup); } /** @@ -364,7 +368,7 @@ public class FragmentNavigation { public void setAsTop(@NonNull final Class fragmentClass, @Nullable final Bundle args, @Nullable final Function transactionSetup) { - addToStack(fragmentClass, null, args, fragmentClass.getName() + ';' + TOP_FRAGMENT_TAG_MARK, transactionSetup); + addToStack(fragmentClass, null, true, args, fragmentClass.getName() + ';' + TOP_FRAGMENT_TAG_MARK, transactionSetup); } /** diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/ViewControllerNavigation.java b/src/main/java/ru/touchin/roboswag/components/navigation/ViewControllerNavigation.java index 58fad74..ab43208 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/ViewControllerNavigation.java +++ b/src/main/java/ru/touchin/roboswag/components/navigation/ViewControllerNavigation.java @@ -59,7 +59,7 @@ public class ViewControllerNavigation void push(@NonNull final Class> fragmentClass, @NonNull final TState state) { - addToStack(fragmentClass, null, ViewControllerFragment.createState(state), null, null); + addToStack(fragmentClass, null, true, ViewControllerFragment.createState(state), null, null); } /** @@ -73,7 +73,7 @@ public class ViewControllerNavigation void push(@NonNull final Class> fragmentClass, @Nullable final TState state, @Nullable final Function transactionSetup) { - addToStack(fragmentClass, null, ViewControllerFragment.createState(state), null, transactionSetup); + addToStack(fragmentClass, null, true, ViewControllerFragment.createState(state), null, transactionSetup); } /** @@ -87,7 +87,7 @@ public class ViewControllerNavigation void pushForResult(@NonNull final Class> fragmentClass, @NonNull final Fragment targetFragment, @NonNull final TState state) { - addToStack(fragmentClass, targetFragment, ViewControllerFragment.createState(state), + addToStack(fragmentClass, targetFragment, true, ViewControllerFragment.createState(state), fragmentClass.getName() + ';' + WITH_TARGET_FRAGMENT_TAG_MARK, null); } @@ -104,7 +104,7 @@ public class ViewControllerNavigation transactionSetup) { - addToStack(fragmentClass, targetFragment, ViewControllerFragment.createState(state), + addToStack(fragmentClass, targetFragment, true, ViewControllerFragment.createState(state), fragmentClass.getName() + ';' + WITH_TARGET_FRAGMENT_TAG_MARK, transactionSetup); } @@ -211,6 +211,28 @@ public class ViewControllerNavigation>> viewControllerClass) { + addToStack(StatelessViewControllerFragment.class, null, false, StatelessViewControllerFragment.createState(viewControllerClass), null, null); + } + + /** + * Pushes {@link ViewController} without adding to stack and with specific {@link ViewControllerFragment#getState()}. + * + * @param viewControllerClass Class of {@link ViewController} to be pushed; + * @param state {@link AbstractState} of {@link ViewController}'s fragment; + * @param Type of state of fragment. + */ + public void pushSingleViewController(@NonNull final Class>> viewControllerClass, @NonNull final TState state) { + addToStack(SimpleViewControllerFragment.class, null, false, SimpleViewControllerFragment.createState(viewControllerClass, state), null, null); + } + /** * Pushes {@link ViewController} on top of stack with specific {@link StatelessTargetedViewControllerFragment#getTarget()}. * @@ -414,7 +436,7 @@ public class ViewControllerNavigation transactionSetup) { - addToStack(StatelessViewControllerFragment.class, targetFragment, + addToStack(StatelessViewControllerFragment.class, targetFragment, true, StatelessViewControllerFragment.createState(viewControllerClass), backStackTag, transactionSetup); } @@ -436,7 +458,7 @@ public class ViewControllerNavigation transactionSetup) { - addToStack(TargetedViewControllerFragment.class, targetFragment, + addToStack(TargetedViewControllerFragment.class, targetFragment, true, TargetedViewControllerFragment.createState(viewControllerClass, state), backStackTag, transactionSetup); } @@ -455,7 +477,7 @@ public class ViewControllerNavigation transactionSetup) { - addToStack(StatelessTargetedViewControllerFragment.class, targetFragment, + addToStack(StatelessTargetedViewControllerFragment.class, targetFragment, true, StatelessTargetedViewControllerFragment.createState(viewControllerClass), backStackTag, transactionSetup); } @@ -475,7 +497,7 @@ public class ViewControllerNavigation transactionSetup) { - addToStack(SimpleViewControllerFragment.class, targetFragment, + addToStack(SimpleViewControllerFragment.class, targetFragment, true, SimpleViewControllerFragment.createState(viewControllerClass, state), backStackTag, transactionSetup); } From 70e6b99e795588852f6ee3301e1117822c4cd8b2 Mon Sep 17 00:00:00 2001 From: Arseniy Borisov Date: Mon, 24 Jul 2017 12:33:29 +0300 Subject: [PATCH 18/95] idea formatting --- build.gradle | 2 +- .../adapters/ItemAdapterDelegate.java | 20 +++++------ .../adapters/ObservableCollectionAdapter.java | 4 +-- .../adapters/PositionAdapterDelegate.java | 6 ++-- .../components/views/LifecycleView.java | 36 +++++++++---------- 5 files changed, 34 insertions(+), 34 deletions(-) diff --git a/build.gradle b/build.gradle index b692c81..f3d6b2c 100644 --- a/build.gradle +++ b/build.gradle @@ -22,5 +22,5 @@ dependencies { provided 'com.android.support:recyclerview-v7:26.0.0-beta2' provided 'io.reactivex.rxjava2:rxandroid:2.0.1' - provided 'io.reactivex.rxjava2:rxjava:2.1.1' + provided 'io.reactivex.rxjava2:rxjava:2.1.2' } diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/ItemAdapterDelegate.java b/src/main/java/ru/touchin/roboswag/components/adapters/ItemAdapterDelegate.java index 20bad88..033e944 100644 --- a/src/main/java/ru/touchin/roboswag/components/adapters/ItemAdapterDelegate.java +++ b/src/main/java/ru/touchin/roboswag/components/adapters/ItemAdapterDelegate.java @@ -26,7 +26,7 @@ public abstract class ItemAdapterDelegate payloads, diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/ObservableCollectionAdapter.java b/src/main/java/ru/touchin/roboswag/components/adapters/ObservableCollectionAdapter.java index 8de74b1..c3d5498 100644 --- a/src/main/java/ru/touchin/roboswag/components/adapters/ObservableCollectionAdapter.java +++ b/src/main/java/ru/touchin/roboswag/components/adapters/ObservableCollectionAdapter.java @@ -624,8 +624,8 @@ public abstract class ObservableCollectionAdapter payloads, final int positionInAdapter) { diff --git a/src/main/java/ru/touchin/roboswag/components/views/LifecycleView.java b/src/main/java/ru/touchin/roboswag/components/views/LifecycleView.java index a644b3e..781d841 100644 --- a/src/main/java/ru/touchin/roboswag/components/views/LifecycleView.java +++ b/src/main/java/ru/touchin/roboswag/components/views/LifecycleView.java @@ -159,17 +159,17 @@ public class LifecycleView extends FrameLayout implements LifecycleBindable { @NonNull @Override public Disposable untilStop(@NonNull final Observable observable, - @NonNull final Consumer onNextAction, - @NonNull final Consumer onErrorAction) { + @NonNull final Consumer onNextAction, + @NonNull final Consumer onErrorAction) { return baseLifecycleBindable.untilStop(observable, onNextAction, onErrorAction); } @NonNull @Override public Disposable untilStop(@NonNull final Observable observable, - @NonNull final Consumer onNextAction, - @NonNull final Consumer onErrorAction, - @NonNull final Action onCompletedAction) { + @NonNull final Consumer onNextAction, + @NonNull final Consumer onErrorAction, + @NonNull final Action onCompletedAction) { return baseLifecycleBindable.untilStop(observable, onNextAction, onErrorAction, onCompletedAction); } @@ -188,8 +188,8 @@ public class LifecycleView extends FrameLayout implements LifecycleBindable { @NonNull @Override public Disposable untilStop(@NonNull final Single single, - @NonNull final Consumer onSuccessAction, - @NonNull final Consumer onErrorAction) { + @NonNull final Consumer onSuccessAction, + @NonNull final Consumer onErrorAction) { return baseLifecycleBindable.untilStop(single, onSuccessAction, onErrorAction); } @@ -208,8 +208,8 @@ public class LifecycleView extends FrameLayout implements LifecycleBindable { @NonNull @Override public Disposable untilStop(@NonNull final Completable completable, - @NonNull final Action onCompletedAction, - @NonNull final Consumer onErrorAction) { + @NonNull final Action onCompletedAction, + @NonNull final Consumer onErrorAction) { return baseLifecycleBindable.untilStop(completable, onCompletedAction, onErrorAction); } @@ -248,17 +248,17 @@ public class LifecycleView extends FrameLayout implements LifecycleBindable { @NonNull @Override public Disposable untilDestroy(@NonNull final Observable observable, - @NonNull final Consumer onNextAction, - @NonNull final Consumer onErrorAction) { + @NonNull final Consumer onNextAction, + @NonNull final Consumer onErrorAction) { return baseLifecycleBindable.untilDestroy(observable, onNextAction, onErrorAction); } @NonNull @Override public Disposable untilDestroy(@NonNull final Observable observable, - @NonNull final Consumer onNextAction, - @NonNull final Consumer onErrorAction, - @NonNull final Action onCompletedAction) { + @NonNull final Consumer onNextAction, + @NonNull final Consumer onErrorAction, + @NonNull final Action onCompletedAction) { return baseLifecycleBindable.untilDestroy(observable, onNextAction, onErrorAction, onCompletedAction); } @@ -277,8 +277,8 @@ public class LifecycleView extends FrameLayout implements LifecycleBindable { @NonNull @Override public Disposable untilDestroy(@NonNull final Single single, - @NonNull final Consumer onSuccessAction, - @NonNull final Consumer onErrorAction) { + @NonNull final Consumer onSuccessAction, + @NonNull final Consumer onErrorAction) { return baseLifecycleBindable.untilDestroy(single, onSuccessAction, onErrorAction); } @@ -297,8 +297,8 @@ public class LifecycleView extends FrameLayout implements LifecycleBindable { @NonNull @Override public Disposable untilDestroy(@NonNull final Completable completable, - @NonNull final Action onCompletedAction, - @NonNull final Consumer onErrorAction) { + @NonNull final Action onCompletedAction, + @NonNull final Consumer onErrorAction) { return baseLifecycleBindable.untilDestroy(completable, onCompletedAction, onErrorAction); } From 97c9a7571b7b5b517153d1a4708e7097db65e958 Mon Sep 17 00:00:00 2001 From: Alexander Bubnov Date: Tue, 25 Jul 2017 18:56:13 +0300 Subject: [PATCH 19/95] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20support=20design=20=D0=B1=D0=B8=D0=B1=D0=BB=D0=B8?= =?UTF-8?q?=D0=BE=D1=82=D0=B5=D0=BA=D1=83,=20=D0=B4=D0=BE=D0=B1=D0=B0?= =?UTF-8?q?=D0=B2=D0=B8=D0=BB=20=D0=B2=20typefactedEditText=20=D0=BF=D0=B5?= =?UTF-8?q?=D1=80=D0=B5=D0=BE=D0=BF=D1=80=D0=B5=D0=B4=D0=B5=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4=D0=B0,=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D1=8B=20?= =?UTF-8?q?=D1=81=20TextInputLayout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 1 + .../components/views/TypefacedEditText.java | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/build.gradle b/build.gradle index f3d6b2c..74c0dd8 100644 --- a/build.gradle +++ b/build.gradle @@ -19,6 +19,7 @@ dependencies { compile project(':libraries:core') provided 'com.android.support:appcompat-v7:26.0.0-beta2' + provided 'com.android.support:design:26.0.0-beta2' provided 'com.android.support:recyclerview-v7:26.0.0-beta2' provided 'io.reactivex.rxjava2:rxandroid:2.0.1' diff --git a/src/main/java/ru/touchin/roboswag/components/views/TypefacedEditText.java b/src/main/java/ru/touchin/roboswag/components/views/TypefacedEditText.java index 33c8493..2a057b6 100644 --- a/src/main/java/ru/touchin/roboswag/components/views/TypefacedEditText.java +++ b/src/main/java/ru/touchin/roboswag/components/views/TypefacedEditText.java @@ -23,6 +23,7 @@ import android.content.Context; import android.content.res.TypedArray; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.design.widget.TextInputLayout; import android.support.v7.widget.AppCompatEditText; import android.text.Editable; import android.text.InputType; @@ -31,6 +32,10 @@ import android.text.TextWatcher; import android.text.method.SingleLineTransformationMethod; import android.text.method.TransformationMethod; import android.util.AttributeSet; +import android.view.View; +import android.view.ViewParent; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; import java.util.ArrayList; import java.util.List; @@ -107,6 +112,21 @@ public class TypefacedEditText extends AppCompatEditText { } } + @Nullable + public InputConnection onCreateInputConnection(@NonNull final EditorInfo attrs) { + final InputConnection inputConnection = super.onCreateInputConnection(attrs); + if (inputConnection != null && attrs.hintText == null) { + for (ViewParent parent = this.getParent(); parent instanceof View; parent = parent.getParent()) { + if (parent instanceof TextInputLayout) { + attrs.hintText = ((TextInputLayout) parent).getHint(); + break; + } + } + } + + return inputConnection; + } + private void checkAttributes(@NonNull final Context context, @NonNull final AttributeSet attrs) { final List errors = new ArrayList<>(); Boolean multiline = null; From 8405c76bf02ce23b31a545b338134c8ae794877d Mon Sep 17 00:00:00 2001 From: Alexander Bubnov Date: Tue, 25 Jul 2017 19:08:59 +0300 Subject: [PATCH 20/95] =?UTF-8?q?=D1=83=D0=B4=D0=B0=D0=BB=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=BD=D0=B5=D0=BD=D1=83=D0=B6=D0=BD=D1=8B=D0=B9=20this?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ru/touchin/roboswag/components/views/TypefacedEditText.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ru/touchin/roboswag/components/views/TypefacedEditText.java b/src/main/java/ru/touchin/roboswag/components/views/TypefacedEditText.java index 2a057b6..bc64a71 100644 --- a/src/main/java/ru/touchin/roboswag/components/views/TypefacedEditText.java +++ b/src/main/java/ru/touchin/roboswag/components/views/TypefacedEditText.java @@ -116,7 +116,7 @@ public class TypefacedEditText extends AppCompatEditText { public InputConnection onCreateInputConnection(@NonNull final EditorInfo attrs) { final InputConnection inputConnection = super.onCreateInputConnection(attrs); if (inputConnection != null && attrs.hintText == null) { - for (ViewParent parent = this.getParent(); parent instanceof View; parent = parent.getParent()) { + for (ViewParent parent = getParent(); parent instanceof View; parent = parent.getParent()) { if (parent instanceof TextInputLayout) { attrs.hintText = ((TextInputLayout) parent).getHint(); break; From 3f8955f31b4a6583041618cf9f4362a4dcecf206 Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Tue, 25 Jul 2017 19:54:06 +0300 Subject: [PATCH 21/95] Build tools update --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 74c0dd8..fedabbe 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ apply plugin: 'me.tatarka.retrolambda' android { compileSdkVersion 26 - buildToolsVersion '26.0.0' + buildToolsVersion '26.0.1' compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 From 0a82b7a57c5aaa595d76b49e1e80f6131d837bfa Mon Sep 17 00:00:00 2001 From: gorodeckii Date: Wed, 26 Jul 2017 17:53:54 +0300 Subject: [PATCH 22/95] rxjava2 version --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 04b1c5e..55b29d6 100644 --- a/build.gradle +++ b/build.gradle @@ -22,5 +22,5 @@ dependencies { provided 'com.android.support:recyclerview-v7:25.4.0' provided 'io.reactivex.rxjava2:rxandroid:2.0.1' - provided 'io.reactivex.rxjava2:rxjava:2.1.1' + provided 'io.reactivex.rxjava2:rxjava:2.1.2' } From fda49db855cee373dc2767492d14b74674c34879 Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Mon, 31 Jul 2017 16:17:33 +0300 Subject: [PATCH 23/95] Removed unnecessary setOnRippleClickListener overloads --- .../ru/touchin/roboswag/components/utils/UiUtils.java | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.java b/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.java index 7cb5df4..8ac11d9 100644 --- a/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.java +++ b/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.java @@ -106,16 +106,6 @@ public final class UiUtils { setOnRippleClickListener(targetView, onClickListener != null ? view -> onClickListener.run() : null, delay); } - /** - * Sets click listener to view. On click it will call something with {@link #RIPPLE_EFFECT_DELAY}. - * - * @param targetView View to set click listener to; - * @param onClickListener Click listener. - */ - public static void setOnRippleClickListener(@NonNull final View targetView, @Nullable final Action onClickListener) { - setOnRippleClickListener(targetView, onClickListener != null ? view -> onClickListener.run() : null, RIPPLE_EFFECT_DELAY); - } - /** * Sets click listener to view. On click it will call something with {@link #RIPPLE_EFFECT_DELAY}. * From dd2732c12901e818e394f05dbf836e38545163c9 Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Tue, 1 Aug 2017 13:04:10 +0300 Subject: [PATCH 24/95] Function setContentView made final (#82) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit setContentView теперь final, чтобы студия не ругалась при вызове этих методов в конструкторе --- .../roboswag/components/navigation/ViewController.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/ViewController.java b/src/main/java/ru/touchin/roboswag/components/navigation/ViewController.java index 98819c2..cfd44be 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/ViewController.java +++ b/src/main/java/ru/touchin/roboswag/components/navigation/ViewController.java @@ -150,7 +150,7 @@ public class ViewController, * * @param layoutResId Resource ID to be inflated. */ - public void setContentView(@LayoutRes final int layoutResId) { + public final void setContentView(@LayoutRes final int layoutResId) { if (getContainer().getChildCount() > 0) { getContainer().removeAllViews(); } @@ -163,7 +163,7 @@ public class ViewController, * * @param view The desired content to display. */ - public void setContentView(@NonNull final View view) { + public final void setContentView(@NonNull final View view) { setContentView(view, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); } @@ -174,7 +174,7 @@ public class ViewController, * @param view The desired content to display; * @param layoutParams Layout parameters for the view. */ - public void setContentView(@NonNull final View view, @NonNull final ViewGroup.LayoutParams layoutParams) { + public final void setContentView(@NonNull final View view, @NonNull final ViewGroup.LayoutParams layoutParams) { if (getContainer().getChildCount() > 0) { getContainer().removeAllViews(); } From 527de35579846be2603a7bacfd4edd6ef9d990a8 Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Thu, 3 Aug 2017 18:21:53 +0300 Subject: [PATCH 25/95] Support lib update (#84) --- build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index fedabbe..5147161 100644 --- a/build.gradle +++ b/build.gradle @@ -18,9 +18,9 @@ android { dependencies { compile project(':libraries:core') - provided 'com.android.support:appcompat-v7:26.0.0-beta2' - provided 'com.android.support:design:26.0.0-beta2' - provided 'com.android.support:recyclerview-v7:26.0.0-beta2' + provided 'com.android.support:appcompat-v7:26.0.0' + provided 'com.android.support:design:26.0.0' + provided 'com.android.support:recyclerview-v7:26.0.0' provided 'io.reactivex.rxjava2:rxandroid:2.0.1' provided 'io.reactivex.rxjava2:rxjava:2.1.2' From 46d00ffc5fbba2825f7327dc2b54146010f1dc97 Mon Sep 17 00:00:00 2001 From: Ilia Kurtov Date: Wed, 9 Aug 2017 18:13:39 +0300 Subject: [PATCH 26/95] restored setOnRippleClickListener for Action and delete for Consumer --- .../touchin/roboswag/components/utils/UiUtils.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.java b/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.java index 8ac11d9..4489461 100644 --- a/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.java +++ b/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.java @@ -112,10 +112,20 @@ public final class UiUtils { * @param targetView View to set click listener to; * @param onClickListener Click listener. */ - public static void setOnRippleClickListener(@NonNull final View targetView, @Nullable final Consumer onClickListener) { - setOnRippleClickListener(targetView, onClickListener, RIPPLE_EFFECT_DELAY); + public static void setOnRippleClickListener(@NonNull final View targetView, @Nullable final Action onClickListener) { + setOnRippleClickListener(targetView, onClickListener != null ? view -> onClickListener.run() : null, RIPPLE_EFFECT_DELAY); } +// /** +// * Sets click listener to view. On click it will call something with {@link #RIPPLE_EFFECT_DELAY}. +// * +// * @param targetView View to set click listener to; +// * @param onClickListener Click listener. +// */ +// public static void setOnRippleClickListener(@NonNull final View targetView, @Nullable final Consumer onClickListener) { +// setOnRippleClickListener(targetView, onClickListener, RIPPLE_EFFECT_DELAY); +// } + /** * Sets click listener to view. On click it will call something after delay. * From 37f5664187430d81df3dec3d5017cf13ae628d6b Mon Sep 17 00:00:00 2001 From: Ilia Kurtov Date: Wed, 9 Aug 2017 18:13:53 +0300 Subject: [PATCH 27/95] static --- .../ru/touchin/roboswag/components/utils/UiUtils.java | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.java b/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.java index 4489461..7596527 100644 --- a/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.java +++ b/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.java @@ -116,16 +116,6 @@ public final class UiUtils { setOnRippleClickListener(targetView, onClickListener != null ? view -> onClickListener.run() : null, RIPPLE_EFFECT_DELAY); } -// /** -// * Sets click listener to view. On click it will call something with {@link #RIPPLE_EFFECT_DELAY}. -// * -// * @param targetView View to set click listener to; -// * @param onClickListener Click listener. -// */ -// public static void setOnRippleClickListener(@NonNull final View targetView, @Nullable final Consumer onClickListener) { -// setOnRippleClickListener(targetView, onClickListener, RIPPLE_EFFECT_DELAY); -// } - /** * Sets click listener to view. On click it will call something after delay. * From 6d0f74a97296a26ac6b236c992c32456893c27c5 Mon Sep 17 00:00:00 2001 From: Elena Bobkova Date: Mon, 14 Aug 2017 15:32:00 +0300 Subject: [PATCH 28/95] fixed on activity result bindings for large files (#89) --- .../roboswag/components/navigation/activities/BaseActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java b/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java index f39ee8a..611152c 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java +++ b/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java @@ -115,7 +115,7 @@ public abstract class BaseActivity extends AppCompatActivity @NonNull public Observable observeActivityResult(final int requestCode) { return lastActivityResult - .switchMap(optional -> { + .concatMap(optional -> { final HalfNullablePair activityResult = optional.get(); if (activityResult == null || activityResult.getFirst() != requestCode) { return Observable.empty(); From d3191cd4726b92b160d608175dbeb8e5fdb0ee4e Mon Sep 17 00:00:00 2001 From: Ilia Kurtov Date: Mon, 14 Aug 2017 17:41:32 +0300 Subject: [PATCH 29/95] libs update (#90) --- build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 5147161..0eae175 100644 --- a/build.gradle +++ b/build.gradle @@ -18,9 +18,9 @@ android { dependencies { compile project(':libraries:core') - provided 'com.android.support:appcompat-v7:26.0.0' - provided 'com.android.support:design:26.0.0' - provided 'com.android.support:recyclerview-v7:26.0.0' + provided 'com.android.support:appcompat-v7:26.0.1' + provided 'com.android.support:design:26.0.1' + provided 'com.android.support:recyclerview-v7:26.0.1' provided 'io.reactivex.rxjava2:rxandroid:2.0.1' provided 'io.reactivex.rxjava2:rxjava:2.1.2' From bab8ceb74fa1a2ad54b5b1497449c7f90bcf9371 Mon Sep 17 00:00:00 2001 From: Ilia Kurtov Date: Tue, 15 Aug 2017 21:02:55 +0300 Subject: [PATCH 30/95] rxjava update (#91) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 0eae175..3d98ca5 100644 --- a/build.gradle +++ b/build.gradle @@ -23,5 +23,5 @@ dependencies { provided 'com.android.support:recyclerview-v7:26.0.1' provided 'io.reactivex.rxjava2:rxandroid:2.0.1' - provided 'io.reactivex.rxjava2:rxjava:2.1.2' + provided 'io.reactivex.rxjava2:rxjava:2.1.3' } From 3a3f416b8c638a1226e0065295f7426124ab0985 Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Wed, 23 Aug 2017 17:05:46 +0300 Subject: [PATCH 31/95] Fonts in xml (#92) --- .../components/views/TypefacedEditText.java | 22 ------------------- .../components/views/TypefacedTextView.java | 15 ------------- .../views/internal/AttributesUtils.java | 4 +--- src/main/res/values/attrs.xml | 2 -- 4 files changed, 1 insertion(+), 42 deletions(-) diff --git a/src/main/java/ru/touchin/roboswag/components/views/TypefacedEditText.java b/src/main/java/ru/touchin/roboswag/components/views/TypefacedEditText.java index bc64a71..d29b60f 100644 --- a/src/main/java/ru/touchin/roboswag/components/views/TypefacedEditText.java +++ b/src/main/java/ru/touchin/roboswag/components/views/TypefacedEditText.java @@ -41,7 +41,6 @@ import java.util.ArrayList; import java.util.List; import ru.touchin.roboswag.components.R; -import ru.touchin.roboswag.components.utils.Typefaces; import ru.touchin.roboswag.components.views.internal.AttributesUtils; import ru.touchin.roboswag.core.log.Lc; @@ -101,10 +100,6 @@ public class TypefacedEditText extends AppCompatEditText { } else { setSingleLine(); } - - if (!isInEditMode()) { - setTypeface(Typefaces.getFromAttributes(context, attrs, R.styleable.TypefacedEditText, R.styleable.TypefacedEditText_customTypeface)); - } typedArray.recycle(); if (inDebugMode) { checkAttributes(context, attrs); @@ -131,8 +126,6 @@ public class TypefacedEditText extends AppCompatEditText { final List errors = new ArrayList<>(); Boolean multiline = null; TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.TypefacedEditText); - AttributesUtils.checkAttribute(typedArray, errors, R.styleable.TypefacedEditText_customTypeface, true, - "customTypeface required parameter"); AttributesUtils.checkAttribute(typedArray, errors, R.styleable.TypefacedEditText_isMultiline, true, "isMultiline required parameter"); if (typedArray.hasValue(R.styleable.TypefacedEditText_isMultiline)) { @@ -159,12 +152,6 @@ public class TypefacedEditText extends AppCompatEditText { private void checkEditTextSpecificAttributes(@NonNull final TypedArray typedArray, @NonNull final Class androidRes, @NonNull final List errors) throws NoSuchFieldException, IllegalAccessException { - AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_typeface"), false, - "remove typeface and use customTypeface"); - AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_textStyle"), false, - "remove textStyle and use customTypeface"); - AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_fontFamily"), false, - "remove fontFamily and use customTypeface"); AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_singleLine"), false, "remove singleLine and use isMultiline"); AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_includeFontPadding"), false, @@ -337,15 +324,6 @@ public class TypefacedEditText extends AppCompatEditText { super.setInputType(type); } - /** - * Sets typeface from 'assets/fonts' folder by name. - * - * @param name Full name of typeface (without extension, e.g. 'Roboto-Regular'). - */ - public void setTypeface(@NonNull final String name) { - setTypeface(Typefaces.getByName(getContext(), name)); - } - public void setOnTextChangedListener(@Nullable final OnTextChangedListener onTextChangedListener) { this.onTextChangedListener = onTextChangedListener; } diff --git a/src/main/java/ru/touchin/roboswag/components/views/TypefacedTextView.java b/src/main/java/ru/touchin/roboswag/components/views/TypefacedTextView.java index 715e77e..b8fddb2 100644 --- a/src/main/java/ru/touchin/roboswag/components/views/TypefacedTextView.java +++ b/src/main/java/ru/touchin/roboswag/components/views/TypefacedTextView.java @@ -34,7 +34,6 @@ import java.util.ArrayList; import java.util.List; import ru.touchin.roboswag.components.R; -import ru.touchin.roboswag.components.utils.Typefaces; import ru.touchin.roboswag.components.utils.UiUtils; import ru.touchin.roboswag.components.views.internal.AttributesUtils; import ru.touchin.roboswag.core.log.Lc; @@ -93,9 +92,6 @@ public class TypefacedTextView extends AppCompatTextView { } else { setLineStrategy(lineStrategy); } - if (!isInEditMode()) { - setTypeface(Typefaces.getFromAttributes(context, attrs, R.styleable.TypefacedTextView, R.styleable.TypefacedTextView_customTypeface)); - } typedArray.recycle(); if (inDebugMode) { checkAttributes(context, attrs); @@ -107,8 +103,6 @@ public class TypefacedTextView extends AppCompatTextView { final List errors = new ArrayList<>(); LineStrategy lineStrategy = null; TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.TypefacedTextView); - AttributesUtils.checkAttribute(typedArray, errors, R.styleable.TypefacedTextView_customTypeface, true, - "customTypeface required parameter"); AttributesUtils.checkAttribute(typedArray, errors, R.styleable.TypefacedTextView_lineStrategy, true, "lineStrategy required parameter"); if (typedArray.hasValue(R.styleable.TypefacedTextView_lineStrategy)) { @@ -311,15 +305,6 @@ public class TypefacedTextView extends AppCompatTextView { Lc.assertion(new IllegalStateException(AttributesUtils.viewError(this, "Do not specify ellipsize use setLineStrategy instead"))); } - /** - * Sets typeface from 'assets/fonts' folder by name. - * - * @param name Full name of typeface (without extension, e.g. 'Roboto-Regular'). - */ - public void setTypeface(@NonNull final String name) { - setTypeface(Typefaces.getByName(getContext(), name)); - } - @Override public void setText(@Nullable final CharSequence text, @Nullable final BufferType type) { super.setText(text, type); diff --git a/src/main/java/ru/touchin/roboswag/components/views/internal/AttributesUtils.java b/src/main/java/ru/touchin/roboswag/components/views/internal/AttributesUtils.java index e1e8bbe..6965e72 100644 --- a/src/main/java/ru/touchin/roboswag/components/views/internal/AttributesUtils.java +++ b/src/main/java/ru/touchin/roboswag/components/views/internal/AttributesUtils.java @@ -94,9 +94,7 @@ public final class AttributesUtils { public static void checkRegularTextViewAttributes(@NonNull final TypedArray typedArray, @NonNull final Class androidRes, @NonNull final Collection errors, @NonNull final String lineStrategyParameterName) throws NoSuchFieldException, IllegalAccessException { - checkAttribute(typedArray, errors, getField(androidRes, "TextView_typeface"), false, "remove typeface and use customTypeface"); - checkAttribute(typedArray, errors, getField(androidRes, "TextView_textStyle"), false, "remove textStyle and use customTypeface"); - checkAttribute(typedArray, errors, getField(androidRes, "TextView_fontFamily"), false, "remove fontFamily and use customTypeface"); + checkAttribute(typedArray, errors, getField(androidRes, "TextView_fontFamily"), true, "fontFamily required parameter"); checkAttribute(typedArray, errors, getField(androidRes, "TextView_includeFontPadding"), false, "includeFontPadding forbid parameter"); checkAttribute(typedArray, errors, getField(androidRes, "TextView_singleLine"), false, "remove singleLine and use " + lineStrategyParameterName); diff --git a/src/main/res/values/attrs.xml b/src/main/res/values/attrs.xml index 263a114..8e9c7e8 100644 --- a/src/main/res/values/attrs.xml +++ b/src/main/res/values/attrs.xml @@ -4,7 +4,6 @@ - @@ -17,7 +16,6 @@ - From 43ad3d62d97d18e8c452f54b80011514d1f97b1f Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Mon, 28 Aug 2017 13:20:55 +0300 Subject: [PATCH 32/95] Added generic in findViewById (#93) --- .../navigation/activities/ViewControllerActivity.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/activities/ViewControllerActivity.java b/src/main/java/ru/touchin/roboswag/components/navigation/activities/ViewControllerActivity.java index b49d1a8..8af5111 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/activities/ViewControllerActivity.java +++ b/src/main/java/ru/touchin/roboswag/components/navigation/activities/ViewControllerActivity.java @@ -103,8 +103,8 @@ public abstract class ViewControllerActivity extends BaseA @NonNull @Override - public View findViewById(@IdRes final int id) { - final View viewById = super.findViewById(id); + public T findViewById(@IdRes final int id) { + final T viewById = super.findViewById(id); if (viewById == null) { throw new ShouldNotHappenException("No view for id=" + getResources().getResourceName(id)); } From 6a6ea0ec08eb439f1aa0ff86a014debd36dcedf3 Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Thu, 7 Sep 2017 13:07:05 +0300 Subject: [PATCH 33/95] onActivityResult in viewControllers (#95) --- .../roboswag/components/navigation/ViewController.java | 8 ++++++++ .../navigation/fragments/ViewControllerFragment.java | 9 +++++++++ 2 files changed, 17 insertions(+) diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/ViewController.java b/src/main/java/ru/touchin/roboswag/components/navigation/ViewController.java index e34f1ec..a56268c 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/ViewController.java +++ b/src/main/java/ru/touchin/roboswag/components/navigation/ViewController.java @@ -19,6 +19,7 @@ package ru.touchin.roboswag.components.navigation; +import android.content.Intent; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.support.annotation.CallSuper; @@ -330,6 +331,13 @@ public class ViewController, destroyed = true; } + /** + * Callback from parent fragment. + */ + public void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) { + // Do nothing + } + /** * Similar to {@link ViewControllerFragment#onOptionsItemSelected(MenuItem)}. * diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java b/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java index 0a80e5f..5fb4273 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java +++ b/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java @@ -20,6 +20,7 @@ package ru.touchin.roboswag.components.navigation.fragments; import android.content.Context; +import android.content.Intent; import android.graphics.Canvas; import android.os.Bundle; import android.os.Parcel; @@ -381,6 +382,14 @@ public abstract class ViewControllerFragment Date: Fri, 22 Sep 2017 16:41:17 +0300 Subject: [PATCH 34/95] update --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 3d98ca5..286dba3 100644 --- a/build.gradle +++ b/build.gradle @@ -23,5 +23,5 @@ dependencies { provided 'com.android.support:recyclerview-v7:26.0.1' provided 'io.reactivex.rxjava2:rxandroid:2.0.1' - provided 'io.reactivex.rxjava2:rxjava:2.1.3' + provided 'io.reactivex.rxjava2:rxjava:2.1.4' } From 352ff5a8b4cb903ebad962d5db6d9785028b52d0 Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Wed, 4 Oct 2017 12:29:08 +0300 Subject: [PATCH 35/95] Versions in constants (#102) --- build.gradle | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/build.gradle b/build.gradle index 286dba3..9ac3bdf 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ apply plugin: 'me.tatarka.retrolambda' android { compileSdkVersion 26 - buildToolsVersion '26.0.1' + buildToolsVersion buildTools compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 @@ -18,10 +18,10 @@ android { dependencies { compile project(':libraries:core') - provided 'com.android.support:appcompat-v7:26.0.1' - provided 'com.android.support:design:26.0.1' - provided 'com.android.support:recyclerview-v7:26.0.1' + provided "com.android.support:appcompat-v7:$supportLibraryVersion" + provided "com.android.support:design:$supportLibraryVersion" + provided "com.android.support:recyclerview-v7:$supportLibraryVersion" - provided 'io.reactivex.rxjava2:rxandroid:2.0.1' - provided 'io.reactivex.rxjava2:rxjava:2.1.4' + provided "io.reactivex.rxjava2:rxandroid:$rxAndroidVersion" + provided "io.reactivex.rxjava2:rxjava:$rxJavaVersion" } From 426a213a444d6484ea510cf201eb04ced01b5af7 Mon Sep 17 00:00:00 2001 From: Arseniy Borisov Date: Mon, 13 Nov 2017 11:58:17 +0300 Subject: [PATCH 36/95] Updating by payload fixed (#105) --- .../components/adapters/ObservableCollectionAdapter.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/ObservableCollectionAdapter.java b/src/main/java/ru/touchin/roboswag/components/adapters/ObservableCollectionAdapter.java index c3d5498..f93b2b5 100644 --- a/src/main/java/ru/touchin/roboswag/components/adapters/ObservableCollectionAdapter.java +++ b/src/main/java/ru/touchin/roboswag/components/adapters/ObservableCollectionAdapter.java @@ -31,10 +31,9 @@ import java.util.Collections; import java.util.LinkedList; import java.util.List; +import io.reactivex.Observable; import io.reactivex.functions.BiConsumer; import io.reactivex.functions.Consumer; - -import io.reactivex.Observable; import io.reactivex.subjects.BehaviorSubject; import ru.touchin.roboswag.components.utils.LifecycleBindable; import ru.touchin.roboswag.components.utils.UiUtils; @@ -104,6 +103,11 @@ public abstract class ObservableCollectionAdapter { final ObservableCollection collection = optional.get(); + if (collection instanceof ObservableList) { + innerCollection.setDiffUtilsSource((ObservableList) collection); + } else { + innerCollection.setDiffUtilsSource(null); + } return collection != null ? collection.observeItems() : Observable.just(Collections.emptyList()); }), innerCollection::set); lifecycleBindable.untilDestroy(createMoreAutoLoadingObservable()); From a3dca51488c1cf9f69644f4dfebff05461ea69fc Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Thu, 30 Nov 2017 18:02:23 +0300 Subject: [PATCH 37/95] Gradle update --- build.gradle | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/build.gradle b/build.gradle index 9ac3bdf..c730fc1 100644 --- a/build.gradle +++ b/build.gradle @@ -1,9 +1,7 @@ apply plugin: 'com.android.library' -apply plugin: 'me.tatarka.retrolambda' android { - compileSdkVersion 26 - buildToolsVersion buildTools + compileSdkVersion compileSdk compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 @@ -16,12 +14,12 @@ android { } dependencies { - compile project(':libraries:core') + api project(':libraries:core') - provided "com.android.support:appcompat-v7:$supportLibraryVersion" - provided "com.android.support:design:$supportLibraryVersion" - provided "com.android.support:recyclerview-v7:$supportLibraryVersion" + compileOnly "com.android.support:appcompat-v7:$supportLibraryVersion" + compileOnly "com.android.support:design:$supportLibraryVersion" + compileOnly "com.android.support:recyclerview-v7:$supportLibraryVersion" - provided "io.reactivex.rxjava2:rxandroid:$rxAndroidVersion" - provided "io.reactivex.rxjava2:rxjava:$rxJavaVersion" + compileOnly "io.reactivex.rxjava2:rxandroid:$rxAndroidVersion" + compileOnly "io.reactivex.rxjava2:rxjava:$rxJavaVersion" } From 8eaa50d891ab67457097fba3153d857cc0a03533 Mon Sep 17 00:00:00 2001 From: maxbach Date: Thu, 28 Dec 2017 15:17:42 +0300 Subject: [PATCH 38/95] Add to MaterialLoadingBar method setColor (#109) --- .../components/views/MaterialLoadingBar.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/main/java/ru/touchin/roboswag/components/views/MaterialLoadingBar.java b/src/main/java/ru/touchin/roboswag/components/views/MaterialLoadingBar.java index a951203..7c462bc 100644 --- a/src/main/java/ru/touchin/roboswag/components/views/MaterialLoadingBar.java +++ b/src/main/java/ru/touchin/roboswag/components/views/MaterialLoadingBar.java @@ -22,6 +22,8 @@ package ru.touchin.roboswag.components.views; import android.content.Context; import android.content.res.TypedArray; import android.os.Build; +import android.support.annotation.ColorInt; +import android.support.annotation.ColorRes; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v7.widget.AppCompatImageView; @@ -89,7 +91,7 @@ public class MaterialLoadingBar extends AppCompatImageView { typedArray.recycle(); progressDrawable = new MaterialProgressDrawable(context, size); - progressDrawable.setColor(color); + setColor(color); progressDrawable.setStrokeWidth(strokeWidth); setScaleType(ScaleType.CENTER); setImageDrawable(progressDrawable); @@ -107,4 +109,13 @@ public class MaterialLoadingBar extends AppCompatImageView { super.onDetachedFromWindow(); } + /** + * Set color of loader. + * + * @param colorInt Color of loader to be set. + */ + public void setColor(@ColorInt final int colorInt) { + progressDrawable.setColor(colorInt); + } + } \ No newline at end of file From 44af99f9cfbab724478ec24a66c0b8c5f9670870 Mon Sep 17 00:00:00 2001 From: maxbach Date: Thu, 28 Dec 2017 16:32:09 +0300 Subject: [PATCH 39/95] Fix import (#112) --- .../ru/touchin/roboswag/components/views/MaterialLoadingBar.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/ru/touchin/roboswag/components/views/MaterialLoadingBar.java b/src/main/java/ru/touchin/roboswag/components/views/MaterialLoadingBar.java index 7c462bc..9ee131d 100644 --- a/src/main/java/ru/touchin/roboswag/components/views/MaterialLoadingBar.java +++ b/src/main/java/ru/touchin/roboswag/components/views/MaterialLoadingBar.java @@ -23,7 +23,6 @@ import android.content.Context; import android.content.res.TypedArray; import android.os.Build; import android.support.annotation.ColorInt; -import android.support.annotation.ColorRes; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v7.widget.AppCompatImageView; From 9b1275ceec94b80b4134c7dfa4c8086a7b91048f Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Wed, 14 Mar 2018 15:44:17 +0300 Subject: [PATCH 40/95] Migration to AAC lifecycle, simplification of some logic --- build.gradle | 3 + .../components/adapters/AdapterDelegate.java | 213 +----- .../adapters/BindableViewHolder.java | 307 -------- .../components/adapters/BindableViewHolder.kt | 34 + .../adapters/ItemAdapterDelegate.java | 28 +- .../adapters/ObservableCollectionAdapter.java | 697 ------------------ .../adapters/PositionAdapterDelegate.java | 7 +- .../components/navigation/AbstractState.java | 52 -- .../components/navigation/ViewController.java | 322 +------- .../navigation/ViewControllerNavigation.java | 504 ------------- .../navigation/ViewControllerNavigation.kt | 174 +++++ .../navigation/activities/BaseActivity.java | 265 +------ .../activities/ViewControllerActivity.java | 114 --- .../SimpleViewControllerFragment.java | 84 --- ...atelessTargetedViewControllerFragment.java | 66 -- .../StatelessViewControllerFragment.java | 64 -- .../TargetedViewControllerFragment.java | 55 -- .../fragments/ViewControllerFragment.java | 196 ++--- .../navigation/fragments/ViewFragment.java | 41 +- .../utils/BaseLifecycleBindable.java | 336 --------- .../components/utils/LifecycleBindable.java | 391 ---------- .../roboswag/components/utils/Logic.java | 110 --- .../roboswag/components/utils/UiUtils.java | 39 +- .../utils/destroyable/BaseDestroyable.kt | 62 ++ .../utils/destroyable/Destroyable.kt | 94 +++ .../views/AspectRatioFrameLayout.java | 254 ------- .../components/views/LifecycleView.java | 325 -------- src/main/res/values/attrs.xml | 7 +- 28 files changed, 493 insertions(+), 4351 deletions(-) delete mode 100644 src/main/java/ru/touchin/roboswag/components/adapters/BindableViewHolder.java create mode 100644 src/main/java/ru/touchin/roboswag/components/adapters/BindableViewHolder.kt delete mode 100644 src/main/java/ru/touchin/roboswag/components/adapters/ObservableCollectionAdapter.java delete mode 100644 src/main/java/ru/touchin/roboswag/components/navigation/AbstractState.java delete mode 100644 src/main/java/ru/touchin/roboswag/components/navigation/ViewControllerNavigation.java create mode 100644 src/main/java/ru/touchin/roboswag/components/navigation/ViewControllerNavigation.kt delete mode 100644 src/main/java/ru/touchin/roboswag/components/navigation/activities/ViewControllerActivity.java delete mode 100644 src/main/java/ru/touchin/roboswag/components/navigation/fragments/SimpleViewControllerFragment.java delete mode 100644 src/main/java/ru/touchin/roboswag/components/navigation/fragments/StatelessTargetedViewControllerFragment.java delete mode 100644 src/main/java/ru/touchin/roboswag/components/navigation/fragments/StatelessViewControllerFragment.java delete mode 100644 src/main/java/ru/touchin/roboswag/components/navigation/fragments/TargetedViewControllerFragment.java delete mode 100644 src/main/java/ru/touchin/roboswag/components/utils/BaseLifecycleBindable.java delete mode 100644 src/main/java/ru/touchin/roboswag/components/utils/LifecycleBindable.java delete mode 100644 src/main/java/ru/touchin/roboswag/components/utils/Logic.java create mode 100644 src/main/java/ru/touchin/roboswag/components/utils/destroyable/BaseDestroyable.kt create mode 100644 src/main/java/ru/touchin/roboswag/components/utils/destroyable/Destroyable.kt delete mode 100644 src/main/java/ru/touchin/roboswag/components/views/AspectRatioFrameLayout.java delete mode 100644 src/main/java/ru/touchin/roboswag/components/views/LifecycleView.java diff --git a/build.gradle b/build.gradle index c730fc1..0a7ccae 100644 --- a/build.gradle +++ b/build.gradle @@ -1,4 +1,5 @@ apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' android { compileSdkVersion compileSdk @@ -16,6 +17,8 @@ android { dependencies { api project(':libraries:core') + compileOnly "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + compileOnly "com.android.support:appcompat-v7:$supportLibraryVersion" compileOnly "com.android.support:design:$supportLibraryVersion" compileOnly "com.android.support:recyclerview-v7:$supportLibraryVersion" diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/AdapterDelegate.java b/src/main/java/ru/touchin/roboswag/components/adapters/AdapterDelegate.java index b9d3a0d..8b38563 100644 --- a/src/main/java/ru/touchin/roboswag/components/adapters/AdapterDelegate.java +++ b/src/main/java/ru/touchin/roboswag/components/adapters/AdapterDelegate.java @@ -19,46 +19,35 @@ package ru.touchin.roboswag.components.adapters; +import android.arch.lifecycle.LifecycleOwner; import android.support.annotation.NonNull; +import android.support.v4.view.ViewCompat; import android.view.ViewGroup; -import io.reactivex.Completable; -import io.reactivex.Maybe; -import io.reactivex.Observable; -import io.reactivex.Single; -import io.reactivex.disposables.Disposable; -import io.reactivex.functions.Action; -import io.reactivex.functions.Consumer; -import ru.touchin.roboswag.components.utils.LifecycleBindable; -import ru.touchin.roboswag.components.utils.UiUtils; - /** * Objects of such class controls creation and binding of specific type of RecyclerView's ViewHolders. * Default {@link #getItemViewType} is generating on construction of object. * * @param Type of {@link BindableViewHolder} of delegate. */ -@SuppressWarnings("PMD.TooManyMethods") -//TooManyMethods: it's ok -public abstract class AdapterDelegate implements LifecycleBindable { +public abstract class AdapterDelegate { + private final int defaultItemViewType = ViewCompat.generateViewId(); @NonNull - private final LifecycleBindable parentLifecycleBindable; - private final int defaultItemViewType; + private final LifecycleOwner lifecycleOwner; - public AdapterDelegate(@NonNull final LifecycleBindable parentLifecycleBindable) { - this.parentLifecycleBindable = parentLifecycleBindable; - this.defaultItemViewType = UiUtils.OfViews.generateViewId(); + public AdapterDelegate(@NonNull final LifecycleOwner lifecycleOwner) { + this.lifecycleOwner = lifecycleOwner; } /** - * Returns parent {@link LifecycleBindable} that this delegate created from (e.g. Activity or ViewController). + * Returns parent {@link LifecycleOwner} that this delegate created from (e.g. Activity, Fragment or ViewController). * - * @return Parent {@link LifecycleBindable}. + * @return Parent {@link LifecycleOwner}. */ @NonNull - public LifecycleBindable getParentLifecycleBindable() { - return parentLifecycleBindable; + public LifecycleOwner getLifecycleOwner() { + return lifecycleOwner; } /** @@ -79,184 +68,4 @@ public abstract class AdapterDelegate im @NonNull public abstract TViewHolder onCreateViewHolder(@NonNull final ViewGroup parent); - @SuppressWarnings("CPD-START") - //CPD: it is same as in other implementation based on BaseLifecycleBindable - @NonNull - @Override - public Disposable untilStop(@NonNull final Observable observable) { - return parentLifecycleBindable.untilStop(observable); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Observable observable, @NonNull final Consumer onNextAction) { - return parentLifecycleBindable.untilStop(observable, onNextAction); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Observable observable, - @NonNull final Consumer onNextAction, - @NonNull final Consumer onErrorAction) { - return parentLifecycleBindable.untilStop(observable, onNextAction, onErrorAction); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Observable observable, - @NonNull final Consumer onNextAction, - @NonNull final Consumer onErrorAction, - @NonNull final Action onCompletedAction) { - return parentLifecycleBindable.untilStop(observable, onNextAction, onErrorAction, onCompletedAction); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Single single) { - return parentLifecycleBindable.untilStop(single); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Single single, @NonNull final Consumer onSuccessAction) { - return parentLifecycleBindable.untilStop(single, onSuccessAction); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Single single, - @NonNull final Consumer onSuccessAction, - @NonNull final Consumer onErrorAction) { - return parentLifecycleBindable.untilStop(single, onSuccessAction, onErrorAction); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Completable completable) { - return parentLifecycleBindable.untilStop(completable); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Completable completable, @NonNull final Action onCompletedAction) { - return parentLifecycleBindable.untilStop(completable, onCompletedAction); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Completable completable, - @NonNull final Action onCompletedAction, - @NonNull final Consumer onErrorAction) { - return parentLifecycleBindable.untilStop(completable, onCompletedAction, onErrorAction); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Maybe maybe) { - return parentLifecycleBindable.untilStop(maybe); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Maybe maybe, @NonNull final Consumer onSuccessAction) { - return parentLifecycleBindable.untilStop(maybe, onSuccessAction); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Maybe maybe, - @NonNull final Consumer onSuccessAction, - @NonNull final Consumer onErrorAction) { - return parentLifecycleBindable.untilStop(maybe, onSuccessAction, onErrorAction); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Observable observable) { - return parentLifecycleBindable.untilDestroy(observable); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Observable observable, @NonNull final Consumer onNextAction) { - return parentLifecycleBindable.untilDestroy(observable, onNextAction); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Observable observable, - @NonNull final Consumer onNextAction, - @NonNull final Consumer onErrorAction) { - return parentLifecycleBindable.untilDestroy(observable, onNextAction, onErrorAction); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Observable observable, - @NonNull final Consumer onNextAction, - @NonNull final Consumer onErrorAction, - @NonNull final Action onCompletedAction) { - return parentLifecycleBindable.untilDestroy(observable, onNextAction, onErrorAction, onCompletedAction); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Single single) { - return parentLifecycleBindable.untilDestroy(single); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Single single, @NonNull final Consumer onSuccessAction) { - return parentLifecycleBindable.untilDestroy(single, onSuccessAction); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Single single, - @NonNull final Consumer onSuccessAction, - @NonNull final Consumer onErrorAction) { - return parentLifecycleBindable.untilDestroy(single, onSuccessAction, onErrorAction); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Completable completable) { - return parentLifecycleBindable.untilDestroy(completable); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Completable completable, @NonNull final Action onCompletedAction) { - return parentLifecycleBindable.untilDestroy(completable, onCompletedAction); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Completable completable, - @NonNull final Action onCompletedAction, - @NonNull final Consumer onErrorAction) { - return parentLifecycleBindable.untilDestroy(completable, onCompletedAction, onErrorAction); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Maybe maybe) { - return parentLifecycleBindable.untilDestroy(maybe); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Maybe maybe, @NonNull final Consumer onSuccessAction) { - return parentLifecycleBindable.untilDestroy(maybe, onSuccessAction); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Maybe maybe, - @NonNull final Consumer onSuccessAction, - @NonNull final Consumer onErrorAction) { - return parentLifecycleBindable.untilDestroy(maybe, onSuccessAction, onErrorAction); - } - } diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/BindableViewHolder.java b/src/main/java/ru/touchin/roboswag/components/adapters/BindableViewHolder.java deleted file mode 100644 index 3ca1fe0..0000000 --- a/src/main/java/ru/touchin/roboswag/components/adapters/BindableViewHolder.java +++ /dev/null @@ -1,307 +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.roboswag.components.adapters; - -import android.graphics.drawable.Drawable; -import android.support.annotation.ColorInt; -import android.support.annotation.ColorRes; -import android.support.annotation.DrawableRes; -import android.support.annotation.IdRes; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.annotation.StringRes; -import android.support.v4.content.ContextCompat; -import android.support.v7.widget.RecyclerView; -import android.view.View; - -import io.reactivex.Completable; -import io.reactivex.Maybe; -import io.reactivex.Observable; -import io.reactivex.Single; -import io.reactivex.disposables.Disposable; -import io.reactivex.functions.Action; -import io.reactivex.functions.Consumer; -import ru.touchin.roboswag.components.utils.LifecycleBindable; -import ru.touchin.roboswag.core.utils.ShouldNotHappenException; - -/** - * Created by Gavriil Sitnikov on 12/8/2016. - * ViewHolder that implements {@link LifecycleBindable} and uses parent bindable object as bridge (Activity, ViewController etc.). - */ -@SuppressWarnings("PMD.TooManyMethods") -public class BindableViewHolder extends RecyclerView.ViewHolder implements LifecycleBindable { - - @NonNull - private final LifecycleBindable baseLifecycleBindable; - - public BindableViewHolder(@NonNull final LifecycleBindable baseLifecycleBindable, @NonNull final View itemView) { - super(itemView); - this.baseLifecycleBindable = baseLifecycleBindable; - } - - /** - * Look for a child view with the given id. If this view has the given id, return this view. - * - * @param id The id to search for; - * @return The view that has the given id in the hierarchy. - */ - @NonNull - @SuppressWarnings("unchecked") - public T findViewById(@IdRes final int id) { - final T viewById = (T) itemView.findViewById(id); - if (viewById == null) { - throw new ShouldNotHappenException("No view for id=" + itemView.getResources().getResourceName(id)); - } - return viewById; - } - - /** - * Return the string value associated with a particular resource ID. It - * will be stripped of any styled text information. - * - * @param resId The resource id to search for data; - * @return String The string data associated with the resource. - */ - @NonNull - public String getString(@StringRes final int resId) { - return itemView.getResources().getString(resId); - } - - /** - * Return the string value associated with a particular resource ID. It - * will be stripped of any styled text information. - * - * @param resId The resource id to search for data; - * @param formatArgs The format arguments that will be used for substitution. - * @return String The string data associated with the resource. - */ - @NonNull - public String getString(@StringRes final int resId, @Nullable final Object... formatArgs) { - return itemView.getResources().getString(resId, formatArgs); - } - - /** - * Return the color value associated with a particular resource ID. - * Starting in {@link android.os.Build.VERSION_CODES#M}, the returned - * color will be styled for the specified Context's theme. - * - * @param resId The resource id to search for data; - * @return int A single color value in the form 0xAARRGGBB. - */ - @ColorInt - public int getColor(@ColorRes final int resId) { - return ContextCompat.getColor(itemView.getContext(), resId); - } - - /** - * Returns a drawable object associated with a particular resource ID. - * Starting in {@link android.os.Build.VERSION_CODES#LOLLIPOP}, the - * returned drawable will be styled for the specified Context's theme. - * - * @param resId The resource id to search for data; - * @return Drawable An object that can be used to draw this resource. - */ - @NonNull - public Drawable getDrawable(@DrawableRes final int resId) { - return ContextCompat.getDrawable(itemView.getContext(), resId); - } - - @SuppressWarnings("CPD-START") - //CPD: it's ok as it's LifecycleBindable - @NonNull - @Override - public Disposable untilStop(@NonNull final Observable observable) { - return baseLifecycleBindable.untilStop(observable); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Observable observable, @NonNull final Consumer onNextAction) { - return baseLifecycleBindable.untilStop(observable, onNextAction); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Observable observable, - @NonNull final Consumer onNextAction, - @NonNull final Consumer onErrorAction) { - return baseLifecycleBindable.untilStop(observable, onNextAction, onErrorAction); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Observable observable, - @NonNull final Consumer onNextAction, - @NonNull final Consumer onErrorAction, - @NonNull final Action onCompletedAction) { - return baseLifecycleBindable.untilStop(observable, onNextAction, onErrorAction, onCompletedAction); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Single single) { - return baseLifecycleBindable.untilStop(single); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Single single, @NonNull final Consumer onSuccessAction) { - return baseLifecycleBindable.untilStop(single, onSuccessAction); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Single single, - @NonNull final Consumer onSuccessAction, - @NonNull final Consumer onErrorAction) { - return baseLifecycleBindable.untilStop(single, onSuccessAction, onErrorAction); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Completable completable) { - return baseLifecycleBindable.untilStop(completable); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Completable completable, @NonNull final Action onCompletedAction) { - return baseLifecycleBindable.untilStop(completable, onCompletedAction); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Completable completable, - @NonNull final Action onCompletedAction, - @NonNull final Consumer onErrorAction) { - return baseLifecycleBindable.untilStop(completable, onCompletedAction, onErrorAction); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Maybe maybe) { - return baseLifecycleBindable.untilStop(maybe); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Maybe maybe, @NonNull final Consumer onSuccessAction) { - return baseLifecycleBindable.untilStop(maybe, onSuccessAction); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Maybe maybe, - @NonNull final Consumer onSuccessAction, - @NonNull final Consumer onErrorAction) { - return baseLifecycleBindable.untilStop(maybe, onSuccessAction, onErrorAction); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Observable observable) { - return baseLifecycleBindable.untilDestroy(observable); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Observable observable, @NonNull final Consumer onNextAction) { - return baseLifecycleBindable.untilDestroy(observable, onNextAction); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Observable observable, - @NonNull final Consumer onNextAction, - @NonNull final Consumer onErrorAction) { - return baseLifecycleBindable.untilDestroy(observable, onNextAction, onErrorAction); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Observable observable, - @NonNull final Consumer onNextAction, - @NonNull final Consumer onErrorAction, - @NonNull final Action onCompletedAction) { - return baseLifecycleBindable.untilDestroy(observable, onNextAction, onErrorAction, onCompletedAction); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Single single) { - return baseLifecycleBindable.untilDestroy(single); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Single single, @NonNull final Consumer onSuccessAction) { - return baseLifecycleBindable.untilDestroy(single, onSuccessAction); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Single single, - @NonNull final Consumer onSuccessAction, - @NonNull final Consumer onErrorAction) { - return baseLifecycleBindable.untilDestroy(single, onSuccessAction, onErrorAction); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Completable completable) { - return baseLifecycleBindable.untilDestroy(completable); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Completable completable, @NonNull final Action onCompletedAction) { - return baseLifecycleBindable.untilDestroy(completable, onCompletedAction); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Completable completable, - @NonNull final Action onCompletedAction, - @NonNull final Consumer onErrorAction) { - return baseLifecycleBindable.untilDestroy(completable, onCompletedAction, onErrorAction); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Maybe maybe) { - return baseLifecycleBindable.untilDestroy(maybe); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Maybe maybe, @NonNull final Consumer onSuccessAction) { - return baseLifecycleBindable.untilDestroy(maybe, onSuccessAction); - } - - @SuppressWarnings("CPD-END") - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Maybe maybe, - @NonNull final Consumer onSuccessAction, - @NonNull final Consumer onErrorAction) { - return baseLifecycleBindable.untilDestroy(maybe, onSuccessAction, onErrorAction); - } - -} diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/BindableViewHolder.kt b/src/main/java/ru/touchin/roboswag/components/adapters/BindableViewHolder.kt new file mode 100644 index 0000000..9222794 --- /dev/null +++ b/src/main/java/ru/touchin/roboswag/components/adapters/BindableViewHolder.kt @@ -0,0 +1,34 @@ +/* + * 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.roboswag.components.adapters + +import android.arch.lifecycle.LifecycleOwner +import android.support.v7.widget.RecyclerView +import android.view.View + +/** + * Created by Denis Karmyshakov 14.03.2018. + * ViewHolder that implements {@link LifecycleOwner} and uses parent lifecycle + * object as bridge ([android.app.Activity], [android.support.v4.app.Fragment] etc.). + */ +open class BindableViewHolder( + private val lifecycleOwner: LifecycleOwner, + itemView: View +) : RecyclerView.ViewHolder(itemView), LifecycleOwner by lifecycleOwner diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/ItemAdapterDelegate.java b/src/main/java/ru/touchin/roboswag/components/adapters/ItemAdapterDelegate.java index 033e944..a7f27d4 100644 --- a/src/main/java/ru/touchin/roboswag/components/adapters/ItemAdapterDelegate.java +++ b/src/main/java/ru/touchin/roboswag/components/adapters/ItemAdapterDelegate.java @@ -1,24 +1,23 @@ package ru.touchin.roboswag.components.adapters; +import android.arch.lifecycle.LifecycleOwner; import android.support.annotation.NonNull; import android.view.ViewGroup; import java.util.List; -import ru.touchin.roboswag.components.utils.LifecycleBindable; - /** * Objects of such class controls creation and binding of specific type of RecyclerView's ViewHolders. * Such delegates are creating and binding ViewHolders for specific items. * Default {@link #getItemViewType} is generating on construction of object. * - * @param Type of {@link BindableViewHolder} of delegate; - * @param Type of items to bind to {@link BindableViewHolder}s. + * @param Type of {@link LifecycleOwner} of delegate; + * @param Type of items to bind to {@link LifecycleOwner}s. */ public abstract class ItemAdapterDelegate extends AdapterDelegate { - public ItemAdapterDelegate(@NonNull final LifecycleBindable parentLifecycleBindable) { - super(parentLifecycleBindable); + public ItemAdapterDelegate(@NonNull final LifecycleOwner lifecycleOwner) { + super(lifecycleOwner); } /** @@ -61,8 +60,12 @@ public abstract class ItemAdapterDelegate payloads, - final int positionInAdapter, final int positionInCollection) { + public void onBindViewHolder( + @NonNull final TViewHolder holder, + @NonNull final TItem item, + @NonNull final List payloads, + final int positionInAdapter, + final int positionInCollection + ) { //do nothing by default } diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/ObservableCollectionAdapter.java b/src/main/java/ru/touchin/roboswag/components/adapters/ObservableCollectionAdapter.java deleted file mode 100644 index f93b2b5..0000000 --- a/src/main/java/ru/touchin/roboswag/components/adapters/ObservableCollectionAdapter.java +++ /dev/null @@ -1,697 +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.roboswag.components.adapters; - -import android.os.Looper; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v7.widget.RecyclerView; -import android.view.ViewGroup; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; - -import io.reactivex.Observable; -import io.reactivex.functions.BiConsumer; -import io.reactivex.functions.Consumer; -import io.reactivex.subjects.BehaviorSubject; -import ru.touchin.roboswag.components.utils.LifecycleBindable; -import ru.touchin.roboswag.components.utils.UiUtils; -import ru.touchin.roboswag.core.log.Lc; -import ru.touchin.roboswag.core.observables.collections.ObservableCollection; -import ru.touchin.roboswag.core.observables.collections.ObservableList; -import ru.touchin.roboswag.core.observables.collections.changes.Change; -import ru.touchin.roboswag.core.observables.collections.changes.ChangePayloadProducer; -import ru.touchin.roboswag.core.observables.collections.changes.CollectionChanges; -import ru.touchin.roboswag.core.observables.collections.changes.SameItemsPredicate; -import ru.touchin.roboswag.core.observables.collections.loadable.LoadingMoreList; -import ru.touchin.roboswag.core.utils.Optional; -import ru.touchin.roboswag.core.utils.ShouldNotHappenException; - -/** - * Created by Gavriil Sitnikov on 20/11/2015. - * Adapter based on {@link ObservableCollection} and providing some useful features like: - * - item-based binding method; - * - delegates by {@link AdapterDelegate} over itemViewType logic; - * - item click listener setup by {@link #setOnItemClickListener(OnItemClickListener)}; - * - allows to inform about footers/headers by overriding base create/bind methods and {@link #getHeadersCount()} plus {@link #getFootersCount()}; - * - by default it is pre-loading items for collections like {@link ru.touchin.roboswag.core.observables.collections.loadable.LoadingMoreList}. - * - * @param Type of items to bind to ViewHolders; - * @param Type of ViewHolders to show items. - */ -@SuppressWarnings({"unchecked", "PMD.TooManyMethods"}) -//TooManyMethods: it's ok -public abstract class ObservableCollectionAdapter - extends RecyclerView.Adapter { - - private static final int PRE_LOADING_COUNT = 20; - - private static boolean inDebugMode; - - /** - * Enables debugging features like checking concurrent delegates. - */ - public static void setInDebugMode() { - inDebugMode = true; - } - - @NonNull - private final BehaviorSubject>> observableCollectionSubject - = BehaviorSubject.createDefault(new Optional<>(null)); - @NonNull - private final BehaviorSubject moreAutoLoadingRequested = BehaviorSubject.create(); - @NonNull - private final LifecycleBindable lifecycleBindable; - @Nullable - private Object onItemClickListener; - private int lastUpdatedChangeNumber = -1; - - @NonNull - private final ObservableList innerCollection = new ObservableList<>(); - private boolean anyChangeApplied; - private long itemClickDelayMillis; - @NonNull - private final List attachedRecyclerViews = new LinkedList<>(); - @NonNull - private final List> delegates = new ArrayList<>(); - - public ObservableCollectionAdapter(@NonNull final LifecycleBindable lifecycleBindable) { - super(); - this.lifecycleBindable = lifecycleBindable; - lifecycleBindable.untilDestroy(innerCollection.observeChanges(), this::onItemsChanged); - lifecycleBindable.untilDestroy(observableCollectionSubject - .switchMap(optional -> { - final ObservableCollection collection = optional.get(); - if (collection instanceof ObservableList) { - innerCollection.setDiffUtilsSource((ObservableList) collection); - } else { - innerCollection.setDiffUtilsSource(null); - } - return collection != null ? collection.observeItems() : Observable.just(Collections.emptyList()); - }), innerCollection::set); - lifecycleBindable.untilDestroy(createMoreAutoLoadingObservable()); - } - - @NonNull - private Observable createMoreAutoLoadingObservable() { - return observableCollectionSubject - .switchMap(collectionOptional -> { - final ObservableCollection collection = collectionOptional.get(); - if (!(collection instanceof LoadingMoreList)) { - return Observable.empty(); - } - return moreAutoLoadingRequested - .distinctUntilChanged() - .switchMap(requested -> { - if (!requested) { - return Observable.empty(); - } - final int size = collection.size(); - return ((LoadingMoreList) collection) - .loadRange(size, size + PRE_LOADING_COUNT) - .onErrorReturnItem(new ArrayList<>()) - .toObservable() - .doOnComplete(() -> moreAutoLoadingRequested.onNext(false)); - }); - }); - } - - /** - * Returns if any change of source collection applied to adapter. - * It's important to not show some footers or headers before first change have applied. - * - * @return True id any change applied. - */ - public boolean isAnyChangeApplied() { - return anyChangeApplied; - } - - @Override - public void onAttachedToRecyclerView(@NonNull final RecyclerView recyclerView) { - super.onAttachedToRecyclerView(recyclerView); - attachedRecyclerViews.add(recyclerView); - } - - private boolean anyRecyclerViewShown() { - for (final RecyclerView recyclerView : attachedRecyclerViews) { - if (recyclerView.isShown()) { - return true; - } - } - return false; - } - - @Override - public void onDetachedFromRecyclerView(@NonNull final RecyclerView recyclerView) { - super.onDetachedFromRecyclerView(recyclerView); - attachedRecyclerViews.remove(recyclerView); - } - - /** - * Returns parent {@link LifecycleBindable} (Activity/ViewController etc.). - * - * @return Parent {@link LifecycleBindable}. - */ - @NonNull - public LifecycleBindable getLifecycleBindable() { - return lifecycleBindable; - } - - /** - * Returns {@link ObservableCollection} which provides items and it's changes. - * - * @return Inner {@link ObservableCollection}. - */ - @Nullable - public ObservableCollection getObservableCollection() { - return observableCollectionSubject.getValue().get(); - } - - /** - * Method to observe {@link ObservableCollection} which provides items and it's changes. - * - * @return Observable of inner {@link ObservableCollection}. - */ - @NonNull - public Observable>> observeObservableCollection() { - return observableCollectionSubject; - } - - /** - * Sets {@link ObservableCollection} which will provide items and it's changes. - * - * @param observableCollection Inner {@link ObservableCollection}. - */ - public void setObservableCollection(@Nullable final ObservableCollection observableCollection) { - this.observableCollectionSubject.onNext(new Optional<>(observableCollection)); - } - - /** - * Simply sets items. - * - * @param items Items to set. - */ - public void setItems(@NonNull final Collection items) { - setObservableCollection(new ObservableList<>(items)); - } - - /** - * Calls when collection changes. - * - * @param collectionChanges Changes of collection. - */ - protected void onItemsChanged(@NonNull final CollectionChanges collectionChanges) { - if (Looper.myLooper() != Looper.getMainLooper()) { - Lc.assertion("Items changes called on not main thread"); - return; - } - if (!anyChangeApplied || !anyRecyclerViewShown()) { - anyChangeApplied = true; - refreshUpdate(); - return; - } - if (collectionChanges.getNumber() != innerCollection.getChangesCount() - || collectionChanges.getNumber() != lastUpdatedChangeNumber + 1) { - if (lastUpdatedChangeNumber < collectionChanges.getNumber()) { - refreshUpdate(); - } - return; - } - notifyAboutChanges(collectionChanges.getChanges()); - lastUpdatedChangeNumber = innerCollection.getChangesCount(); - } - - private void refreshUpdate() { - notifyDataSetChanged(); - lastUpdatedChangeNumber = innerCollection.getChangesCount(); - } - - private void notifyAboutChanges(@NonNull final Collection changes) { - for (final Change change : changes) { - if (change instanceof Change.Inserted) { - final Change.Inserted castedChange = (Change.Inserted) change; - notifyItemRangeInserted(castedChange.getPosition() + getHeadersCount(), castedChange.getCount()); - } else if (change instanceof Change.Removed) { - if (getItemCount() - getHeadersCount() == 0) { - //TODO: bug of recyclerview? - notifyDataSetChanged(); - } else { - final Change.Removed castedChange = (Change.Removed) change; - notifyItemRangeRemoved(castedChange.getPosition() + getHeadersCount(), castedChange.getCount()); - } - } else if (change instanceof Change.Moved) { - final Change.Moved castedChange = (Change.Moved) change; - notifyItemMoved(castedChange.getFromPosition() + getHeadersCount(), castedChange.getToPosition() + getHeadersCount()); - } else if (change instanceof Change.Changed) { - final Change.Changed castedChange = (Change.Changed) change; - notifyItemRangeChanged( - castedChange.getPosition() + getHeadersCount(), - castedChange.getCount(), - castedChange.getPayload()); - } else { - Lc.assertion("Not supported " + change); - } - } - } - - /** - * Returns headers count goes before items. - * - * @return Headers count. - */ - protected int getHeadersCount() { - return 0; - } - - /** - * Returns footers count goes after items and headers. - * - * @return Footers count. - */ - protected int getFootersCount() { - return 0; - } - - /** - * Returns list of added delegates. - * - * @return List of {@link AdapterDelegate}. - */ - @NonNull - public List> getDelegates() { - return Collections.unmodifiableList(delegates); - } - - /** - * Adds {@link ItemAdapterDelegate} to adapter. - * - * @param delegate Delegate to add. - */ - public void addDelegate(@NonNull final ItemAdapterDelegate delegate) { - addDelegateInternal(delegate); - } - - /** - * Adds {@link PositionAdapterDelegate} to adapter. - * - * @param delegate Delegate to add. - */ - public void addDelegate(@NonNull final PositionAdapterDelegate delegate) { - addDelegateInternal(delegate); - } - - private void addDelegateInternal(@NonNull final AdapterDelegate delegate) { - if (inDebugMode) { - for (final AdapterDelegate addedDelegate : delegates) { - if (addedDelegate.getItemViewType() == delegate.getItemViewType()) { - Lc.assertion("AdapterDelegate with viewType=" + delegate.getItemViewType() + " already added"); - return; - } - } - } - delegates.add(delegate); - notifyDataSetChanged(); - } - - /** - * Removes {@link AdapterDelegate} from adapter. - * - * @param delegate Delegate to remove. - */ - public void removeDelegate(@NonNull final AdapterDelegate delegate) { - delegates.remove(delegate); - notifyDataSetChanged(); - } - - private void checkDelegates(@Nullable final AdapterDelegate alreadyPickedDelegate, @NonNull final AdapterDelegate currentDelegate) { - if (alreadyPickedDelegate != null) { - throw new ShouldNotHappenException("Concurrent delegates: " + currentDelegate + " and " + alreadyPickedDelegate); - } - } - - private int getItemPositionInCollection(final int positionInAdapter) { - final int shiftedPosition = positionInAdapter - getHeadersCount(); - return shiftedPosition >= 0 && shiftedPosition < innerCollection.size() ? shiftedPosition : -1; - } - - @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.ModifiedCyclomaticComplexity", "PMD.StdCyclomaticComplexity", "PMD.NPathComplexity"}) - //Complexity: because of debug code - @Override - public int getItemViewType(final int positionInAdapter) { - AdapterDelegate delegateOfViewType = null; - final int positionInCollection = getItemPositionInCollection(positionInAdapter); - final TItem item = positionInCollection >= 0 ? innerCollection.get(positionInCollection) : null; - for (final AdapterDelegate delegate : delegates) { - if (delegate instanceof ItemAdapterDelegate) { - if (item != null && ((ItemAdapterDelegate) delegate).isForViewType(item, positionInAdapter, positionInCollection)) { - checkDelegates(delegateOfViewType, delegate); - delegateOfViewType = delegate; - if (!inDebugMode) { - break; - } - } - } else if (delegate instanceof PositionAdapterDelegate) { - if (((PositionAdapterDelegate) delegate).isForViewType(positionInAdapter)) { - checkDelegates(delegateOfViewType, delegate); - delegateOfViewType = delegate; - if (!inDebugMode) { - break; - } - } - } else { - Lc.assertion("Delegate of type " + delegate.getClass()); - } - } - - return delegateOfViewType != null ? delegateOfViewType.getItemViewType() : super.getItemViewType(positionInAdapter); - } - - @Override - public long getItemId(final int positionInAdapter) { - final LongContainer result = new LongContainer(); - tryDelegateAction(positionInAdapter, - (itemAdapterDelegate, positionInCollection) -> - result.value = itemAdapterDelegate.getItemId(innerCollection.get(positionInCollection), - positionInAdapter, positionInCollection), - positionAdapterDelegate -> result.value = positionAdapterDelegate.getItemId(positionInAdapter), - (positionInCollection) -> result.value = super.getItemId(positionInAdapter)); - return result.value; - } - - @SuppressWarnings("PMD.CyclomaticComplexity") - private void tryDelegateAction(final int positionInAdapter, - @NonNull final BiConsumer itemAdapterDelegateAction, - @NonNull final Consumer positionAdapterDelegateAction, - @NonNull final Consumer defaultAction) { - final int viewType = getItemViewType(positionInAdapter); - final int positionInCollection = getItemPositionInCollection(positionInAdapter); - for (final AdapterDelegate delegate : delegates) { - if (delegate instanceof ItemAdapterDelegate) { - if (positionInCollection >= 0 && viewType == delegate.getItemViewType()) { - try { - itemAdapterDelegateAction.accept((ItemAdapterDelegate) delegate, positionInCollection); - } catch (final Exception exception) { - Lc.assertion(exception); - } - return; - } - } else if (delegate instanceof PositionAdapterDelegate) { - if (viewType == delegate.getItemViewType()) { - try { - positionAdapterDelegateAction.accept((PositionAdapterDelegate) delegate); - } catch (final Exception exception) { - Lc.assertion(exception); - } - return; - } - } else { - Lc.assertion("Delegate of type " + delegate.getClass()); - } - } - try { - defaultAction.accept(positionInCollection); - } catch (final Exception exception) { - Lc.assertion(exception); - } - } - - @Override - public int getItemCount() { - return getHeadersCount() + innerCollection.size() + getFootersCount(); - } - - @NonNull - @Override - public BindableViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { - for (final AdapterDelegate delegate : delegates) { - if (delegate.getItemViewType() == viewType) { - return delegate.onCreateViewHolder(parent); - } - } - throw new ShouldNotHappenException("Add some AdapterDelegate or override this method"); - } - - @Override - public void onBindViewHolder(@NonNull final BindableViewHolder holder, final int positionInAdapter) { - lastUpdatedChangeNumber = innerCollection.getChangesCount(); - - tryDelegateAction(positionInAdapter, - (itemAdapterDelegate, positionInCollection) -> { - bindItemViewHolder(itemAdapterDelegate, holder, innerCollection.get(positionInCollection), - null, positionInAdapter, positionInCollection); - updateMoreAutoLoadingRequest(positionInCollection); - }, - positionAdapterDelegate -> positionAdapterDelegate.onBindViewHolder(holder, positionInAdapter), - (positionInCollection) -> { - if (positionInCollection >= 0) { - bindItemViewHolder(null, holder, innerCollection.get(positionInCollection), null, positionInAdapter, positionInCollection); - } - }); - } - - @Override - public void onBindViewHolder(@NonNull final BindableViewHolder holder, final int positionInAdapter, @NonNull final List payloads) { - super.onBindViewHolder(holder, positionInAdapter, payloads); - tryDelegateAction(positionInAdapter, - (itemAdapterDelegate, positionInCollection) -> { - bindItemViewHolder(itemAdapterDelegate, holder, innerCollection.get(positionInCollection), - payloads, positionInAdapter, positionInCollection); - updateMoreAutoLoadingRequest(positionInCollection); - }, - positionAdapterDelegate -> positionAdapterDelegate.onBindViewHolder(holder, positionInAdapter), - (positionInCollection) -> { - if (positionInCollection >= 0) { - bindItemViewHolder(null, holder, innerCollection.get(positionInCollection), - payloads, positionInAdapter, positionInCollection); - } - }); - } - - private void bindItemViewHolder(@Nullable final ItemAdapterDelegate itemAdapterDelegate, - @NonNull final BindableViewHolder holder, @NonNull final TItem item, @Nullable final List payloads, - final int positionInAdapter, final int positionInCollection) { - final TItemViewHolder itemViewHolder; - try { - itemViewHolder = (TItemViewHolder) holder; - } catch (final ClassCastException exception) { - Lc.assertion(exception); - return; - } - updateClickListener(holder, item, positionInAdapter, positionInCollection); - if (itemAdapterDelegate != null) { - if (payloads == null) { - itemAdapterDelegate.onBindViewHolder(itemViewHolder, item, positionInAdapter, positionInCollection); - } else { - itemAdapterDelegate.onBindViewHolder(itemViewHolder, item, payloads, positionInAdapter, positionInCollection); - } - } else { - if (payloads == null) { - onBindItemToViewHolder(itemViewHolder, positionInAdapter, item); - } else { - onBindItemToViewHolder(itemViewHolder, positionInAdapter, item, payloads); - } - } - } - - private void updateClickListener(@NonNull final BindableViewHolder holder, @NonNull final TItem item, - final int positionInAdapter, final int positionInCollection) { - if (onItemClickListener != null && !isOnClickListenerDisabled(item, positionInAdapter, positionInCollection)) { - UiUtils.setOnRippleClickListener(holder.itemView, - () -> { - if (onItemClickListener instanceof OnItemClickListener) { - ((OnItemClickListener) onItemClickListener).onItemClicked(item); - } else if (onItemClickListener instanceof OnItemWithPositionClickListener) { - ((OnItemWithPositionClickListener) onItemClickListener).onItemClicked(item, positionInAdapter, positionInCollection); - } else { - Lc.assertion("Unexpected onItemClickListener type " + onItemClickListener); - } - }, - itemClickDelayMillis); - } - } - - private void updateMoreAutoLoadingRequest(final int positionInCollection) { - if (positionInCollection > innerCollection.size() - PRE_LOADING_COUNT) { - return; - } - moreAutoLoadingRequested.onNext(true); - } - - /** - * Method to bind item (from {@link #getObservableCollection()}) to item-specific ViewHolder. - * It is not calling for headers and footer which counts are returned by {@link #getHeadersCount()} and @link #getFootersCount()}. - * You don't need to override this method if you have delegates for every view type. - * - * @param holder ViewHolder to bind item to; - * @param positionInAdapter Position of ViewHolder (NOT item!); - * @param item Item returned by position (WITH HEADER OFFSET!). - */ - protected void onBindItemToViewHolder(@NonNull final TItemViewHolder holder, final int positionInAdapter, @NonNull final TItem item) { - // do nothing by default - let delegates do it - } - - /** - * Method to bind item (from {@link #getObservableCollection()}) to item-specific ViewHolder with payloads. - * It is not calling for headers and footer which counts are returned by {@link #getHeadersCount()} and @link #getFootersCount()}. - * - * @param holder ViewHolder to bind item to; - * @param positionInAdapter Position of ViewHolder in adapter (NOT item!); - * @param item Item returned by position (WITH HEADER OFFSET!); - * @param payloads Payloads. - */ - protected void onBindItemToViewHolder(@NonNull final TItemViewHolder holder, final int positionInAdapter, @NonNull final TItem item, - @NonNull final List payloads) { - // do nothing by default - let delegates do it - } - - @Nullable - public TItem getItem(final int positionInAdapter) { - final int positionInCollection = getItemPositionInCollection(positionInAdapter); - return positionInCollection >= 0 ? innerCollection.get(positionInCollection) : null; - } - - /** - * Sets item click listener. - * - * @param onItemClickListener Item click listener. - */ - public void setOnItemClickListener(@Nullable final OnItemClickListener onItemClickListener) { - this.setOnItemClickListener(onItemClickListener, UiUtils.RIPPLE_EFFECT_DELAY); - } - - /** - * Sets item click listener. - * - * @param onItemClickListener Item click listener; - * @param itemClickDelayMillis Delay of calling click listener. - */ - public void setOnItemClickListener(@Nullable final OnItemClickListener onItemClickListener, final long itemClickDelayMillis) { - this.onItemClickListener = onItemClickListener; - this.itemClickDelayMillis = itemClickDelayMillis; - refreshUpdate(); - } - - /** - * Sets item click listener. - * - * @param onItemClickListener Item click listener. - */ - public void setOnItemClickListener(@Nullable final OnItemWithPositionClickListener onItemClickListener) { - this.setOnItemClickListener(onItemClickListener, UiUtils.RIPPLE_EFFECT_DELAY); - } - - /** - * Sets item click listener. - * - * @param onItemClickListener Item click listener; - * @param itemClickDelayMillis Delay of calling click listener. - */ - public void setOnItemClickListener(@Nullable final OnItemWithPositionClickListener onItemClickListener, final long itemClickDelayMillis) { - this.onItemClickListener = onItemClickListener; - this.itemClickDelayMillis = itemClickDelayMillis; - refreshUpdate(); - } - - /** - * Returns if click listening disabled or not for specific item. - * - * @param item Item to check click availability; - * @param positionInAdapter Position of clicked item in adapter (with headers); - * @param positionInCollection Position of clicked item in inner collection; - * @return True if click listener enabled for such item. - */ - public boolean isOnClickListenerDisabled(@NonNull final TItem item, final int positionInAdapter, final int positionInCollection) { - return false; - } - - /** - * Enable diff utils algorithm in collection changes. - * - * @param detectMoves The flag that determines whether the {@link Change.Moved} changes will be generated or not; - * @param sameItemsPredicate Predicate for the determination of the same elements; - * @param changePayloadProducer Function that calculate change payload when items the same but contents are different. - */ - public void enableDiffUtils(final boolean detectMoves, - @NonNull final SameItemsPredicate sameItemsPredicate, - @Nullable final ChangePayloadProducer changePayloadProducer) { - innerCollection.enableDiffUtils(detectMoves, sameItemsPredicate, changePayloadProducer); - } - - /** - * Disable diff utils algorithm. - */ - public void disableDiffUtils() { - innerCollection.disableDiffUtils(); - } - - /** - * Returns enabled flag of diff utils. - * - * @return true if diff utils is enabled. - */ - public boolean diffUtilsIsEnabled() { - return innerCollection.diffUtilsIsEnabled(); - } - - /** - * Interface to simply add item click listener. - * - * @param Type of item - */ - public interface OnItemClickListener { - - /** - * Calls when item have clicked. - * - * @param item Clicked item. - */ - void onItemClicked(@NonNull TItem item); - - } - - /** - * Interface to simply add item click listener based on item position in adapter and collection. - * - * @param Type of item - */ - public interface OnItemWithPositionClickListener { - - /** - * Calls when item have clicked. - * - * @param item Clicked item; - * @param positionInAdapter Position of clicked item in adapter (with headers); - * @param positionInCollection Position of clicked item in inner collection. - */ - void onItemClicked(@NonNull TItem item, final int positionInAdapter, final int positionInCollection); - - } - - private class LongContainer { - - private long value; - - } - -} diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/PositionAdapterDelegate.java b/src/main/java/ru/touchin/roboswag/components/adapters/PositionAdapterDelegate.java index a3a9f9e..5fc5f4a 100644 --- a/src/main/java/ru/touchin/roboswag/components/adapters/PositionAdapterDelegate.java +++ b/src/main/java/ru/touchin/roboswag/components/adapters/PositionAdapterDelegate.java @@ -1,12 +1,11 @@ package ru.touchin.roboswag.components.adapters; +import android.arch.lifecycle.LifecycleOwner; import android.support.annotation.NonNull; import android.view.ViewGroup; import java.util.List; -import ru.touchin.roboswag.components.utils.LifecycleBindable; - /** * Objects of such class controls creation and binding of specific type of RecyclerView's ViewHolders. * Such delegates are creating and binding ViewHolders by position in adapter. @@ -16,8 +15,8 @@ import ru.touchin.roboswag.components.utils.LifecycleBindable; */ public abstract class PositionAdapterDelegate extends AdapterDelegate { - public PositionAdapterDelegate(@NonNull final LifecycleBindable parentLifecycleBindable) { - super(parentLifecycleBindable); + public PositionAdapterDelegate(@NonNull final LifecycleOwner lifecycleOwner) { + super(lifecycleOwner); } /** diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/AbstractState.java b/src/main/java/ru/touchin/roboswag/components/navigation/AbstractState.java deleted file mode 100644 index d747a73..0000000 --- a/src/main/java/ru/touchin/roboswag/components/navigation/AbstractState.java +++ /dev/null @@ -1,52 +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.roboswag.components.navigation; - -import android.os.Bundle; -import android.support.v4.app.Fragment; - -import java.io.Serializable; - -/** - * Created by Ilia Kurtov on 13/04/2016. - * Basic state of {@link ru.touchin.roboswag.components.navigation.fragments.ViewControllerFragment}. - * This object is saving as serializable in {@link android.os.Bundle} at {@link Fragment#onSaveInstanceState(Bundle)} point. - * Also this object is passing into {@link Fragment#getArguments()} on fragment instantiation. - * Do NOT store such object in fields outside of it's {@link ru.touchin.roboswag.components.navigation.fragments.ViewControllerFragment}: - * 1) it should be used as state of fragment but not state of other fragments or parts of logic; - * 2) if you want to modify such object then you should pass it's fragment as {@link Fragment#getTargetFragment()}; - * 3) if you are using {@link ViewControllerNavigation} then just use ***ForResult methods to pass target; - * 4) as it is serializable object then all initialization logic (like binding) should NOT be in constructor. Use {@link #onCreate()} method. - */ -@SuppressWarnings("PMD.AbstractClassWithoutAbstractMethod") -//AbstractClassWithoutAbstractMethod: objects of this class actually shouldn't exist -public abstract class AbstractState implements Serializable { - - private static final long serialVersionUID = 1L; - - /** - * Calls right after construction. All inner object's instantiation logic should be in this method. - * Do NOT do some instantiation logic in constructor except fields setup. - */ - public void onCreate() { - // do nothing - } - -} diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/ViewController.java b/src/main/java/ru/touchin/roboswag/components/navigation/ViewController.java index a56268c..a29f0a0 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/ViewController.java +++ b/src/main/java/ru/touchin/roboswag/components/navigation/ViewController.java @@ -19,39 +19,27 @@ package ru.touchin.roboswag.components.navigation; +import android.arch.lifecycle.Lifecycle; +import android.arch.lifecycle.LifecycleOwner; +import android.arch.lifecycle.LifecycleRegistry; import android.content.Intent; -import android.graphics.drawable.Drawable; import android.os.Bundle; import android.support.annotation.CallSuper; -import android.support.annotation.ColorInt; -import android.support.annotation.ColorRes; -import android.support.annotation.DrawableRes; import android.support.annotation.IdRes; import android.support.annotation.LayoutRes; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.support.annotation.StringRes; import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentActivity; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import io.reactivex.Completable; -import io.reactivex.Maybe; -import io.reactivex.Observable; -import io.reactivex.Single; -import io.reactivex.disposables.Disposable; -import io.reactivex.functions.Action; -import io.reactivex.functions.Consumer; -import ru.touchin.roboswag.components.navigation.activities.ViewControllerActivity; import ru.touchin.roboswag.components.navigation.fragments.ViewControllerFragment; -import ru.touchin.roboswag.components.utils.BaseLifecycleBindable; -import ru.touchin.roboswag.components.utils.LifecycleBindable; import ru.touchin.roboswag.components.utils.UiUtils; import ru.touchin.roboswag.core.log.Lc; -import ru.touchin.roboswag.core.utils.ShouldNotHappenException; /** * Created by Gavriil Sitnikov on 21/10/2015. @@ -60,20 +48,16 @@ import ru.touchin.roboswag.core.utils.ShouldNotHappenException; * @param Type of activity where such {@link ViewController} could be; * @param Type of fragment where such {@link ViewController} could be; */ -@SuppressWarnings({"PMD.TooManyMethods", "PMD.ExcessivePublicCount"}) -public class ViewController, - TFragment extends ViewControllerFragment> - implements LifecycleBindable { +public class ViewController> implements LifecycleOwner { + @NonNull + private final LifecycleRegistry lifecycleRegistry = new LifecycleRegistry(this); @NonNull private final TActivity activity; @NonNull private final TFragment fragment; @NonNull private final ViewGroup container; - @NonNull - private final BaseLifecycleBindable baseLifecycleBindable = new BaseLifecycleBindable(); - private boolean destroyed; @SuppressWarnings({"unchecked", "PMD.UnusedFormalParameter"}) //UnusedFormalParameter: savedInstanceState could be used by children @@ -83,6 +67,12 @@ public class ViewController, this.container = creationContext.container; } + @NonNull + @Override + public Lifecycle getLifecycle() { + return lifecycleRegistry; + } + /** * Returns activity where {@link ViewController} could be. * @@ -114,37 +104,6 @@ public class ViewController, return container; } - /** - * Returns if {@link ViewController} destroyed or not. - * - * @return True if it is destroyed. - */ - public final boolean isDestroyed() { - return destroyed; - } - - /** - * Return a localized string from the application's package's default string table. - * - * @param resId Resource id for the string - */ - @NonNull - public final String getString(@StringRes final int resId) { - return getActivity().getString(resId); - } - - /** - * Return a localized formatted string from the application's package's default string table, substituting the format arguments as defined in - * {@link java.util.Formatter} and {@link java.lang.String#format}. - * - * @param resId Resource id for the format string - * @param formatArgs The format arguments that will be used for substitution. - */ - @NonNull - public final String getString(@StringRes final int resId, @NonNull final Object... formatArgs) { - return getActivity().getString(resId, formatArgs); - } - /** * Set the view controller content from a layout resource. * This layout is placed directly into the container's ({@link #getContainer()}) view hierarchy. @@ -189,39 +148,8 @@ public class ViewController, * @return The view that has the given id in the hierarchy. */ @NonNull - @SuppressWarnings("unchecked") - public T findViewById(@IdRes final int id) { - final T viewById = (T) getContainer().findViewById(id); - if (viewById == null) { - throw new ShouldNotHappenException("No view for id=" + getActivity().getResources().getResourceName(id)); - } - return viewById; - } - - /** - * Return the color value associated with a particular resource ID. - * Starting in {@link android.os.Build.VERSION_CODES#M}, the returned - * color will be styled for the specified Context's theme. - * - * @param resId The resource id to search for data; - * @return int A single color value in the form 0xAARRGGBB. - */ - @ColorInt - public int getColor(@ColorRes final int resId) { - return getActivity().getColorCompat(resId); - } - - /** - * Returns a drawable object associated with a particular resource ID. - * Starting in {@link android.os.Build.VERSION_CODES#LOLLIPOP}, the - * returned drawable will be styled for the specified Context's theme. - * - * @param resId The resource id to search for data; - * @return Drawable An object that can be used to draw this resource. - */ - @NonNull - public Drawable getDrawable(@DrawableRes final int resId) { - return getActivity().getDrawableCompat(resId); + public final T findViewById(@IdRes final int id) { + return getContainer().findViewById(id); } /** @@ -231,28 +159,28 @@ public class ViewController, * @param menu The options menu in which you place your items; * @param inflater Helper to inflate menu items. */ - public void onConfigureNavigation(@NonNull final Menu menu, @NonNull final MenuInflater inflater) { + public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) { // do nothing } /** * Calls right after construction of {@link ViewController}. - * Happens at {@link ViewControllerFragment#onActivityCreated(View, ViewControllerActivity, Bundle)}. + * Happens at {@link ViewControllerFragment#onActivityCreated(Bundle)}. */ @CallSuper public void onCreate() { UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this)); - baseLifecycleBindable.onCreate(); + lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE); } /** * Calls when {@link ViewController} have started. - * Happens at {@link ViewControllerFragment#onStart(View, ViewControllerActivity)}. + * Happens at {@link ViewControllerFragment#onStart()}. */ @CallSuper public void onStart() { UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this)); - baseLifecycleBindable.onStart(); + lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START); } /** @@ -265,12 +193,12 @@ public class ViewController, /** * Calls when {@link ViewController} have resumed. - * Happens at {@link ViewControllerFragment#onResume(View, ViewControllerActivity)}. + * Happens at {@link ViewControllerFragment#onResume()}. */ @CallSuper public void onResume() { UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this)); - baseLifecycleBindable.onResume(); + lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME); } /** @@ -284,11 +212,12 @@ public class ViewController, /** * Calls when {@link ViewController} have paused. - * Happens at {@link ViewControllerFragment#onPause(View, ViewControllerActivity)}. + * Happens at {@link ViewControllerFragment#onPause()}. */ @CallSuper public void onPause() { UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this)); + lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE); } /** @@ -298,7 +227,6 @@ public class ViewController, */ @CallSuper public void onSaveInstanceState(@NonNull final Bundle savedInstanceState) { - baseLifecycleBindable.onSaveInstanceState(); UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this)); } @@ -312,30 +240,29 @@ public class ViewController, /** * Calls when {@link ViewController} have stopped. - * Happens at {@link ViewControllerFragment#onStop(View, ViewControllerActivity)}. + * Happens at {@link ViewControllerFragment#onStop()}. */ @CallSuper public void onStop() { UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this)); - baseLifecycleBindable.onStop(); + lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP); } /** * Calls when {@link ViewController} have destroyed. - * Happens usually at {@link ViewControllerFragment#onDestroyView(View)}. In some cases at {@link ViewControllerFragment#onDestroy()}. + * Happens usually at {@link ViewControllerFragment#onDestroyView()}. In some cases at {@link ViewControllerFragment#onDestroy()}. */ @CallSuper public void onDestroy() { UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this)); - baseLifecycleBindable.onDestroy(); - destroyed = true; + lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY); } /** * Callback from parent fragment. */ public void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) { - // Do nothing + UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this)); } /** @@ -348,200 +275,23 @@ public class ViewController, return false; } - @NonNull - @Override - public Disposable untilStop(@NonNull final Observable observable) { - return baseLifecycleBindable.untilStop(observable); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Observable observable, @NonNull final Consumer onNextAction) { - return baseLifecycleBindable.untilStop(observable, onNextAction); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Observable observable, - @NonNull final Consumer onNextAction, - @NonNull final Consumer onErrorAction) { - return baseLifecycleBindable.untilStop(observable, onNextAction, onErrorAction); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Observable observable, - @NonNull final Consumer onNextAction, - @NonNull final Consumer onErrorAction, - @NonNull final Action onCompletedAction) { - return baseLifecycleBindable.untilStop(observable, onNextAction, onErrorAction, onCompletedAction); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Single single) { - return baseLifecycleBindable.untilStop(single); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Single single, @NonNull final Consumer onSuccessAction) { - return baseLifecycleBindable.untilStop(single, onSuccessAction); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Single single, - @NonNull final Consumer onSuccessAction, - @NonNull final Consumer onErrorAction) { - return baseLifecycleBindable.untilStop(single, onSuccessAction, onErrorAction); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Completable completable) { - return baseLifecycleBindable.untilStop(completable); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Completable completable, @NonNull final Action onCompletedAction) { - return baseLifecycleBindable.untilStop(completable, onCompletedAction); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Completable completable, - @NonNull final Action onCompletedAction, - @NonNull final Consumer onErrorAction) { - return baseLifecycleBindable.untilStop(completable, onCompletedAction, onErrorAction); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Maybe maybe) { - return baseLifecycleBindable.untilStop(maybe); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Maybe maybe, @NonNull final Consumer onSuccessAction) { - return baseLifecycleBindable.untilStop(maybe, onSuccessAction); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Maybe maybe, - @NonNull final Consumer onSuccessAction, - @NonNull final Consumer onErrorAction) { - return baseLifecycleBindable.untilStop(maybe, onSuccessAction, onErrorAction); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Observable observable) { - return baseLifecycleBindable.untilDestroy(observable); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Observable observable, @NonNull final Consumer onNextAction) { - return baseLifecycleBindable.untilDestroy(observable, onNextAction); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Observable observable, - @NonNull final Consumer onNextAction, - @NonNull final Consumer onErrorAction) { - return baseLifecycleBindable.untilDestroy(observable, onNextAction, onErrorAction); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Observable observable, - @NonNull final Consumer onNextAction, - @NonNull final Consumer onErrorAction, - @NonNull final Action onCompletedAction) { - return baseLifecycleBindable.untilDestroy(observable, onNextAction, onErrorAction, onCompletedAction); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Single single) { - return baseLifecycleBindable.untilDestroy(single); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Single single, @NonNull final Consumer onSuccessAction) { - return baseLifecycleBindable.untilDestroy(single, onSuccessAction); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Single single, - @NonNull final Consumer onSuccessAction, - @NonNull final Consumer onErrorAction) { - return baseLifecycleBindable.untilDestroy(single, onSuccessAction, onErrorAction); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Completable completable) { - return baseLifecycleBindable.untilDestroy(completable); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Completable completable, @NonNull final Action onCompletedAction) { - return baseLifecycleBindable.untilDestroy(completable, onCompletedAction); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Completable completable, - @NonNull final Action onCompletedAction, - @NonNull final Consumer onErrorAction) { - return baseLifecycleBindable.untilDestroy(completable, onCompletedAction, onErrorAction); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Maybe maybe) { - return baseLifecycleBindable.untilDestroy(maybe); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Maybe maybe, @NonNull final Consumer onCompletedAction) { - return baseLifecycleBindable.untilDestroy(maybe, onCompletedAction); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Maybe maybe, - @NonNull final Consumer onCompletedAction, - @NonNull final Consumer onErrorAction) { - return baseLifecycleBindable.untilDestroy(maybe, onCompletedAction, onErrorAction); - } - - @SuppressWarnings("CPD-END") /* * Helper class to simplify constructor override. */ public static class CreationContext { @NonNull - private final ViewControllerActivity activity; + private final FragmentActivity activity; @NonNull private final ViewControllerFragment fragment; @NonNull private final ViewGroup container; - public CreationContext(@NonNull final ViewControllerActivity activity, - @NonNull final ViewControllerFragment fragment, - @NonNull final ViewGroup container) { + public CreationContext( + @NonNull final FragmentActivity activity, + @NonNull final ViewControllerFragment fragment, + @NonNull final ViewGroup container + ) { this.activity = activity; this.fragment = fragment; this.container = container; @@ -549,4 +299,4 @@ public class ViewController, } -} \ No newline at end of file +} diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/ViewControllerNavigation.java b/src/main/java/ru/touchin/roboswag/components/navigation/ViewControllerNavigation.java deleted file mode 100644 index ab43208..0000000 --- a/src/main/java/ru/touchin/roboswag/components/navigation/ViewControllerNavigation.java +++ /dev/null @@ -1,504 +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.roboswag.components.navigation; - -import android.content.Context; -import android.support.annotation.IdRes; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentTransaction; - -import io.reactivex.functions.Function; -import ru.touchin.roboswag.components.navigation.activities.ViewControllerActivity; -import ru.touchin.roboswag.components.navigation.fragments.SimpleViewControllerFragment; -import ru.touchin.roboswag.components.navigation.fragments.StatelessTargetedViewControllerFragment; -import ru.touchin.roboswag.components.navigation.fragments.StatelessViewControllerFragment; -import ru.touchin.roboswag.components.navigation.fragments.TargetedViewControllerFragment; -import ru.touchin.roboswag.components.navigation.fragments.ViewControllerFragment; - -/** - * Created by Gavriil Sitnikov on 07/03/2016. - * Navigation based on {@link ViewController}s which are creating by {@link Fragment}s. - * So basically it is just {@link FragmentNavigation} where most of fragments should be inherited from {@link ViewControllerFragment}. - * - * @param Type of activity where {@link ViewController}s should be showed. - */ -public class ViewControllerNavigation> extends FragmentNavigation { - - public ViewControllerNavigation(@NonNull final Context context, - @NonNull final FragmentManager fragmentManager, - @IdRes final int containerViewId) { - super(context, fragmentManager, containerViewId); - } - - /** - * Pushes {@link ViewControllerFragment} on top of stack. - * - * @param fragmentClass Class of {@link ViewControllerFragment} to instantiate; - * @param state Specific {@link AbstractState} of {@link ViewControllerFragment}; - * @param Type of state of fragment. - */ - public void push(@NonNull final Class> fragmentClass, - @NonNull final TState state) { - addToStack(fragmentClass, null, true, ViewControllerFragment.createState(state), null, null); - } - - /** - * Pushes {@link ViewControllerFragment} on top of stack with specific transaction setup. - * - * @param fragmentClass Class of {@link ViewControllerFragment} to instantiate; - * @param state Specific {@link AbstractState} of {@link ViewControllerFragment}; - * @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info; - * @param Type of state of fragment. - */ - public void push(@NonNull final Class> fragmentClass, - @Nullable final TState state, - @Nullable final Function transactionSetup) { - addToStack(fragmentClass, null, true, ViewControllerFragment.createState(state), null, transactionSetup); - } - - /** - * Pushes {@link ViewControllerFragment} on top of stack with specific target fragment. - * - * @param fragmentClass Class of {@link ViewControllerFragment} to instantiate; - * @param targetFragment Target fragment to be set as {@link Fragment#getTargetFragment()} of instantiated {@link Fragment}; - * @param state Specific {@link AbstractState} of {@link ViewControllerFragment}; - * @param Type of state of fragment. - */ - public void pushForResult(@NonNull final Class> fragmentClass, - @NonNull final Fragment targetFragment, - @NonNull final TState state) { - addToStack(fragmentClass, targetFragment, true, ViewControllerFragment.createState(state), - fragmentClass.getName() + ';' + WITH_TARGET_FRAGMENT_TAG_MARK, null); - } - - /** - * Pushes {@link ViewControllerFragment} on top of stack with specific target fragment and specific transaction setup. - * - * @param fragmentClass Class of {@link ViewControllerFragment} to instantiate; - * @param targetFragment Target fragment to be set as {@link Fragment#getTargetFragment()} of instantiated {@link Fragment}; - * @param state Specific {@link AbstractState} of {@link ViewControllerFragment}; - * @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info; - * @param Type of state of fragment. - */ - public void pushForResult(@NonNull final Class> fragmentClass, - @NonNull final Fragment targetFragment, - @Nullable final TState state, - @Nullable final Function transactionSetup) { - addToStack(fragmentClass, targetFragment, true, ViewControllerFragment.createState(state), - fragmentClass.getName() + ';' + WITH_TARGET_FRAGMENT_TAG_MARK, transactionSetup); - } - - /** - * Pushes {@link ViewControllerFragment} on top of stack with {@link #TOP_FRAGMENT_TAG_MARK} tag used for simple up/back navigation. - * - * @param fragmentClass Class of {@link ViewControllerFragment} to instantiate. - * @param state Specific {@link AbstractState} of {@link ViewControllerFragment}; - * @param Type of state of fragment. - */ - public void setAsTop(@NonNull final Class> fragmentClass, - @NonNull final TState state) { - setAsTop(fragmentClass, ViewControllerFragment.createState(state), null); - } - - /** - * Pushes {@link ViewControllerFragment} on top of stack with specific transaction setup - * and with {@link #TOP_FRAGMENT_TAG_MARK} tag used for simple up/back navigation. - * - * @param fragmentClass Class of {@link ViewControllerFragment} to instantiate. - * @param state Specific {@link AbstractState} of {@link ViewControllerFragment}; - * @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info; - * @param Type of state of fragment. - */ - public void setAsTop(@NonNull final Class> fragmentClass, - @Nullable final TState state, - @Nullable final Function transactionSetup) { - setAsTop(fragmentClass, ViewControllerFragment.createState(state), transactionSetup); - } - - /** - * Pops all {@link Fragment}s and places new initial {@link ViewControllerFragment} on top of stack. - * - * @param fragmentClass Class of {@link ViewControllerFragment} to instantiate; - * @param state Specific {@link AbstractState} of {@link ViewControllerFragment}; - * @param Type of state of fragment. - */ - public void setInitial(@NonNull final Class> fragmentClass, - @NonNull final TState state) { - setInitial(fragmentClass, ViewControllerFragment.createState(state), null); - } - - /** - * Pops all {@link Fragment}s and places new initial {@link ViewControllerFragment} on top of stack with specific transaction setup. - * - * @param fragmentClass Class of {@link ViewControllerFragment} to instantiate; - * @param state Specific {@link AbstractState} of {@link ViewControllerFragment}; - * @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info; - * @param Type of state of fragment. - */ - public void setInitial(@NonNull final Class> fragmentClass, - @Nullable final TState state, - @Nullable final Function transactionSetup) { - setInitial(fragmentClass, ViewControllerFragment.createState(state), transactionSetup); - } - - /** - * Pushes {@link ViewController} on top of stack. - * - * @param viewControllerClass Class of {@link ViewController} to be pushed. - */ - public void pushViewController(@NonNull final Class>> viewControllerClass) { - addStatelessViewControllerToStack(viewControllerClass, null, null, null); - } - - /** - * Pushes {@link ViewController} on top of stack with specific {@link ViewControllerFragment#getState()}. - * - * @param viewControllerClass Class of {@link ViewController} to be pushed; - * @param state {@link AbstractState} of {@link ViewController}'s fragment; - * @param Type of state of fragment. - */ - public void pushViewController(@NonNull final Class>> viewControllerClass, - @NonNull final TState state) { - addViewControllerToStack(viewControllerClass, null, state, null, null); - } - - /** - * Pushes {@link ViewController} on top of stack with specific transaction setup. - * - * @param viewControllerClass Class of {@link ViewController} to be pushed; - * @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info;. - */ - public void pushViewController( - @NonNull final Class>> viewControllerClass, - @Nullable final Function transactionSetup) { - addStatelessViewControllerToStack(viewControllerClass, null, null, transactionSetup); - } - - /** - * Pushes {@link ViewController} on top of stack with specific {@link ViewControllerFragment#getState()} and with specific transaction setup. - * - * @param viewControllerClass Class of {@link ViewController} to be pushed; - * @param state {@link AbstractState} of {@link ViewController}'s fragment; - * @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info; - * @param Type of state of fragment. - */ - public void pushViewController( - @NonNull final Class>> viewControllerClass, - @NonNull final TState state, - @Nullable final Function transactionSetup) { - addViewControllerToStack(viewControllerClass, null, state, null, transactionSetup); - } - - /** - * Pushes {@link ViewController} without adding to stack. - * - * @param viewControllerClass Class of {@link ViewController} to be pushed. - */ - public void pushSingleViewController(@NonNull final Class>> viewControllerClass) { - addToStack(StatelessViewControllerFragment.class, null, false, StatelessViewControllerFragment.createState(viewControllerClass), null, null); - } - - /** - * Pushes {@link ViewController} without adding to stack and with specific {@link ViewControllerFragment#getState()}. - * - * @param viewControllerClass Class of {@link ViewController} to be pushed; - * @param state {@link AbstractState} of {@link ViewController}'s fragment; - * @param Type of state of fragment. - */ - public void pushSingleViewController(@NonNull final Class>> viewControllerClass, @NonNull final TState state) { - addToStack(SimpleViewControllerFragment.class, null, false, SimpleViewControllerFragment.createState(viewControllerClass, state), null, null); - } - - /** - * Pushes {@link ViewController} on top of stack with specific {@link StatelessTargetedViewControllerFragment#getTarget()}. - * - * @param viewControllerClass Class of {@link ViewController} to be pushed; - * @param targetFragment {@link ViewControllerFragment} to be set as target; - * @param Type of state of target fragment. State is using to affect on that fragment; - * @param Type of target fragment. - */ - public > void pushViewControllerForResult( - @NonNull final Class>> viewControllerClass, - @NonNull final TTargetFragment targetFragment) { - addTargetedStatelessViewControllerToStack(viewControllerClass, targetFragment, - viewControllerClass.getName() + ';' + WITH_TARGET_FRAGMENT_TAG_MARK, null); - } - - /** - * Pushes {@link ViewController} on top of stack with specific {@link StatelessTargetedViewControllerFragment#getTarget()} and transaction setup. - * - * @param viewControllerClass Class of {@link ViewController} to be pushed; - * @param targetFragment {@link ViewControllerFragment} to be set as target; - * @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info; - * @param Type of state of target fragment. State is using to affect on that fragment; - * @param Type of target fragment. - */ - public > void pushViewControllerForResult( - @NonNull final Class>> viewControllerClass, - @NonNull final TTargetFragment targetFragment, - @Nullable final Function transactionSetup) { - addTargetedStatelessViewControllerToStack(viewControllerClass, targetFragment, - viewControllerClass.getName() + ';' + WITH_TARGET_FRAGMENT_TAG_MARK, transactionSetup); - } - - /** - * Pushes {@link ViewController} on top of stack with specific with specific {@link ViewControllerFragment#getState()} - * and with specific {@link TargetedViewControllerFragment#getTarget()}. - * - * @param viewControllerClass Class of {@link ViewController} to be pushed; - * @param targetFragment {@link ViewControllerFragment} to be set as target; - * @param state {@link AbstractState} of {@link ViewController}'s fragment; - * @param Type of state of fragment; - * @param Type of state of target fragment. State is using to affect on that fragment; - * @param Type of target fragment. - */ - @SuppressWarnings("CPD-START") - public > void pushViewControllerForResult( - @NonNull final Class>> viewControllerClass, - @NonNull final TTargetFragment targetFragment, - @NonNull final TState state) { - addTargetedViewControllerToStack(viewControllerClass, targetFragment, state, - viewControllerClass.getName() + ';' + WITH_TARGET_FRAGMENT_TAG_MARK, null); - } - - /** - * Pushes {@link ViewController} on top of stack with specific {@link ViewControllerFragment#getState()} - * and with specific {@link TargetedViewControllerFragment#getTarget()} and transaction setup. - * - * @param viewControllerClass Class of {@link ViewController} to be pushed; - * @param targetFragment {@link ViewControllerFragment} to be set as target; - * @param state {@link AbstractState} of {@link ViewController}'s fragment; - * @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info; - * @param Type of state of fragment; - * @param Type of state of target fragment. State is using to affect on that fragment; - * @param Type of target fragment. - */ - @SuppressWarnings("CPD-END") - public > void pushViewControllerForResult( - @NonNull final Class>> viewControllerClass, - @NonNull final TTargetFragment targetFragment, - @NonNull final TState state, - @Nullable final Function transactionSetup) { - addTargetedViewControllerToStack(viewControllerClass, targetFragment, state, - viewControllerClass.getName() + ';' + WITH_TARGET_FRAGMENT_TAG_MARK, transactionSetup); - } - - /** - * Pushes {@link ViewController} on top of stack with {@link #TOP_FRAGMENT_TAG_MARK} tag used for simple up/back navigation. - * - * @param viewControllerClass Class of {@link ViewController} to be pushed. - */ - public void setViewControllerAsTop( - @NonNull final Class>> viewControllerClass) { - addStatelessViewControllerToStack(viewControllerClass, null, viewControllerClass.getName() + ' ' + TOP_FRAGMENT_TAG_MARK, null); - } - - /** - * Pushes {@link ViewController} on top of stack with specific {@link ViewControllerFragment#getState()} - * and with {@link #TOP_FRAGMENT_TAG_MARK} tag used for simple up/back navigation. - * - * @param viewControllerClass Class of {@link ViewController} to be pushed; - * @param state {@link AbstractState} of {@link ViewController}'s fragment; - * @param Type of state of fragment. - */ - public void setViewControllerAsTop( - @NonNull final Class>> viewControllerClass, - @NonNull final TState state) { - addViewControllerToStack(viewControllerClass, null, state, viewControllerClass.getName() + ' ' + TOP_FRAGMENT_TAG_MARK, null); - } - - /** - * Pushes {@link ViewController} on top of stack with specific transaction setup - * and with {@link #TOP_FRAGMENT_TAG_MARK} tag used for simple up/back navigation. - * - * @param viewControllerClass Class of {@link ViewController} to be pushed; - * @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info. - */ - public void setViewControllerAsTop( - @NonNull final Class>> viewControllerClass, - @Nullable final Function transactionSetup) { - addStatelessViewControllerToStack(viewControllerClass, null, viewControllerClass.getName() + ' ' + TOP_FRAGMENT_TAG_MARK, transactionSetup); - } - - /** - * Pushes {@link ViewController} on top of stack with specific {@link ViewControllerFragment#getState()} and with specific transaction setup - * and with {@link #TOP_FRAGMENT_TAG_MARK} tag used for simple up/back navigation. - * - * @param viewControllerClass Class of {@link ViewController} to be pushed; - * @param state {@link AbstractState} of {@link ViewController}'s fragment; - * @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info; - * @param Type of state of fragment. - */ - public void setViewControllerAsTop( - @NonNull final Class>> viewControllerClass, - @NonNull final TState state, - @Nullable final Function transactionSetup) { - addViewControllerToStack(viewControllerClass, null, state, viewControllerClass.getName() + ' ' + TOP_FRAGMENT_TAG_MARK, transactionSetup); - } - - /** - * Pops all {@link Fragment}s and places new initial {@link ViewController} on top of stack. - * - * @param viewControllerClass Class of {@link ViewController} to be pushed; - */ - public void setInitialViewController( - @NonNull final Class>> viewControllerClass) { - beforeSetInitialActions(); - setViewControllerAsTop(viewControllerClass); - } - - /** - * Pops all {@link Fragment}s and places new initial {@link ViewController} on top of stack - * with specific {@link ViewControllerFragment#getState()}. - * - * @param viewControllerClass Class of {@link ViewController} to be pushed; - * @param state {@link AbstractState} of {@link ViewController}'s fragment; - * @param Type of state of fragment. - */ - public void setInitialViewController( - @NonNull final Class>> viewControllerClass, - @NonNull final TState state) { - setInitialViewController(viewControllerClass, state, null); - } - - /** - * Pops all {@link Fragment}s and places new initial {@link ViewController} on top of stack with specific transaction setup. - * - * @param viewControllerClass Class of {@link ViewController} to be pushed; - * @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info; - */ - public void setInitialViewController( - @NonNull final Class>> viewControllerClass, - @Nullable final Function transactionSetup) { - beforeSetInitialActions(); - setViewControllerAsTop(viewControllerClass, transactionSetup); - } - - /** - * Pops all {@link Fragment}s and places new initial {@link ViewController} on top of stack - * with specific {@link ViewControllerFragment#getState()} and specific transaction setup. - * - * @param viewControllerClass Class of {@link ViewController} to be pushed; - * @param state {@link AbstractState} of {@link ViewController}'s fragment; - * @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info; - * @param Type of state of fragment. - */ - public void setInitialViewController( - @NonNull final Class>> viewControllerClass, - @NonNull final TState state, - @Nullable final Function transactionSetup) { - beforeSetInitialActions(); - setViewControllerAsTop(viewControllerClass, state, transactionSetup); - } - - /** - * Base method to push stateless {@link ViewControllerFragment} to stack. - * - * @param viewControllerClass Class of {@link ViewController} to be pushed; - * @param targetFragment {@link ViewControllerFragment} to be set as target; - * @param backStackTag Tag of {@link ViewControllerFragment} in back stack; - * @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info. - */ - protected void addStatelessViewControllerToStack( - @NonNull final Class>> viewControllerClass, - @Nullable final Fragment targetFragment, - @Nullable final String backStackTag, - @Nullable final Function transactionSetup) { - addToStack(StatelessViewControllerFragment.class, targetFragment, true, - StatelessViewControllerFragment.createState(viewControllerClass), backStackTag, transactionSetup); - } - - /** - * Base method to push stateful {@link ViewControllerFragment} with target to stack. - * - * @param viewControllerClass Class of {@link ViewController} to be pushed; - * @param targetFragment {@link ViewControllerFragment} to be set as target; - * @param state {@link AbstractState} of {@link ViewController}'s fragment; - * @param backStackTag Tag of {@link ViewControllerFragment} in back stack; - * @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info; - * @param Type of state of fragment. - * @param Type of state of target fragment. State is using to affect on that fragment; - */ - protected void addTargetedViewControllerToStack( - @NonNull final Class>> viewControllerClass, - @NonNull final Fragment targetFragment, - @NonNull final TState state, - @Nullable final String backStackTag, - @Nullable final Function transactionSetup) { - addToStack(TargetedViewControllerFragment.class, targetFragment, true, - TargetedViewControllerFragment.createState(viewControllerClass, state), backStackTag, transactionSetup); - } - - /** - * Base method to push stateless {@link ViewControllerFragment} with target to stack. - * - * @param viewControllerClass Class of {@link ViewController} to be pushed; - * @param targetFragment {@link ViewControllerFragment} to be set as target; - * @param backStackTag Tag of {@link ViewControllerFragment} in back stack; - * @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info; - * @param Type of state of fragment. - */ - protected void addTargetedStatelessViewControllerToStack( - @NonNull final Class>> viewControllerClass, - @NonNull final Fragment targetFragment, - @Nullable final String backStackTag, - @Nullable final Function transactionSetup) { - addToStack(StatelessTargetedViewControllerFragment.class, targetFragment, true, - StatelessTargetedViewControllerFragment.createState(viewControllerClass), backStackTag, transactionSetup); - } - - /** - * Base method to push stateful {@link ViewControllerFragment} to stack. - * - * @param viewControllerClass Class of {@link ViewController} to be pushed; - * @param targetFragment {@link ViewControllerFragment} to be set as target; - * @param state {@link AbstractState} of {@link ViewController}'s fragment; - * @param backStackTag Tag of {@link ViewControllerFragment} in back stack; - * @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info; - * @param Type of state of fragment. - */ - protected void addViewControllerToStack( - @NonNull final Class>> viewControllerClass, - @Nullable final Fragment targetFragment, - @NonNull final TState state, - @Nullable final String backStackTag, - @Nullable final Function transactionSetup) { - addToStack(SimpleViewControllerFragment.class, targetFragment, true, - SimpleViewControllerFragment.createState(viewControllerClass, state), backStackTag, transactionSetup); - } - -} \ No newline at end of file diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/ViewControllerNavigation.kt b/src/main/java/ru/touchin/roboswag/components/navigation/ViewControllerNavigation.kt new file mode 100644 index 0000000..81efec4 --- /dev/null +++ b/src/main/java/ru/touchin/roboswag/components/navigation/ViewControllerNavigation.kt @@ -0,0 +1,174 @@ +/* + * 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.roboswag.components.navigation + +import android.content.Context +import android.os.Parcelable +import android.support.annotation.IdRes +import android.support.v4.app.Fragment +import android.support.v4.app.FragmentActivity +import android.support.v4.app.FragmentManager +import android.support.v4.app.FragmentTransaction + +import io.reactivex.functions.Function +import ru.touchin.roboswag.components.navigation.fragments.ViewControllerFragment + +/** + * Created by Gavriil Sitnikov on 07/03/2016. + * Navigation based on [ViewController]s which are creating by [Fragment]s. + * So basically it is just [FragmentNavigation] where most of fragments should be inherited from [ViewControllerFragment]. + * + * @param TActivity Type of activity where [ViewController]s should be showed. + */ +open class ViewControllerNavigation( + context: Context, + fragmentManager: FragmentManager, + @IdRes containerViewId: Int +) : FragmentNavigation(context, fragmentManager, containerViewId) { + + /** + * Pushes [ViewController] on top of stack with specific [ViewControllerFragment.getState] and with specific transaction setup. + * + * @param viewControllerClass Class of [ViewController] to be pushed; + * @param state [Parcelable] of [ViewController]'s fragment; + * @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info; + * @param TState Type of state of fragment. + */ + fun pushViewController( + viewControllerClass: Class>>, + state: TState, + transactionSetup: Function? = null + ) { + addViewControllerToStack(viewControllerClass, null, true, state, null, transactionSetup) + } + + /** + * Pushes [ViewController] without adding to stack and with specific [ViewControllerFragment.getState]. + * + * @param viewControllerClass Class of [ViewController] to be pushed; + * @param state [Parcelable] of [ViewController]'s fragment; + * @param TState Type of state of fragment. + */ + fun pushSingleViewController( + viewControllerClass: Class>>, + state: TState, + transactionSetup: Function? = null + ) { + addViewControllerToStack(viewControllerClass, null, false, state, null, transactionSetup) + } + + /** + * Pushes [ViewController] on top of stack with specific [ViewControllerFragment.getState] + * and with specific [TTargetFragment] and transaction setup. + * + * @param viewControllerClass Class of [ViewController] to be pushed; + * @param targetFragment [ViewControllerFragment] to be set as target; + * @param state [Parcelable] of [ViewController]'s fragment; + * @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info; + * @param TState Type of state of fragment; + * @param TTargetFragment Type of target fragment. + */ + fun pushViewControllerForResult( + viewControllerClass: Class>>, + state: TState, + targetFragment: TTargetFragment, + transactionSetup: Function? = null + ) { + addViewControllerToStack( + viewControllerClass, + targetFragment, + true, + state, + viewControllerClass.name + ';'.toString() + FragmentNavigation.WITH_TARGET_FRAGMENT_TAG_MARK, + transactionSetup + ) + } + + /** + * Pushes [ViewController] on top of stack with specific [ViewControllerFragment.getState] and with specific transaction setup + * and with [.TOP_FRAGMENT_TAG_MARK] tag used for simple up/back navigation. + * + * @param viewControllerClass Class of [ViewController] to be pushed; + * @param state [Parcelable] of [ViewController]'s fragment; + * @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info; + * @param TState Type of state of fragment. + */ + fun setViewControllerAsTop( + viewControllerClass: Class>>, + state: TState, + transactionSetup: Function? = null + ) { + addViewControllerToStack( + viewControllerClass, + null, + true, + state, + viewControllerClass.name + ';'.toString() + FragmentNavigation.TOP_FRAGMENT_TAG_MARK, + transactionSetup + ) + } + + /** + * Pops all [Fragment]s and places new initial [ViewController] on top of stack + * with specific [ViewControllerFragment.getState] and specific transaction setup. + * + * @param viewControllerClass Class of [ViewController] to be pushed; + * @param state [Parcelable] of [ViewController]'s fragment; + * @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info; + * @param TState Type of state of fragment. + */ + fun setInitialViewController( + viewControllerClass: Class>>, + state: TState, + transactionSetup: Function? = null + ) { + beforeSetInitialActions() + setViewControllerAsTop(viewControllerClass, state, transactionSetup) + } + + /** + * Base method to push stateful [ViewControllerFragment] to stack. + * + * @param viewControllerClass Class of [ViewController] to be pushed; + * @param targetFragment [ViewControllerFragment] to be set as target; + * @param state [Parcelable] of [ViewController]'s fragment; + * @param backStackTag Tag of [ViewControllerFragment] in back stack; + * @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info; + * @param TState Type of state of fragment. + */ + protected fun addViewControllerToStack( + viewControllerClass: Class>>, + targetFragment: Fragment?, + addToStack: Boolean, + state: TState, + backStackTag: String?, + transactionSetup: Function? + ) { + addToStack( + ViewControllerFragment::class.java, + targetFragment, + addToStack, + ViewControllerFragment.args(viewControllerClass, state), + backStackTag, + transactionSetup + ) + } + +} diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java b/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java index 611152c..762d926 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java +++ b/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java @@ -35,130 +35,52 @@ import android.view.inputmethod.InputMethodManager; import java.util.ArrayList; -import io.reactivex.Completable; -import io.reactivex.Maybe; -import io.reactivex.Observable; -import io.reactivex.Single; -import io.reactivex.disposables.Disposable; -import io.reactivex.functions.Action; -import io.reactivex.functions.Consumer; -import io.reactivex.subjects.BehaviorSubject; -import ru.touchin.roboswag.components.utils.BaseLifecycleBindable; -import ru.touchin.roboswag.components.utils.LifecycleBindable; import ru.touchin.roboswag.components.utils.UiUtils; import ru.touchin.roboswag.core.log.Lc; -import ru.touchin.roboswag.core.utils.Optional; -import ru.touchin.roboswag.core.utils.pairs.HalfNullablePair; /** * Created by Gavriil Sitnikov on 08/03/2016. * Base activity to use in components repository. */ -@SuppressWarnings("PMD.TooManyMethods") -public abstract class BaseActivity extends AppCompatActivity - implements LifecycleBindable { - - private static final String ACTIVITY_RESULT_CODE_EXTRA = "ACTIVITY_RESULT_CODE_EXTRA"; - private static final String ACTIVITY_RESULT_DATA_EXTRA = "ACTIVITY_RESULT_DATA_EXTRA"; +public abstract class BaseActivity extends AppCompatActivity { @NonNull private final ArrayList onBackPressedListeners = new ArrayList<>(); - @NonNull - private final BaseLifecycleBindable baseLifecycleBindable = new BaseLifecycleBindable(); - private boolean resumed; - - @NonNull - private final BehaviorSubject>> lastActivityResult - = BehaviorSubject.createDefault(new Optional>(null)); - - /** - * Returns if activity resumed. - * - * @return True if resumed. - */ - public boolean isActuallyResumed() { - return resumed; - } @Override protected void onCreate(@Nullable final Bundle savedInstanceState) { super.onCreate(savedInstanceState); UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this)); - baseLifecycleBindable.onCreate(); - restoreLastActivityResult(savedInstanceState); - } - - private void restoreLastActivityResult(@Nullable final Bundle savedInstanceState) { - if (savedInstanceState == null) { - return; - } - - lastActivityResult.onNext(new Optional<>(new HalfNullablePair<>(savedInstanceState.getInt(ACTIVITY_RESULT_CODE_EXTRA), - savedInstanceState.getParcelable(ACTIVITY_RESULT_DATA_EXTRA)))); } @Override protected void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) { super.onActivityResult(requestCode, resultCode, data); UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this) + " requestCode: " + requestCode + "; resultCode: " + resultCode); - if (resultCode == RESULT_OK) { - lastActivityResult.onNext(new Optional<>(new HalfNullablePair<>(requestCode, data))); - } - } - - /** - * Observes activity result by request code coming from {@link #onActivityResult(int, int, Intent)} - * - * @param requestCode Unique code to identify activity result; - * @return {@link Observable} which will emit data (Intents) from other activities (endlessly). - */ - @NonNull - public Observable observeActivityResult(final int requestCode) { - return lastActivityResult - .concatMap(optional -> { - final HalfNullablePair activityResult = optional.get(); - if (activityResult == null || activityResult.getFirst() != requestCode) { - return Observable.empty(); - } - return Observable.just(activityResult.getSecond() != null ? activityResult.getSecond() : new Intent()) - .doOnNext(result -> lastActivityResult.onNext(new Optional<>(null))); - }); } @Override protected void onStart() { super.onStart(); UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this)); - baseLifecycleBindable.onStart(); } @Override protected void onResume() { super.onResume(); UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this)); - resumed = true; - baseLifecycleBindable.onResume(); } @Override protected void onPause() { UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this)); - resumed = false; super.onPause(); } @Override protected void onSaveInstanceState(@NonNull final Bundle stateToSave) { super.onSaveInstanceState(stateToSave); - baseLifecycleBindable.onSaveInstanceState(); UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this)); - final HalfNullablePair activityResult = lastActivityResult.getValue().get(); - if (activityResult != null) { - stateToSave.putInt(ACTIVITY_RESULT_CODE_EXTRA, activityResult.getFirst()); - if (activityResult.getSecond() != null) { - stateToSave.putParcelable(ACTIVITY_RESULT_DATA_EXTRA, activityResult.getSecond()); - } - } } @Override @@ -170,14 +92,12 @@ public abstract class BaseActivity extends AppCompatActivity @Override protected void onStop() { UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this)); - baseLifecycleBindable.onStop(); super.onStop(); } @Override protected void onDestroy() { UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this)); - baseLifecycleBindable.onDestroy(); super.onDestroy(); } @@ -229,7 +149,7 @@ public abstract class BaseActivity extends AppCompatActivity * @param resId The resource id to search for data; * @return Drawable An object that can be used to draw this resource. */ - @NonNull + @Nullable public Drawable getDrawableCompat(@DrawableRes final int resId) { return ContextCompat.getDrawable(this, resId); } @@ -257,187 +177,6 @@ public abstract class BaseActivity extends AppCompatActivity } } - @SuppressWarnings("CPD-START") - //CPD: it's ok as it's LifecycleBindable - @NonNull - @Override - public Disposable untilStop(@NonNull final Observable observable) { - return baseLifecycleBindable.untilStop(observable); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Observable observable, @NonNull final Consumer onNextAction) { - return baseLifecycleBindable.untilStop(observable, onNextAction); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Observable observable, - @NonNull final Consumer onNextAction, - @NonNull final Consumer onErrorAction) { - return baseLifecycleBindable.untilStop(observable, onNextAction, onErrorAction); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Observable observable, - @NonNull final Consumer onNextAction, - @NonNull final Consumer onErrorAction, - @NonNull final Action onCompletedAction) { - return baseLifecycleBindable.untilStop(observable, onNextAction, onErrorAction, onCompletedAction); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Single single) { - return baseLifecycleBindable.untilStop(single); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Single single, @NonNull final Consumer onSuccessAction) { - return baseLifecycleBindable.untilStop(single, onSuccessAction); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Single single, - @NonNull final Consumer onSuccessAction, - @NonNull final Consumer onErrorAction) { - return baseLifecycleBindable.untilStop(single, onSuccessAction, onErrorAction); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Completable completable) { - return baseLifecycleBindable.untilStop(completable); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Completable completable, @NonNull final Action onCompletedAction) { - return baseLifecycleBindable.untilStop(completable, onCompletedAction); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Completable completable, - @NonNull final Action onCompletedAction, - @NonNull final Consumer onErrorAction) { - return baseLifecycleBindable.untilStop(completable, onCompletedAction, onErrorAction); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Maybe maybe) { - return baseLifecycleBindable.untilStop(maybe); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Maybe maybe, @NonNull final Consumer onSuccessAction) { - return baseLifecycleBindable.untilStop(maybe, onSuccessAction); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Maybe maybe, - @NonNull final Consumer onSuccessAction, - @NonNull final Consumer onErrorAction) { - return baseLifecycleBindable.untilStop(maybe, onSuccessAction, onErrorAction); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Observable observable) { - return baseLifecycleBindable.untilDestroy(observable); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Observable observable, @NonNull final Consumer onNextAction) { - return baseLifecycleBindable.untilDestroy(observable, onNextAction); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Observable observable, - @NonNull final Consumer onNextAction, - @NonNull final Consumer onErrorAction) { - return baseLifecycleBindable.untilDestroy(observable, onNextAction, onErrorAction); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Observable observable, - @NonNull final Consumer onNextAction, - @NonNull final Consumer onErrorAction, - @NonNull final Action onCompletedAction) { - return baseLifecycleBindable.untilDestroy(observable, onNextAction, onErrorAction, onCompletedAction); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Single single) { - return baseLifecycleBindable.untilDestroy(single); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Single single, @NonNull final Consumer onSuccessAction) { - return baseLifecycleBindable.untilDestroy(single, onSuccessAction); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Single single, - @NonNull final Consumer onSuccessAction, - @NonNull final Consumer onErrorAction) { - return baseLifecycleBindable.untilDestroy(single, onSuccessAction, onErrorAction); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Completable completable) { - return baseLifecycleBindable.untilDestroy(completable); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Completable completable, @NonNull final Action onCompletedAction) { - return baseLifecycleBindable.untilDestroy(completable, onCompletedAction); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Completable completable, - @NonNull final Action onCompletedAction, - @NonNull final Consumer onErrorAction) { - return baseLifecycleBindable.untilDestroy(completable, onCompletedAction, onErrorAction); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Maybe maybe) { - return baseLifecycleBindable.untilDestroy(maybe); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Maybe maybe, @NonNull final Consumer onCompletedAction) { - return baseLifecycleBindable.untilDestroy(maybe, onCompletedAction); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Maybe maybe, - @NonNull final Consumer onCompletedAction, - @NonNull final Consumer onErrorAction) { - return baseLifecycleBindable.untilDestroy(maybe, onCompletedAction, onErrorAction); - } - - @SuppressWarnings("CPD-END") /* * Interface to be implemented for someone who want to intercept device back button pressing event. */ diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/activities/ViewControllerActivity.java b/src/main/java/ru/touchin/roboswag/components/navigation/activities/ViewControllerActivity.java deleted file mode 100644 index 8af5111..0000000 --- a/src/main/java/ru/touchin/roboswag/components/navigation/activities/ViewControllerActivity.java +++ /dev/null @@ -1,114 +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.roboswag.components.navigation.activities; - -import android.support.annotation.IdRes; -import android.support.annotation.NonNull; -import android.view.Menu; -import android.view.View; - -import ru.touchin.roboswag.components.utils.Logic; -import ru.touchin.roboswag.core.utils.ShouldNotHappenException; - -/** - * Created by Gavriil Sitnikov on 07/03/2016. - * Activity which is containing specific {@link Logic} - * to support navigation based on {@link ru.touchin.roboswag.components.navigation.ViewController}s. - * - * @param Type of application's {@link Logic}. - */ -public abstract class ViewControllerActivity extends BaseActivity { - - //it is needed to hold strong reference to logic - private TLogic reference; - - /** - * It should return specific class where all logic will be. - * - * @return Returns class of specific {@link Logic}. - */ - @NonNull - protected abstract Class getLogicClass(); - - /** - * Returns (and creates if needed) application's logic. - * - * @return Object which represents application's logic. - */ - @NonNull - public TLogic getLogic() { - synchronized (ViewControllerActivity.class) { - if (reference == null) { - reference = Logic.getInstance(this, getLogicClass()); - } - } - return reference; - } - - @Override - @Deprecated - // use {@link #reconfigureNavigation} - public void invalidateOptionsMenu() { - super.invalidateOptionsMenu(); - } - - @Override - @Deprecated - // use {@link #reconfigureNavigation} - public void supportInvalidateOptionsMenu() { - super.supportInvalidateOptionsMenu(); - } - - /** - * Invalidates navigation and calls {@link #onConfigureNavigation} for all navigation elements. - */ - public void reconfigureNavigation() { - super.supportInvalidateOptionsMenu(); - } - - @Override - @Deprecated - // use {@link #onConfigureNavigation} - public boolean onCreateOptionsMenu(@NonNull final Menu menu) { - onConfigureNavigation(menu); - return super.onCreateOptionsMenu(menu); - } - - /** - * Calls when activity configuring ActionBar, Toolbar, Sidebar, AppBar etc. - * It is calling before it's {@link ru.touchin.roboswag.components.navigation.ViewController}'s. - * - * @param menu The options menu in which you place your menu items. - */ - public void onConfigureNavigation(@NonNull final Menu menu) { - // do nothing - } - - @NonNull - @Override - public T findViewById(@IdRes final int id) { - final T viewById = super.findViewById(id); - if (viewById == null) { - throw new ShouldNotHappenException("No view for id=" + getResources().getResourceName(id)); - } - return viewById; - } - -} diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/fragments/SimpleViewControllerFragment.java b/src/main/java/ru/touchin/roboswag/components/navigation/fragments/SimpleViewControllerFragment.java deleted file mode 100644 index 900804d..0000000 --- a/src/main/java/ru/touchin/roboswag/components/navigation/fragments/SimpleViewControllerFragment.java +++ /dev/null @@ -1,84 +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.roboswag.components.navigation.fragments; - -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import ru.touchin.roboswag.components.navigation.AbstractState; -import ru.touchin.roboswag.components.navigation.ViewController; -import ru.touchin.roboswag.components.navigation.activities.ViewControllerActivity; - -/** - * Created by Gavriil Sitnikov on 07/03/2016. - * Simple {@link ViewControllerFragment} which is using by {@link ru.touchin.roboswag.components.navigation.ViewControllerNavigation}. - * - * @param Type of object which is representing it's fragment state; - * @param Type of {@link ViewControllerActivity} where fragment could be attached to. - */ -public class SimpleViewControllerFragment> - extends ViewControllerFragment { - - private static final String VIEW_CONTROLLER_CLASS_EXTRA = "VIEW_CONTROLLER_CLASS_EXTRA"; - - /** - * Creates {@link Bundle} which will store state and {@link ViewController}'s class. - * - * @param viewControllerClass Class of {@link ViewController} which will be instantiated inside this fragment; - * @param state State to use into {@link ViewController}; - * @return Returns {@link Bundle} with state inside. - */ - @NonNull - public static Bundle createState(@NonNull final Class viewControllerClass, - @NonNull final AbstractState state) { - final Bundle result = createState(state); - result.putSerializable(VIEW_CONTROLLER_CLASS_EXTRA, viewControllerClass); - return result; - } - - private Class>> viewControllerClass; - - @NonNull - @Override - public Class>> getViewControllerClass() { - return viewControllerClass; - } - - @Override - protected boolean isStateRequired() { - return true; - } - - @SuppressWarnings("unchecked") - @Override - public void onCreate(@Nullable final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - viewControllerClass = (Class>>) getArguments().getSerializable(VIEW_CONTROLLER_CLASS_EXTRA); - } - - protected static class DefaultState extends AbstractState { - // just default implementation - } - -} diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/fragments/StatelessTargetedViewControllerFragment.java b/src/main/java/ru/touchin/roboswag/components/navigation/fragments/StatelessTargetedViewControllerFragment.java deleted file mode 100644 index bc5421a..0000000 --- a/src/main/java/ru/touchin/roboswag/components/navigation/fragments/StatelessTargetedViewControllerFragment.java +++ /dev/null @@ -1,66 +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.roboswag.components.navigation.fragments; - -import android.os.Bundle; -import android.support.annotation.NonNull; - -import ru.touchin.roboswag.components.navigation.AbstractState; -import ru.touchin.roboswag.components.navigation.ViewController; -import ru.touchin.roboswag.components.navigation.activities.ViewControllerActivity; -import ru.touchin.roboswag.core.log.Lc; - -/** - * Created by Gavriil Sitnikov on 11/04/2016. - * Simple {@link ViewControllerFragment} with no state and with attached {@link #getTargetFragment()} - * which is using by {@link ru.touchin.roboswag.components.navigation.ViewControllerNavigation}. - * - * @param Type of {@link ViewControllerActivity} where fragment could be attached to. - */ -@SuppressWarnings("PMD.UseUtilityClass") -//UseUtilityClass: PMD bug -public class StatelessTargetedViewControllerFragment> - extends TargetedViewControllerFragment { - - /** - * Creates {@link Bundle} which will store state and {@link ViewController}'s class. - * - * @param viewControllerClass Class of {@link ViewController} which will be instantiated inside this fragment; - * @return Returns {@link Bundle} with state inside. - */ - @NonNull - public static Bundle createState(@NonNull final Class viewControllerClass) { - return createState(viewControllerClass, new DefaultState()); - } - - @Override - protected boolean isStateRequired() { - return false; - } - - @NonNull - @Override - public AbstractState getState() { - Lc.assertion("Trying to access to state of stateless fragment of " + getViewControllerClass()); - return super.getState(); - } - -} diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/fragments/StatelessViewControllerFragment.java b/src/main/java/ru/touchin/roboswag/components/navigation/fragments/StatelessViewControllerFragment.java deleted file mode 100644 index f463ae6..0000000 --- a/src/main/java/ru/touchin/roboswag/components/navigation/fragments/StatelessViewControllerFragment.java +++ /dev/null @@ -1,64 +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.roboswag.components.navigation.fragments; - -import android.os.Bundle; -import android.support.annotation.NonNull; - -import ru.touchin.roboswag.components.navigation.AbstractState; -import ru.touchin.roboswag.components.navigation.ViewController; -import ru.touchin.roboswag.components.navigation.activities.ViewControllerActivity; -import ru.touchin.roboswag.core.log.Lc; - -/** - * Created by Gavriil Sitnikov on 12/03/2016. - * Simple {@link ViewControllerFragment} with no state which is using by {@link ru.touchin.roboswag.components.navigation.ViewControllerNavigation}. - * - * @param Type of {@link ViewControllerActivity} where fragment could be attached to. - */ -@SuppressWarnings("PMD.UseUtilityClass") -//UseUtilityClass: PMD bug -public class StatelessViewControllerFragment> - extends SimpleViewControllerFragment { - - /** - * Creates {@link Bundle} which will store state and {@link ViewController}'s class. - * - * @param viewControllerClass Class of {@link ViewController} which will be instantiated inside this fragment; - * @return Returns {@link Bundle} with state inside. - */ - @NonNull - public static Bundle createState(@NonNull final Class viewControllerClass) { - return createState(viewControllerClass, new DefaultState()); - } - - @NonNull - @Override - public AbstractState getState() { - Lc.assertion("Trying to access to state of stateless fragment of " + getViewControllerClass()); - return super.getState(); - } - - @Override - protected boolean isStateRequired() { - return false; - } - -} diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/fragments/TargetedViewControllerFragment.java b/src/main/java/ru/touchin/roboswag/components/navigation/fragments/TargetedViewControllerFragment.java deleted file mode 100644 index 5f5f676..0000000 --- a/src/main/java/ru/touchin/roboswag/components/navigation/fragments/TargetedViewControllerFragment.java +++ /dev/null @@ -1,55 +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.roboswag.components.navigation.fragments; - -import android.support.annotation.NonNull; - -import ru.touchin.roboswag.components.navigation.AbstractState; -import ru.touchin.roboswag.components.navigation.activities.ViewControllerActivity; -import ru.touchin.roboswag.core.utils.ShouldNotHappenException; - -/** - * Created by Gavriil Sitnikov on 11/04/2016. - * Simple {@link ViewControllerFragment} with attached {@link #getTargetFragment()} - * which is using by {@link ru.touchin.roboswag.components.navigation.ViewControllerNavigation}. - * - * @param Type of object which is representing it's fragment state; - * @param Type of {@link ViewControllerActivity} where fragment could be attached to. - */ -public class TargetedViewControllerFragment> - extends SimpleViewControllerFragment { - - /** - * Returns specific {@link ViewControllerFragment} which is attached to this fragment as {@link #getTargetFragment()}. - * - * @return Target fragment. - */ - @SuppressWarnings("unchecked") - @NonNull - public ViewControllerFragment getTarget() { - if (!(getTargetFragment() instanceof ViewControllerFragment)) { - throw new ShouldNotHappenException(); - } - return (ViewControllerFragment) getTargetFragment(); - } - -} diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java b/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java index 5fb4273..977cd29 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java +++ b/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java @@ -24,10 +24,11 @@ import android.content.Intent; import android.graphics.Canvas; import android.os.Bundle; import android.os.Parcel; +import android.os.Parcelable; import android.os.SystemClock; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.view.InflateException; +import android.support.v4.app.FragmentActivity; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -36,33 +37,23 @@ import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; -import java.io.Serializable; import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import io.reactivex.Observable; -import io.reactivex.disposables.Disposable; -import io.reactivex.subjects.BehaviorSubject; -import ru.touchin.roboswag.components.navigation.AbstractState; import ru.touchin.roboswag.components.navigation.ViewController; -import ru.touchin.roboswag.components.navigation.activities.ViewControllerActivity; import ru.touchin.roboswag.components.utils.UiUtils; import ru.touchin.roboswag.core.log.Lc; -import ru.touchin.roboswag.core.utils.Optional; import ru.touchin.roboswag.core.utils.ShouldNotHappenException; -import ru.touchin.roboswag.core.utils.pairs.NullablePair; /** * Created by Gavriil Sitnikov on 21/10/2015. * Fragment instantiated in specific activity of {@link TActivity} type that is holding {@link ViewController} inside. * * @param Type of object which is representing it's fragment state; - * @param Type of {@link ViewControllerActivity} where fragment could be attached to. + * @param Type of {@link FragmentActivity} where fragment could be attached to. */ -@SuppressWarnings("PMD.TooManyMethods") -public abstract class ViewControllerFragment> - extends ViewFragment { +public abstract class ViewControllerFragment extends ViewFragment { + private static final String VIEW_CONTROLLER_CLASS_EXTRA = "VIEW_CONTROLLER_CLASS_EXTRA"; private static final String VIEW_CONTROLLER_STATE_EXTRA = "VIEW_CONTROLLER_STATE_EXTRA"; private static boolean inDebugMode; @@ -84,17 +75,16 @@ public abstract class ViewControllerFragment T reserialize(@NonNull final T serializable) { + private static T reserialize(@NonNull final T parcelable) { Parcel parcel = Parcel.obtain(); - parcel.writeSerializable(serializable); + parcel.writeParcelable(parcelable, 0); final byte[] serializableBytes = parcel.marshall(); parcel.recycle(); parcel = Parcel.obtain(); parcel.unmarshall(serializableBytes, 0, serializableBytes.length); parcel.setDataPosition(0); - final T result = (T) parcel.readSerializable(); + final T result = parcel.readParcelable(parcelable.getClass().getClassLoader()); parcel.recycle(); return result; } @@ -106,32 +96,20 @@ public abstract class ViewControllerFragment viewControllerClass, @Nullable final Parcelable state) { final Bundle result = new Bundle(); - result.putSerializable(VIEW_CONTROLLER_STATE_EXTRA, state); + result.putSerializable(VIEW_CONTROLLER_CLASS_EXTRA, viewControllerClass); + result.putParcelable(VIEW_CONTROLLER_STATE_EXTRA, state); return result; } - @NonNull - private final BehaviorSubject> activitySubject = BehaviorSubject.create(); - @NonNull - private final BehaviorSubject> viewSubject = BehaviorSubject.create(); @Nullable private ViewController viewController; - private Disposable viewControllerSubscription; + private Class>> viewControllerClass; private TState state; - private boolean started; - private boolean stateCreated; - - private void tryCreateState(@Nullable final Context context) { - if (!stateCreated && state != null && context != null) { - state.onCreate(); - stateCreated = true; - } - } /** - * Returns specific {@link AbstractState} which contains state of fragment and it's {@link ViewController}. + * Returns specific {@link Parcelable} which contains state of fragment and it's {@link ViewController}. * * @return Object represents state. */ @@ -140,64 +118,37 @@ public abstract class ViewControllerFragment>> getViewControllerClass(); - - /** - * Returns if ViewControllerFragment requires state or not. - * - * @return true if state is required - */ - protected abstract boolean isStateRequired(); - - @SuppressWarnings("unchecked") @Override public void onCreate(@Nullable final Bundle savedInstanceState) { super.onCreate(savedInstanceState); setHasOptionsMenu(!isChildFragment()); + //noinspection unchecked + viewControllerClass = (Class>>) + getArguments().getSerializable(VIEW_CONTROLLER_CLASS_EXTRA); state = savedInstanceState != null - ? (TState) savedInstanceState.getSerializable(VIEW_CONTROLLER_STATE_EXTRA) - : (getArguments() != null ? (TState) getArguments().getSerializable(VIEW_CONTROLLER_STATE_EXTRA) : null); + ? savedInstanceState.getParcelable(VIEW_CONTROLLER_STATE_EXTRA) + : (getArguments() != null ? getArguments().getParcelable(VIEW_CONTROLLER_STATE_EXTRA) : null); if (state != null) { if (inDebugMode) { state = reserialize(state); } - tryCreateState(getContext()); - } else if (isStateRequired()) { + } else { Lc.assertion("State is required and null"); } - viewControllerSubscription = Observable - .combineLatest(activitySubject.distinctUntilChanged(), viewSubject.distinctUntilChanged(), - (activityOptional, viewInfo) -> { - final TActivity activity = activityOptional.get(); - final PlaceholderView container = viewInfo.getFirst(); - if (activity == null || container == null) { - return new Optional(null); - } - final ViewController newViewController = createViewController(activity, container, viewInfo.getSecond()); - newViewController.onCreate(); - return new Optional<>(newViewController); - }) - .subscribe(this::onViewControllerChanged, - throwable -> Lc.cutAssertion(throwable, InvocationTargetException.class, InflateException.class)); } @NonNull - private ViewController createViewController(@NonNull final TActivity activity, @NonNull final PlaceholderView view, - @Nullable final Bundle savedInstanceState) { - - if (getViewControllerClass().getConstructors().length != 1) { - throw new ShouldNotHappenException("There should be single constructor for " + getViewControllerClass()); + private ViewController createViewController( + @NonNull final FragmentActivity activity, + @NonNull final PlaceholderView view, + @Nullable final Bundle savedInstanceState + ) { + if (viewControllerClass.getConstructors().length != 1) { + throw new ShouldNotHappenException("There should be single constructor for " + viewControllerClass); } - final Constructor constructor = getViewControllerClass().getConstructors()[0]; + final Constructor constructor = viewControllerClass.getConstructors()[0]; final ViewController.CreationContext creationContext = new ViewController.CreationContext(activity, this, view); final long creationTime = inDebugMode ? SystemClock.elapsedRealtime() : 0; try { @@ -209,7 +160,7 @@ public abstract class ViewControllerFragment acceptableUiCalculationTime) { - UiUtils.UI_METRICS_LC_GROUP.w("Creation of %s took too much: %dms", getViewControllerClass(), creationPeriod); + UiUtils.UI_METRICS_LC_GROUP.w("Creation of %s took too much: %dms", viewControllerClass, creationPeriod); } } } - @Override - public void onAttach(@NonNull final Context context) { - super.onAttach(context); - tryCreateState(context); - } - - @Deprecated @NonNull @Override - public View onCreateView(@NonNull final LayoutInflater inflater, - @Nullable final ViewGroup container, - @Nullable final Bundle savedInstanceState) { - return new PlaceholderView(inflater.getContext(), getViewControllerClass().getName()); + public final View onCreateView( + @NonNull final LayoutInflater inflater, + @Nullable final ViewGroup container, + @Nullable final Bundle savedInstanceState + ) { + return new PlaceholderView(inflater.getContext(), viewControllerClass.getName()); } @Override - public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - if (view instanceof PlaceholderView) { - viewSubject.onNext(new NullablePair<>((PlaceholderView) view, savedInstanceState)); - } else { - Lc.assertion("View should be instanceof PlaceholderView"); - } - } - - @Override - public void onActivityCreated(@NonNull final View view, @NonNull final TActivity activity, @Nullable final Bundle savedInstanceState) { - super.onActivityCreated(view, activity, savedInstanceState); - activitySubject.onNext(new Optional<>(activity)); + public void onActivityCreated(@Nullable final Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + //noinspection ConstantConditions + viewController = createViewController(requireActivity(), (PlaceholderView) getView(), savedInstanceState); + viewController.onCreate(); + requireActivity().invalidateOptionsMenu(); } @Override protected void onStart(@NonNull final View view, @NonNull final TActivity activity) { super.onStart(view, activity); - started = true; if (viewController != null) { viewController.onStart(); } @@ -289,23 +227,12 @@ public abstract class ViewControllerFragment viewControllerOptional) { - if (this.viewController != null) { - this.viewController.onDestroy(); - } - this.viewController = viewControllerOptional.get(); - if (this.viewController != null) { - if (started) { - this.viewController.onStart(); - } - this.viewController.getActivity().reconfigureNavigation(); - } - } - @Override protected void onPause(@NonNull final View view, @NonNull final TActivity activity) { super.onPause(view, activity); @@ -340,7 +254,7 @@ public abstract class ViewControllerFragment(null, null)); - super.onDestroyView(view); - } - - @Override - public void onDetach() { - activitySubject.onNext(new Optional<>(null)); - super.onDetach(); - } - - @Override - public void onDestroy() { - viewControllerSubscription.dispose(); - if (viewController != null && !viewController.isDestroyed()) { + public void onDestroyView() { + if (viewController != null) { viewController.onDestroy(); viewController = null; } - super.onDestroy(); + super.onDestroyView(); } @Override diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewFragment.java b/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewFragment.java index 6bc2b0c..54c9cf6 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewFragment.java +++ b/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewFragment.java @@ -24,7 +24,7 @@ import android.support.annotation.CallSuper; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; -import android.support.v7.app.AppCompatActivity; +import android.support.v4.app.FragmentActivity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -39,8 +39,7 @@ import ru.touchin.roboswag.core.log.Lc; * * @param Type of activity which to such fragment could be attached. */ -public abstract class ViewFragment extends Fragment - implements OnFragmentStartedListener { +public abstract class ViewFragment extends Fragment implements OnFragmentStartedListener { private boolean appeared; private boolean started; @@ -88,27 +87,12 @@ public abstract class ViewFragment extends //do nothing } - @Deprecated @Override public void onActivityCreated(@Nullable final Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); if (getView() == null || getBaseActivity() == null) { Lc.assertion("View and activity shouldn't be null"); - return; } - onActivityCreated(getView(), getBaseActivity(), savedInstanceState); - } - - /** - * Replacement of {@link #onActivityCreated} with non null activity as first parameter. - * - * @param view Instantiated view. - * @param activity Activity which fragment attached to. - * @param savedInstanceState If the fragment is being re-created from a previous saved state, this is the state. - */ - @CallSuper - public void onActivityCreated(@NonNull final View view, @NonNull final TActivity activity, @Nullable final Bundle savedInstanceState) { - //do nothing } private void callMethodAfterInstantiation(@NonNull final BiConsumer action) { @@ -243,25 +227,4 @@ public abstract class ViewFragment extends } } - @Deprecated - @Override - public void onDestroyView() { - if (getView() == null) { - Lc.assertion("View shouldn't be null"); - return; - } - onDestroyView(getView()); - super.onDestroyView(); - } - - /** - * Replacement of {@link #onDestroyView} with non null activity as first parameter. - * - * @param view Instantiated view. - */ - @CallSuper - protected void onDestroyView(@NonNull final View view) { - //do nothing - } - } diff --git a/src/main/java/ru/touchin/roboswag/components/utils/BaseLifecycleBindable.java b/src/main/java/ru/touchin/roboswag/components/utils/BaseLifecycleBindable.java deleted file mode 100644 index 15645da..0000000 --- a/src/main/java/ru/touchin/roboswag/components/utils/BaseLifecycleBindable.java +++ /dev/null @@ -1,336 +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.roboswag.components.utils; - -import android.support.annotation.NonNull; - -import io.reactivex.Completable; -import io.reactivex.Maybe; -import io.reactivex.Observable; -import io.reactivex.Single; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.disposables.Disposable; -import io.reactivex.functions.Action; -import io.reactivex.functions.Consumer; -import io.reactivex.internal.functions.Functions; -import io.reactivex.subjects.BehaviorSubject; -import ru.touchin.roboswag.core.log.Lc; -import ru.touchin.roboswag.core.utils.ShouldNotHappenException; - -/** - * Created by Gavriil Sitnikov on 18/04/16. - * Simple implementation of {@link LifecycleBindable}. Could be used to not implement interface but use such object inside. - */ -@SuppressWarnings("PMD.TooManyMethods") -public class BaseLifecycleBindable implements LifecycleBindable { - - private static final String UNTIL_DESTROY_METHOD = "untilDestroy"; - private static final String UNTIL_STOP_METHOD = "untilStop"; - - @NonNull - private final BehaviorSubject isCreatedSubject = BehaviorSubject.create(); - @NonNull - private final BehaviorSubject isStartedSubject = BehaviorSubject.create(); - @NonNull - private final BehaviorSubject isInAfterSaving = BehaviorSubject.createDefault(false); - - /** - * Call it on parent's onCreate method. - */ - public void onCreate() { - isCreatedSubject.onNext(true); - } - - /** - * Call it on parent's onStart method. - */ - public void onStart() { - isStartedSubject.onNext(true); - } - - /** - * Call it on parent's onResume method. - * It is needed as sometimes onSaveInstanceState() calling after onPause() with no onStop call. So lifecycle object going in stopped state. - * In that case onResume will be called after onSaveInstanceState so lifecycle object is becoming started. - */ - public void onResume() { - isInAfterSaving.onNext(false); - } - - /** - * Call it on parent's onSaveInstanceState method. - */ - public void onSaveInstanceState() { - isInAfterSaving.onNext(true); - } - - /** - * Call it on parent's onStop method. - */ - public void onStop() { - isStartedSubject.onNext(false); - } - - /** - * Call it on parent's onDestroy method. - */ - public void onDestroy() { - isCreatedSubject.onNext(false); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Observable observable) { - final String codePoint = Lc.getCodePoint(this, 2); - return untilStop(observable, Functions.emptyConsumer(), getActionThrowableForAssertion(codePoint, UNTIL_STOP_METHOD), Functions.EMPTY_ACTION); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Observable observable, @NonNull final Consumer onNextAction) { - final String codePoint = Lc.getCodePoint(this, 2); - return untilStop(observable, onNextAction, getActionThrowableForAssertion(codePoint, UNTIL_STOP_METHOD), Functions.EMPTY_ACTION); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Observable observable, - @NonNull final Consumer onNextAction, - @NonNull final Consumer onErrorAction) { - return untilStop(observable, onNextAction, onErrorAction, Functions.EMPTY_ACTION); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Observable observable, - @NonNull final Consumer onNextAction, - @NonNull final Consumer onErrorAction, - @NonNull final Action onCompletedAction) { - return until(observable, isStartedSubject.map(started -> !started) - .delay(item -> isInAfterSaving.filter(inAfterSaving -> !inAfterSaving)), - onNextAction, onErrorAction, onCompletedAction); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Single single) { - final String codePoint = Lc.getCodePoint(this, 2); - return untilStop(single, Functions.emptyConsumer(), getActionThrowableForAssertion(codePoint, UNTIL_STOP_METHOD)); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Single single, @NonNull final Consumer onSuccessAction) { - final String codePoint = Lc.getCodePoint(this, 2); - return untilStop(single, onSuccessAction, getActionThrowableForAssertion(codePoint, UNTIL_STOP_METHOD)); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Single single, - @NonNull final Consumer onSuccessAction, - @NonNull final Consumer onErrorAction) { - return until(single.toObservable(), isStartedSubject.map(started -> !started) - .delay(item -> isInAfterSaving.filter(inAfterSaving -> !inAfterSaving)), - onSuccessAction, onErrorAction, Functions.EMPTY_ACTION); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Completable completable) { - final String codePoint = Lc.getCodePoint(this, 2); - return untilStop(completable, Functions.EMPTY_ACTION, getActionThrowableForAssertion(codePoint, UNTIL_STOP_METHOD)); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Completable completable, - @NonNull final Action onCompletedAction) { - final String codePoint = Lc.getCodePoint(this, 2); - return untilStop(completable, onCompletedAction, getActionThrowableForAssertion(codePoint, UNTIL_STOP_METHOD)); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Completable completable, - @NonNull final Action onCompletedAction, - @NonNull final Consumer onErrorAction) { - return until(completable.toObservable(), isStartedSubject.map(started -> !started) - .delay(item -> isInAfterSaving.filter(inAfterSaving -> !inAfterSaving)), - Functions.emptyConsumer(), onErrorAction, onCompletedAction); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Maybe maybe) { - final String codePoint = Lc.getCodePoint(this, 2); - return untilStop(maybe, Functions.emptyConsumer(), getActionThrowableForAssertion(codePoint, UNTIL_STOP_METHOD)); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Maybe maybe, @NonNull final Consumer onSuccessAction) { - final String codePoint = Lc.getCodePoint(this, 2); - return untilStop(maybe, onSuccessAction, getActionThrowableForAssertion(codePoint, UNTIL_STOP_METHOD)); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Maybe maybe, - @NonNull final Consumer onSuccessAction, - @NonNull final Consumer onErrorAction) { - return until(maybe.toObservable(), isStartedSubject.map(started -> !started), onSuccessAction, onErrorAction, Functions.EMPTY_ACTION); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Observable observable) { - final String codePoint = Lc.getCodePoint(this, 2); - return untilDestroy(observable, Functions.emptyConsumer(), - getActionThrowableForAssertion(codePoint, UNTIL_DESTROY_METHOD), Functions.EMPTY_ACTION); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Observable observable, - @NonNull final Consumer onNextAction) { - final String codePoint = Lc.getCodePoint(this, 2); - return untilDestroy(observable, onNextAction, getActionThrowableForAssertion(codePoint, UNTIL_DESTROY_METHOD), Functions.EMPTY_ACTION); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Observable observable, - @NonNull final Consumer onNextAction, - @NonNull final Consumer onErrorAction) { - return untilDestroy(observable, onNextAction, onErrorAction, Functions.EMPTY_ACTION); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Observable observable, - @NonNull final Consumer onNextAction, - @NonNull final Consumer onErrorAction, - @NonNull final Action onCompletedAction) { - return until(observable, isCreatedSubject.map(created -> !created), onNextAction, onErrorAction, onCompletedAction); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Single single) { - final String codePoint = Lc.getCodePoint(this, 2); - return untilDestroy(single, Functions.emptyConsumer(), getActionThrowableForAssertion(codePoint, UNTIL_DESTROY_METHOD)); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Single single, @NonNull final Consumer onSuccessAction) { - final String codePoint = Lc.getCodePoint(this, 2); - return untilDestroy(single, onSuccessAction, getActionThrowableForAssertion(codePoint, UNTIL_DESTROY_METHOD)); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Single single, - @NonNull final Consumer onSuccessAction, - @NonNull final Consumer onErrorAction) { - return until(single.toObservable(), isCreatedSubject.map(created -> !created), onSuccessAction, onErrorAction, Functions.EMPTY_ACTION); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Completable completable) { - final String codePoint = Lc.getCodePoint(this, 2); - return untilDestroy(completable, Functions.EMPTY_ACTION, getActionThrowableForAssertion(codePoint, UNTIL_DESTROY_METHOD)); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Completable completable, @NonNull final Action onCompletedAction) { - final String codePoint = Lc.getCodePoint(this, 2); - return untilDestroy(completable, onCompletedAction, getActionThrowableForAssertion(codePoint, UNTIL_DESTROY_METHOD)); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Completable completable, - @NonNull final Action onCompletedAction, - @NonNull final Consumer onErrorAction) { - return until(completable.toObservable(), isCreatedSubject.map(created -> !created), - Functions.emptyConsumer(), onErrorAction, onCompletedAction); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Maybe maybe) { - final String codePoint = Lc.getCodePoint(this, 2); - return untilDestroy(maybe, Functions.emptyConsumer(), getActionThrowableForAssertion(codePoint, UNTIL_DESTROY_METHOD)); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Maybe maybe, @NonNull final Consumer onSuccessAction) { - final String codePoint = Lc.getCodePoint(this, 2); - return untilDestroy(maybe, onSuccessAction, getActionThrowableForAssertion(codePoint, UNTIL_DESTROY_METHOD)); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Maybe maybe, - @NonNull final Consumer onSuccessAction, - @NonNull final Consumer onErrorAction) { - return until(maybe.toObservable(), isCreatedSubject.map(created -> !created), onSuccessAction, onErrorAction, Functions.EMPTY_ACTION); - } - - @NonNull - private Disposable until(@NonNull final Observable observable, - @NonNull final Observable conditionSubject, - @NonNull final Consumer onNextAction, - @NonNull final Consumer onErrorAction, - @NonNull final Action onCompletedAction) { - final Observable actualObservable; - if (onNextAction == Functions.emptyConsumer() && onErrorAction == (Consumer) Functions.emptyConsumer() - && onCompletedAction == Functions.EMPTY_ACTION) { - actualObservable = observable; - } else { - actualObservable = observable.observeOn(AndroidSchedulers.mainThread()) - .doOnComplete(onCompletedAction) - .doOnNext(onNextAction) - .doOnError(onErrorAction); - } - - return isCreatedSubject.firstOrError() - .flatMapObservable(created -> created ? actualObservable : Observable.empty()) - .takeUntil(conditionSubject.filter(condition -> condition)) - .onErrorResumeNext(throwable -> { - if (throwable instanceof RuntimeException) { - Lc.assertion(throwable); - } - return Observable.empty(); - }) - .subscribe(); - } - - @NonNull - private Consumer getActionThrowableForAssertion(@NonNull final String codePoint, @NonNull final String method) { - return throwable -> Lc.assertion(new ShouldNotHappenException("Unexpected error on " + method + " at " + codePoint, throwable)); - } - -} \ No newline at end of file diff --git a/src/main/java/ru/touchin/roboswag/components/utils/LifecycleBindable.java b/src/main/java/ru/touchin/roboswag/components/utils/LifecycleBindable.java deleted file mode 100644 index e49ddc9..0000000 --- a/src/main/java/ru/touchin/roboswag/components/utils/LifecycleBindable.java +++ /dev/null @@ -1,391 +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.roboswag.components.utils; - -import android.support.annotation.NonNull; - -import io.reactivex.Completable; -import io.reactivex.CompletableEmitter; -import io.reactivex.Emitter; -import io.reactivex.Maybe; -import io.reactivex.MaybeEmitter; -import io.reactivex.Observable; -import io.reactivex.Single; -import io.reactivex.SingleEmitter; -import io.reactivex.disposables.Disposable; -import io.reactivex.functions.Action; -import io.reactivex.functions.Consumer; - -/** - * Created by Gavriil Sitnikov on 15/04/16. - * Interface that should be implemented by lifecycle-based elements ({@link android.app.Activity}, {@link android.support.v4.app.Fragment} etc.) - * to not manually manage subscriptions. - * Use {@link #untilStop(Observable)} method to subscribe to observable where you want and unsubscribe onStop. - * Use {@link #untilDestroy(Observable)} method to subscribe to observable where you want and unsubscribe onDestroy. - */ -@SuppressWarnings("PMD.TooManyMethods") -public interface LifecycleBindable { - - /** - * Method should be used to guarantee that observable won't be subscribed after onStop. - * It is automatically subscribing to the observable. - * Usually it is using to stop requests/execution while element is off or to not do illegal actions after onStop like fragment's stack changing. - * Don't forget to process errors if observable can emit them. - * - * @param observable {@link Observable} to subscribe until onStop; - * @param Type of emitted by observable items; - * @return {@link Disposable} which will unsubscribes from observable onStop. - */ - @NonNull - Disposable untilStop(@NonNull Observable observable); - - /** - * Method should be used to guarantee that observable won't be subscribed after onStop. - * It is automatically subscribing to the observable and calls onNextAction on every emitted item. - * Usually it is using to stop requests/execution while element is off or to not do illegal actions after onStop like fragment's stack changing. - * Don't forget to process errors if observable can emit them. - * - * @param observable {@link Observable} to subscribe until onStop; - * @param onNextAction Action which will raise on every {@link Emitter#onNext(Object)} item; - * @param Type of emitted by observable items; - * @return {@link Disposable} which will unsubscribes from observable onStop. - */ - @NonNull - Disposable untilStop(@NonNull Observable observable, @NonNull Consumer onNextAction); - - /** - * Method should be used to guarantee that observable won't be subscribed after onStop. - * It is automatically subscribing to the observable and calls onNextAction and onErrorAction on observable events. - * Usually it is using to stop requests/execution while element is off or to not do illegal actions after onStop like fragment's stack changing. - * Don't forget to process errors if observable can emit them. - * - * @param observable {@link Observable} to subscribe until onStop; - * @param onNextAction Action which will raise on every {@link Emitter#onNext(Object)} item; - * @param onErrorAction Action which will raise on every {@link Emitter#onError(Throwable)} throwable; - * @param Type of emitted by observable items; - * @return {@link Disposable} which will unsubscribes from observable onStop. - */ - @NonNull - Disposable untilStop(@NonNull Observable observable, @NonNull Consumer onNextAction, @NonNull Consumer onErrorAction); - - /** - * Method should be used to guarantee that observable won't be subscribed after onStop. - * It is automatically subscribing to the observable and calls onNextAction, onErrorAction and onCompletedAction on observable events. - * Usually it is using to stop requests/execution while element is off or to not do illegal actions after onStop like fragment's stack changing. - * Don't forget to process errors if observable can emit them. - * - * @param observable {@link Observable} to subscribe until onStop; - * @param onNextAction Action which will raise on every {@link Emitter#onNext(Object)} item; - * @param onErrorAction Action which will raise on every {@link Emitter#onError(Throwable)} throwable; - * @param onCompletedAction Action which will raise at {@link Emitter#onComplete()} on completion of observable; - * @param Type of emitted by observable items; - * @return {@link Disposable} which is wrapping source observable to unsubscribe from it onStop. - */ - @NonNull - Disposable untilStop(@NonNull Observable observable, - @NonNull Consumer onNextAction, @NonNull Consumer onErrorAction, @NonNull Action onCompletedAction); - - /** - * Method should be used to guarantee that single won't be subscribed after onStop. - * It is automatically subscribing to the single. - * Usually it is using to stop requests/execution while element is off or to not do illegal actions after onStop like fragment's stack changing. - * Don't forget to process errors if single can emit them. - * - * @param single {@link Single} to subscribe until onStop; - * @param Type of emitted by single item; - * @return {@link Disposable} which will unsubscribes from single onStop. - */ - @NonNull - Disposable untilStop(@NonNull Single single); - - /** - * Method should be used to guarantee that single won't be subscribed after onStop. - * It is automatically subscribing to the single and calls onSuccessAction on the emitted item. - * Usually it is using to stop requests/execution while element is off or to not do illegal actions after onStop like fragment's stack changing. - * Don't forget to process errors if single can emit them. - * - * @param single {@link Single} to subscribe until onStop; - * @param onSuccessAction Action which will raise on every {@link SingleEmitter#onSuccess(Object)} item; - * @param Type of emitted by single item; - * @return {@link Disposable} which will unsubscribes from single onStop. - */ - @NonNull - Disposable untilStop(@NonNull Single single, @NonNull Consumer onSuccessAction); - - /** - * Method should be used to guarantee that single won't be subscribed after onStop. - * It is automatically subscribing to the single and calls onSuccessAction and onErrorAction on single events. - * Usually it is using to stop requests/execution while element is off or to not do illegal actions after onStop like fragment's stack changing. - * Don't forget to process errors if single can emit them. - * - * @param single {@link Single} to subscribe until onStop; - * @param onSuccessAction Action which will raise on every {@link SingleEmitter#onSuccess(Object)} item; - * @param onErrorAction Action which will raise on every {@link SingleEmitter#onError(Throwable)} throwable; - * @param Type of emitted by observable items; - * @return {@link Disposable} which is wrapping source single to unsubscribe from it onStop. - */ - @NonNull - Disposable untilStop(@NonNull Single single, @NonNull Consumer onSuccessAction, @NonNull Consumer onErrorAction); - - /** - * Method should be used to guarantee that completable won't be subscribed after onStop. - * It is automatically subscribing to the completable. - * Usually it is using to stop requests/execution while element is off or to not do illegal actions after onStop like fragment's stack changing. - * Don't forget to process errors if completable can emit them. - * - * @param completable {@link Completable} to subscribe until onStop; - * @return {@link Disposable} which will unsubscribes from completable onStop. - */ - @NonNull - Disposable untilStop(@NonNull Completable completable); - - /** - * Method should be used to guarantee that completable won't be subscribed after onStop. - * It is automatically subscribing to the completable and calls onCompletedAction on completable item. - * Usually it is using to stop requests/execution while element is off or to not do illegal actions after onStop like fragment's stack changing. - * Don't forget to process errors if completable can emit them. - * - * @param completable {@link Completable} to subscribe until onStop; - * @param onCompletedAction Action which will raise at {@link CompletableEmitter#onComplete()} on completion of observable; - * @return {@link Disposable} which is wrapping source completable to unsubscribe from it onStop. - */ - @NonNull - Disposable untilStop(@NonNull Completable completable, @NonNull Action onCompletedAction); - - /** - * Method should be used to guarantee that completable won't be subscribed after onStop. - * It is automatically subscribing to the completable and calls onCompletedAction and onErrorAction on completable item. - * Usually it is using to stop requests/execution while element is off or to not do illegal actions after onStop like fragment's stack changing. - * Don't forget to process errors if completable can emit them. - * - * @param completable {@link Completable} to subscribe until onStop; - * @param onCompletedAction Action which will raise at {@link CompletableEmitter#onComplete()} on completion of observable; - * @param onErrorAction Action which will raise on every {@link CompletableEmitter#onError(Throwable)} throwable; - * @return {@link Disposable} which is wrapping source completable to unsubscribe from it onStop. - */ - @NonNull - Disposable untilStop(@NonNull Completable completable, @NonNull Action onCompletedAction, @NonNull Consumer onErrorAction); - - /** - * Method should be used to guarantee that maybe won't be subscribed after onStop. - * It is automatically subscribing to the maybe. - * Usually it is using to stop requests/execution while element is off or to not do illegal actions after onStop like fragment's stack changing. - * Don't forget to process errors if completable can emit them. - * - * @param maybe {@link Maybe} to subscribe until onStop; - * @return {@link Disposable} which will unsubscribes from completable onStop. - */ - @NonNull - Disposable untilStop(@NonNull Maybe maybe); - - /** - * Method should be used to guarantee that maybe won't be subscribed after onStop. - * It is automatically subscribing to the maybe and calls onCompletedAction on maybe item. - * Usually it is using to stop requests/execution while element is off or to not do illegal actions after onStop like fragment's stack changing. - * Don't forget to process errors if completable can emit them. - * - * @param maybe {@link Maybe} to subscribe until onStop; - * @param onSuccessAction Action which will raise at {@link MaybeEmitter#onSuccess(Object)} ()} on completion of observable; - * @return {@link Disposable} which is wrapping source maybe to unsubscribe from it onStop. - */ - @NonNull - Disposable untilStop(@NonNull Maybe maybe, @NonNull Consumer onSuccessAction); - - /** - * Method should be used to guarantee that maybe won't be subscribed after onStop. - * It is automatically subscribing to the maybe and calls onCompletedAction and onErrorAction on maybe item. - * Usually it is using to stop requests/execution while element is off or to not do illegal actions after onStop like fragment's stack changing. - * Don't forget to process errors if completable can emit them. - * - * @param maybe {@link Maybe} to subscribe until onStop; - * @param onSuccessAction Action which will raise at {@link MaybeEmitter#onSuccess(Object)} ()} on completion of observable; - * @param onErrorAction Action which will raise on every {@link MaybeEmitter#onError(Throwable)} throwable; - * @return {@link Disposable} which is wrapping source maybe to unsubscribe from it onStop. - */ - @NonNull - Disposable untilStop(@NonNull Maybe maybe, @NonNull Consumer onSuccessAction, @NonNull Consumer onErrorAction); - - /** - * Method should be used to guarantee that observable won't be subscribed after onDestroy. - * It is automatically subscribing to the observable. - * Don't forget to process errors if observable can emit them. - * - * @param observable {@link Observable} to subscribe until onDestroy; - * @param Type of emitted by observable items; - * @return {@link Disposable} which is wrapping source maybe to unsubscribe from it onDestroy. - */ - @NonNull - Disposable untilDestroy(@NonNull Observable observable); - - /** - * Method should be used to guarantee that observable won't be subscribed after onDestroy. - * It is automatically subscribing to the 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 Emitter#onNext(Object)} item; - * @param Type of emitted by observable items; - * @return {@link Disposable} which is wrapping source observable to unsubscribe from it onDestroy. - */ - @NonNull - Disposable untilDestroy(@NonNull Observable observable, @NonNull Consumer onNextAction); - - /** - * Method should be used to guarantee that observable won't be subscribed after onDestroy. - * It is automatically subscribing to the 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 Emitter#onNext(Object)} item; - * @param onErrorAction Action which will raise on every {@link Emitter#onError(Throwable)} throwable; - * @param Type of emitted by observable items; - * @return {@link Disposable} which is wrapping source observable to unsubscribe from it onDestroy. - */ - @NonNull - Disposable untilDestroy(@NonNull Observable observable, @NonNull Consumer onNextAction, @NonNull Consumer onErrorAction); - - /** - * Method should be used to guarantee that observable won't be subscribed after onDestroy. - * It is automatically subscribing to the 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 Emitter#onNext(Object)} item; - * @param onErrorAction Action which will raise on every {@link Emitter#onError(Throwable)} throwable; - * @param onCompletedAction Action which will raise at {@link Emitter#onComplete()} on completion of observable; - * @param Type of emitted by observable items; - * @return {@link Disposable} which is wrapping source observable to unsubscribe from it onDestroy. - */ - @NonNull - Disposable untilDestroy(@NonNull Observable observable, - @NonNull Consumer onNextAction, @NonNull Consumer onErrorAction, @NonNull Action onCompletedAction); - - /** - * Method should be used to guarantee that single won't be subscribed after onDestroy. - * It is automatically subscribing to the single. - * Don't forget to process errors if single can emit them. - * - * @param single {@link Single} to subscribe until onDestroy; - * @param Type of emitted by single items; - * @return {@link Disposable} which is wrapping source single to unsubscribe from it onDestroy. - */ - @NonNull - Disposable untilDestroy(@NonNull Single single); - - /** - * Method should be used to guarantee that single won't be subscribed after onDestroy. - * It is automatically subscribing to the single and calls onSuccessAction on every 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 every {@link SingleEmitter#onSuccess(Object)} item; - * @param Type of emitted by single items; - * @return {@link Disposable} which is wrapping source single to unsubscribe from it onDestroy. - */ - @NonNull - Disposable untilDestroy(@NonNull Single single, @NonNull Consumer onSuccessAction); - - /** - * Method should be used to guarantee that single won't be subscribed after onDestroy. - * It is automatically subscribing to the 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 every {@link SingleEmitter#onSuccess(Object)} item; - * @param onErrorAction Action which will raise on every {@link SingleEmitter#onError(Throwable)} throwable; - * @param Type of emitted by single items; - * @return {@link Disposable} which is wrapping source single to unsubscribe from it onDestroy. - */ - @NonNull - Disposable untilDestroy(@NonNull Single single, @NonNull Consumer onSuccessAction, @NonNull Consumer onErrorAction); - - /** - * Method should be used to guarantee that completable won't be subscribed after onDestroy. - * It is automatically subscribing to the completable. - * Don't forget to process errors if completable can emit them. - * - * @param completable {@link Completable} to subscribe until onDestroy; - * @return {@link Disposable} which is wrapping source completable to unsubscribe from it onDestroy. - */ - @NonNull - Disposable untilDestroy(@NonNull Completable completable); - - /** - * Method should be used to guarantee that completable won't be subscribed after onDestroy. - * It is automatically subscribing to the completable and calls onCompletedAction on completable item. - * 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 CompletableEmitter#onComplete()} item; - * @return {@link Disposable} which is wrapping source single to unsubscribe from it onDestroy. - */ - @NonNull - Disposable untilDestroy(@NonNull Completable completable, @NonNull Action onCompletedAction); - - /** - * Method should be used to guarantee that completable won't be subscribed after onDestroy. - * It is automatically subscribing to the completable and calls onCompletedAction and onErrorAction on completable events. - * 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 CompletableEmitter#onComplete()} item; - * @param onErrorAction Action which will raise on every {@link CompletableEmitter#onError(Throwable)} throwable; - * @return {@link Disposable} which is wrapping source completable to unsubscribe from it onDestroy. - */ - @NonNull - Disposable untilDestroy(@NonNull Completable completable, @NonNull Action onCompletedAction, @NonNull Consumer onErrorAction); - - /** - * Method should be used to guarantee that maybe won't be subscribed after onDestroy. - * It is automatically subscribing to the maybe. - * Don't forget to process errors if maybe can emit them. - * - * @param maybe {@link Maybe} to subscribe until onDestroy; - * @return {@link Disposable} which is wrapping source maybe to unsubscribe from it onDestroy. - */ - @NonNull - Disposable untilDestroy(@NonNull Maybe maybe); - - /** - * Method should be used to guarantee that maybe won't be subscribed after onDestroy. - * It is automatically subscribing to the maybe and calls onCompletedAction on maybe item. - * Don't forget to process errors if maybe can emit them. - * - * @param maybe {@link Maybe} to subscribe until onDestroy; - * @param onSuccessAction Action which will raise on every {@link MaybeEmitter#onSuccess(Object)} ()} item; - * @return {@link Disposable} which is wrapping source maybe to unsubscribe from it onDestroy. - */ - @NonNull - Disposable untilDestroy(@NonNull Maybe maybe, @NonNull Consumer onSuccessAction); - - /** - * Method should be used to guarantee that maybe won't be subscribed after onDestroy. - * It is automatically subscribing to the maybe and calls onSuccessAction and onErrorAction on maybe events. - * Don't forget to process errors if completable can emit them. - * - * @param maybe {@link Maybe} to subscribe until onDestroy; - * @param onSuccessAction Action which will raise on every {@link MaybeEmitter#onSuccess(Object)} ()} item; - * @param onErrorAction Action which will raise on every {@link MaybeEmitter#onError(Throwable)} throwable; - * @return {@link Disposable} which is wrapping source maybe to unsubscribe from it onDestroy. - */ - @NonNull - Disposable untilDestroy(@NonNull Maybe maybe, @NonNull Consumer onSuccessAction, @NonNull Consumer onErrorAction); - -} \ No newline at end of file diff --git a/src/main/java/ru/touchin/roboswag/components/utils/Logic.java b/src/main/java/ru/touchin/roboswag/components/utils/Logic.java deleted file mode 100644 index 84c2e87..0000000 --- a/src/main/java/ru/touchin/roboswag/components/utils/Logic.java +++ /dev/null @@ -1,110 +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.roboswag.components.utils; - -import android.content.Context; -import android.support.annotation.NonNull; - -import java.lang.ref.WeakReference; -import java.lang.reflect.Constructor; -import java.util.HashMap; -import java.util.Map; - -import io.reactivex.Observable; -import ru.touchin.roboswag.core.utils.ShouldNotHappenException; - -/** - * Created by Gavriil Sitnikov on 24/03/16. - * Base class representing application's logic. - * In specific application it should be child of it (usually one) which contains all methods/objects related to logic. - * It should contains interface to work with API/preferences/database/file system/system parameters etc. - * Be sure that all objects/instances/services created to represents logic are not getting a lot of time to be instantiated, if they take a lot time - * for instantiation then it is wrong logic and it should be moved into asynchronous operations via {@link Observable} or so. - * Also it shouldn't create massive data objects and a lot of objects instantly. Basically it should just create bunch of interfaces inside - * which will allows to access to some logic methods. - * In fact it is similar to dependency injection pattern but with full control of instantiation and only one single instance of {@link Logic} per app. - * If you want to use it then just create getter in {@link android.app.Service}/{@link android.app.Activity}/{@link android.content.BroadcastReceiver} - * or any else context-based elements and do not forget to store reference to {@link Logic} into field because else it will be consumed by GC. - * Sample of {@link Logic} using is in {@link ru.touchin.roboswag.components.navigation.activities.ViewControllerActivity}. - * NOTE: Ideally creation of logic should be asynchronous and stored in specific {@link android.app.Service} so it should be accessed - * asynchronously via {@link Observable} or so. But in fact it requires {@link android.app.Service} plus more complex methods to access to logic. - * So currently it is more simple to access via simple bridge based on singletons stored into {@link WeakReference} because anyway instantiation of - * logic have to be as fast as it can. If it's not then it is just a bug and problem of optimization. - */ -public class Logic { - - private static final Map, WeakReference> LOGIC_INSTANCES = new HashMap<>(); - - /** - * Returns instance of {@link Logic} depends on class. There should be no more than one instance per class. - * - * @param context Context of application where this {@link Logic} related to; - * @param logicClass Class of {@link Logic}; - * @param Type of class of {@link Logic}; - * @return Instance of {@link Logic}. - */ - @SuppressWarnings({"unchecked", "PMD.SingletonClassReturningNewInstance"}) - //SingletonClassReturningNewInstance: it is OK to create instance every time if WeakReference have died - @NonNull - public static T getInstance(@NonNull final Context context, @NonNull final Class logicClass) { - T result; - synchronized (LOGIC_INSTANCES) { - final WeakReference reference = LOGIC_INSTANCES.get(logicClass); - result = reference != null ? (T) reference.get() : null; - if (result == null) { - result = constructLogic(context.getApplicationContext(), logicClass); - LOGIC_INSTANCES.put(logicClass, new WeakReference<>(result)); - } - } - return result; - } - - @NonNull - @SuppressWarnings("unchecked") - private static T constructLogic(@NonNull final Context context, @NonNull final Class logicClass) { - if (logicClass.getConstructors().length != 1 || logicClass.getConstructors()[0].getParameterTypes().length != 1) { - throw new ShouldNotHappenException("There should be only one public constructor(Context) for class " + logicClass); - } - final Constructor constructor = logicClass.getConstructors()[0]; - try { - return (T) constructor.newInstance(context); - } catch (final Exception exception) { - throw new ShouldNotHappenException(exception); - } - } - - @NonNull - private final Context context; - - public Logic(@NonNull final Context context) { - this.context = context; - } - - /** - * Returns {@link android.app.Application}'s context. - * - * @return Context (possibly application). - */ - @NonNull - public Context getContext() { - return context; - } - -} diff --git a/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.java b/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.java index 7596527..985e9e2 100644 --- a/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.java +++ b/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.java @@ -21,13 +21,14 @@ package ru.touchin.roboswag.components.utils; import android.app.Activity; import android.app.Application; +import android.arch.lifecycle.Lifecycle; +import android.arch.lifecycle.LifecycleOwner; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.os.Build; import android.os.Handler; import android.os.Looper; -import android.support.annotation.IdRes; import android.support.annotation.LayoutRes; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -41,11 +42,8 @@ import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; -import java.util.concurrent.atomic.AtomicInteger; - import io.reactivex.functions.Action; import io.reactivex.functions.Consumer; -import ru.touchin.roboswag.components.navigation.activities.BaseActivity; import ru.touchin.roboswag.core.log.Lc; import ru.touchin.roboswag.core.log.LcGroup; @@ -130,9 +128,11 @@ public final class UiUtils { } final Runnable runnable = () -> { + final Context context = targetView.getContext(); if (targetView.getWindowVisibility() != View.VISIBLE || !targetView.hasWindowFocus() - || (targetView.getContext() instanceof BaseActivity && !((BaseActivity) targetView.getContext()).isActuallyResumed())) { + || (context instanceof LifecycleOwner + && !((LifecycleOwner) context).getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED))) { return; } try { @@ -292,35 +292,6 @@ public final class UiUtils { */ public static class OfViews { - private static final int GENERATED_ID_THRESHOLD = 0x00FFFFFF; - private static final AtomicInteger NEXT_GENERATED_ID = new AtomicInteger(1); - - /** - * Generates unique ID for view. See android {@link View#generateViewId()}. - * - * @return Unique ID. - */ - @IdRes - public static int generateViewId() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - return View.generateViewId(); - } - int result = 0; - boolean isGenerated = false; - while (!isGenerated) { - result = NEXT_GENERATED_ID.get(); - // aapt-generated IDs have the high byte nonzero; clamp to the range under that. - int newValue = result + 1; - if (newValue > GENERATED_ID_THRESHOLD) { - newValue = 1; // Roll over to 1, not 0. - } - if (NEXT_GENERATED_ID.compareAndSet(result, newValue)) { - isGenerated = true; - } - } - return result; - } - /** * Returns string representation of {@link View}'s ID. * diff --git a/src/main/java/ru/touchin/roboswag/components/utils/destroyable/BaseDestroyable.kt b/src/main/java/ru/touchin/roboswag/components/utils/destroyable/BaseDestroyable.kt new file mode 100644 index 0000000..dae5cd3 --- /dev/null +++ b/src/main/java/ru/touchin/roboswag/components/utils/destroyable/BaseDestroyable.kt @@ -0,0 +1,62 @@ +package ru.touchin.roboswag.components.utils.destroyable + +import io.reactivex.Completable +import io.reactivex.Maybe +import io.reactivex.Observable +import io.reactivex.Single +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.disposables.Disposable + +/** + * Created by Oksana Pokrovskaya on 7/03/18. + * Simple implementation of [Destroyable]. Could be used to not implement interface but use such object inside. + */ +open class BaseDestroyable : Destroyable { + + private val subscriptions = CompositeDisposable() + + /** + * Call it on parent's onDestroy method. + */ + fun onDestroy() = subscriptions.dispose() + + override fun untilDestroy( + observable: Observable, + onNextAction: (T) -> Unit, + onErrorAction: (Throwable) -> Unit, + onCompletedAction: () -> Unit + ): Disposable = observable + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(onNextAction, onErrorAction, onCompletedAction) + .also { subscriptions.add(it) } + + override fun untilDestroy( + single: Single, + onSuccessAction: (T) -> Unit, + onErrorAction: (Throwable) -> Unit + ): Disposable = single + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(onSuccessAction, onErrorAction) + .also { subscriptions.add(it) } + + override fun untilDestroy( + completable: Completable, + onCompletedAction: () -> Unit, + onErrorAction: (Throwable) -> Unit + ): Disposable = completable + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(onCompletedAction, onErrorAction) + .also { subscriptions.add(it) } + + override fun untilDestroy( + maybe: Maybe, + onSuccessAction: (T) -> Unit, + onErrorAction: (Throwable) -> Unit, + onCompletedAction: () -> Unit + ): Disposable = maybe + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(onSuccessAction, onErrorAction, onCompletedAction) + .also { subscriptions.add(it) } + +} diff --git a/src/main/java/ru/touchin/roboswag/components/utils/destroyable/Destroyable.kt b/src/main/java/ru/touchin/roboswag/components/utils/destroyable/Destroyable.kt new file mode 100644 index 0000000..ea2e1eb --- /dev/null +++ b/src/main/java/ru/touchin/roboswag/components/utils/destroyable/Destroyable.kt @@ -0,0 +1,94 @@ +package ru.touchin.roboswag.components.utils.destroyable + +import io.reactivex.Completable +import io.reactivex.Maybe +import io.reactivex.Observable +import io.reactivex.Single +import io.reactivex.disposables.Disposable +import io.reactivex.internal.functions.Functions +import ru.touchin.roboswag.core.log.Lc +import ru.touchin.roboswag.core.utils.ShouldNotHappenException + +/** + * Created by Oksana Pokrovskaya on 7/03/18. + * Interface that should be implemented by ([android.arch.lifecycle.ViewModel] etc.) + * to not manually manage subscriptions. + * Use [.untilDestroy] method to subscribe to observable where you want and unsubscribe onDestroy. + */ +interface Destroyable { + + companion object { + private fun getActionThrowableForAssertion(codePoint: String, method: String = "untilDestroy"): (Throwable) -> Unit = { throwable -> + Lc.assertion(ShouldNotHappenException("Unexpected error on $method at $codePoint", throwable)) + } + } + + /** + * Method should be used to guarantee that observable won't be subscribed after onDestroy. + * It is automatically subscribing to the observable and calls onNextAction and onErrorAction on observable events. + * Don't forget to process errors if observable can emit them. + * + * @param observable [Observable] to subscribe until onDestroy; + * @param onNextAction Action which will raise on every [io.reactivex.Emitter.onNext] item; + * @param onErrorAction Action which will raise on every [io.reactivex.Emitter.onError] throwable; + * @param T Type of emitted by observable items; + * @return [Disposable] which is wrapping source observable to unsubscribe from it onDestroy. + */ + fun untilDestroy( + observable: Observable, + onNextAction: (T) -> Unit = Functions.emptyConsumer()::accept, + onErrorAction: (Throwable) -> Unit = getActionThrowableForAssertion(Lc.getCodePoint(this, 2)), + onCompletedAction: () -> Unit = Functions.EMPTY_ACTION::run + ): Disposable + + /** + * Method should be used to guarantee that single won't be subscribed after onDestroy. + * It is automatically subscribing to the single and calls onSuccessAction and onErrorAction on single events. + * Don't forget to process errors if single can emit them. + * + * @param single [Single] to subscribe until onDestroy; + * @param onSuccessAction Action which will raise on every [io.reactivex.SingleEmitter.onSuccess] item; + * @param onErrorAction Action which will raise on every [io.reactivex.SingleEmitter.onError] throwable; + * @param T Type of emitted by single items; + * @return [Disposable] which is wrapping source single to unsubscribe from it onDestroy. + */ + fun untilDestroy( + single: Single, + onSuccessAction: (T) -> Unit = Functions.emptyConsumer()::accept, + onErrorAction: (Throwable) -> Unit = getActionThrowableForAssertion(Lc.getCodePoint(this, 2)) + ): Disposable + + /** + * Method should be used to guarantee that completable won't be subscribed after onDestroy. + * It is automatically subscribing to the completable and calls onCompletedAction and onErrorAction on completable events. + * Don't forget to process errors if completable can emit them. + * + * @param completable [Completable] to subscribe until onDestroy; + * @param onCompletedAction Action which will raise on every [io.reactivex.CompletableEmitter.onComplete] item; + * @param onErrorAction Action which will raise on every [io.reactivex.CompletableEmitter.onError] throwable; + * @return [Disposable] which is wrapping source completable to unsubscribe from it onDestroy. + */ + fun untilDestroy( + completable: Completable, + onCompletedAction: () -> Unit = Functions.EMPTY_ACTION::run, + onErrorAction: (Throwable) -> Unit = getActionThrowableForAssertion(Lc.getCodePoint(this, 2)) + ): Disposable + + /** + * Method should be used to guarantee that maybe won't be subscribed after onDestroy. + * It is automatically subscribing to the maybe and calls onSuccessAction and onErrorAction on maybe events. + * Don't forget to process errors if completable can emit them. + * + * @param maybe [Maybe] to subscribe until onDestroy; + * @param onSuccessAction Action which will raise on every [io.reactivex.MaybeEmitter.onSuccess] ()} item; + * @param onErrorAction Action which will raise on every [io.reactivex.MaybeEmitter.onError] throwable; + * @return [Disposable] which is wrapping source maybe to unsubscribe from it onDestroy. + */ + fun untilDestroy( + maybe: Maybe, + onSuccessAction: (T) -> Unit = Functions.emptyConsumer()::accept, + onErrorAction: (Throwable) -> Unit = getActionThrowableForAssertion(Lc.getCodePoint(this, 2)), + onCompletedAction: () -> Unit = Functions.EMPTY_ACTION::run + ): Disposable + +} diff --git a/src/main/java/ru/touchin/roboswag/components/views/AspectRatioFrameLayout.java b/src/main/java/ru/touchin/roboswag/components/views/AspectRatioFrameLayout.java deleted file mode 100644 index 3291bbe..0000000 --- a/src/main/java/ru/touchin/roboswag/components/views/AspectRatioFrameLayout.java +++ /dev/null @@ -1,254 +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.roboswag.components.views; - -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Point; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.util.AttributeSet; -import android.util.DisplayMetrics; -import android.view.View; -import android.view.ViewGroup; -import android.widget.FrameLayout; - -import ru.touchin.roboswag.components.R; - - -/** - * Created by Gavriil Sitnikov on 01/07/14. - * FrameLayout that holds specific aspect ratio sizes. - * For example if aspect ratio equals 1.0 then this view will layout as square. - */ -public class AspectRatioFrameLayout extends FrameLayout { - - private static final float DEFAULT_ASPECT_RATIO = 1.0f; - private static final float EPSILON = 0.0000001f; - - private float aspectRatio; - private boolean wrapToContent; - - public AspectRatioFrameLayout(@NonNull final Context context) { - this(context, null); - } - - public AspectRatioFrameLayout(@NonNull final Context context, @Nullable final AttributeSet attrs) { - this(context, attrs, 0); - } - - public AspectRatioFrameLayout(@NonNull final Context context, @Nullable final AttributeSet attrs, final int defStyle) { - super(context, attrs, defStyle); - - if (attrs == null) { - wrapToContent = false; - aspectRatio = DEFAULT_ASPECT_RATIO; - } else { - final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.AspectRatioFrameLayout); - wrapToContent = typedArray.getBoolean(R.styleable.AspectRatioFrameLayout_wrapToContent, false); - aspectRatio = typedArray.getFloat(R.styleable.AspectRatioFrameLayout_aspectRatio, DEFAULT_ASPECT_RATIO); - typedArray.recycle(); - } - } - - /* Returns aspect ratio of layout */ - public float getAspectRatio() { - return aspectRatio; - } - - /* Sets aspect ratio of layout */ - public void setAspectRatio(final float aspectRatio) { - if (Math.abs(aspectRatio - this.aspectRatio) < EPSILON) { - return; - } - - this.aspectRatio = aspectRatio; - requestLayout(); - } - - /* Returns if layout is wrapping to content but holds aspect ratio */ - - /** - * Returns if layout is wrapping to content but holds aspect ratio. - * If it is true it means that minimum size of view will equals to maximum size of it's child (biggest width or height) depends on aspect ratio. - * Else maximum size of view will equals to minimum available size which parent could give to this view depends on aspect ratio. - * - * @return True if wrapping to content. - */ - public boolean isWrapToContent() { - return wrapToContent; - } - - /** - * Sets if layout is wrapping to content but holds aspect ratio. - * - * @param wrapToContent True if wrapping to content. - */ - public void setWrapToContent(final boolean wrapToContent) { - if (wrapToContent == this.wrapToContent) { - return; - } - - this.wrapToContent = wrapToContent; - requestLayout(); - } - - private void setMeasuredDimensionWithAspectOfLesser(final int measuredWidth, final int measuredHeight) { - final float heightBasedOnMw = measuredWidth / aspectRatio; - if (heightBasedOnMw > measuredHeight) { - setMeasuredDimension((int) (measuredHeight * aspectRatio), measuredHeight); - } else { - setMeasuredDimension(measuredWidth, (int) heightBasedOnMw); - } - } - - private void setMeasuredDimensionWithAspectOfHigher(final int measuredWidth, final int measuredHeight) { - final float heightBasedOnMw = measuredWidth / aspectRatio; - if (heightBasedOnMw < measuredHeight) { - setMeasuredDimension((int) (measuredHeight * aspectRatio), measuredHeight); - } else { - setMeasuredDimension(measuredWidth, (int) heightBasedOnMw); - } - } - - @NonNull - private Point measureWrapChildren(final int widthMeasureSpec, final int heightMeasureSpec) { - final Point result = new Point(); - for (int i = 0; i < getChildCount(); i++) { - final View child = getChildAt(i); - child.measure(widthMeasureSpec, heightMeasureSpec); - if (result.x < child.getMeasuredWidth()) { - result.x = child.getMeasuredWidth(); - } - if (result.y < child.getMeasuredHeight()) { - result.y = child.getMeasuredHeight(); - } - } - return result; - } - - @Override - protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - int width = MeasureSpec.getSize(widthMeasureSpec); - int height = MeasureSpec.getSize(heightMeasureSpec); - final int widthMode = MeasureSpec.getMode(widthMeasureSpec); - final int heightMode = MeasureSpec.getMode(heightMeasureSpec); - - if (wrapToContent) { - final Point bounds = measureWrapChildren(widthMeasureSpec, heightMeasureSpec); - width = widthMode == MeasureSpec.UNSPECIFIED ? bounds.x : Math.min(bounds.x, width); - height = heightMode == MeasureSpec.UNSPECIFIED ? bounds.y : Math.min(bounds.y, height); - } - - if (widthMode == MeasureSpec.UNSPECIFIED) { - if (heightMode == MeasureSpec.UNSPECIFIED) { - measureBothUnspecified(width, height); - } else { - measureOnlyUnspecifiedWidth(width, height); - } - } else if (heightMode == MeasureSpec.UNSPECIFIED) { - measureOnlyUnspecifiedHeight(width, height); - } else { - measureBothSpecified(width, height); - } - } - - private void measureBothSpecified(final int width, final int height) { - if (wrapToContent) { - setMeasuredDimensionWithAspectOfHigher(width, height); - } else { - setMeasuredDimensionWithAspectOfLesser(width, height); - } - } - - private void measureOnlyUnspecifiedHeight(final int width, final int height) { - if (wrapToContent) { - measureWrapToContent(width, height); - } else { - setMeasuredDimension(width, (int) (width / aspectRatio)); - } - } - - private void measureWrapToContent(final int width, final int height) { - if (width < (int) (height * aspectRatio)) { - setMeasuredDimension((int) (height * aspectRatio), height); - } else { - setMeasuredDimension(width, (int) (width / aspectRatio)); - } - } - - private void measureOnlyUnspecifiedWidth(final int width, final int height) { - if (wrapToContent) { - measureWrapToContent(width, height); - } else { - setMeasuredDimension((int) (height * aspectRatio), height); - } - } - - private void measureBothUnspecified(final int width, final int height) { - if (wrapToContent) { - setMeasuredDimensionWithAspectOfHigher(width, height); - } else { - final DisplayMetrics metrics = getResources().getDisplayMetrics(); - setMeasuredDimensionWithAspectOfLesser(metrics.widthPixels, metrics.heightPixels); - } - } - - @Override - protected void onLayout(final boolean changed, final int left, final int top, final int right, final int bottom) { - for (int i = 0; i < getChildCount(); i++) { - final View child = getChildAt(i); - final ViewGroup.LayoutParams lp = child.getLayoutParams(); - final int widthMeasureSpec; - final int heightMeasureSpec; - final int width = getMeasuredWidth() - getPaddingLeft() - getPaddingRight(); - final int height = getMeasuredHeight() - getPaddingTop() - getPaddingBottom(); - switch (lp.width) { - case ViewGroup.LayoutParams.MATCH_PARENT: - widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY); - break; - case ViewGroup.LayoutParams.WRAP_CONTENT: - widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST); - break; - default: - widthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY); - break; - } - - switch (lp.height) { - case ViewGroup.LayoutParams.MATCH_PARENT: - heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); - break; - case ViewGroup.LayoutParams.WRAP_CONTENT: - heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST); - break; - default: - heightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY); - break; - } - - getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec); - } - - super.onLayout(changed, left, top, right, bottom); - } - -} \ No newline at end of file diff --git a/src/main/java/ru/touchin/roboswag/components/views/LifecycleView.java b/src/main/java/ru/touchin/roboswag/components/views/LifecycleView.java deleted file mode 100644 index 781d841..0000000 --- a/src/main/java/ru/touchin/roboswag/components/views/LifecycleView.java +++ /dev/null @@ -1,325 +0,0 @@ -/* - * Copyright (c) 2017 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.roboswag.components.views; - - -import android.content.Context; -import android.os.Parcelable; -import android.support.annotation.AttrRes; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.util.AttributeSet; -import android.widget.FrameLayout; - -import io.reactivex.Maybe; -import ru.touchin.roboswag.components.utils.BaseLifecycleBindable; -import ru.touchin.roboswag.components.utils.LifecycleBindable; -import io.reactivex.Completable; -import io.reactivex.Observable; -import io.reactivex.Single; -import io.reactivex.disposables.Disposable; -import io.reactivex.functions.Action; -import io.reactivex.functions.Consumer; - - -/** - * Created by Gavriil Sitnikov on 18/05/17. - * FrameLayout that realizes LifecycleBindable interface. - */ -@SuppressWarnings({"CPD-START", "PMD.TooManyMethods"}) -public class LifecycleView extends FrameLayout implements LifecycleBindable { - - @NonNull - private final BaseLifecycleBindable baseLifecycleBindable; - private boolean created; - private boolean started; - - public LifecycleView(@NonNull final Context context) { - super(context); - baseLifecycleBindable = new BaseLifecycleBindable(); - } - - public LifecycleView(@NonNull final Context context, @Nullable final AttributeSet attrs) { - super(context, attrs); - baseLifecycleBindable = new BaseLifecycleBindable(); - } - - public LifecycleView(@NonNull final Context context, @Nullable final AttributeSet attrs, @AttrRes final int defStyleAttr) { - super(context, attrs, defStyleAttr); - baseLifecycleBindable = new BaseLifecycleBindable(); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - onCreate(); - if (!started && getWindowSystemUiVisibility() == VISIBLE) { - onStart(); - } - } - - /** - * Calls when view attached to window and ready to use. - */ - protected void onCreate() { - created = true; - baseLifecycleBindable.onCreate(); - } - - /** - * Calls when view's window showed or state restored. - */ - protected void onStart() { - started = true; - baseLifecycleBindable.onStart(); - } - - @Override - protected void onRestoreInstanceState(@NonNull final Parcelable state) { - super.onRestoreInstanceState(state); - if (created && !started) { - onStart(); - } - } - - @NonNull - @Override - protected Parcelable onSaveInstanceState() { - started = false; - baseLifecycleBindable.onSaveInstanceState(); - return super.onSaveInstanceState(); - } - - /** - * Calls when view's window hided or state saved. - */ - protected void onStop() { - started = false; - baseLifecycleBindable.onStop(); - } - - /** - * Calls when view detached from window. - */ - protected void onDestroy() { - if (started) { - onStop(); - } - created = false; - baseLifecycleBindable.onDestroy(); - } - - @Override - protected void onDetachedFromWindow() { - onDestroy(); - super.onDetachedFromWindow(); - } - - @Override - protected void onWindowVisibilityChanged(final int visibility) { - super.onWindowVisibilityChanged(visibility); - if (visibility == VISIBLE) { - if (created && !started) { - baseLifecycleBindable.onStart(); - } - } else if (started) { - baseLifecycleBindable.onStop(); - } - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Observable observable) { - return baseLifecycleBindable.untilStop(observable); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Observable observable, @NonNull final Consumer onNextAction) { - return baseLifecycleBindable.untilStop(observable, onNextAction); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Observable observable, - @NonNull final Consumer onNextAction, - @NonNull final Consumer onErrorAction) { - return baseLifecycleBindable.untilStop(observable, onNextAction, onErrorAction); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Observable observable, - @NonNull final Consumer onNextAction, - @NonNull final Consumer onErrorAction, - @NonNull final Action onCompletedAction) { - return baseLifecycleBindable.untilStop(observable, onNextAction, onErrorAction, onCompletedAction); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Single single) { - return baseLifecycleBindable.untilStop(single); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Single single, @NonNull final Consumer onSuccessAction) { - return baseLifecycleBindable.untilStop(single, onSuccessAction); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Single single, - @NonNull final Consumer onSuccessAction, - @NonNull final Consumer onErrorAction) { - return baseLifecycleBindable.untilStop(single, onSuccessAction, onErrorAction); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Completable completable) { - return baseLifecycleBindable.untilStop(completable); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Completable completable, @NonNull final Action onCompletedAction) { - return baseLifecycleBindable.untilStop(completable, onCompletedAction); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Completable completable, - @NonNull final Action onCompletedAction, - @NonNull final Consumer onErrorAction) { - return baseLifecycleBindable.untilStop(completable, onCompletedAction, onErrorAction); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Maybe maybe) { - return baseLifecycleBindable.untilStop(maybe); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Maybe maybe, @NonNull final Consumer onSuccessAction) { - return baseLifecycleBindable.untilStop(maybe, onSuccessAction); - } - - @NonNull - @Override - public Disposable untilStop(@NonNull final Maybe maybe, - @NonNull final Consumer onSuccessAction, - @NonNull final Consumer onErrorAction) { - return baseLifecycleBindable.untilStop(maybe, onSuccessAction, onErrorAction); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Observable observable) { - return baseLifecycleBindable.untilDestroy(observable); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Observable observable, @NonNull final Consumer onNextAction) { - return baseLifecycleBindable.untilDestroy(observable, onNextAction); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Observable observable, - @NonNull final Consumer onNextAction, - @NonNull final Consumer onErrorAction) { - return baseLifecycleBindable.untilDestroy(observable, onNextAction, onErrorAction); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Observable observable, - @NonNull final Consumer onNextAction, - @NonNull final Consumer onErrorAction, - @NonNull final Action onCompletedAction) { - return baseLifecycleBindable.untilDestroy(observable, onNextAction, onErrorAction, onCompletedAction); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Single single) { - return baseLifecycleBindable.untilDestroy(single); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Single single, @NonNull final Consumer onSuccessAction) { - return baseLifecycleBindable.untilDestroy(single, onSuccessAction); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Single single, - @NonNull final Consumer onSuccessAction, - @NonNull final Consumer onErrorAction) { - return baseLifecycleBindable.untilDestroy(single, onSuccessAction, onErrorAction); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Completable completable) { - return baseLifecycleBindable.untilDestroy(completable); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Completable completable, @NonNull final Action onCompletedAction) { - return baseLifecycleBindable.untilDestroy(completable, onCompletedAction); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Completable completable, - @NonNull final Action onCompletedAction, - @NonNull final Consumer onErrorAction) { - return baseLifecycleBindable.untilDestroy(completable, onCompletedAction, onErrorAction); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Maybe maybe) { - return baseLifecycleBindable.untilDestroy(maybe); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Maybe maybe, @NonNull final Consumer onSuccessAction) { - return baseLifecycleBindable.untilDestroy(maybe, onSuccessAction); - } - - @NonNull - @Override - public Disposable untilDestroy(@NonNull final Maybe maybe, - @NonNull final Consumer onSuccessAction, - @NonNull final Consumer onErrorAction) { - return baseLifecycleBindable.untilDestroy(maybe, onSuccessAction, onErrorAction); - } - -} diff --git a/src/main/res/values/attrs.xml b/src/main/res/values/attrs.xml index 8e9c7e8..7320080 100644 --- a/src/main/res/values/attrs.xml +++ b/src/main/res/values/attrs.xml @@ -19,11 +19,6 @@ - - - - - @@ -31,4 +26,4 @@ - \ No newline at end of file + From dc87783252de7d2512abc9728d8587fa6957981a Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Wed, 14 Mar 2018 16:25:06 +0300 Subject: [PATCH 41/95] Renaming BindableViewHolder to LifecycleViewHolder --- .../roboswag/components/adapters/AdapterDelegate.java | 4 ++-- .../roboswag/components/adapters/ItemAdapterDelegate.java | 6 +++--- .../{BindableViewHolder.kt => LifecycleViewHolder.kt} | 2 +- .../components/adapters/PositionAdapterDelegate.java | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) rename src/main/java/ru/touchin/roboswag/components/adapters/{BindableViewHolder.kt => LifecycleViewHolder.kt} (97%) diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/AdapterDelegate.java b/src/main/java/ru/touchin/roboswag/components/adapters/AdapterDelegate.java index 8b38563..3c70482 100644 --- a/src/main/java/ru/touchin/roboswag/components/adapters/AdapterDelegate.java +++ b/src/main/java/ru/touchin/roboswag/components/adapters/AdapterDelegate.java @@ -28,9 +28,9 @@ import android.view.ViewGroup; * Objects of such class controls creation and binding of specific type of RecyclerView's ViewHolders. * Default {@link #getItemViewType} is generating on construction of object. * - * @param Type of {@link BindableViewHolder} of delegate. + * @param Type of {@link LifecycleViewHolder} of delegate. */ -public abstract class AdapterDelegate { +public abstract class AdapterDelegate { private final int defaultItemViewType = ViewCompat.generateViewId(); @NonNull diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/ItemAdapterDelegate.java b/src/main/java/ru/touchin/roboswag/components/adapters/ItemAdapterDelegate.java index a7f27d4..4618479 100644 --- a/src/main/java/ru/touchin/roboswag/components/adapters/ItemAdapterDelegate.java +++ b/src/main/java/ru/touchin/roboswag/components/adapters/ItemAdapterDelegate.java @@ -11,10 +11,10 @@ import java.util.List; * Such delegates are creating and binding ViewHolders for specific items. * Default {@link #getItemViewType} is generating on construction of object. * - * @param Type of {@link LifecycleOwner} of delegate; - * @param Type of items to bind to {@link LifecycleOwner}s. + * @param Type of {@link LifecycleViewHolder} of delegate; + * @param Type of items to bind to {@link LifecycleViewHolder}s. */ -public abstract class ItemAdapterDelegate extends AdapterDelegate { +public abstract class ItemAdapterDelegate extends AdapterDelegate { public ItemAdapterDelegate(@NonNull final LifecycleOwner lifecycleOwner) { super(lifecycleOwner); diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/BindableViewHolder.kt b/src/main/java/ru/touchin/roboswag/components/adapters/LifecycleViewHolder.kt similarity index 97% rename from src/main/java/ru/touchin/roboswag/components/adapters/BindableViewHolder.kt rename to src/main/java/ru/touchin/roboswag/components/adapters/LifecycleViewHolder.kt index 9222794..42b4931 100644 --- a/src/main/java/ru/touchin/roboswag/components/adapters/BindableViewHolder.kt +++ b/src/main/java/ru/touchin/roboswag/components/adapters/LifecycleViewHolder.kt @@ -28,7 +28,7 @@ import android.view.View * ViewHolder that implements {@link LifecycleOwner} and uses parent lifecycle * object as bridge ([android.app.Activity], [android.support.v4.app.Fragment] etc.). */ -open class BindableViewHolder( +open class LifecycleViewHolder( private val lifecycleOwner: LifecycleOwner, itemView: View ) : RecyclerView.ViewHolder(itemView), LifecycleOwner by lifecycleOwner diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/PositionAdapterDelegate.java b/src/main/java/ru/touchin/roboswag/components/adapters/PositionAdapterDelegate.java index 5fc5f4a..1271d82 100644 --- a/src/main/java/ru/touchin/roboswag/components/adapters/PositionAdapterDelegate.java +++ b/src/main/java/ru/touchin/roboswag/components/adapters/PositionAdapterDelegate.java @@ -11,9 +11,9 @@ import java.util.List; * Such delegates are creating and binding ViewHolders by position in adapter. * Default {@link #getItemViewType} is generating on construction of object. * - * @param Type of {@link BindableViewHolder} of delegate. + * @param Type of {@link LifecycleViewHolder} of delegate. */ -public abstract class PositionAdapterDelegate extends AdapterDelegate { +public abstract class PositionAdapterDelegate extends AdapterDelegate { public PositionAdapterDelegate(@NonNull final LifecycleOwner lifecycleOwner) { super(lifecycleOwner); From b64e1c9e3593d4d4be81184f257d9d32917c5e49 Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Wed, 14 Mar 2018 19:58:18 +0300 Subject: [PATCH 42/95] Added empty Parcelable state and state getter in ViewController class --- .../fragments/ViewControllerFragment.java | 8 ++++---- .../navigation/viewcontrollers/EmptyState.kt | 19 +++++++++++++++++++ .../{ => viewcontrollers}/ViewController.java | 18 ++++++++++++++++-- .../ViewControllerNavigation.kt | 19 ++++++++++--------- 4 files changed, 49 insertions(+), 15 deletions(-) create mode 100644 src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/EmptyState.kt rename src/main/java/ru/touchin/roboswag/components/navigation/{ => viewcontrollers}/ViewController.java (95%) rename src/main/java/ru/touchin/roboswag/components/navigation/{ => viewcontrollers}/ViewControllerNavigation.kt (92%) diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java b/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java index 977cd29..a63a0be 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java +++ b/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java @@ -39,7 +39,7 @@ import android.widget.FrameLayout; import java.lang.reflect.Constructor; -import ru.touchin.roboswag.components.navigation.ViewController; +import ru.touchin.roboswag.components.navigation.viewcontrollers.ViewController; import ru.touchin.roboswag.components.utils.UiUtils; import ru.touchin.roboswag.core.log.Lc; import ru.touchin.roboswag.core.utils.ShouldNotHappenException; @@ -51,7 +51,7 @@ import ru.touchin.roboswag.core.utils.ShouldNotHappenException; * @param Type of object which is representing it's fragment state; * @param Type of {@link FragmentActivity} where fragment could be attached to. */ -public abstract class ViewControllerFragment extends ViewFragment { +public class ViewControllerFragment extends ViewFragment { private static final String VIEW_CONTROLLER_CLASS_EXTRA = "VIEW_CONTROLLER_CLASS_EXTRA"; private static final String VIEW_CONTROLLER_STATE_EXTRA = "VIEW_CONTROLLER_STATE_EXTRA"; @@ -105,7 +105,7 @@ public abstract class ViewControllerFragment>> viewControllerClass; + private Class, TState>> viewControllerClass; private TState state; /** @@ -125,7 +125,7 @@ public abstract class ViewControllerFragment>>) + viewControllerClass = (Class, TState>>) getArguments().getSerializable(VIEW_CONTROLLER_CLASS_EXTRA); state = savedInstanceState != null ? savedInstanceState.getParcelable(VIEW_CONTROLLER_STATE_EXTRA) diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/EmptyState.kt b/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/EmptyState.kt new file mode 100644 index 0000000..12424bc --- /dev/null +++ b/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/EmptyState.kt @@ -0,0 +1,19 @@ +package ru.touchin.roboswag.components.navigation.viewcontrollers + +import android.os.Parcel +import android.os.Parcelable + +object EmptyState : Parcelable { + + override fun writeToParcel(parcel: Parcel, flags: Int) = Unit + + override fun describeContents() = 0 + + @JvmField + val CREATOR = object : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel) = EmptyState + + override fun newArray(size: Int): Array = arrayOfNulls(size) + } + +} diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/ViewController.java b/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java similarity index 95% rename from src/main/java/ru/touchin/roboswag/components/navigation/ViewController.java rename to src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java index a29f0a0..ab3a618 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/ViewController.java +++ b/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java @@ -17,13 +17,14 @@ * */ -package ru.touchin.roboswag.components.navigation; +package ru.touchin.roboswag.components.navigation.viewcontrollers; import android.arch.lifecycle.Lifecycle; import android.arch.lifecycle.LifecycleOwner; import android.arch.lifecycle.LifecycleRegistry; import android.content.Intent; import android.os.Bundle; +import android.os.Parcelable; import android.support.annotation.CallSuper; import android.support.annotation.IdRes; import android.support.annotation.LayoutRes; @@ -48,7 +49,10 @@ import ru.touchin.roboswag.core.log.Lc; * @param Type of activity where such {@link ViewController} could be; * @param Type of fragment where such {@link ViewController} could be; */ -public class ViewController> implements LifecycleOwner { +public class ViewController< + TActivity extends FragmentActivity, + TFragment extends ViewControllerFragment, + TState extends Parcelable> implements LifecycleOwner { @NonNull private final LifecycleRegistry lifecycleRegistry = new LifecycleRegistry(this); @@ -83,6 +87,16 @@ public class ViewController( * @param TState Type of state of fragment. */ fun pushViewController( - viewControllerClass: Class>>, + viewControllerClass: Class, TState>>, state: TState, transactionSetup: Function? = null ) { @@ -67,7 +68,7 @@ open class ViewControllerNavigation( * @param TState Type of state of fragment. */ fun pushSingleViewController( - viewControllerClass: Class>>, + viewControllerClass: Class, TState>>, state: TState, transactionSetup: Function? = null ) { @@ -86,7 +87,7 @@ open class ViewControllerNavigation( * @param TTargetFragment Type of target fragment. */ fun pushViewControllerForResult( - viewControllerClass: Class>>, + viewControllerClass: Class, TState>>, state: TState, targetFragment: TTargetFragment, transactionSetup: Function? = null @@ -96,7 +97,7 @@ open class ViewControllerNavigation( targetFragment, true, state, - viewControllerClass.name + ';'.toString() + FragmentNavigation.WITH_TARGET_FRAGMENT_TAG_MARK, + viewControllerClass.name + ';'.toString() + WITH_TARGET_FRAGMENT_TAG_MARK, transactionSetup ) } @@ -111,7 +112,7 @@ open class ViewControllerNavigation( * @param TState Type of state of fragment. */ fun setViewControllerAsTop( - viewControllerClass: Class>>, + viewControllerClass: Class, TState>>, state: TState, transactionSetup: Function? = null ) { @@ -120,7 +121,7 @@ open class ViewControllerNavigation( null, true, state, - viewControllerClass.name + ';'.toString() + FragmentNavigation.TOP_FRAGMENT_TAG_MARK, + viewControllerClass.name + ';'.toString() + TOP_FRAGMENT_TAG_MARK, transactionSetup ) } @@ -135,7 +136,7 @@ open class ViewControllerNavigation( * @param TState Type of state of fragment. */ fun setInitialViewController( - viewControllerClass: Class>>, + viewControllerClass: Class, TState>>, state: TState, transactionSetup: Function? = null ) { @@ -154,7 +155,7 @@ open class ViewControllerNavigation( * @param TState Type of state of fragment. */ protected fun addViewControllerToStack( - viewControllerClass: Class>>, + viewControllerClass: Class, TState>>, targetFragment: Fragment?, addToStack: Boolean, state: TState, From e3e978b7f7ed18befad97d6d30a2c826fa0140de Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Thu, 15 Mar 2018 11:26:38 +0300 Subject: [PATCH 43/95] LifecycleViewHolder removed --- .../components/adapters/AdapterDelegate.java | 22 ++---------- .../adapters/ItemAdapterDelegate.java | 12 +++---- .../adapters/LifecycleViewHolder.kt | 34 ------------------- .../adapters/PositionAdapterDelegate.java | 10 ++---- 4 files changed, 10 insertions(+), 68 deletions(-) delete mode 100644 src/main/java/ru/touchin/roboswag/components/adapters/LifecycleViewHolder.kt diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/AdapterDelegate.java b/src/main/java/ru/touchin/roboswag/components/adapters/AdapterDelegate.java index 3c70482..ee6155c 100644 --- a/src/main/java/ru/touchin/roboswag/components/adapters/AdapterDelegate.java +++ b/src/main/java/ru/touchin/roboswag/components/adapters/AdapterDelegate.java @@ -19,36 +19,20 @@ package ru.touchin.roboswag.components.adapters; -import android.arch.lifecycle.LifecycleOwner; import android.support.annotation.NonNull; import android.support.v4.view.ViewCompat; +import android.support.v7.widget.RecyclerView; import android.view.ViewGroup; /** * Objects of such class controls creation and binding of specific type of RecyclerView's ViewHolders. * Default {@link #getItemViewType} is generating on construction of object. * - * @param Type of {@link LifecycleViewHolder} of delegate. + * @param Type of {@link RecyclerView.ViewHolder} of delegate. */ -public abstract class AdapterDelegate { +public abstract class AdapterDelegate { private final int defaultItemViewType = ViewCompat.generateViewId(); - @NonNull - private final LifecycleOwner lifecycleOwner; - - public AdapterDelegate(@NonNull final LifecycleOwner lifecycleOwner) { - this.lifecycleOwner = lifecycleOwner; - } - - /** - * Returns parent {@link LifecycleOwner} that this delegate created from (e.g. Activity, Fragment or ViewController). - * - * @return Parent {@link LifecycleOwner}. - */ - @NonNull - public LifecycleOwner getLifecycleOwner() { - return lifecycleOwner; - } /** * Unique ID of AdapterDelegate. diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/ItemAdapterDelegate.java b/src/main/java/ru/touchin/roboswag/components/adapters/ItemAdapterDelegate.java index 4618479..b7a3a44 100644 --- a/src/main/java/ru/touchin/roboswag/components/adapters/ItemAdapterDelegate.java +++ b/src/main/java/ru/touchin/roboswag/components/adapters/ItemAdapterDelegate.java @@ -1,7 +1,7 @@ package ru.touchin.roboswag.components.adapters; -import android.arch.lifecycle.LifecycleOwner; import android.support.annotation.NonNull; +import android.support.v7.widget.RecyclerView; import android.view.ViewGroup; import java.util.List; @@ -11,14 +11,10 @@ import java.util.List; * Such delegates are creating and binding ViewHolders for specific items. * Default {@link #getItemViewType} is generating on construction of object. * - * @param Type of {@link LifecycleViewHolder} of delegate; - * @param Type of items to bind to {@link LifecycleViewHolder}s. + * @param Type of {@link RecyclerView.ViewHolder} of delegate; + * @param Type of items to bind to {@link RecyclerView.ViewHolder}s. */ -public abstract class ItemAdapterDelegate extends AdapterDelegate { - - public ItemAdapterDelegate(@NonNull final LifecycleOwner lifecycleOwner) { - super(lifecycleOwner); - } +public abstract class ItemAdapterDelegate extends AdapterDelegate { /** * Returns if object is processable by this delegate. diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/LifecycleViewHolder.kt b/src/main/java/ru/touchin/roboswag/components/adapters/LifecycleViewHolder.kt deleted file mode 100644 index 42b4931..0000000 --- a/src/main/java/ru/touchin/roboswag/components/adapters/LifecycleViewHolder.kt +++ /dev/null @@ -1,34 +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.roboswag.components.adapters - -import android.arch.lifecycle.LifecycleOwner -import android.support.v7.widget.RecyclerView -import android.view.View - -/** - * Created by Denis Karmyshakov 14.03.2018. - * ViewHolder that implements {@link LifecycleOwner} and uses parent lifecycle - * object as bridge ([android.app.Activity], [android.support.v4.app.Fragment] etc.). - */ -open class LifecycleViewHolder( - private val lifecycleOwner: LifecycleOwner, - itemView: View -) : RecyclerView.ViewHolder(itemView), LifecycleOwner by lifecycleOwner diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/PositionAdapterDelegate.java b/src/main/java/ru/touchin/roboswag/components/adapters/PositionAdapterDelegate.java index 1271d82..ac83c51 100644 --- a/src/main/java/ru/touchin/roboswag/components/adapters/PositionAdapterDelegate.java +++ b/src/main/java/ru/touchin/roboswag/components/adapters/PositionAdapterDelegate.java @@ -1,7 +1,7 @@ package ru.touchin.roboswag.components.adapters; -import android.arch.lifecycle.LifecycleOwner; import android.support.annotation.NonNull; +import android.support.v7.widget.RecyclerView; import android.view.ViewGroup; import java.util.List; @@ -11,13 +11,9 @@ import java.util.List; * Such delegates are creating and binding ViewHolders by position in adapter. * Default {@link #getItemViewType} is generating on construction of object. * - * @param Type of {@link LifecycleViewHolder} of delegate. + * @param Type of {@link RecyclerView.ViewHolder} of delegate. */ -public abstract class PositionAdapterDelegate extends AdapterDelegate { - - public PositionAdapterDelegate(@NonNull final LifecycleOwner lifecycleOwner) { - super(lifecycleOwner); - } +public abstract class PositionAdapterDelegate extends AdapterDelegate { /** * Returns if object is processable by this delegate. From 69f1c9af4a914314bfa9c6281adbaa8f12160cfe Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Fri, 16 Mar 2018 17:54:36 +0300 Subject: [PATCH 44/95] Added DelegationListAdapter, small refactor of delegates --- .../components/adapters/AdapterDelegate.java | 41 +++++++ .../components/adapters/DelegatesManager.kt | 60 ++++++++++ .../adapters/DelegationListAdapter.kt | 82 ++++++++++++++ .../adapters/ItemAdapterDelegate.java | 86 +++++++------- .../adapters/OffsetAdapterUpdateCallback.kt | 24 ++++ .../adapters/PositionAdapterDelegate.java | 52 +++++---- .../roboswag/components/utils/Typefaces.java | 105 ------------------ 7 files changed, 278 insertions(+), 172 deletions(-) create mode 100644 src/main/java/ru/touchin/roboswag/components/adapters/DelegatesManager.kt create mode 100644 src/main/java/ru/touchin/roboswag/components/adapters/DelegationListAdapter.kt create mode 100644 src/main/java/ru/touchin/roboswag/components/adapters/OffsetAdapterUpdateCallback.kt delete mode 100644 src/main/java/ru/touchin/roboswag/components/utils/Typefaces.java diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/AdapterDelegate.java b/src/main/java/ru/touchin/roboswag/components/adapters/AdapterDelegate.java index ee6155c..25be0c9 100644 --- a/src/main/java/ru/touchin/roboswag/components/adapters/AdapterDelegate.java +++ b/src/main/java/ru/touchin/roboswag/components/adapters/AdapterDelegate.java @@ -24,6 +24,8 @@ import android.support.v4.view.ViewCompat; import android.support.v7.widget.RecyclerView; import android.view.ViewGroup; +import java.util.List; + /** * Objects of such class controls creation and binding of specific type of RecyclerView's ViewHolders. * Default {@link #getItemViewType} is generating on construction of object. @@ -43,6 +45,28 @@ public abstract class AdapterDelegate items, final int adapterPosition, final int collectionPosition); + + /** + * Returns unique ID of item to support stable ID's logic of RecyclerView's adapter. + * + * @param items Items in adapter; + * @param adapterPosition Position of item in adapter; + * @param collectionPosition Position of item in collection; + * @return Unique item ID. + */ + public long getItemId(@NonNull final List items, final int adapterPosition, final int collectionPosition) { + return 0; + } + /** * Creates ViewHolder to bind item to it later. * @@ -52,4 +76,21 @@ public abstract class AdapterDelegate items, + final int adapterPosition, + final int collectionPosition, + @NonNull final List payloads + ); + } diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/DelegatesManager.kt b/src/main/java/ru/touchin/roboswag/components/adapters/DelegatesManager.kt new file mode 100644 index 0000000..bb70eb3 --- /dev/null +++ b/src/main/java/ru/touchin/roboswag/components/adapters/DelegatesManager.kt @@ -0,0 +1,60 @@ +package ru.touchin.roboswag.components.adapters + +import android.support.v7.widget.RecyclerView +import android.util.SparseArray +import android.view.ViewGroup + +/** + * Manager for delegation callbacks from [RecyclerView.Adapter] to delegates. + */ +class DelegatesManager { + + private val delegates = SparseArray>() + + fun getItemViewType(items: List<*>, adapterPosition: Int, collectionPosition: Int): Int { + for (index in 0 until delegates.size()) { + val delegate = delegates.valueAt(index) + if (delegate.isForViewType(items, adapterPosition, collectionPosition)) { + return delegate.itemViewType + } + } + throw IllegalStateException("Delegate not found") + } + + fun getItemId(items: List<*>, adapterPosition: Int, collectionPosition: Int): Long { + val delegate = getDelegate(getItemViewType(items, adapterPosition, collectionPosition)) + return delegate.getItemId(items, adapterPosition, collectionPosition) + } + + fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder = getDelegate(viewType).onCreateViewHolder(parent) + + fun onBindViewHolder(holder: RecyclerView.ViewHolder, items: List<*>, adapterPosition: Int, collectionPosition: Int, payloads: List) { + val delegate = getDelegate(getItemViewType(items, adapterPosition, collectionPosition)) + delegate.onBindViewHolder(holder, items, adapterPosition, collectionPosition, payloads) + } + + /** + * Adds [ItemAdapterDelegate] to adapter. + * + * @param delegate Delegate to add. + */ + fun addDelegate(delegate: ItemAdapterDelegate<*, *>) = delegates.put(delegate.itemViewType, delegate) + + /** + * Adds [PositionAdapterDelegate] to adapter. + * + * @param delegate Delegate to add. + */ + fun addDelegate(delegate: PositionAdapterDelegate<*>) = delegates.put(delegate.itemViewType, delegate) + + /** + * Removes [AdapterDelegate] from adapter. + * + * @param delegate Delegate to remove. + */ + fun removeDelegate(delegate: AdapterDelegate<*>) = delegates.remove(delegate.itemViewType) + + private fun getDelegate(viewType: Int): AdapterDelegate<*> = + delegates[viewType] ?: throw IllegalStateException("No AdapterDelegate added for view type: $viewType") + +} diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/DelegationListAdapter.kt b/src/main/java/ru/touchin/roboswag/components/adapters/DelegationListAdapter.kt new file mode 100644 index 0000000..24e56fb --- /dev/null +++ b/src/main/java/ru/touchin/roboswag/components/adapters/DelegationListAdapter.kt @@ -0,0 +1,82 @@ +package ru.touchin.roboswag.components.adapters + +import android.support.v7.recyclerview.extensions.AsyncDifferConfig +import android.support.v7.recyclerview.extensions.AsyncListDiffer +import android.support.v7.util.DiffUtil +import android.support.v7.widget.RecyclerView +import android.view.ViewGroup + +/** + * Base adapter with delegation and diff computing on background thread. + */ +open class DelegationListAdapter(diffCallback: DiffUtil.ItemCallback) : RecyclerView.Adapter() { + + var itemClickListener: ((TItem, RecyclerView.ViewHolder) -> Unit)? = null + + private val delegatesManager = DelegatesManager() + private var differ = AsyncListDiffer(OffsetAdapterUpdateCallback(this, ::getHeadersCount), AsyncDifferConfig.Builder(diffCallback).build()) + + open fun getHeadersCount(): Int = 0 + + open fun getFootersCount(): Int = 0 + + override fun getItemCount(): Int = getHeadersCount() + differ.currentList.size + getFootersCount() + + override fun getItemViewType(position: Int): Int = delegatesManager.getItemViewType(getList(), position, getCollectionPosition(position)) + + override fun getItemId(position: Int): Long = delegatesManager.getItemId(getList(), position, getCollectionPosition(position)) + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder = delegatesManager.onCreateViewHolder(parent, viewType) + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int, payloads: List) { + val collectionPosition = getCollectionPosition(position) + if (itemClickListener != null && collectionPosition in 0 until getList().size) { + holder.itemView.setOnClickListener { itemClickListener?.invoke(getList()[collectionPosition], holder) } + } else { + holder.itemView.setOnClickListener(null) + } + delegatesManager.onBindViewHolder(holder, getList(), position, collectionPosition, payloads) + } + + final override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) = Unit + + /** + * Adds [ItemAdapterDelegate] to adapter. + * + * @param delegate Delegate to add. + */ + fun addDelegate(delegate: ItemAdapterDelegate<*, *>) = delegatesManager.addDelegate(delegate) + + /** + * Adds [PositionAdapterDelegate] to adapter. + * + * @param delegate Delegate to add. + */ + fun addDelegate(delegate: PositionAdapterDelegate<*>) = delegatesManager.addDelegate(delegate) + + /** + * Submits a new list to be diffed, and displayed. + * + * If a list is already being displayed, a diff will be computed on a background thread, which + * will dispatch Adapter.notifyItem events on the main thread. + * + * @param list The new list to be displayed. + */ + fun submitList(list: List) = differ.submitList(list) + + /** + * Get the current List - any diffing to present this list has already been computed and + * dispatched via the ListUpdateCallback. + *

+ * If a null List, or no List has been submitted, an empty list will be returned. + *

+ * The returned list may not be mutated - mutations to content must be done through + * {@link #submitList(List)}. + * + * @return current List. + */ + fun getList(): List = differ.currentList + + fun getCollectionPosition(adapterPosition: Int): Int = adapterPosition - getHeadersCount() + +} diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/ItemAdapterDelegate.java b/src/main/java/ru/touchin/roboswag/components/adapters/ItemAdapterDelegate.java index b7a3a44..44a9902 100644 --- a/src/main/java/ru/touchin/roboswag/components/adapters/ItemAdapterDelegate.java +++ b/src/main/java/ru/touchin/roboswag/components/adapters/ItemAdapterDelegate.java @@ -2,7 +2,6 @@ package ru.touchin.roboswag.components.adapters; import android.support.annotation.NonNull; import android.support.v7.widget.RecyclerView; -import android.view.ViewGroup; import java.util.List; @@ -14,72 +13,73 @@ import java.util.List; * @param Type of {@link RecyclerView.ViewHolder} of delegate; * @param Type of items to bind to {@link RecyclerView.ViewHolder}s. */ +@SuppressWarnings("unchecked") public abstract class ItemAdapterDelegate extends AdapterDelegate { + @Override + public boolean isForViewType(@NonNull final List items, final int adapterPosition, final int collectionPosition) { + return collectionPosition >= 0 + && collectionPosition < items.size() + && isForViewType(items.get(collectionPosition), adapterPosition, collectionPosition); + } + /** * Returns if object is processable by this delegate. - * This item will be casted to {@link TItem} and passes to {@link #onBindViewHolder(TViewHolder, TItem, int, int)}. + * This item will be casted to {@link TItem} and passes to {@link #onBindViewHolder(TViewHolder, TItem, int, int, List)}. * - * @param item Item to check; - * @param positionInAdapter Position of item in adapter; - * @param itemCollectionPosition Position of item in collection that contains item; + * @param item Item to check; + * @param adapterPosition Position of item in adapter; + * @param collectionPosition Position of item in collection that contains item; * @return True if item is processable by this delegate. */ - public abstract boolean isForViewType(@NonNull final Object item, final int positionInAdapter, final int itemCollectionPosition); + public boolean isForViewType(@NonNull final Object item, final int adapterPosition, final int collectionPosition) { + return true; + } + + @Override + public long getItemId(@NonNull final List items, final int adapterPosition, final int collectionPosition) { + return getItemId((TItem) items.get(collectionPosition), adapterPosition, collectionPosition); + } /** * Returns unique ID of item to support stable ID's logic of RecyclerView's adapter. * - * @param item Item to check; - * @param positionInAdapter Position of item in adapter; - * @param positionInCollection Position of item in collection that contains item; + * @param item Item in adapter; + * @param adapterPosition Position of item in adapter; + * @param collectionPosition Position of item in collection that contains item; * @return Unique item ID. */ - public long getItemId(@NonNull final TItem item, final int positionInAdapter, final int positionInCollection) { + public long getItemId(@NonNull final TItem item, final int adapterPosition, final int collectionPosition) { return 0; } - /** - * Creates ViewHolder to bind item to it later. - * - * @param parent Container of ViewHolder's view. - * @return New ViewHolder. - */ - @NonNull - public abstract TViewHolder onCreateViewHolder(@NonNull final ViewGroup parent); - - /** - * Binds item to created by this object ViewHolder. - * - * @param holder ViewHolder to bind item to; - * @param item Item to check; - * @param positionInAdapter Position of item in adapter; - * @param positionInCollection Position of item in collection that contains item; - */ - public abstract void onBindViewHolder( - @NonNull final TViewHolder holder, - @NonNull final TItem item, - final int positionInAdapter, - final int positionInCollection - ); + @Override + public void onBindViewHolder( + @NonNull final RecyclerView.ViewHolder holder, + @NonNull final List items, + final int adapterPosition, + final int collectionPosition, + @NonNull final List payloads + ) { + //noinspection unchecked + onBindViewHolder((TViewHolder) holder, (TItem) items.get(collectionPosition), adapterPosition, collectionPosition, payloads); + } /** * Binds item with payloads to created by this object ViewHolder. * * @param holder ViewHolder to bind item to; - * @param item Item to check; + * @param item Item in adapter; + * @param adapterPosition Position of item in adapter; + * @param collectionPosition Position of item in collection that contains item; * @param payloads Payloads; - * @param positionInAdapter Position of item in adapter; - * @param positionInCollection Position of item in collection that contains item; */ - public void onBindViewHolder( + public abstract void onBindViewHolder( @NonNull final TViewHolder holder, @NonNull final TItem item, - @NonNull final List payloads, - final int positionInAdapter, - final int positionInCollection - ) { - //do nothing by default - } + final int adapterPosition, + final int collectionPosition, + @NonNull final List payloads + ); } diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/OffsetAdapterUpdateCallback.kt b/src/main/java/ru/touchin/roboswag/components/adapters/OffsetAdapterUpdateCallback.kt new file mode 100644 index 0000000..73bc999 --- /dev/null +++ b/src/main/java/ru/touchin/roboswag/components/adapters/OffsetAdapterUpdateCallback.kt @@ -0,0 +1,24 @@ +package ru.touchin.roboswag.components.adapters + +import android.support.v7.util.ListUpdateCallback +import android.support.v7.widget.RecyclerView + +class OffsetAdapterUpdateCallback(private val adapter: RecyclerView.Adapter<*>, private val offsetProvider: () -> Int) : ListUpdateCallback { + + override fun onInserted(position: Int, count: Int) { + adapter.notifyItemInserted(position + offsetProvider()) + } + + override fun onRemoved(position: Int, count: Int) { + adapter.notifyItemRangeRemoved(position + offsetProvider(), count) + } + + override fun onMoved(fromPosition: Int, toPosition: Int) { + adapter.notifyItemMoved(fromPosition + offsetProvider(), toPosition + offsetProvider()) + } + + override fun onChanged(position: Int, count: Int, payload: Any?) { + adapter.notifyItemRangeChanged(position + offsetProvider(), count, payload) + } + +} diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/PositionAdapterDelegate.java b/src/main/java/ru/touchin/roboswag/components/adapters/PositionAdapterDelegate.java index ac83c51..ada8888 100644 --- a/src/main/java/ru/touchin/roboswag/components/adapters/PositionAdapterDelegate.java +++ b/src/main/java/ru/touchin/roboswag/components/adapters/PositionAdapterDelegate.java @@ -2,7 +2,6 @@ package ru.touchin.roboswag.components.adapters; import android.support.annotation.NonNull; import android.support.v7.widget.RecyclerView; -import android.view.ViewGroup; import java.util.List; @@ -15,49 +14,54 @@ import java.util.List; */ public abstract class PositionAdapterDelegate extends AdapterDelegate { + @Override + public boolean isForViewType(@NonNull final List items, final int adapterPosition, final int collectionPosition) { + return isForViewType(adapterPosition); + } + /** * Returns if object is processable by this delegate. * - * @param positionInAdapter Position of item in adapter; + * @param adapterPosition Position of item in adapter; * @return True if item is processable by this delegate. */ - public abstract boolean isForViewType(final int positionInAdapter); + public abstract boolean isForViewType(final int adapterPosition); + + @Override + public long getItemId(@NonNull final List objects, final int adapterPosition, final int itemsOffset) { + return getItemId(adapterPosition); + } /** * Returns unique ID of item to support stable ID's logic of RecyclerView's adapter. * - * @param positionInAdapter Position of item in adapter; + * @param adapterPosition Position of item in adapter; * @return Unique item ID. */ - public long getItemId(final int positionInAdapter) { + public long getItemId(final int adapterPosition) { return 0; } - /** - * Creates ViewHolder to bind position to it later. - * - * @param parent Container of ViewHolder's view. - * @return New ViewHolder. - */ - @NonNull - public abstract TViewHolder onCreateViewHolder(@NonNull final ViewGroup parent); - - /** - * Binds position to ViewHolder. - * - * @param holder ViewHolder to bind position to; - * @param positionInAdapter Position of item in adapter. - */ - public abstract void onBindViewHolder(@NonNull final TViewHolder holder, final int positionInAdapter); + @Override + public void onBindViewHolder( + @NonNull final RecyclerView.ViewHolder holder, + @NonNull final List items, + final int adapterPosition, + final int collectionPosition, + @NonNull final List payloads + ) { + //noinspection unchecked + onBindViewHolder((TViewHolder) holder, adapterPosition, payloads); + } /** * Binds position with payloads to ViewHolder. * * @param holder ViewHolder to bind position to; - * @param payloads Payloads; - * @param positionInAdapter Position of item in adapter. + * @param adapterPosition Position of item in adapter; + * @param payloads Payloads. */ - public void onBindViewHolder(@NonNull final TViewHolder holder, @NonNull final List payloads, final int positionInAdapter) { + public void onBindViewHolder(@NonNull final TViewHolder holder, final int adapterPosition, @NonNull final List payloads) { //do nothing by default } diff --git a/src/main/java/ru/touchin/roboswag/components/utils/Typefaces.java b/src/main/java/ru/touchin/roboswag/components/utils/Typefaces.java deleted file mode 100644 index d8be010..0000000 --- a/src/main/java/ru/touchin/roboswag/components/utils/Typefaces.java +++ /dev/null @@ -1,105 +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.roboswag.components.utils; - -import android.content.Context; -import android.content.res.AssetManager; -import android.content.res.TypedArray; -import android.graphics.Typeface; -import android.support.annotation.NonNull; -import android.support.annotation.StyleableRes; -import android.util.AttributeSet; - -import java.io.IOException; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import ru.touchin.roboswag.core.log.Lc; -import ru.touchin.roboswag.core.utils.ShouldNotHappenException; - -/** - * Created by Gavriil Sitnikov on 18/07/2014. - * Manager for typefaces stored in 'assets/fonts' folder. - */ -public final class Typefaces { - - private static final Map TYPEFACES_MAP = new HashMap<>(); - - /** - * Returns {@link Typeface} by name from assets 'fonts' folder. - * - * @param context Context of assets where typeface file stored in; - * @param name Full name of typeface (without extension, e.g. 'Roboto-Regular'); - * @return {@link Typeface} from assets. - */ - @NonNull - public static Typeface getByName(@NonNull final Context context, @NonNull final String name) { - synchronized (TYPEFACES_MAP) { - Typeface result = TYPEFACES_MAP.get(name); - if (result == null) { - final AssetManager assetManager = context.getAssets(); - result = Typeface.DEFAULT; - try { - final List fonts = Arrays.asList(assetManager.list("fonts")); - if (fonts.contains(name + ".ttf")) { - result = Typeface.createFromAsset(assetManager, "fonts/" + name + ".ttf"); - } else if (fonts.contains(name + ".otf")) { - result = Typeface.createFromAsset(assetManager, "fonts/" + name + ".otf"); - } else { - Lc.assertion("Can't find .otf or .ttf file in assets folder 'fonts' with name: " + name); - } - } catch (final IOException exception) { - Lc.assertion(new ShouldNotHappenException("Can't get font " + name + '.' - + "Did you forget to create assets folder named 'fonts'?", exception)); - } - TYPEFACES_MAP.put(name, result); - } - return result; - } - } - - /** - * Returns {@link Typeface} by name from assets 'fonts' folder. - * - * @param context Context of assets where typeface file stored in; - * @param attrs Attributes of view to get font from; - * @param styleableId Id of attribute set; - * @param attributeId Id of attribute; - * @return {@link Typeface} from assets. - */ - @NonNull - public static Typeface getFromAttributes(@NonNull final Context context, @NonNull final AttributeSet attrs, - @NonNull @StyleableRes final int[] styleableId, @StyleableRes final int attributeId) { - final TypedArray typedArray = context.obtainStyledAttributes(attrs, styleableId); - final String customTypeface = typedArray.getString(attributeId); - typedArray.recycle(); - if (customTypeface != null) { - return getByName(context, customTypeface); - } - Lc.w("Couldn't find custom typeface. Returns default"); - return Typeface.DEFAULT; - } - - private Typefaces() { - } - -} \ No newline at end of file From 5f6152050b6148851acbeddbfd1db6ea36495ad6 Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Mon, 19 Mar 2018 12:29:25 +0300 Subject: [PATCH 45/95] Navigation refactor, DelegationListAdapter fix --- .../components/adapters/DelegatesManager.kt | 11 +- .../adapters/DelegationListAdapter.kt | 10 +- .../navigation/FragmentNavigation.java | 433 ------------------ .../navigation/FragmentNavigation.kt | 261 +++++++++++ .../fragments/ViewControllerFragment.java | 22 +- .../navigation/fragments/ViewFragment.java | 5 +- .../ViewControllerNavigation.kt | 84 ++-- 7 files changed, 318 insertions(+), 508 deletions(-) delete mode 100644 src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.java create mode 100644 src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.kt diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/DelegatesManager.kt b/src/main/java/ru/touchin/roboswag/components/adapters/DelegatesManager.kt index bb70eb3..1da1498 100644 --- a/src/main/java/ru/touchin/roboswag/components/adapters/DelegatesManager.kt +++ b/src/main/java/ru/touchin/roboswag/components/adapters/DelegatesManager.kt @@ -18,7 +18,7 @@ class DelegatesManager { return delegate.itemViewType } } - throw IllegalStateException("Delegate not found") + throw IllegalStateException("Delegate not found for adapterPosition: $adapterPosition") } fun getItemId(items: List<*>, adapterPosition: Int, collectionPosition: Int): Long { @@ -33,19 +33,12 @@ class DelegatesManager { delegate.onBindViewHolder(holder, items, adapterPosition, collectionPosition, payloads) } - /** - * Adds [ItemAdapterDelegate] to adapter. - * - * @param delegate Delegate to add. - */ - fun addDelegate(delegate: ItemAdapterDelegate<*, *>) = delegates.put(delegate.itemViewType, delegate) - /** * Adds [PositionAdapterDelegate] to adapter. * * @param delegate Delegate to add. */ - fun addDelegate(delegate: PositionAdapterDelegate<*>) = delegates.put(delegate.itemViewType, delegate) + fun addDelegate(delegate: AdapterDelegate<*>) = delegates.put(delegate.itemViewType, delegate) /** * Removes [AdapterDelegate] from adapter. diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/DelegationListAdapter.kt b/src/main/java/ru/touchin/roboswag/components/adapters/DelegationListAdapter.kt index 24e56fb..edf4707 100644 --- a/src/main/java/ru/touchin/roboswag/components/adapters/DelegationListAdapter.kt +++ b/src/main/java/ru/touchin/roboswag/components/adapters/DelegationListAdapter.kt @@ -41,18 +41,18 @@ open class DelegationListAdapter(diffCallback: DiffUtil.ItemCallback) = delegatesManager.addDelegate(delegate) + fun addDelegate(delegate: AdapterDelegate<*>) = delegatesManager.addDelegate(delegate) /** - * Adds [PositionAdapterDelegate] to adapter. + * Removes [AdapterDelegate] from adapter. * - * @param delegate Delegate to add. + * @param delegate Delegate to remove. */ - fun addDelegate(delegate: PositionAdapterDelegate<*>) = delegatesManager.addDelegate(delegate) + fun removeDelegate(delegate: AdapterDelegate<*>) = delegatesManager.removeDelegate(delegate) /** * Submits a new list to be diffed, and displayed. diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.java b/src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.java deleted file mode 100644 index dad6ee8..0000000 --- a/src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.java +++ /dev/null @@ -1,433 +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.roboswag.components.navigation; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.os.Bundle; -import android.support.annotation.IdRes; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentTransaction; -import android.view.MenuItem; - -import io.reactivex.functions.Function; -import ru.touchin.roboswag.core.log.Lc; - -/** - * Created by Gavriil Sitnikov on 07/03/2016. - * Navigation which is controlling fragments on activity using {@link android.support.v4.app.FragmentManager}. - * Basically there are 4 main actions to add fragments to activity. - * 1) {@link #setInitial} means to set fragment on top and remove all previously added fragments from stack; - * 2) {@link #push} means to simply add fragment on top of the stack; - * 3) {@link #setAsTop} means to push fragment on top of the stack with specific {@link #TOP_FRAGMENT_TAG_MARK} tag. - * It is useful to realize up/back navigation: if {@link #up()} method will be called then stack will go to nearest fragment with TOP tag. - * If {@link #back()} method will be called then stack will go to previous fragment. - * Usually such logic using to set as top fragments from sidebar and show hamburger when some of them appeared; - * 4) {@link #pushForResult} means to push fragment with target fragment. It is also adding {@link #WITH_TARGET_FRAGMENT_TAG_MARK} tag. - * Also if such up/back navigation logic is not OK then {@link #backTo(Function)} method could be used with any condition to back to. - * In that case in any stack-change method it is allowed to setup fragment transactions. - */ -public class FragmentNavigation { - - protected static final String TOP_FRAGMENT_TAG_MARK = "TOP_FRAGMENT"; - protected static final String WITH_TARGET_FRAGMENT_TAG_MARK = "FRAGMENT_WITH_TARGET"; - - @NonNull - private final Context context; - @NonNull - private final FragmentManager fragmentManager; - @IdRes - private final int containerViewId; - - public FragmentNavigation(@NonNull final Context context, @NonNull final FragmentManager fragmentManager, @IdRes final int containerViewId) { - this.context = context; - this.fragmentManager = fragmentManager; - this.containerViewId = containerViewId; - } - - /** - * Returns {@link Context} that is using to instantiate fragments. - * - * @return {@link Context}. - */ - @NonNull - public Context getContext() { - return context; - } - - /** - * Returns {@link FragmentManager} using for navigation. - * - * @return {@link FragmentManager}. - */ - @NonNull - public FragmentManager getFragmentManager() { - return fragmentManager; - } - - /** - * Returns if last fragment in stack is top (added by {@link #setAsTop} or {@link #setInitial}) like fragment from sidebar menu. - * - * @return True if last fragment on stack has TOP_FRAGMENT_TAG_MARK. - */ - public boolean isCurrentFragmentTop() { - if (fragmentManager.getBackStackEntryCount() == 0) { - return true; - } - - final String topFragmentTag = fragmentManager - .getBackStackEntryAt(fragmentManager.getBackStackEntryCount() - 1) - .getName(); - return topFragmentTag != null && topFragmentTag.contains(TOP_FRAGMENT_TAG_MARK); - } - - /** - * Allowed to react on {@link android.app.Activity}'s menu item selection. - * - * @param item Selected menu item; - * @return True if reaction fired. - */ - @SuppressLint("InlinedApi") - //InlinedApi: it is ok as android.R.id.home contains in latest SDK - public boolean onOptionsItemSelected(@NonNull final MenuItem item) { - return item.getItemId() == android.R.id.home && back(); - } - - /** - * Base method which is adding fragment to stack. - * - * @param fragmentClass Class of {@link Fragment} to instantiate; - * @param targetFragment Target fragment to be set as {@link Fragment#getTargetFragment()} of instantiated {@link Fragment}; - * @param addToStack Flag to add this transaction to the back stack; - * @param args Bundle to be set as {@link Fragment#getArguments()} of instantiated {@link Fragment}; - * @param backStackTag Tag of {@link Fragment} in back stack; - * @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info. - */ - @SuppressLint("CommitTransaction") - //CommitTransaction: it is ok as we could setup transaction before commit - protected void addToStack(@NonNull final Class fragmentClass, - @Nullable final Fragment targetFragment, - final boolean addToStack, - @Nullable final Bundle args, - @Nullable final String backStackTag, - @Nullable final Function transactionSetup) { - if (fragmentManager.isDestroyed()) { - Lc.assertion("FragmentManager is destroyed"); - return; - } - - final Fragment fragment = Fragment.instantiate(context, fragmentClass.getName(), args); - if (targetFragment != null) { - if (fragmentManager != targetFragment.getFragmentManager()) { - Lc.assertion("FragmentManager of target is differ then of creating fragment. Target will be lost after restoring activity. " - + targetFragment.getFragmentManager() + " != " + fragmentManager); - } - fragment.setTargetFragment(targetFragment, 0); - } - - final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction() - .replace(containerViewId, fragment, null); - if (addToStack) { - fragmentTransaction.addToBackStack(backStackTag); - } - if (fragmentManager.getBackStackEntryCount() != 0) { - fragmentTransaction.setTransition(getDefaultTransition()); - } - if (transactionSetup != null) { - try { - transactionSetup.apply(fragmentTransaction).commit(); - } catch (final Exception exception) { - Lc.assertion(exception); - fragmentTransaction.commit(); - } - } else { - fragmentTransaction.commit(); - } - } - - /** - * Returns default transition animation. - * - * @return {@link FragmentTransaction#TRANSIT_FRAGMENT_OPEN}. - */ - protected int getDefaultTransition() { - return FragmentTransaction.TRANSIT_FRAGMENT_OPEN; - } - - /** - * Simply calls {@link FragmentManager#popBackStack()}. - * - * @return True if it have back to some entry in stack. - */ - public boolean back() { - if (fragmentManager.getBackStackEntryCount() > 1) { - fragmentManager.popBackStack(); - return true; - } - return false; - } - - /** - * Backs to fragment which back stack's entry satisfy to specific condition. - * - * @param condition Condition of back stack entry to be satisfied; - * @return True if it have back to some entry in stack. - */ - public boolean backTo(@NonNull final Function condition) { - final int stackSize = fragmentManager.getBackStackEntryCount(); - Integer id = null; - try { - for (int i = stackSize - 2; i >= 0; i--) { - final FragmentManager.BackStackEntry backStackEntry = fragmentManager.getBackStackEntryAt(i); - id = backStackEntry.getId(); - if (condition.apply(backStackEntry)) { - break; - } - } - } catch (final Exception exception) { - Lc.assertion(exception); - return false; - } - if (id != null) { - fragmentManager.popBackStack(id, 0); - return true; - } - return false; - } - - /** - * Backs to fragment with specific {@link #TOP_FRAGMENT_TAG_MARK} tag. - * This tag is adding if fragment added to stack via {@link #setInitial} or {@link #setAsTop(Class)} methods. - * It can be used to create simple up/back navigation. - * - * @return True if it have back to some entry in stack. - */ - @SuppressWarnings("PMD.ShortMethodName") - //ShortMethodName: it is ok because method name is good! - public boolean up() { - return backTo(backStackEntry -> - backStackEntry.getName() != null && backStackEntry.getName().endsWith(TOP_FRAGMENT_TAG_MARK)); - } - - /** - * Pushes {@link Fragment} on top of stack. - * - * @param fragmentClass Class of {@link Fragment} to instantiate. - */ - public void push(@NonNull final Class fragmentClass) { - addToStack(fragmentClass, null, true, null, null, null); - } - - /** - * Pushes {@link Fragment} on top of stack with specific arguments. - * - * @param fragmentClass Class of {@link Fragment} to instantiate; - * @param args Bundle to be set as {@link Fragment#getArguments()} of instantiated {@link Fragment}. - */ - public void push(@NonNull final Class fragmentClass, - @NonNull final Bundle args) { - addToStack(fragmentClass, null, true, args, null, null); - } - - /** - * Pushes {@link Fragment} on top of stack with specific transaction setup. - * - * @param fragmentClass Class of {@link Fragment} to instantiate; - * @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info. - */ - public void push(@NonNull final Class fragmentClass, - @NonNull final Function transactionSetup) { - addToStack(fragmentClass, null, true, null, null, transactionSetup); - } - - /** - * Pushes {@link Fragment} on top of stack with specific arguments and transaction setup. - * - * @param fragmentClass Class of {@link Fragment} to instantiate; - * @param args Bundle to be set as {@link Fragment#getArguments()} of instantiated {@link Fragment}; - * @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info. - */ - public void push(@NonNull final Class fragmentClass, - @Nullable final Bundle args, - @Nullable final Function transactionSetup) { - addToStack(fragmentClass, null, true, args, null, transactionSetup); - } - - /** - * Pushes {@link Fragment} on top of stack with specific target fragment. - * - * @param fragmentClass Class of {@link Fragment} to instantiate; - * @param targetFragment Target fragment to be set as {@link Fragment#getTargetFragment()} of instantiated {@link Fragment}. - */ - public void pushForResult(@NonNull final Class fragmentClass, - @NonNull final Fragment targetFragment) { - addToStack(fragmentClass, targetFragment, true, null, fragmentClass.getName() + ';' + WITH_TARGET_FRAGMENT_TAG_MARK, null); - } - - /** - * Pushes {@link Fragment} on top of stack with specific target fragment and arguments. - * - * @param fragmentClass Class of {@link Fragment} to instantiate; - * @param targetFragment Target fragment to be set as {@link Fragment#getTargetFragment()} of instantiated {@link Fragment}; - * @param args Bundle to be set as {@link Fragment#getArguments()} of instantiated {@link Fragment}. - */ - public void pushForResult(@NonNull final Class fragmentClass, - @NonNull final Fragment targetFragment, - @NonNull final Bundle args) { - addToStack(fragmentClass, targetFragment, true, args, fragmentClass.getName() + ';' + WITH_TARGET_FRAGMENT_TAG_MARK, null); - } - - /** - * Pushes {@link Fragment} on top of stack with specific target fragment and transaction setup. - * - * @param fragmentClass Class of {@link Fragment} to instantiate; - * @param targetFragment Target fragment to be set as {@link Fragment#getTargetFragment()} of instantiated {@link Fragment}; - * @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info. - */ - public void pushForResult(@NonNull final Class fragmentClass, - @NonNull final Fragment targetFragment, - @NonNull final Function transactionSetup) { - addToStack(fragmentClass, targetFragment, true, null, fragmentClass.getName() + ';' + WITH_TARGET_FRAGMENT_TAG_MARK, transactionSetup); - } - - /** - * Pushes {@link Fragment} on top of stack with specific target fragment, arguments and transaction setup. - * - * @param fragmentClass Class of {@link Fragment} to instantiate; - * @param targetFragment Target fragment to be set as {@link Fragment#getTargetFragment()} of instantiated {@link Fragment}; - * @param args Bundle to be set as {@link Fragment#getArguments()} of instantiated {@link Fragment}; - * @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info. - */ - public void pushForResult(@NonNull final Class fragmentClass, - @NonNull final Fragment targetFragment, - @Nullable final Bundle args, - @Nullable final Function transactionSetup) { - addToStack(fragmentClass, targetFragment, true, args, fragmentClass.getName() + ';' + WITH_TARGET_FRAGMENT_TAG_MARK, transactionSetup); - } - - /** - * Pushes {@link Fragment} on top of stack with {@link #TOP_FRAGMENT_TAG_MARK} tag used for simple up/back navigation. - * - * @param fragmentClass Class of {@link Fragment} to instantiate. - */ - public void setAsTop(@NonNull final Class fragmentClass) { - addToStack(fragmentClass, null, true, null, fragmentClass.getName() + ';' + TOP_FRAGMENT_TAG_MARK, null); - } - - /** - * Pushes {@link Fragment} on top of stack with specific arguments and with {@link #TOP_FRAGMENT_TAG_MARK} tag used for simple up/back navigation. - * - * @param fragmentClass Class of {@link Fragment} to instantiate; - * @param args Bundle to be set as {@link Fragment#getArguments()} of instantiated {@link Fragment}. - */ - public void setAsTop(@NonNull final Class fragmentClass, - @NonNull final Bundle args) { - addToStack(fragmentClass, null, true, args, fragmentClass.getName() + ';' + TOP_FRAGMENT_TAG_MARK, null); - } - - /** - * Pushes {@link Fragment} on top of stack with specific transaction setup - * and with {@link #TOP_FRAGMENT_TAG_MARK} tag used for simple up/back navigation. - * - * @param fragmentClass Class of {@link Fragment} to instantiate; - * @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info. - */ - public void setAsTop(@NonNull final Class fragmentClass, - @NonNull final Function transactionSetup) { - addToStack(fragmentClass, null, true, null, fragmentClass.getName() + ';' + TOP_FRAGMENT_TAG_MARK, transactionSetup); - } - - /** - * Pushes {@link Fragment} on top of stack with specific transaction setup, arguments - * and with {@link #TOP_FRAGMENT_TAG_MARK} tag used for simple up/back navigation. - * - * @param fragmentClass Class of {@link Fragment} to instantiate; - * @param args Bundle to be set as {@link Fragment#getArguments()} of instantiated {@link Fragment}; - * @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info. - */ - public void setAsTop(@NonNull final Class fragmentClass, - @Nullable final Bundle args, - @Nullable final Function transactionSetup) { - addToStack(fragmentClass, null, true, args, fragmentClass.getName() + ';' + TOP_FRAGMENT_TAG_MARK, transactionSetup); - } - - /** - * Pops all {@link Fragment}s and places new initial {@link Fragment} on top of stack. - * - * @param fragmentClass Class of {@link Fragment} to instantiate. - */ - public void setInitial(@NonNull final Class fragmentClass) { - setInitial(fragmentClass, null, null); - } - - /** - * Pops all {@link Fragment}s and places new initial {@link Fragment} on top of stack with specific arguments. - * - * @param fragmentClass Class of {@link Fragment} to instantiate; - * @param args Bundle to be set as {@link Fragment#getArguments()} of instantiated {@link Fragment}. - */ - public void setInitial(@NonNull final Class fragmentClass, - @NonNull final Bundle args) { - setInitial(fragmentClass, args, null); - } - - /** - * Pops all {@link Fragment}s and places new initial {@link Fragment} on top of stack with specific transaction setup. - * - * @param fragmentClass Class of {@link Fragment} to instantiate; - * @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info. - */ - public void setInitial(@NonNull final Class fragmentClass, - @NonNull final Function transactionSetup) { - setInitial(fragmentClass, null, transactionSetup); - } - - /** - * Pops all {@link Fragment}s and places new initial {@link Fragment} on top of stack with specific transaction setup and arguments. - * - * @param fragmentClass Class of {@link Fragment} to instantiate; - * @param args Bundle to be set as {@link Fragment#getArguments()} of instantiated {@link Fragment}; - * @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info. - */ - public void setInitial(@NonNull final Class fragmentClass, - @Nullable final Bundle args, - @Nullable final Function transactionSetup) { - beforeSetInitialActions(); - setAsTop(fragmentClass, args, transactionSetup); - } - - /** - * Method calls every time before initial {@link Fragment} will be placed. - */ - protected void beforeSetInitialActions() { - if (fragmentManager.isDestroyed()) { - Lc.assertion("FragmentManager is destroyed"); - return; - } - - if (fragmentManager.getBackStackEntryCount() > 0) { - fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE); - } - } - -} diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.kt b/src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.kt new file mode 100644 index 0000000..fe63d34 --- /dev/null +++ b/src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.kt @@ -0,0 +1,261 @@ +/* + * 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.roboswag.components.navigation + +import android.content.Context +import android.os.Bundle +import android.support.annotation.IdRes +import android.support.v4.app.Fragment +import android.support.v4.app.FragmentManager +import android.support.v4.app.FragmentTransaction +import android.view.MenuItem + +import ru.touchin.roboswag.core.log.Lc + +/** + * Created by Gavriil Sitnikov on 07/03/2016. + * Navigation which is controlling fragments on activity using [android.support.v4.app.FragmentManager]. + * Basically there are 4 main actions to add fragments to activity. + * 1) [.setInitial] means to set fragment on top and remove all previously added fragments from stack; + * 2) [.push] means to simply add fragment on top of the stack; + * 3) [.setAsTop] means to push fragment on top of the stack with specific [.TOP_FRAGMENT_TAG_MARK] tag. + * It is useful to realize up/back navigation: if [.up] method will be called then stack will go to nearest fragment with TOP tag. + * If [.back] method will be called then stack will go to previous fragment. + * Usually such logic using to set as top fragments from sidebar and show hamburger when some of them appeared; + * 4) [.pushForResult] means to push fragment with target fragment. It is also adding [.WITH_TARGET_FRAGMENT_TAG_MARK] tag. + * Also if such up/back navigation logic is not OK then [.backTo] method could be used with any condition to back to. + * In that case in any stack-change method it is allowed to setup fragment transactions. + */ +open class FragmentNavigation( + private val context: Context, + private val fragmentManager: FragmentManager, + @IdRes private val containerViewId: Int, + private val transition: Int = FragmentTransaction.TRANSIT_FRAGMENT_OPEN +) { + + companion object { + const val TOP_FRAGMENT_TAG_MARK = "TOP_FRAGMENT" + const val WITH_TARGET_FRAGMENT_TAG_MARK = "FRAGMENT_WITH_TARGET" + } + + /** + * Returns if last fragment in stack is top (added by [.setAsTop] or [.setInitial]) like fragment from sidebar menu. + * + * @return True if last fragment on stack has TOP_FRAGMENT_TAG_MARK. + */ + fun isCurrentFragmentTop(): Boolean { + if (fragmentManager.backStackEntryCount == 0) { + return true + } + val topFragmentTag = fragmentManager + .getBackStackEntryAt(fragmentManager.backStackEntryCount - 1) + .name + return topFragmentTag != null && topFragmentTag.contains(TOP_FRAGMENT_TAG_MARK) + } + + /** + * Allowed to react on [android.app.Activity]'s menu item selection. + * + * @param item Selected menu item; + * @return True if reaction fired. + */ + fun onOptionsItemSelected(item: MenuItem): Boolean = item.itemId == android.R.id.home && back() + + /** + * Base method which is adding fragment to stack. + * + * @param fragmentClass Class of [Fragment] to instantiate; + * @param targetFragment Target fragment to be set as [Fragment.getTargetFragment] of instantiated [Fragment]; + * @param addToStack Flag to add this transaction to the back stack; + * @param args Bundle to be set as [Fragment.getArguments] of instantiated [Fragment]; + * @param backStackTag Tag of [Fragment] in back stack; + * @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info. + */ + fun addToStack( + fragmentClass: Class, + targetFragment: Fragment?, + targetRequestCode: Int, + addToStack: Boolean, + args: Bundle?, + backStackTag: String?, + transactionSetup: ((FragmentTransaction) -> Unit)? + ) { + if (fragmentManager.isDestroyed) { + Lc.assertion("FragmentManager is destroyed") + return + } + + val fragment = Fragment.instantiate(context, fragmentClass.name, args) + if (targetFragment != null) { + if (fragmentManager !== targetFragment.fragmentManager) { + Lc.assertion("FragmentManager of target is differ then of creating fragment. Target will be lost after restoring activity. " + + targetFragment.fragmentManager + " != " + fragmentManager) + } + fragment.setTargetFragment(targetFragment, targetRequestCode) + } + + val fragmentTransaction = fragmentManager.beginTransaction() + .replace(containerViewId, fragment, null) + if (addToStack) { + fragmentTransaction.addToBackStack(backStackTag) + } + if (fragmentManager.backStackEntryCount != 0) { + fragmentTransaction.setTransition(transition) + } + transactionSetup?.invoke(fragmentTransaction) + fragmentTransaction.commit() + } + + /** + * Simply calls [FragmentManager.popBackStack]. + * + * @return True if it have back to some entry in stack. + */ + fun back(): Boolean { + if (fragmentManager.backStackEntryCount > 1) { + fragmentManager.popBackStack() + return true + } + return false + } + + /** + * Backs to fragment which back stack's entry satisfy to specific condition. + * + * @param condition Condition of back stack entry to be satisfied; + * @return True if it have back to some entry in stack. + */ + fun backTo(condition: (FragmentManager.BackStackEntry) -> Boolean): Boolean { + val stackSize = fragmentManager.backStackEntryCount + var id: Int? = null + for (i in stackSize - 2 downTo 0) { + val backStackEntry = fragmentManager.getBackStackEntryAt(i) + if (condition(backStackEntry)) { + id = backStackEntry.id + break + } + } + if (id != null) { + fragmentManager.popBackStack(id, 0) + return true + } + return false + } + + /** + * Backs to fragment with specific [.TOP_FRAGMENT_TAG_MARK] tag. + * This tag is adding if fragment added to stack via [.setInitial] or [.setAsTop] methods. + * It can be used to create simple up/back navigation. + * + * @return True if it have back to some entry in stack. + */ + //ShortMethodName: it is ok because method name is good! + fun up(): Boolean = backTo { backStackEntry -> backStackEntry.name != null && backStackEntry.name.endsWith(TOP_FRAGMENT_TAG_MARK) } + + /** + * Pushes [Fragment] on top of stack with specific arguments and transaction setup. + * + * @param fragmentClass Class of [Fragment] to instantiate; + * @param args Bundle to be set as [Fragment.getArguments] of instantiated [Fragment]; + * @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info. + */ + fun push( + fragmentClass: Class, + args: Bundle? = null, + addToStack: Boolean = true, + transactionSetup: ((FragmentTransaction) -> Unit)? = null + ) { + addToStack(fragmentClass, null, 0, addToStack, args, null, transactionSetup) + } + + /** + * Pushes [Fragment] on top of stack with specific target fragment, arguments and transaction setup. + * + * @param fragmentClass Class of [Fragment] to instantiate; + * @param targetFragment Target fragment to be set as [Fragment.getTargetFragment] of instantiated [Fragment]; + * @param args Bundle to be set as [Fragment.getArguments] of instantiated [Fragment]; + * @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info. + */ + fun pushForResult( + fragmentClass: Class, + targetFragment: Fragment, + targetRequestCode: Int, + args: Bundle? = null, + transactionSetup: ((FragmentTransaction) -> Unit)? = null + ) { + addToStack( + fragmentClass, + targetFragment, + targetRequestCode, + true, + args, + fragmentClass.name + ';'.toString() + WITH_TARGET_FRAGMENT_TAG_MARK, + transactionSetup + ) + } + + /** + * Pushes [Fragment] on top of stack with specific transaction setup, arguments + * and with [.TOP_FRAGMENT_TAG_MARK] tag used for simple up/back navigation. + * + * @param fragmentClass Class of [Fragment] to instantiate; + * @param args Bundle to be set as [Fragment.getArguments] of instantiated [Fragment]; + * @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info. + */ + fun setAsTop( + fragmentClass: Class, + args: Bundle? = null, + transactionSetup: ((FragmentTransaction) -> Unit)? = null + ) { + addToStack(fragmentClass, null, 0, true, args, fragmentClass.name + ';'.toString() + TOP_FRAGMENT_TAG_MARK, transactionSetup) + } + + /** + * Pops all [Fragment]s and places new initial [Fragment] on top of stack with specific transaction setup and arguments. + * + * @param fragmentClass Class of [Fragment] to instantiate; + * @param args Bundle to be set as [Fragment.getArguments] of instantiated [Fragment]; + * @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info. + */ + @JvmOverloads + fun setInitial( + fragmentClass: Class, + args: Bundle? = null, + transactionSetup: ((FragmentTransaction) -> Unit)? = null + ) { + beforeSetInitialActions() + setAsTop(fragmentClass, args, transactionSetup) + } + + /** + * Method calls every time before initial [Fragment] will be placed. + */ + protected fun beforeSetInitialActions() { + if (fragmentManager.isDestroyed) { + Lc.assertion("FragmentManager is destroyed") + return + } + + if (fragmentManager.backStackEntryCount > 0) { + fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE) + } + } + +} diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java b/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java index a63a0be..91e4c87 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java +++ b/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java @@ -107,6 +107,8 @@ public class ViewControllerFragment, TState>> viewControllerClass; private TState state; + @Nullable + private ActivityResult pendingActivityResult; /** * Returns specific {@link Parcelable} which contains state of fragment and it's {@link ViewController}. @@ -192,6 +194,10 @@ public class ViewControllerFragment extends Fragment implements OnFragmentStartedListener { private boolean appeared; - private boolean started; /** * Returns if fragment have parent fragment. @@ -111,7 +111,6 @@ public abstract class ViewFragment extends F @Override public void onStart() { super.onStart(); - started = true; callMethodAfterInstantiation(this::onStart); } @@ -168,6 +167,7 @@ public abstract class ViewFragment extends F public void setMenuVisibility(final boolean menuVisible) { super.setMenuVisibility(menuVisible); if (getBaseActivity() != null && getView() != null) { + final boolean started = getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED); if (!appeared && menuVisible && started) { onAppear(getView(), getBaseActivity()); } @@ -209,7 +209,6 @@ public abstract class ViewFragment extends F @Deprecated @Override public void onStop() { - started = false; callMethodAfterInstantiation(this::onStop); super.onStop(); } diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewControllerNavigation.kt b/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewControllerNavigation.kt index ba67da9..dde03c9 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewControllerNavigation.kt +++ b/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewControllerNavigation.kt @@ -27,7 +27,6 @@ import android.support.v4.app.FragmentActivity import android.support.v4.app.FragmentManager import android.support.v4.app.FragmentTransaction -import io.reactivex.functions.Function import ru.touchin.roboswag.components.navigation.FragmentNavigation import ru.touchin.roboswag.components.navigation.fragments.ViewControllerFragment @@ -41,38 +40,34 @@ import ru.touchin.roboswag.components.navigation.fragments.ViewControllerFragmen open class ViewControllerNavigation( context: Context, fragmentManager: FragmentManager, - @IdRes containerViewId: Int -) : FragmentNavigation(context, fragmentManager, containerViewId) { + @IdRes containerViewId: Int, + transition: Int = FragmentTransaction.TRANSIT_FRAGMENT_OPEN +) : FragmentNavigation(context, fragmentManager, containerViewId, transition) { /** * Pushes [ViewController] on top of stack with specific [ViewControllerFragment.getState] and with specific transaction setup. * * @param viewControllerClass Class of [ViewController] to be pushed; * @param state [Parcelable] of [ViewController]'s fragment; + * @param addToStack Flag to add this transaction to the back stack; * @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info; * @param TState Type of state of fragment. */ fun pushViewController( viewControllerClass: Class, TState>>, state: TState, - transactionSetup: Function? = null + addToStack: Boolean = true, + transactionSetup: ((FragmentTransaction) -> Unit)? = null ) { - addViewControllerToStack(viewControllerClass, null, true, state, null, transactionSetup) - } - - /** - * Pushes [ViewController] without adding to stack and with specific [ViewControllerFragment.getState]. - * - * @param viewControllerClass Class of [ViewController] to be pushed; - * @param state [Parcelable] of [ViewController]'s fragment; - * @param TState Type of state of fragment. - */ - fun pushSingleViewController( - viewControllerClass: Class, TState>>, - state: TState, - transactionSetup: Function? = null - ) { - addViewControllerToStack(viewControllerClass, null, false, state, null, transactionSetup) + addToStack( + ViewControllerFragment::class.java, + null, + 0, + addToStack, + ViewControllerFragment.args(viewControllerClass, state), + null, + transactionSetup + ) } /** @@ -90,13 +85,15 @@ open class ViewControllerNavigation( viewControllerClass: Class, TState>>, state: TState, targetFragment: TTargetFragment, - transactionSetup: Function? = null + targetRequestCode: Int, + transactionSetup: ((FragmentTransaction) -> Unit)? = null ) { - addViewControllerToStack( - viewControllerClass, + addToStack( + ViewControllerFragment::class.java, targetFragment, + targetRequestCode, true, - state, + ViewControllerFragment.args(viewControllerClass, state), viewControllerClass.name + ';'.toString() + WITH_TARGET_FRAGMENT_TAG_MARK, transactionSetup ) @@ -114,13 +111,14 @@ open class ViewControllerNavigation( fun setViewControllerAsTop( viewControllerClass: Class, TState>>, state: TState, - transactionSetup: Function? = null + transactionSetup: ((FragmentTransaction) -> Unit)? = null ) { - addViewControllerToStack( - viewControllerClass, + addToStack( + ViewControllerFragment::class.java, null, + 0, true, - state, + ViewControllerFragment.args(viewControllerClass, state), viewControllerClass.name + ';'.toString() + TOP_FRAGMENT_TAG_MARK, transactionSetup ) @@ -138,38 +136,10 @@ open class ViewControllerNavigation( fun setInitialViewController( viewControllerClass: Class, TState>>, state: TState, - transactionSetup: Function? = null + transactionSetup: ((FragmentTransaction) -> Unit)? = null ) { beforeSetInitialActions() setViewControllerAsTop(viewControllerClass, state, transactionSetup) } - /** - * Base method to push stateful [ViewControllerFragment] to stack. - * - * @param viewControllerClass Class of [ViewController] to be pushed; - * @param targetFragment [ViewControllerFragment] to be set as target; - * @param state [Parcelable] of [ViewController]'s fragment; - * @param backStackTag Tag of [ViewControllerFragment] in back stack; - * @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info; - * @param TState Type of state of fragment. - */ - protected fun addViewControllerToStack( - viewControllerClass: Class, TState>>, - targetFragment: Fragment?, - addToStack: Boolean, - state: TState, - backStackTag: String?, - transactionSetup: Function? - ) { - addToStack( - ViewControllerFragment::class.java, - targetFragment, - addToStack, - ViewControllerFragment.args(viewControllerClass, state), - backStackTag, - transactionSetup - ) - } - } From 7e35087c0aed34f7d049203637a319071e603281 Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Tue, 20 Mar 2018 18:05:28 +0300 Subject: [PATCH 46/95] Diff config for DelegationListAdapter, added DefaultViewController --- .../components/adapters/DelegatesManager.kt | 3 +- .../adapters/DelegationListAdapter.kt | 30 +++++++++++-------- .../adapters/ItemAdapterDelegate.java | 2 +- .../viewcontrollers/DefaultViewController.kt | 22 ++++++++++++++ 4 files changed, 41 insertions(+), 16 deletions(-) create mode 100644 src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/DefaultViewController.kt diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/DelegatesManager.kt b/src/main/java/ru/touchin/roboswag/components/adapters/DelegatesManager.kt index 1da1498..e3ba14f 100644 --- a/src/main/java/ru/touchin/roboswag/components/adapters/DelegatesManager.kt +++ b/src/main/java/ru/touchin/roboswag/components/adapters/DelegatesManager.kt @@ -47,7 +47,6 @@ class DelegatesManager { */ fun removeDelegate(delegate: AdapterDelegate<*>) = delegates.remove(delegate.itemViewType) - private fun getDelegate(viewType: Int): AdapterDelegate<*> = - delegates[viewType] ?: throw IllegalStateException("No AdapterDelegate added for view type: $viewType") + private fun getDelegate(viewType: Int) = delegates[viewType] ?: throw IllegalStateException("No AdapterDelegate added for view type: $viewType") } diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/DelegationListAdapter.kt b/src/main/java/ru/touchin/roboswag/components/adapters/DelegationListAdapter.kt index edf4707..8f22d28 100644 --- a/src/main/java/ru/touchin/roboswag/components/adapters/DelegationListAdapter.kt +++ b/src/main/java/ru/touchin/roboswag/components/adapters/DelegationListAdapter.kt @@ -9,31 +9,35 @@ import android.view.ViewGroup /** * Base adapter with delegation and diff computing on background thread. */ -open class DelegationListAdapter(diffCallback: DiffUtil.ItemCallback) : RecyclerView.Adapter() { +open class DelegationListAdapter(config: AsyncDifferConfig) : RecyclerView.Adapter() { + + constructor(diffCallback: DiffUtil.ItemCallback) : this(AsyncDifferConfig.Builder(diffCallback).build()) var itemClickListener: ((TItem, RecyclerView.ViewHolder) -> Unit)? = null private val delegatesManager = DelegatesManager() - private var differ = AsyncListDiffer(OffsetAdapterUpdateCallback(this, ::getHeadersCount), AsyncDifferConfig.Builder(diffCallback).build()) + private var differ = AsyncListDiffer(OffsetAdapterUpdateCallback(this, ::getHeadersCount), config) - open fun getHeadersCount(): Int = 0 + open fun getHeadersCount() = 0 - open fun getFootersCount(): Int = 0 + open fun getFootersCount() = 0 - override fun getItemCount(): Int = getHeadersCount() + differ.currentList.size + getFootersCount() + override fun getItemCount() = getHeadersCount() + getList().size + getFootersCount() - override fun getItemViewType(position: Int): Int = delegatesManager.getItemViewType(getList(), position, getCollectionPosition(position)) + override fun getItemViewType(position: Int) = delegatesManager.getItemViewType(getList(), position, getCollectionPosition(position)) - override fun getItemId(position: Int): Long = delegatesManager.getItemId(getList(), position, getCollectionPosition(position)) + override fun getItemId(position: Int) = delegatesManager.getItemId(getList(), position, getCollectionPosition(position)) - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder = delegatesManager.onCreateViewHolder(parent, viewType) + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = delegatesManager.onCreateViewHolder(parent, viewType) override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int, payloads: List) { val collectionPosition = getCollectionPosition(position) - if (itemClickListener != null && collectionPosition in 0 until getList().size) { - holder.itemView.setOnClickListener { itemClickListener?.invoke(getList()[collectionPosition], holder) } - } else { - holder.itemView.setOnClickListener(null) + if (collectionPosition in 0 until getList().size) { + if (itemClickListener != null) { + holder.itemView.setOnClickListener { itemClickListener?.invoke(getList()[collectionPosition], holder) } + } else { + holder.itemView.setOnClickListener(null) + } } delegatesManager.onBindViewHolder(holder, getList(), position, collectionPosition, payloads) } @@ -77,6 +81,6 @@ open class DelegationListAdapter(diffCallback: DiffUtil.ItemCallback = differ.currentList - fun getCollectionPosition(adapterPosition: Int): Int = adapterPosition - getHeadersCount() + fun getCollectionPosition(adapterPosition: Int) = adapterPosition - getHeadersCount() } diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/ItemAdapterDelegate.java b/src/main/java/ru/touchin/roboswag/components/adapters/ItemAdapterDelegate.java index 44a9902..324bcf4 100644 --- a/src/main/java/ru/touchin/roboswag/components/adapters/ItemAdapterDelegate.java +++ b/src/main/java/ru/touchin/roboswag/components/adapters/ItemAdapterDelegate.java @@ -13,7 +13,6 @@ import java.util.List; * @param Type of {@link RecyclerView.ViewHolder} of delegate; * @param Type of items to bind to {@link RecyclerView.ViewHolder}s. */ -@SuppressWarnings("unchecked") public abstract class ItemAdapterDelegate extends AdapterDelegate { @Override @@ -38,6 +37,7 @@ public abstract class ItemAdapterDelegate items, final int adapterPosition, final int collectionPosition) { + //noinspection unchecked return getItemId((TItem) items.get(collectionPosition), adapterPosition, collectionPosition); } diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/DefaultViewController.kt b/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/DefaultViewController.kt new file mode 100644 index 0000000..41326ba --- /dev/null +++ b/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/DefaultViewController.kt @@ -0,0 +1,22 @@ +package ru.touchin.roboswag.components.navigation.viewcontrollers + +import android.os.Bundle +import android.os.Parcelable +import android.support.annotation.LayoutRes +import android.support.v4.app.FragmentActivity +import ru.touchin.roboswag.components.navigation.fragments.ViewControllerFragment + +abstract class DefaultViewController( + @LayoutRes layoutRes: Int, + creationContext: CreationContext, + savedInstanceState: Bundle? +) : ViewController, TState>( + creationContext, + savedInstanceState +) { + + init { + setContentView(layoutRes) + } + +} From bbc73d3d129892dff885b0a06ee91a8646a1bb56 Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Wed, 21 Mar 2018 16:51:31 +0300 Subject: [PATCH 47/95] SoftInput method moved to UiUtils --- .../SimpleActionBarDrawerToggle.java | 9 ++- .../navigation/activities/BaseActivity.java | 73 +------------------ .../fragments/ViewControllerFragment.java | 6 +- .../viewcontrollers/DefaultViewController.kt | 2 +- .../viewcontrollers/ViewController.java | 4 +- .../ViewControllerNavigation.kt | 8 +- .../roboswag/components/utils/UiUtils.java | 37 ++++++++++ 7 files changed, 56 insertions(+), 83 deletions(-) diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/SimpleActionBarDrawerToggle.java b/src/main/java/ru/touchin/roboswag/components/navigation/SimpleActionBarDrawerToggle.java index 423fc98..e2d88f6 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/SimpleActionBarDrawerToggle.java +++ b/src/main/java/ru/touchin/roboswag/components/navigation/SimpleActionBarDrawerToggle.java @@ -29,6 +29,7 @@ import android.view.MenuItem; import android.view.View; import ru.touchin.roboswag.components.navigation.activities.BaseActivity; +import ru.touchin.roboswag.components.utils.UiUtils; /** * Created by Gavriil Sitnikov on 11/03/16. @@ -206,7 +207,7 @@ public class SimpleActionBarDrawerToggle extends ActionBarDrawerToggle @Override public void onDrawerClosed(@NonNull final View view) { if (isInvalidateOptionsMenuSupported) { - activity.supportInvalidateOptionsMenu(); + activity.invalidateOptionsMenu(); } } @@ -221,9 +222,9 @@ public class SimpleActionBarDrawerToggle extends ActionBarDrawerToggle @Override public void onDrawerOpened(@NonNull final View drawerView) { - activity.hideSoftInput(); + UiUtils.OfViews.hideSoftInput(activity); if (isInvalidateOptionsMenuSupported) { - activity.supportInvalidateOptionsMenu(); + activity.invalidateOptionsMenu(); } } @@ -245,4 +246,4 @@ public class SimpleActionBarDrawerToggle extends ActionBarDrawerToggle super.onDrawerSlide(drawerView, this.slideOffset); } -} \ No newline at end of file +} diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java b/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java index 762d926..974fa46 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java +++ b/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java @@ -19,21 +19,14 @@ package ru.touchin.roboswag.components.navigation.activities; -import android.app.Activity; import android.content.Intent; -import android.graphics.drawable.Drawable; import android.os.Bundle; -import android.support.annotation.ColorInt; -import android.support.annotation.ColorRes; -import android.support.annotation.DrawableRes; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.support.v4.content.ContextCompat; +import android.support.v4.util.ArraySet; import android.support.v7.app.AppCompatActivity; -import android.view.View; -import android.view.inputmethod.InputMethodManager; -import java.util.ArrayList; +import java.util.Set; import ru.touchin.roboswag.components.utils.UiUtils; import ru.touchin.roboswag.core.log.Lc; @@ -45,7 +38,7 @@ import ru.touchin.roboswag.core.log.Lc; public abstract class BaseActivity extends AppCompatActivity { @NonNull - private final ArrayList onBackPressedListeners = new ArrayList<>(); + private final Set onBackPressedListeners = new ArraySet<>(); @Override protected void onCreate(@Nullable final Bundle savedInstanceState) { @@ -101,59 +94,6 @@ public abstract class BaseActivity extends AppCompatActivity { super.onDestroy(); } - /** - * Hides device keyboard that is showing over {@link Activity}. - * Do NOT use it if keyboard is over {@link android.app.Dialog} - it won't work as they have different {@link Activity#getWindow()}. - */ - public void hideSoftInput() { - if (getCurrentFocus() == null) { - return; - } - final InputMethodManager inputManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); - inputManager.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0); - getWindow().getDecorView().requestFocus(); - } - - /** - * Shows device keyboard over {@link Activity} and focuses {@link View}. - * Do NOT use it if keyboard is over {@link android.app.Dialog} - it won't work as they have different {@link Activity#getWindow()}. - * Do NOT use it if you are not sure that view is already added on screen. - * Better use it onStart of element if view is part of it or onConfigureNavigation if view is part of navigation. - * - * @param view View to get focus for input from keyboard. - */ - public void showSoftInput(@NonNull final View view) { - view.requestFocus(); - final InputMethodManager inputManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); - inputManager.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT); - } - - /** - * Return the color value associated with a particular resource ID. - * Starting in {@link android.os.Build.VERSION_CODES#M}, the returned - * color will be styled for the specified Context's theme. - * - * @param resId The resource id to search for data; - * @return int A single color value in the form 0xAARRGGBB. - */ - @ColorInt - public int getColorCompat(@ColorRes final int resId) { - return ContextCompat.getColor(this, resId); - } - - /** - * Returns a drawable object associated with a particular resource ID. - * Starting in {@link android.os.Build.VERSION_CODES#LOLLIPOP}, the - * returned drawable will be styled for the specified Context's theme. - * - * @param resId The resource id to search for data; - * @return Drawable An object that can be used to draw this resource. - */ - @Nullable - public Drawable getDrawableCompat(@DrawableRes final int resId) { - return ContextCompat.getDrawable(this, resId); - } - public void addOnBackPressedListener(@NonNull final OnBackPressedListener onBackPressedListener) { onBackPressedListeners.add(onBackPressedListener); } @@ -169,12 +109,7 @@ public abstract class BaseActivity extends AppCompatActivity { return; } } - - if (getSupportFragmentManager().getBackStackEntryCount() <= 1) { - supportFinishAfterTransition(); - } else { - getSupportFragmentManager().popBackStack(); - } + super.onBackPressed(); } /* diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java b/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java index 91e4c87..68f9be0 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java +++ b/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java @@ -51,7 +51,7 @@ import ru.touchin.roboswag.core.utils.ShouldNotHappenException; * @param Type of object which is representing it's fragment state; * @param Type of {@link FragmentActivity} where fragment could be attached to. */ -public class ViewControllerFragment extends ViewFragment { +public class ViewControllerFragment extends ViewFragment { private static final String VIEW_CONTROLLER_CLASS_EXTRA = "VIEW_CONTROLLER_CLASS_EXTRA"; private static final String VIEW_CONTROLLER_STATE_EXTRA = "VIEW_CONTROLLER_STATE_EXTRA"; @@ -105,7 +105,7 @@ public class ViewControllerFragment, TState>> viewControllerClass; + private Class, TState>> viewControllerClass; private TState state; @Nullable private ActivityResult pendingActivityResult; @@ -127,7 +127,7 @@ public class ViewControllerFragment, TState>>) + viewControllerClass = (Class, TState>>) getArguments().getSerializable(VIEW_CONTROLLER_CLASS_EXTRA); state = savedInstanceState != null ? savedInstanceState.getParcelable(VIEW_CONTROLLER_STATE_EXTRA) diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/DefaultViewController.kt b/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/DefaultViewController.kt index 41326ba..6b5014b 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/DefaultViewController.kt +++ b/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/DefaultViewController.kt @@ -10,7 +10,7 @@ abstract class DefaultViewController, TState>( +) : ViewController, TState>( creationContext, savedInstanceState ) { diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java b/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java index ab3a618..42ecaf5 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java +++ b/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java @@ -51,7 +51,7 @@ import ru.touchin.roboswag.core.log.Lc; */ public class ViewController< TActivity extends FragmentActivity, - TFragment extends ViewControllerFragment, + TFragment extends ViewControllerFragment, TState extends Parcelable> implements LifecycleOwner { @NonNull @@ -276,7 +276,7 @@ public class ViewController< * Callback from parent fragment. */ public void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) { - UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this)); + // do nothing } /** diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewControllerNavigation.kt b/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewControllerNavigation.kt index dde03c9..103c9f7 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewControllerNavigation.kt +++ b/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewControllerNavigation.kt @@ -54,7 +54,7 @@ open class ViewControllerNavigation( * @param TState Type of state of fragment. */ fun pushViewController( - viewControllerClass: Class, TState>>, + viewControllerClass: Class, TState>>, state: TState, addToStack: Boolean = true, transactionSetup: ((FragmentTransaction) -> Unit)? = null @@ -82,7 +82,7 @@ open class ViewControllerNavigation( * @param TTargetFragment Type of target fragment. */ fun pushViewControllerForResult( - viewControllerClass: Class, TState>>, + viewControllerClass: Class, TState>>, state: TState, targetFragment: TTargetFragment, targetRequestCode: Int, @@ -109,7 +109,7 @@ open class ViewControllerNavigation( * @param TState Type of state of fragment. */ fun setViewControllerAsTop( - viewControllerClass: Class, TState>>, + viewControllerClass: Class, TState>>, state: TState, transactionSetup: ((FragmentTransaction) -> Unit)? = null ) { @@ -134,7 +134,7 @@ open class ViewControllerNavigation( * @param TState Type of state of fragment. */ fun setInitialViewController( - viewControllerClass: Class, TState>>, + viewControllerClass: Class, TState>>, state: TState, transactionSetup: ((FragmentTransaction) -> Unit)? = null ) { diff --git a/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.java b/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.java index 985e9e2..9343d00 100644 --- a/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.java +++ b/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.java @@ -41,6 +41,7 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; import io.reactivex.functions.Action; import io.reactivex.functions.Consumer; @@ -307,6 +308,42 @@ public final class UiUtils { } } + /** + * Hides device keyboard for target activity. + */ + public static void hideSoftInput(@NonNull final Activity activity) { + final View focusedView = activity.getCurrentFocus(); + if (focusedView != null) { + hideSoftInput(focusedView); + } + } + + /** + * Hides device keyboard for target view. + */ + public static void hideSoftInput(@NonNull final View view) { + view.clearFocus(); + final InputMethodManager inputManager = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + if (inputManager != null) { + inputManager.hideSoftInputFromWindow(view.getWindowToken(), 0); + } + } + + /** + * Shows device keyboard over {@link Activity} and focuses {@link View}. + * Do NOT use it if keyboard is over {@link android.app.Dialog} - it won't work as they have different {@link Activity#getWindow()}. + * Do NOT use it if you are not sure that view is already added on screen. + * + * @param view View to get focus for input from keyboard. + */ + public static void showSoftInput(@NonNull final View view) { + view.requestFocus(); + final InputMethodManager inputManager = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + if (inputManager != null) { + inputManager.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT); + } + } + private OfViews() { } From cd9db1203a6206fcdeb98d85d93c76e5fc7952ed Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Tue, 27 Mar 2018 17:40:34 +0300 Subject: [PATCH 48/95] Flowable handling --- .../utils/destroyable/BaseDestroyable.kt | 11 +++++++++++ .../utils/destroyable/Destroyable.kt | 19 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/main/java/ru/touchin/roboswag/components/utils/destroyable/BaseDestroyable.kt b/src/main/java/ru/touchin/roboswag/components/utils/destroyable/BaseDestroyable.kt index dae5cd3..f2a47a9 100644 --- a/src/main/java/ru/touchin/roboswag/components/utils/destroyable/BaseDestroyable.kt +++ b/src/main/java/ru/touchin/roboswag/components/utils/destroyable/BaseDestroyable.kt @@ -1,6 +1,7 @@ package ru.touchin.roboswag.components.utils.destroyable import io.reactivex.Completable +import io.reactivex.Flowable import io.reactivex.Maybe import io.reactivex.Observable import io.reactivex.Single @@ -21,6 +22,16 @@ open class BaseDestroyable : Destroyable { */ fun onDestroy() = subscriptions.dispose() + override fun untilDestroy( + flowable: Flowable, + onNextAction: (T) -> Unit, + onErrorAction: (Throwable) -> Unit, + onCompletedAction: () -> Unit + ): Disposable = flowable + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(onNextAction, onErrorAction, onCompletedAction) + .also { subscriptions.add(it) } + override fun untilDestroy( observable: Observable, onNextAction: (T) -> Unit, diff --git a/src/main/java/ru/touchin/roboswag/components/utils/destroyable/Destroyable.kt b/src/main/java/ru/touchin/roboswag/components/utils/destroyable/Destroyable.kt index ea2e1eb..6d2aea7 100644 --- a/src/main/java/ru/touchin/roboswag/components/utils/destroyable/Destroyable.kt +++ b/src/main/java/ru/touchin/roboswag/components/utils/destroyable/Destroyable.kt @@ -1,6 +1,7 @@ package ru.touchin.roboswag.components.utils.destroyable import io.reactivex.Completable +import io.reactivex.Flowable import io.reactivex.Maybe import io.reactivex.Observable import io.reactivex.Single @@ -23,6 +24,24 @@ interface Destroyable { } } + /** + * Method should be used to guarantee that observable won't be subscribed after onDestroy. + * It is automatically subscribing to the observable and calls onNextAction and onErrorAction on observable events. + * Don't forget to process errors if observable can emit them. + * + * @param flowable [Flowable] to subscribe until onDestroy; + * @param onNextAction Action which will raise on every [io.reactivex.Emitter.onNext] item; + * @param onErrorAction Action which will raise on every [io.reactivex.Emitter.onError] throwable; + * @param T Type of emitted by observable items; + * @return [Disposable] which is wrapping source observable to unsubscribe from it onDestroy. + */ + fun untilDestroy( + flowable: Flowable, + onNextAction: (T) -> Unit = Functions.emptyConsumer()::accept, + onErrorAction: (Throwable) -> Unit = getActionThrowableForAssertion(Lc.getCodePoint(this, 2)), + onCompletedAction: () -> Unit = Functions.EMPTY_ACTION::run + ): Disposable + /** * Method should be used to guarantee that observable won't be subscribed after onDestroy. * It is automatically subscribing to the observable and calls onNextAction and onErrorAction on observable events. From 496abde610653ebb5d6553c6b95a042db8d0d868 Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Fri, 30 Mar 2018 15:25:22 +0300 Subject: [PATCH 49/95] setOnRippleClickListener moved to extension --- .../adapters/DelegationListAdapter.kt | 3 +- .../roboswag/components/extensions/View.kt | 23 +++++++ .../roboswag/components/utils/UiUtils.java | 69 ------------------- 3 files changed, 25 insertions(+), 70 deletions(-) create mode 100644 src/main/java/ru/touchin/roboswag/components/extensions/View.kt diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/DelegationListAdapter.kt b/src/main/java/ru/touchin/roboswag/components/adapters/DelegationListAdapter.kt index 8f22d28..b8115d1 100644 --- a/src/main/java/ru/touchin/roboswag/components/adapters/DelegationListAdapter.kt +++ b/src/main/java/ru/touchin/roboswag/components/adapters/DelegationListAdapter.kt @@ -5,6 +5,7 @@ import android.support.v7.recyclerview.extensions.AsyncListDiffer import android.support.v7.util.DiffUtil import android.support.v7.widget.RecyclerView import android.view.ViewGroup +import ru.touchin.roboswag.components.extensions.setOnRippleClickListener /** * Base adapter with delegation and diff computing on background thread. @@ -34,7 +35,7 @@ open class DelegationListAdapter(config: AsyncDifferConfig) : Recy val collectionPosition = getCollectionPosition(position) if (collectionPosition in 0 until getList().size) { if (itemClickListener != null) { - holder.itemView.setOnClickListener { itemClickListener?.invoke(getList()[collectionPosition], holder) } + holder.itemView.setOnRippleClickListener { itemClickListener?.invoke(getList()[collectionPosition], holder) } } else { holder.itemView.setOnClickListener(null) } diff --git a/src/main/java/ru/touchin/roboswag/components/extensions/View.kt b/src/main/java/ru/touchin/roboswag/components/extensions/View.kt new file mode 100644 index 0000000..89744f6 --- /dev/null +++ b/src/main/java/ru/touchin/roboswag/components/extensions/View.kt @@ -0,0 +1,23 @@ +package ru.touchin.roboswag.components.extensions + +import android.os.Build +import android.view.View + +private const val RIPPLE_EFFECT_DELAY = 150L + +/** + * Sets click listener to view. On click it will call something after delay. + * + * @param delay Delay after which click listener will be called; + * @param listener Click listener. + */ +fun View.setOnRippleClickListener(delay: Long = RIPPLE_EFFECT_DELAY, listener: (View) -> Unit) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + setOnClickListener { view -> + handler?.removeCallbacksAndMessages(null) + postDelayed({ if (hasWindowFocus()) listener(view) }, delay) + } + } else { + setOnClickListener(listener) + } +} diff --git a/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.java b/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.java index 9343d00..b6eb724 100644 --- a/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.java +++ b/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.java @@ -21,17 +21,12 @@ package ru.touchin.roboswag.components.utils; import android.app.Activity; import android.app.Application; -import android.arch.lifecycle.Lifecycle; -import android.arch.lifecycle.LifecycleOwner; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.os.Build; -import android.os.Handler; -import android.os.Looper; import android.support.annotation.LayoutRes; import android.support.annotation.NonNull; -import android.support.annotation.Nullable; import android.util.DisplayMetrics; import android.util.TypedValue; import android.view.Display; @@ -43,9 +38,6 @@ import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.inputmethod.InputMethodManager; -import io.reactivex.functions.Action; -import io.reactivex.functions.Consumer; -import ru.touchin.roboswag.core.log.Lc; import ru.touchin.roboswag.core.log.LcGroup; /** @@ -62,12 +54,6 @@ public final class UiUtils { * Logging group to log UI lifecycle (onCreate, onStart, onResume etc.). */ public static final LcGroup UI_LIFECYCLE_LC_GROUP = new LcGroup("UI_LIFECYCLE"); - /** - * Delay to let user view ripple effect before screen changed. - */ - public static final long RIPPLE_EFFECT_DELAY = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? 150 : 0; - - private static final Handler RIPPLE_HANDLER = new Handler(Looper.getMainLooper()); /** * Method to inflate view with right layout parameters based on container and add inflated view as a child to it. @@ -94,61 +80,6 @@ public final class UiUtils { return LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false); } - /** - * Sets click listener to view. On click it will call something after delay. - * - * @param targetView View to set click listener to; - * @param onClickListener Click listener; - * @param delay Delay after which click listener will be called. - */ - public static void setOnRippleClickListener(@NonNull final View targetView, @Nullable final Action onClickListener, final long delay) { - setOnRippleClickListener(targetView, onClickListener != null ? view -> onClickListener.run() : null, delay); - } - - /** - * Sets click listener to view. On click it will call something with {@link #RIPPLE_EFFECT_DELAY}. - * - * @param targetView View to set click listener to; - * @param onClickListener Click listener. - */ - public static void setOnRippleClickListener(@NonNull final View targetView, @Nullable final Action onClickListener) { - setOnRippleClickListener(targetView, onClickListener != null ? view -> onClickListener.run() : null, RIPPLE_EFFECT_DELAY); - } - - /** - * Sets click listener to view. On click it will call something after delay. - * - * @param targetView View to set click listener to; - * @param onClickListener Click listener; - * @param delay Delay after which click listener will be called. - */ - public static void setOnRippleClickListener(@NonNull final View targetView, @Nullable final Consumer onClickListener, final long delay) { - if (onClickListener == null) { - targetView.setOnClickListener(null); - return; - } - - final Runnable runnable = () -> { - final Context context = targetView.getContext(); - if (targetView.getWindowVisibility() != View.VISIBLE - || !targetView.hasWindowFocus() - || (context instanceof LifecycleOwner - && !((LifecycleOwner) context).getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED))) { - return; - } - try { - onClickListener.accept(targetView); - } catch (final Exception exception) { - Lc.assertion(exception); - } - }; - - targetView.setOnClickListener(v -> { - RIPPLE_HANDLER.removeCallbacksAndMessages(null); - RIPPLE_HANDLER.postDelayed(runnable, delay); - }); - } - private UiUtils() { } From 399ec7d7f5bc6b32c71cf5972787ec1ae4a5b54b Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Fri, 30 Mar 2018 16:24:27 +0300 Subject: [PATCH 50/95] Getters for compat resources --- .../viewcontrollers/ViewController.java | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java b/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java index 42ecaf5..0a627f0 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java +++ b/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java @@ -23,15 +23,22 @@ import android.arch.lifecycle.Lifecycle; import android.arch.lifecycle.LifecycleOwner; import android.arch.lifecycle.LifecycleRegistry; import android.content.Intent; +import android.content.res.ColorStateList; +import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Parcelable; import android.support.annotation.CallSuper; +import android.support.annotation.ColorInt; +import android.support.annotation.ColorRes; +import android.support.annotation.DrawableRes; import android.support.annotation.IdRes; import android.support.annotation.LayoutRes; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.annotation.StringRes; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentActivity; +import android.support.v4.content.ContextCompat; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; @@ -166,6 +173,72 @@ public class ViewController< return getContainer().findViewById(id); } + /** + * Return a localized string from the application's package's default string table. + * + * @param resId Resource id for the string + */ + @NonNull + public final String getString(@StringRes final int resId) { + return getActivity().getString(resId); + } + + /** + * Return a localized formatted string from the application's package's default string table, substituting the format arguments as defined in + * {@link java.util.Formatter} and {@link java.lang.String#format}. + * + * @param resId Resource id for the format string + * @param formatArgs The format arguments that will be used for substitution. + */ + @NonNull + public final String getString(@StringRes final int resId, @NonNull final Object... formatArgs) { + return getActivity().getString(resId, formatArgs); + } + + /** + * Return the color value associated with a particular resource ID. + * Starting in {@link android.os.Build.VERSION_CODES#M}, the returned + * color will be styled for the specified Context's theme. + * + * @param resId The resource id to search for data; + * @return int A single color value in the form 0xAARRGGBB. + */ + @ColorInt + public final int getColor(@ColorRes final int resId) { + return ContextCompat.getColor(activity, resId); + } + + /** + * Returns a color state list associated with a particular resource ID. + *

+ * Starting in {@link android.os.Build.VERSION_CODES#M}, the returned + * color state list will be styled for the specified Context's theme. + * + * @param resId The desired resource identifier, as generated by the aapt + * tool. This integer encodes the package, type, and resource + * entry. The value 0 is an invalid identifier. + * @return A color state list, or {@code null} if the resource could not be resolved. + * @throws android.content.res.Resources.NotFoundException if the given ID + * does not exist. + */ + @Nullable + public final ColorStateList getColorStateList(@ColorRes final int resId) { + return ContextCompat.getColorStateList(getActivity(), resId); + } + + /** + * Returns a drawable object associated with a particular resource ID. + * Starting in {@link android.os.Build.VERSION_CODES#LOLLIPOP}, the + * returned drawable will be styled for the specified Context's theme. + * + * @param resId The resource id to search for data; + * @return Drawable An object that can be used to draw this resource. + */ + @Nullable + public final Drawable getDrawable(@DrawableRes final int resId) { + return ContextCompat.getDrawable(getActivity(), resId); + } + /** * Calls when activity configuring ActionBar, Toolbar, Sidebar etc. * If it will be called or not depends on {@link Fragment#hasOptionsMenu()} and {@link Fragment#isMenuVisible()}. From 0cbf55d81bdff72e13b73caa85bbe105c27f8553 Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Fri, 30 Mar 2018 18:19:11 +0300 Subject: [PATCH 51/95] ViewHolder extensions --- .../components/extensions/ViewHolder.kt | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 src/main/java/ru/touchin/roboswag/components/extensions/ViewHolder.kt diff --git a/src/main/java/ru/touchin/roboswag/components/extensions/ViewHolder.kt b/src/main/java/ru/touchin/roboswag/components/extensions/ViewHolder.kt new file mode 100644 index 0000000..f3601c7 --- /dev/null +++ b/src/main/java/ru/touchin/roboswag/components/extensions/ViewHolder.kt @@ -0,0 +1,26 @@ +package ru.touchin.roboswag.components.extensions + +import android.content.res.ColorStateList +import android.graphics.drawable.Drawable +import android.support.annotation.ColorInt +import android.support.annotation.ColorRes +import android.support.annotation.IdRes +import android.support.annotation.StringRes +import android.support.v4.content.ContextCompat +import android.support.v7.widget.RecyclerView +import android.view.View + +fun RecyclerView.ViewHolder.findViewById(@IdRes resId: Int): T = itemView.findViewById(resId) + +fun RecyclerView.ViewHolder.getText(@StringRes resId: Int): CharSequence = itemView.context.getText(resId) + +fun RecyclerView.ViewHolder.getString(@StringRes resId: Int): String = itemView.context.getString(resId) + +fun RecyclerView.ViewHolder.getString(@StringRes resId: Int, vararg args: Any): String = itemView.context.getString(resId, args) + +@ColorInt +fun RecyclerView.ViewHolder.getColor(@ColorRes resId: Int): Int = ContextCompat.getColor(itemView.context, resId) + +fun RecyclerView.ViewHolder.getColorStateList(@ColorRes resId: Int): ColorStateList? = ContextCompat.getColorStateList(itemView.context, resId) + +fun RecyclerView.ViewHolder.getDrawable(@ColorRes resId: Int): Drawable? = ContextCompat.getDrawable(itemView.context, resId) From b06a1232b715e185686f309c913ce4521446b8ed Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Fri, 30 Mar 2018 18:25:42 +0300 Subject: [PATCH 52/95] getText in ViewController --- .../components/extensions/ViewHolder.kt | 3 ++- .../viewcontrollers/ViewController.java | 19 +++++++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/main/java/ru/touchin/roboswag/components/extensions/ViewHolder.kt b/src/main/java/ru/touchin/roboswag/components/extensions/ViewHolder.kt index f3601c7..e1ea239 100644 --- a/src/main/java/ru/touchin/roboswag/components/extensions/ViewHolder.kt +++ b/src/main/java/ru/touchin/roboswag/components/extensions/ViewHolder.kt @@ -4,6 +4,7 @@ import android.content.res.ColorStateList import android.graphics.drawable.Drawable import android.support.annotation.ColorInt import android.support.annotation.ColorRes +import android.support.annotation.DrawableRes import android.support.annotation.IdRes import android.support.annotation.StringRes import android.support.v4.content.ContextCompat @@ -23,4 +24,4 @@ fun RecyclerView.ViewHolder.getColor(@ColorRes resId: Int): Int = ContextCompat. fun RecyclerView.ViewHolder.getColorStateList(@ColorRes resId: Int): ColorStateList? = ContextCompat.getColorStateList(itemView.context, resId) -fun RecyclerView.ViewHolder.getDrawable(@ColorRes resId: Int): Drawable? = ContextCompat.getDrawable(itemView.context, resId) +fun RecyclerView.ViewHolder.getDrawable(@DrawableRes resId: Int): Drawable? = ContextCompat.getDrawable(itemView.context, resId) diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java b/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java index 0a627f0..7111d99 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java +++ b/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java @@ -173,6 +173,17 @@ public class ViewController< return getContainer().findViewById(id); } + /** + * Return a localized, styled CharSequence from the application's package's + * default string table. + * + * @param resId Resource id for the CharSequence text + */ + @NonNull + public final CharSequence getText(@StringRes final int resId) { + return activity.getText(resId); + } + /** * Return a localized string from the application's package's default string table. * @@ -180,7 +191,7 @@ public class ViewController< */ @NonNull public final String getString(@StringRes final int resId) { - return getActivity().getString(resId); + return activity.getString(resId); } /** @@ -192,7 +203,7 @@ public class ViewController< */ @NonNull public final String getString(@StringRes final int resId, @NonNull final Object... formatArgs) { - return getActivity().getString(resId, formatArgs); + return activity.getString(resId, formatArgs); } /** @@ -223,7 +234,7 @@ public class ViewController< */ @Nullable public final ColorStateList getColorStateList(@ColorRes final int resId) { - return ContextCompat.getColorStateList(getActivity(), resId); + return ContextCompat.getColorStateList(activity, resId); } /** @@ -236,7 +247,7 @@ public class ViewController< */ @Nullable public final Drawable getDrawable(@DrawableRes final int resId) { - return ContextCompat.getDrawable(getActivity(), resId); + return ContextCompat.getDrawable(activity, resId); } /** From ed7cd8bd42878a096618545a4cdf0596485b62c7 Mon Sep 17 00:00:00 2001 From: nbnbbs Date: Tue, 3 Apr 2018 13:09:52 +0300 Subject: [PATCH 53/95] small sA fixes (#122) * small sA fixes * small fixes * remove suppress --- .../navigation/fragments/ViewControllerFragment.java | 8 ++++---- .../navigation/viewcontrollers/ViewController.java | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java b/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java index 68f9be0..c75fc1d 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java +++ b/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java @@ -84,7 +84,7 @@ public class ViewControllerFragment - * Starting in {@link android.os.Build.VERSION_CODES#M}, the returned + * + *

Starting in {@link android.os.Build.VERSION_CODES#M}, the returned * color state list will be styled for the specified Context's theme. * * @param resId The desired resource identifier, as generated by the aapt From b64452b086d3f1a22679ff77a6fe83c86fb8e233 Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Wed, 4 Apr 2018 18:55:32 +0300 Subject: [PATCH 54/95] Initial fragment fix in navigation --- .../roboswag/components/navigation/FragmentNavigation.kt | 8 ++++---- .../viewcontrollers/ViewControllerNavigation.kt | 9 +++++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.kt b/src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.kt index fe63d34..118060d 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.kt +++ b/src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.kt @@ -52,7 +52,6 @@ open class FragmentNavigation( companion object { const val TOP_FRAGMENT_TAG_MARK = "TOP_FRAGMENT" - const val WITH_TARGET_FRAGMENT_TAG_MARK = "FRAGMENT_WITH_TARGET" } /** @@ -206,7 +205,7 @@ open class FragmentNavigation( targetRequestCode, true, args, - fragmentClass.name + ';'.toString() + WITH_TARGET_FRAGMENT_TAG_MARK, + null, transactionSetup ) } @@ -222,9 +221,10 @@ open class FragmentNavigation( fun setAsTop( fragmentClass: Class, args: Bundle? = null, + addToStack: Boolean = true, transactionSetup: ((FragmentTransaction) -> Unit)? = null ) { - addToStack(fragmentClass, null, 0, true, args, fragmentClass.name + ';'.toString() + TOP_FRAGMENT_TAG_MARK, transactionSetup) + addToStack(fragmentClass, null, 0, addToStack, args, "${fragmentClass.name};$TOP_FRAGMENT_TAG_MARK", transactionSetup) } /** @@ -241,7 +241,7 @@ open class FragmentNavigation( transactionSetup: ((FragmentTransaction) -> Unit)? = null ) { beforeSetInitialActions() - setAsTop(fragmentClass, args, transactionSetup) + setAsTop(fragmentClass, args, false, transactionSetup) } /** diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewControllerNavigation.kt b/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewControllerNavigation.kt index 103c9f7..b6a6fba 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewControllerNavigation.kt +++ b/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewControllerNavigation.kt @@ -94,7 +94,7 @@ open class ViewControllerNavigation( targetRequestCode, true, ViewControllerFragment.args(viewControllerClass, state), - viewControllerClass.name + ';'.toString() + WITH_TARGET_FRAGMENT_TAG_MARK, + null, transactionSetup ) } @@ -111,15 +111,16 @@ open class ViewControllerNavigation( fun setViewControllerAsTop( viewControllerClass: Class, TState>>, state: TState, + addToStack: Boolean = true, transactionSetup: ((FragmentTransaction) -> Unit)? = null ) { addToStack( ViewControllerFragment::class.java, null, 0, - true, + addToStack, ViewControllerFragment.args(viewControllerClass, state), - viewControllerClass.name + ';'.toString() + TOP_FRAGMENT_TAG_MARK, + "${viewControllerClass.name};$TOP_FRAGMENT_TAG_MARK", transactionSetup ) } @@ -139,7 +140,7 @@ open class ViewControllerNavigation( transactionSetup: ((FragmentTransaction) -> Unit)? = null ) { beforeSetInitialActions() - setViewControllerAsTop(viewControllerClass, state, transactionSetup) + setViewControllerAsTop(viewControllerClass, state, false, transactionSetup) } } From 9508946b5a3dcac8bc18f304a70a7babd8a25fc3 Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Wed, 11 Apr 2018 13:40:57 +0300 Subject: [PATCH 55/95] ViewHolder.context extension, ViewController startActivity methods --- .../components/extensions/ViewHolder.kt | 16 +++++---- .../viewcontrollers/ViewController.java | 36 +++++++++++-------- 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/src/main/java/ru/touchin/roboswag/components/extensions/ViewHolder.kt b/src/main/java/ru/touchin/roboswag/components/extensions/ViewHolder.kt index e1ea239..9a42e71 100644 --- a/src/main/java/ru/touchin/roboswag/components/extensions/ViewHolder.kt +++ b/src/main/java/ru/touchin/roboswag/components/extensions/ViewHolder.kt @@ -1,5 +1,6 @@ package ru.touchin.roboswag.components.extensions +import android.content.Context import android.content.res.ColorStateList import android.graphics.drawable.Drawable import android.support.annotation.ColorInt @@ -13,15 +14,18 @@ import android.view.View fun RecyclerView.ViewHolder.findViewById(@IdRes resId: Int): T = itemView.findViewById(resId) -fun RecyclerView.ViewHolder.getText(@StringRes resId: Int): CharSequence = itemView.context.getText(resId) +val RecyclerView.ViewHolder.context: Context + get() = itemView.context -fun RecyclerView.ViewHolder.getString(@StringRes resId: Int): String = itemView.context.getString(resId) +fun RecyclerView.ViewHolder.getText(@StringRes resId: Int): CharSequence = context.getText(resId) -fun RecyclerView.ViewHolder.getString(@StringRes resId: Int, vararg args: Any): String = itemView.context.getString(resId, args) +fun RecyclerView.ViewHolder.getString(@StringRes resId: Int): String = context.getString(resId) + +fun RecyclerView.ViewHolder.getString(@StringRes resId: Int, vararg args: Any): String = context.getString(resId, args) @ColorInt -fun RecyclerView.ViewHolder.getColor(@ColorRes resId: Int): Int = ContextCompat.getColor(itemView.context, resId) +fun RecyclerView.ViewHolder.getColor(@ColorRes resId: Int): Int = ContextCompat.getColor(context, resId) -fun RecyclerView.ViewHolder.getColorStateList(@ColorRes resId: Int): ColorStateList? = ContextCompat.getColorStateList(itemView.context, resId) +fun RecyclerView.ViewHolder.getColorStateList(@ColorRes resId: Int): ColorStateList? = ContextCompat.getColorStateList(context, resId) -fun RecyclerView.ViewHolder.getDrawable(@DrawableRes resId: Int): Drawable? = ContextCompat.getDrawable(itemView.context, resId) +fun RecyclerView.ViewHolder.getDrawable(@DrawableRes resId: Int): Drawable? = ContextCompat.getDrawable(context, resId) diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java b/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java index 91af235..f01877e 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java +++ b/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java @@ -94,16 +94,6 @@ public class ViewController< return activity; } - /** - * Returns state from fragment. - * - * @return Returns state. - */ - @NonNull - public final TState getState() { - return fragment.getState(); - } - /** * Returns fragment where {@link ViewController} could be. * @@ -114,6 +104,16 @@ public class ViewController< return fragment; } + /** + * Returns state from fragment. + * + * @return Returns state. + */ + @NonNull + protected final TState getState() { + return fragment.getState(); + } + /** * Returns view instantiated in {@link #getFragment()} fragment attached to {@link #getActivity()} activity. * Use it to inflate your views into at construction of this {@link ViewController}. @@ -121,7 +121,7 @@ public class ViewController< * @return Returns view. */ @NonNull - public final ViewGroup getContainer() { + protected final ViewGroup getContainer() { return container; } @@ -131,7 +131,7 @@ public class ViewController< * * @param layoutResId Resource ID to be inflated. */ - public final void setContentView(@LayoutRes final int layoutResId) { + protected final void setContentView(@LayoutRes final int layoutResId) { if (getContainer().getChildCount() > 0) { getContainer().removeAllViews(); } @@ -144,7 +144,7 @@ public class ViewController< * * @param view The desired content to display. */ - public final void setContentView(@NonNull final View view) { + protected final void setContentView(@NonNull final View view) { setContentView(view, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); } @@ -155,7 +155,7 @@ public class ViewController< * @param view The desired content to display; * @param layoutParams Layout parameters for the view. */ - public final void setContentView(@NonNull final View view, @NonNull final ViewGroup.LayoutParams layoutParams) { + protected final void setContentView(@NonNull final View view, @NonNull final ViewGroup.LayoutParams layoutParams) { if (getContainer().getChildCount() > 0) { getContainer().removeAllViews(); } @@ -250,6 +250,14 @@ public class ViewController< return ContextCompat.getDrawable(activity, resId); } + public final void startActivity(@NonNull final Intent intent) { + fragment.startActivity(intent); + } + + public final void startActivityForResult(@NonNull final Intent intent, final int requestCode) { + fragment.startActivityForResult(intent, requestCode); + } + /** * Calls when activity configuring ActionBar, Toolbar, Sidebar etc. * If it will be called or not depends on {@link Fragment#hasOptionsMenu()} and {@link Fragment#isMenuVisible()}. From 496e0da5dae537f4782571ed9ae6b2c58800fc3d Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Sat, 21 Apr 2018 02:37:35 +0300 Subject: [PATCH 56/95] Removed TFragment generic in ViewController --- .../fragments/ViewControllerFragment.java | 5 ++--- .../viewcontrollers/DefaultViewController.kt | 3 +-- .../viewcontrollers/ViewController.java | 15 ++++++--------- .../viewcontrollers/ViewControllerNavigation.kt | 8 ++++---- 4 files changed, 13 insertions(+), 18 deletions(-) diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java b/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java index c75fc1d..1f63981 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java +++ b/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java @@ -105,7 +105,7 @@ public class ViewControllerFragment, TState>> viewControllerClass; + private Class> viewControllerClass; private TState state; @Nullable private ActivityResult pendingActivityResult; @@ -127,8 +127,7 @@ public class ViewControllerFragment, TState>>) - getArguments().getSerializable(VIEW_CONTROLLER_CLASS_EXTRA); + viewControllerClass = (Class>) getArguments().getSerializable(VIEW_CONTROLLER_CLASS_EXTRA); state = savedInstanceState != null ? savedInstanceState.getParcelable(VIEW_CONTROLLER_STATE_EXTRA) : (getArguments() != null ? getArguments().getParcelable(VIEW_CONTROLLER_STATE_EXTRA) : null); diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/DefaultViewController.kt b/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/DefaultViewController.kt index 6b5014b..0fae24f 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/DefaultViewController.kt +++ b/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/DefaultViewController.kt @@ -4,13 +4,12 @@ import android.os.Bundle import android.os.Parcelable import android.support.annotation.LayoutRes import android.support.v4.app.FragmentActivity -import ru.touchin.roboswag.components.navigation.fragments.ViewControllerFragment abstract class DefaultViewController( @LayoutRes layoutRes: Int, creationContext: CreationContext, savedInstanceState: Bundle? -) : ViewController, TState>( +) : ViewController( creationContext, savedInstanceState ) { diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java b/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java index f01877e..883538c 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java +++ b/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java @@ -54,19 +54,16 @@ import ru.touchin.roboswag.core.log.Lc; * Class to control view of specific fragment, activity and application by logic bridge. * * @param Type of activity where such {@link ViewController} could be; - * @param Type of fragment where such {@link ViewController} could be; + * @param Type of state; */ -public class ViewController< - TActivity extends FragmentActivity, - TFragment extends ViewControllerFragment, - TState extends Parcelable> implements LifecycleOwner { +public class ViewController implements LifecycleOwner { @NonNull private final LifecycleRegistry lifecycleRegistry = new LifecycleRegistry(this); @NonNull private final TActivity activity; @NonNull - private final TFragment fragment; + private final ViewControllerFragment fragment; @NonNull private final ViewGroup container; @@ -74,7 +71,7 @@ public class ViewController< //UnusedFormalParameter: savedInstanceState could be used by children public ViewController(@NonNull final CreationContext creationContext, @Nullable final Bundle savedInstanceState) { this.activity = (TActivity) creationContext.activity; - this.fragment = (TFragment) creationContext.fragment; + this.fragment = creationContext.fragment; this.container = creationContext.container; } @@ -100,7 +97,7 @@ public class ViewController< * @return Returns fragment. */ @NonNull - public final TFragment getFragment() { + public final ViewControllerFragment getFragment() { return fragment; } @@ -111,7 +108,7 @@ public class ViewController< */ @NonNull protected final TState getState() { - return fragment.getState(); + return fragment.getState() ; } /** diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewControllerNavigation.kt b/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewControllerNavigation.kt index b6a6fba..9bf99dd 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewControllerNavigation.kt +++ b/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewControllerNavigation.kt @@ -54,7 +54,7 @@ open class ViewControllerNavigation( * @param TState Type of state of fragment. */ fun pushViewController( - viewControllerClass: Class, TState>>, + viewControllerClass: Class>, state: TState, addToStack: Boolean = true, transactionSetup: ((FragmentTransaction) -> Unit)? = null @@ -82,7 +82,7 @@ open class ViewControllerNavigation( * @param TTargetFragment Type of target fragment. */ fun pushViewControllerForResult( - viewControllerClass: Class, TState>>, + viewControllerClass: Class>, state: TState, targetFragment: TTargetFragment, targetRequestCode: Int, @@ -109,7 +109,7 @@ open class ViewControllerNavigation( * @param TState Type of state of fragment. */ fun setViewControllerAsTop( - viewControllerClass: Class, TState>>, + viewControllerClass: Class>, state: TState, addToStack: Boolean = true, transactionSetup: ((FragmentTransaction) -> Unit)? = null @@ -135,7 +135,7 @@ open class ViewControllerNavigation( * @param TState Type of state of fragment. */ fun setInitialViewController( - viewControllerClass: Class, TState>>, + viewControllerClass: Class>, state: TState, transactionSetup: ((FragmentTransaction) -> Unit)? = null ) { From 7edc1fabe18ea5fc236891bf014ea0839b20deb4 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 22 Apr 2018 21:11:37 +0300 Subject: [PATCH 57/95] Fix back stack --- .../roboswag/components/navigation/FragmentNavigation.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.kt b/src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.kt index 118060d..384ef16 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.kt +++ b/src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.kt @@ -128,7 +128,7 @@ open class FragmentNavigation( * @return True if it have back to some entry in stack. */ fun back(): Boolean { - if (fragmentManager.backStackEntryCount > 1) { + if (fragmentManager.backStackEntryCount >= 1) { fragmentManager.popBackStack() return true } From ebdba5a90b918340946d6c73eaabeaa00c9045b1 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 22 Apr 2018 21:28:44 +0300 Subject: [PATCH 58/95] Delete space betwen semicolon --- .../components/navigation/viewcontrollers/ViewController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java b/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java index 883538c..e1e7c14 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java +++ b/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java @@ -108,7 +108,7 @@ public class ViewController Date: Sun, 22 Apr 2018 23:53:22 +0300 Subject: [PATCH 59/95] setOnRippleClickListener fix --- .../java/ru/touchin/roboswag/components/extensions/View.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/main/java/ru/touchin/roboswag/components/extensions/View.kt b/src/main/java/ru/touchin/roboswag/components/extensions/View.kt index 89744f6..9f4ddca 100644 --- a/src/main/java/ru/touchin/roboswag/components/extensions/View.kt +++ b/src/main/java/ru/touchin/roboswag/components/extensions/View.kt @@ -13,10 +13,7 @@ private const val RIPPLE_EFFECT_DELAY = 150L */ fun View.setOnRippleClickListener(delay: Long = RIPPLE_EFFECT_DELAY, listener: (View) -> Unit) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - setOnClickListener { view -> - handler?.removeCallbacksAndMessages(null) - postDelayed({ if (hasWindowFocus()) listener(view) }, delay) - } + setOnClickListener { view -> postDelayed({ if (hasWindowFocus()) listener(view) }, delay) } } else { setOnClickListener(listener) } From 400ae4f00dab78d754f7bc1c10e0951fcf3139e8 Mon Sep 17 00:00:00 2001 From: Arseniy Borisov Date: Tue, 24 Apr 2018 17:34:29 +0300 Subject: [PATCH 60/95] fix getString --- .../ru/touchin/roboswag/components/extensions/ViewHolder.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/ru/touchin/roboswag/components/extensions/ViewHolder.kt b/src/main/java/ru/touchin/roboswag/components/extensions/ViewHolder.kt index 9a42e71..db7165f 100644 --- a/src/main/java/ru/touchin/roboswag/components/extensions/ViewHolder.kt +++ b/src/main/java/ru/touchin/roboswag/components/extensions/ViewHolder.kt @@ -21,7 +21,8 @@ fun RecyclerView.ViewHolder.getText(@StringRes resId: Int): CharSequence = conte fun RecyclerView.ViewHolder.getString(@StringRes resId: Int): String = context.getString(resId) -fun RecyclerView.ViewHolder.getString(@StringRes resId: Int, vararg args: Any): String = context.getString(resId, args) +@SuppressWarnings("SpreadOperator") // it's OK for small arrays +fun RecyclerView.ViewHolder.getString(@StringRes resId: Int, vararg args: Any): String = context.getString(resId, *args) @ColorInt fun RecyclerView.ViewHolder.getColor(@ColorRes resId: Int): Int = ContextCompat.getColor(context, resId) From e2107e9990187d7aec1f6244dab9e72855c8139e Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Fri, 27 Apr 2018 14:02:29 +0300 Subject: [PATCH 61/95] Simple delegate for properties --- .../roboswag/components/extensions/Delegates.kt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/main/java/ru/touchin/roboswag/components/extensions/Delegates.kt diff --git a/src/main/java/ru/touchin/roboswag/components/extensions/Delegates.kt b/src/main/java/ru/touchin/roboswag/components/extensions/Delegates.kt new file mode 100644 index 0000000..c8e0538 --- /dev/null +++ b/src/main/java/ru/touchin/roboswag/components/extensions/Delegates.kt @@ -0,0 +1,16 @@ +package ru.touchin.roboswag.components.extensions + +import kotlin.properties.Delegates +import kotlin.properties.ObservableProperty +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty + +/** + * Simple observable delegate only for notification of new value. + */ +inline fun Delegates.observable( + initialValue: T, + crossinline onChange: (newValue: T) -> Unit +): ReadWriteProperty = object : ObservableProperty(initialValue) { + override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = onChange(newValue) +} From 0815cdd4211f8a1f8fa2ddb65f7f49f4ebf83fe5 Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Sat, 28 Apr 2018 18:07:57 +0300 Subject: [PATCH 62/95] Invalidation menu fix --- .../components/navigation/fragments/ViewControllerFragment.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java b/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java index 1f63981..d14cda0 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java +++ b/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java @@ -197,7 +197,6 @@ public class ViewControllerFragment Date: Fri, 4 May 2018 20:45:38 +0300 Subject: [PATCH 63/95] Added getter for viewControllerClass --- .../navigation/fragments/ViewControllerFragment.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java b/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java index d14cda0..60038b9 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java +++ b/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java @@ -120,6 +120,11 @@ public class ViewControllerFragment> getViewControllerClass() { + return viewControllerClass; + } + @Override public void onCreate(@Nullable final Bundle savedInstanceState) { super.onCreate(savedInstanceState); From d71938c87f26653641ced49a215b916caff2ded7 Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Thu, 10 May 2018 21:24:06 +0300 Subject: [PATCH 64/95] ViewControllerNavigation: added wildcard for activity --- .../viewcontrollers/ViewControllerNavigation.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewControllerNavigation.kt b/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewControllerNavigation.kt index 9bf99dd..a8e4b87 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewControllerNavigation.kt +++ b/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewControllerNavigation.kt @@ -54,7 +54,7 @@ open class ViewControllerNavigation( * @param TState Type of state of fragment. */ fun pushViewController( - viewControllerClass: Class>, + viewControllerClass: Class>, state: TState, addToStack: Boolean = true, transactionSetup: ((FragmentTransaction) -> Unit)? = null @@ -82,7 +82,7 @@ open class ViewControllerNavigation( * @param TTargetFragment Type of target fragment. */ fun pushViewControllerForResult( - viewControllerClass: Class>, + viewControllerClass: Class>, state: TState, targetFragment: TTargetFragment, targetRequestCode: Int, @@ -109,7 +109,7 @@ open class ViewControllerNavigation( * @param TState Type of state of fragment. */ fun setViewControllerAsTop( - viewControllerClass: Class>, + viewControllerClass: Class>, state: TState, addToStack: Boolean = true, transactionSetup: ((FragmentTransaction) -> Unit)? = null @@ -135,7 +135,7 @@ open class ViewControllerNavigation( * @param TState Type of state of fragment. */ fun setInitialViewController( - viewControllerClass: Class>, + viewControllerClass: Class>, state: TState, transactionSetup: ((FragmentTransaction) -> Unit)? = null ) { From 77a0160fce8b25fea5b0816b2158693783fbe6b5 Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Wed, 16 May 2018 02:44:38 +0300 Subject: [PATCH 65/95] Fragment custom animations fix --- .../roboswag/components/navigation/FragmentNavigation.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.kt b/src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.kt index 384ef16..ef179ab 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.kt +++ b/src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.kt @@ -111,14 +111,14 @@ open class FragmentNavigation( } val fragmentTransaction = fragmentManager.beginTransaction() - .replace(containerViewId, fragment, null) + transactionSetup?.invoke(fragmentTransaction) + fragmentTransaction.replace(containerViewId, fragment, null) if (addToStack) { fragmentTransaction.addToBackStack(backStackTag) } if (fragmentManager.backStackEntryCount != 0) { fragmentTransaction.setTransition(transition) } - transactionSetup?.invoke(fragmentTransaction) fragmentTransaction.commit() } From a3c113125d9460bbe6dd8058063c16170b12d639 Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Fri, 25 May 2018 21:08:25 +0300 Subject: [PATCH 66/95] Destroyable: added clear subscriptions method --- .../roboswag/components/utils/destroyable/BaseDestroyable.kt | 2 ++ .../roboswag/components/utils/destroyable/Destroyable.kt | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/src/main/java/ru/touchin/roboswag/components/utils/destroyable/BaseDestroyable.kt b/src/main/java/ru/touchin/roboswag/components/utils/destroyable/BaseDestroyable.kt index f2a47a9..418f2c4 100644 --- a/src/main/java/ru/touchin/roboswag/components/utils/destroyable/BaseDestroyable.kt +++ b/src/main/java/ru/touchin/roboswag/components/utils/destroyable/BaseDestroyable.kt @@ -17,6 +17,8 @@ open class BaseDestroyable : Destroyable { private val subscriptions = CompositeDisposable() + override fun clearSubscriptions() = subscriptions.clear() + /** * Call it on parent's onDestroy method. */ diff --git a/src/main/java/ru/touchin/roboswag/components/utils/destroyable/Destroyable.kt b/src/main/java/ru/touchin/roboswag/components/utils/destroyable/Destroyable.kt index 6d2aea7..b5c7c16 100644 --- a/src/main/java/ru/touchin/roboswag/components/utils/destroyable/Destroyable.kt +++ b/src/main/java/ru/touchin/roboswag/components/utils/destroyable/Destroyable.kt @@ -24,6 +24,11 @@ interface Destroyable { } } + /** + * Removes all subscriptions + */ + fun clearSubscriptions() + /** * Method should be used to guarantee that observable won't be subscribed after onDestroy. * It is automatically subscribing to the observable and calls onNextAction and onErrorAction on observable events. From 818d8aedaf05a0443f96bfb7d4c21d8a1ebc2237 Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Sat, 26 May 2018 23:18:17 +0300 Subject: [PATCH 67/95] ViewController: added onCreateAnimation callbacks Navigation: some fixes Resources: added slide fragment transition animations --- .../navigation/FragmentNavigation.kt | 6 +-- .../navigation/activities/BaseActivity.java | 11 +++++ .../fragments/ViewControllerFragment.java | 30 +++++++++++++ .../viewcontrollers/ViewController.java | 44 +++++++++++++++++++ ...agment_slide_in_left_paralax_animation.xml | 6 +++ .../fragment_slide_in_right_animation.xml | 6 +++ ...gment_slide_out_left_paralax_animation.xml | 6 +++ .../fragment_slide_out_right_animation.xml | 6 +++ src/main/res/values/integers.xml | 4 ++ 9 files changed, 116 insertions(+), 3 deletions(-) create mode 100644 src/main/res/anim/fragment_slide_in_left_paralax_animation.xml create mode 100644 src/main/res/anim/fragment_slide_in_right_animation.xml create mode 100644 src/main/res/anim/fragment_slide_out_left_paralax_animation.xml create mode 100644 src/main/res/anim/fragment_slide_out_right_animation.xml create mode 100644 src/main/res/values/integers.xml diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.kt b/src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.kt index ef179ab..39d33cc 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.kt +++ b/src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.kt @@ -111,13 +111,13 @@ open class FragmentNavigation( } val fragmentTransaction = fragmentManager.beginTransaction() + .setTransition(transition) transactionSetup?.invoke(fragmentTransaction) fragmentTransaction.replace(containerViewId, fragment, null) if (addToStack) { fragmentTransaction.addToBackStack(backStackTag) - } - if (fragmentManager.backStackEntryCount != 0) { - fragmentTransaction.setTransition(transition) + } else { + fragmentTransaction.setPrimaryNavigationFragment(fragment) } fragmentTransaction.commit() } diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java b/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java index 974fa46..2343af1 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java +++ b/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java @@ -25,6 +25,7 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.util.ArraySet; import android.support.v7.app.AppCompatActivity; + import android.view.MenuItem; import java.util.Set; @@ -94,6 +95,16 @@ public abstract class BaseActivity extends AppCompatActivity { super.onDestroy(); } + @Override + public boolean onOptionsItemSelected(@NonNull final MenuItem item) { + if (item.getItemId() == android.R.id.home) { + onBackPressed(); + return true; + } else { + return super.onOptionsItemSelected(item); + } + } + public void addOnBackPressedListener(@NonNull final OnBackPressedListener onBackPressedListener) { onBackPressedListeners.add(onBackPressedListener); } diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java b/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java index 60038b9..94655cf 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java +++ b/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java @@ -19,6 +19,7 @@ package ru.touchin.roboswag.components.navigation.fragments; +import android.animation.Animator; import android.content.Context; import android.content.Intent; import android.graphics.Canvas; @@ -29,16 +30,19 @@ import android.os.SystemClock; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.FragmentActivity; +import android.support.v4.view.ViewCompat; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.view.animation.Animation; import android.widget.FrameLayout; import java.lang.reflect.Constructor; +import ru.touchin.roboswag.components.R; import ru.touchin.roboswag.components.navigation.viewcontrollers.ViewController; import ru.touchin.roboswag.components.utils.UiUtils; import ru.touchin.roboswag.core.log.Lc; @@ -51,6 +55,7 @@ import ru.touchin.roboswag.core.utils.ShouldNotHappenException; * @param Type of object which is representing it's fragment state; * @param Type of {@link FragmentActivity} where fragment could be attached to. */ +@SuppressWarnings("PMD.TooManyMethods") public class ViewControllerFragment extends ViewFragment { private static final String VIEW_CONTROLLER_CLASS_EXTRA = "VIEW_CONTROLLER_CLASS_EXTRA"; @@ -204,6 +209,31 @@ public class ViewControllerFragment diff --git a/src/main/res/anim/fragment_slide_in_right_animation.xml b/src/main/res/anim/fragment_slide_in_right_animation.xml new file mode 100644 index 0000000..76be496 --- /dev/null +++ b/src/main/res/anim/fragment_slide_in_right_animation.xml @@ -0,0 +1,6 @@ + diff --git a/src/main/res/anim/fragment_slide_out_left_paralax_animation.xml b/src/main/res/anim/fragment_slide_out_left_paralax_animation.xml new file mode 100644 index 0000000..67b35b0 --- /dev/null +++ b/src/main/res/anim/fragment_slide_out_left_paralax_animation.xml @@ -0,0 +1,6 @@ + diff --git a/src/main/res/anim/fragment_slide_out_right_animation.xml b/src/main/res/anim/fragment_slide_out_right_animation.xml new file mode 100644 index 0000000..9757e23 --- /dev/null +++ b/src/main/res/anim/fragment_slide_out_right_animation.xml @@ -0,0 +1,6 @@ + diff --git a/src/main/res/values/integers.xml b/src/main/res/values/integers.xml new file mode 100644 index 0000000..93ec0fc --- /dev/null +++ b/src/main/res/values/integers.xml @@ -0,0 +1,4 @@ + + + 250 + From b528ec185ef1374e2b804f08f21ac994c854b045 Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Sun, 27 May 2018 00:03:01 +0300 Subject: [PATCH 68/95] Static analysis --- .../roboswag/components/navigation/activities/BaseActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java b/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java index 2343af1..7a3f0b7 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java +++ b/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java @@ -25,7 +25,7 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.util.ArraySet; import android.support.v7.app.AppCompatActivity; - import android.view.MenuItem; +import android.view.MenuItem; import java.util.Set; From 78d213abc5c7438410979000d6e994e565b050ec Mon Sep 17 00:00:00 2001 From: Unknown Date: Mon, 28 May 2018 20:06:00 +0300 Subject: [PATCH 69/95] Add on request permissons result callback --- .../navigation/fragments/ViewControllerFragment.java | 8 ++++++++ .../components/navigation/fragments/ViewFragment.java | 5 +++++ .../navigation/viewcontrollers/ViewController.java | 10 ++++++++++ 3 files changed, 23 insertions(+) diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java b/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java index 94655cf..e334a04 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java +++ b/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java @@ -321,6 +321,14 @@ public class ViewControllerFragment extends F super.onPause(); } + @Override + public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + } + /** * Replacement of {@link #onPause} with non null activity as first parameter. * diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java b/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java index d8aa54b..18a89e6 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java +++ b/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java @@ -405,6 +405,16 @@ public class ViewController Date: Mon, 28 May 2018 20:46:21 +0300 Subject: [PATCH 70/95] add comment --- .../components/navigation/viewcontrollers/ViewController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java b/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java index 18a89e6..ce7fa5b 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java +++ b/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java @@ -412,7 +412,7 @@ public class ViewController Date: Tue, 29 May 2018 09:40:44 +0300 Subject: [PATCH 71/95] Delete method --- .../components/navigation/fragments/ViewFragment.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewFragment.java b/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewFragment.java index c2f1296..5cd873f 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewFragment.java +++ b/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewFragment.java @@ -184,11 +184,6 @@ public abstract class ViewFragment extends F super.onPause(); } - @Override - public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - } - /** * Replacement of {@link #onPause} with non null activity as first parameter. * From a789ae9efe21a558ffb35a3737254887a4432fb6 Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Tue, 29 May 2018 12:54:12 +0300 Subject: [PATCH 72/95] Static Analysis --- .../components/navigation/viewcontrollers/ViewController.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java b/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java index ce7fa5b..a025f87 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java +++ b/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java @@ -411,6 +411,7 @@ public class ViewController Date: Tue, 29 May 2018 12:58:41 +0300 Subject: [PATCH 73/95] Transition fix --- .../roboswag/components/navigation/FragmentNavigation.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.kt b/src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.kt index 39d33cc..4e82491 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.kt +++ b/src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.kt @@ -111,11 +111,10 @@ open class FragmentNavigation( } val fragmentTransaction = fragmentManager.beginTransaction() - .setTransition(transition) transactionSetup?.invoke(fragmentTransaction) fragmentTransaction.replace(containerViewId, fragment, null) if (addToStack) { - fragmentTransaction.addToBackStack(backStackTag) + fragmentTransaction.addToBackStack(backStackTag).setTransition(transition) } else { fragmentTransaction.setPrimaryNavigationFragment(fragment) } From 5b3ccdbf91955a8c4ebda46a9916e8dd78602535 Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Tue, 29 May 2018 13:52:17 +0300 Subject: [PATCH 74/95] List adapter click listener fix --- .../roboswag/components/adapters/DelegationListAdapter.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/DelegationListAdapter.kt b/src/main/java/ru/touchin/roboswag/components/adapters/DelegationListAdapter.kt index b8115d1..094b298 100644 --- a/src/main/java/ru/touchin/roboswag/components/adapters/DelegationListAdapter.kt +++ b/src/main/java/ru/touchin/roboswag/components/adapters/DelegationListAdapter.kt @@ -35,7 +35,9 @@ open class DelegationListAdapter(config: AsyncDifferConfig) : Recy val collectionPosition = getCollectionPosition(position) if (collectionPosition in 0 until getList().size) { if (itemClickListener != null) { - holder.itemView.setOnRippleClickListener { itemClickListener?.invoke(getList()[collectionPosition], holder) } + holder.itemView.setOnRippleClickListener { + itemClickListener?.invoke(getList()[getCollectionPosition(holder.adapterPosition)], holder) + } } else { holder.itemView.setOnClickListener(null) } From f3d076aa6c046b98af38b517dfbc65e63b8b88df Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Wed, 30 May 2018 16:59:01 +0300 Subject: [PATCH 75/95] Diff computing fix --- .../roboswag/components/adapters/OffsetAdapterUpdateCallback.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/OffsetAdapterUpdateCallback.kt b/src/main/java/ru/touchin/roboswag/components/adapters/OffsetAdapterUpdateCallback.kt index 73bc999..9715eb2 100644 --- a/src/main/java/ru/touchin/roboswag/components/adapters/OffsetAdapterUpdateCallback.kt +++ b/src/main/java/ru/touchin/roboswag/components/adapters/OffsetAdapterUpdateCallback.kt @@ -6,7 +6,7 @@ import android.support.v7.widget.RecyclerView class OffsetAdapterUpdateCallback(private val adapter: RecyclerView.Adapter<*>, private val offsetProvider: () -> Int) : ListUpdateCallback { override fun onInserted(position: Int, count: Int) { - adapter.notifyItemInserted(position + offsetProvider()) + adapter.notifyItemRangeInserted(position + offsetProvider(), count) } override fun onRemoved(position: Int, count: Int) { From 945385671345ec2a4b7533ba17e059e74eac933e Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Thu, 7 Jun 2018 18:05:02 +0300 Subject: [PATCH 76/95] Up navigation fix --- .../roboswag/components/navigation/FragmentNavigation.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.kt b/src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.kt index 4e82491..7bf87c5 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.kt +++ b/src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.kt @@ -164,8 +164,11 @@ open class FragmentNavigation( * * @return True if it have back to some entry in stack. */ - //ShortMethodName: it is ok because method name is good! - fun up(): Boolean = backTo { backStackEntry -> backStackEntry.name != null && backStackEntry.name.endsWith(TOP_FRAGMENT_TAG_MARK) } + fun up() { + if (!backTo { backStackEntry -> backStackEntry.name != null && backStackEntry.name.endsWith(TOP_FRAGMENT_TAG_MARK) }) { + fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE) + } + } /** * Pushes [Fragment] on top of stack with specific arguments and transaction setup. From 42750812ddebe7b8f6ae3085d3554957aaf00446 Mon Sep 17 00:00:00 2001 From: Arseniy Borisov Date: Mon, 2 Jul 2018 12:23:53 +0300 Subject: [PATCH 77/95] hide keyboard onStart ViewController --- .../components/navigation/viewcontrollers/ViewController.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java b/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java index a025f87..436d9fa 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java +++ b/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java @@ -328,6 +328,7 @@ public class ViewController Date: Tue, 3 Jul 2018 15:00:36 +0300 Subject: [PATCH 78/95] untilDestroy moved to extension --- .../components/extensions/Delegates.kt | 8 +++++ .../utils/destroyable/BaseDestroyable.kt | 30 +++++++------------ .../utils/destroyable/Destroyable.kt | 15 ++++------ 3 files changed, 23 insertions(+), 30 deletions(-) diff --git a/src/main/java/ru/touchin/roboswag/components/extensions/Delegates.kt b/src/main/java/ru/touchin/roboswag/components/extensions/Delegates.kt index c8e0538..050bd6b 100644 --- a/src/main/java/ru/touchin/roboswag/components/extensions/Delegates.kt +++ b/src/main/java/ru/touchin/roboswag/components/extensions/Delegates.kt @@ -14,3 +14,11 @@ inline fun Delegates.observable( ): ReadWriteProperty = object : ObservableProperty(initialValue) { override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = onChange(newValue) } + +inline fun Delegates.distinctUntilChanged( + initialValue: T, + crossinline onChange: (newValue: T) -> Unit +): ReadWriteProperty = object : ObservableProperty(initialValue) { + override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = + if (newValue != null && oldValue != newValue) onChange(newValue) else Unit +} diff --git a/src/main/java/ru/touchin/roboswag/components/utils/destroyable/BaseDestroyable.kt b/src/main/java/ru/touchin/roboswag/components/utils/destroyable/BaseDestroyable.kt index 418f2c4..7d7f520 100644 --- a/src/main/java/ru/touchin/roboswag/components/utils/destroyable/BaseDestroyable.kt +++ b/src/main/java/ru/touchin/roboswag/components/utils/destroyable/BaseDestroyable.kt @@ -24,51 +24,41 @@ open class BaseDestroyable : Destroyable { */ fun onDestroy() = subscriptions.dispose() - override fun untilDestroy( - flowable: Flowable, + override fun Flowable.untilDestroy( onNextAction: (T) -> Unit, onErrorAction: (Throwable) -> Unit, onCompletedAction: () -> Unit - ): Disposable = flowable - .observeOn(AndroidSchedulers.mainThread()) + ): Disposable = observeOn(AndroidSchedulers.mainThread()) .subscribe(onNextAction, onErrorAction, onCompletedAction) .also { subscriptions.add(it) } - override fun untilDestroy( - observable: Observable, + override fun Observable.untilDestroy( onNextAction: (T) -> Unit, onErrorAction: (Throwable) -> Unit, onCompletedAction: () -> Unit - ): Disposable = observable - .observeOn(AndroidSchedulers.mainThread()) + ): Disposable = observeOn(AndroidSchedulers.mainThread()) .subscribe(onNextAction, onErrorAction, onCompletedAction) .also { subscriptions.add(it) } - override fun untilDestroy( - single: Single, + override fun Single.untilDestroy( onSuccessAction: (T) -> Unit, onErrorAction: (Throwable) -> Unit - ): Disposable = single - .observeOn(AndroidSchedulers.mainThread()) + ): Disposable = observeOn(AndroidSchedulers.mainThread()) .subscribe(onSuccessAction, onErrorAction) .also { subscriptions.add(it) } - override fun untilDestroy( - completable: Completable, + override fun Completable.untilDestroy( onCompletedAction: () -> Unit, onErrorAction: (Throwable) -> Unit - ): Disposable = completable - .observeOn(AndroidSchedulers.mainThread()) + ): Disposable = observeOn(AndroidSchedulers.mainThread()) .subscribe(onCompletedAction, onErrorAction) .also { subscriptions.add(it) } - override fun untilDestroy( - maybe: Maybe, + override fun Maybe.untilDestroy( onSuccessAction: (T) -> Unit, onErrorAction: (Throwable) -> Unit, onCompletedAction: () -> Unit - ): Disposable = maybe - .observeOn(AndroidSchedulers.mainThread()) + ): Disposable = observeOn(AndroidSchedulers.mainThread()) .subscribe(onSuccessAction, onErrorAction, onCompletedAction) .also { subscriptions.add(it) } diff --git a/src/main/java/ru/touchin/roboswag/components/utils/destroyable/Destroyable.kt b/src/main/java/ru/touchin/roboswag/components/utils/destroyable/Destroyable.kt index b5c7c16..270318d 100644 --- a/src/main/java/ru/touchin/roboswag/components/utils/destroyable/Destroyable.kt +++ b/src/main/java/ru/touchin/roboswag/components/utils/destroyable/Destroyable.kt @@ -40,8 +40,7 @@ interface Destroyable { * @param T Type of emitted by observable items; * @return [Disposable] which is wrapping source observable to unsubscribe from it onDestroy. */ - fun untilDestroy( - flowable: Flowable, + fun Flowable.untilDestroy( onNextAction: (T) -> Unit = Functions.emptyConsumer()::accept, onErrorAction: (Throwable) -> Unit = getActionThrowableForAssertion(Lc.getCodePoint(this, 2)), onCompletedAction: () -> Unit = Functions.EMPTY_ACTION::run @@ -58,8 +57,7 @@ interface Destroyable { * @param T Type of emitted by observable items; * @return [Disposable] which is wrapping source observable to unsubscribe from it onDestroy. */ - fun untilDestroy( - observable: Observable, + fun Observable.untilDestroy( onNextAction: (T) -> Unit = Functions.emptyConsumer()::accept, onErrorAction: (Throwable) -> Unit = getActionThrowableForAssertion(Lc.getCodePoint(this, 2)), onCompletedAction: () -> Unit = Functions.EMPTY_ACTION::run @@ -76,8 +74,7 @@ interface Destroyable { * @param T Type of emitted by single items; * @return [Disposable] which is wrapping source single to unsubscribe from it onDestroy. */ - fun untilDestroy( - single: Single, + fun Single.untilDestroy( onSuccessAction: (T) -> Unit = Functions.emptyConsumer()::accept, onErrorAction: (Throwable) -> Unit = getActionThrowableForAssertion(Lc.getCodePoint(this, 2)) ): Disposable @@ -92,8 +89,7 @@ interface Destroyable { * @param onErrorAction Action which will raise on every [io.reactivex.CompletableEmitter.onError] throwable; * @return [Disposable] which is wrapping source completable to unsubscribe from it onDestroy. */ - fun untilDestroy( - completable: Completable, + fun Completable.untilDestroy( onCompletedAction: () -> Unit = Functions.EMPTY_ACTION::run, onErrorAction: (Throwable) -> Unit = getActionThrowableForAssertion(Lc.getCodePoint(this, 2)) ): Disposable @@ -108,8 +104,7 @@ interface Destroyable { * @param onErrorAction Action which will raise on every [io.reactivex.MaybeEmitter.onError] throwable; * @return [Disposable] which is wrapping source maybe to unsubscribe from it onDestroy. */ - fun untilDestroy( - maybe: Maybe, + fun Maybe.untilDestroy( onSuccessAction: (T) -> Unit = Functions.emptyConsumer()::accept, onErrorAction: (Throwable) -> Unit = getActionThrowableForAssertion(Lc.getCodePoint(this, 2)), onCompletedAction: () -> Unit = Functions.EMPTY_ACTION::run From 276e697d83257bebdd910548a2dcf7fdb96cd75f Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Tue, 3 Jul 2018 15:14:21 +0300 Subject: [PATCH 79/95] untilDestroy parameters naming --- .../utils/destroyable/BaseDestroyable.kt | 36 ++++++------ .../utils/destroyable/Destroyable.kt | 57 +++++++++---------- 2 files changed, 44 insertions(+), 49 deletions(-) diff --git a/src/main/java/ru/touchin/roboswag/components/utils/destroyable/BaseDestroyable.kt b/src/main/java/ru/touchin/roboswag/components/utils/destroyable/BaseDestroyable.kt index 7d7f520..fafa855 100644 --- a/src/main/java/ru/touchin/roboswag/components/utils/destroyable/BaseDestroyable.kt +++ b/src/main/java/ru/touchin/roboswag/components/utils/destroyable/BaseDestroyable.kt @@ -25,41 +25,41 @@ open class BaseDestroyable : Destroyable { fun onDestroy() = subscriptions.dispose() override fun Flowable.untilDestroy( - onNextAction: (T) -> Unit, - onErrorAction: (Throwable) -> Unit, - onCompletedAction: () -> Unit + onNext: (T) -> Unit, + onError: (Throwable) -> Unit, + onComplete: () -> Unit ): Disposable = observeOn(AndroidSchedulers.mainThread()) - .subscribe(onNextAction, onErrorAction, onCompletedAction) + .subscribe(onNext, onError, onComplete) .also { subscriptions.add(it) } override fun Observable.untilDestroy( - onNextAction: (T) -> Unit, - onErrorAction: (Throwable) -> Unit, - onCompletedAction: () -> Unit + onNext: (T) -> Unit, + onError: (Throwable) -> Unit, + onComplete: () -> Unit ): Disposable = observeOn(AndroidSchedulers.mainThread()) - .subscribe(onNextAction, onErrorAction, onCompletedAction) + .subscribe(onNext, onError, onComplete) .also { subscriptions.add(it) } override fun Single.untilDestroy( - onSuccessAction: (T) -> Unit, - onErrorAction: (Throwable) -> Unit + onSuccess: (T) -> Unit, + onError: (Throwable) -> Unit ): Disposable = observeOn(AndroidSchedulers.mainThread()) - .subscribe(onSuccessAction, onErrorAction) + .subscribe(onSuccess, onError) .also { subscriptions.add(it) } override fun Completable.untilDestroy( - onCompletedAction: () -> Unit, - onErrorAction: (Throwable) -> Unit + onComplete: () -> Unit, + onError: (Throwable) -> Unit ): Disposable = observeOn(AndroidSchedulers.mainThread()) - .subscribe(onCompletedAction, onErrorAction) + .subscribe(onComplete, onError) .also { subscriptions.add(it) } override fun Maybe.untilDestroy( - onSuccessAction: (T) -> Unit, - onErrorAction: (Throwable) -> Unit, - onCompletedAction: () -> Unit + onSuccess: (T) -> Unit, + onError: (Throwable) -> Unit, + onComplete: () -> Unit ): Disposable = observeOn(AndroidSchedulers.mainThread()) - .subscribe(onSuccessAction, onErrorAction, onCompletedAction) + .subscribe(onSuccess, onError, onComplete) .also { subscriptions.add(it) } } diff --git a/src/main/java/ru/touchin/roboswag/components/utils/destroyable/Destroyable.kt b/src/main/java/ru/touchin/roboswag/components/utils/destroyable/Destroyable.kt index 270318d..8ad3012 100644 --- a/src/main/java/ru/touchin/roboswag/components/utils/destroyable/Destroyable.kt +++ b/src/main/java/ru/touchin/roboswag/components/utils/destroyable/Destroyable.kt @@ -34,16 +34,15 @@ interface Destroyable { * It is automatically subscribing to the observable and calls onNextAction and onErrorAction on observable events. * Don't forget to process errors if observable can emit them. * - * @param flowable [Flowable] to subscribe until onDestroy; - * @param onNextAction Action which will raise on every [io.reactivex.Emitter.onNext] item; - * @param onErrorAction Action which will raise on every [io.reactivex.Emitter.onError] throwable; - * @param T Type of emitted by observable items; + * @param onNext Action which will raise on every [io.reactivex.Emitter.onNext] item; + * @param onError Action which will raise on every [io.reactivex.Emitter.onError] throwable; + * @param onComplete Action which will raise on every [io.reactivex.Emitter.onComplete] item; * @return [Disposable] which is wrapping source observable to unsubscribe from it onDestroy. */ fun Flowable.untilDestroy( - onNextAction: (T) -> Unit = Functions.emptyConsumer()::accept, - onErrorAction: (Throwable) -> Unit = getActionThrowableForAssertion(Lc.getCodePoint(this, 2)), - onCompletedAction: () -> Unit = Functions.EMPTY_ACTION::run + onNext: (T) -> Unit = Functions.emptyConsumer()::accept, + onError: (Throwable) -> Unit = getActionThrowableForAssertion(Lc.getCodePoint(this, 2)), + onComplete: () -> Unit = Functions.EMPTY_ACTION::run ): Disposable /** @@ -51,16 +50,15 @@ interface Destroyable { * It is automatically subscribing to the observable and calls onNextAction and onErrorAction on observable events. * Don't forget to process errors if observable can emit them. * - * @param observable [Observable] to subscribe until onDestroy; - * @param onNextAction Action which will raise on every [io.reactivex.Emitter.onNext] item; - * @param onErrorAction Action which will raise on every [io.reactivex.Emitter.onError] throwable; - * @param T Type of emitted by observable items; + * @param onNext Action which will raise on every [io.reactivex.Emitter.onNext] item; + * @param onError Action which will raise on every [io.reactivex.Emitter.onError] throwable; + * @param onComplete Action which will raise on every [io.reactivex.Emitter.onComplete] item; * @return [Disposable] which is wrapping source observable to unsubscribe from it onDestroy. */ fun Observable.untilDestroy( - onNextAction: (T) -> Unit = Functions.emptyConsumer()::accept, - onErrorAction: (Throwable) -> Unit = getActionThrowableForAssertion(Lc.getCodePoint(this, 2)), - onCompletedAction: () -> Unit = Functions.EMPTY_ACTION::run + onNext: (T) -> Unit = Functions.emptyConsumer()::accept, + onError: (Throwable) -> Unit = getActionThrowableForAssertion(Lc.getCodePoint(this, 2)), + onComplete: () -> Unit = Functions.EMPTY_ACTION::run ): Disposable /** @@ -68,15 +66,13 @@ interface Destroyable { * It is automatically subscribing to the single and calls onSuccessAction and onErrorAction on single events. * Don't forget to process errors if single can emit them. * - * @param single [Single] to subscribe until onDestroy; - * @param onSuccessAction Action which will raise on every [io.reactivex.SingleEmitter.onSuccess] item; - * @param onErrorAction Action which will raise on every [io.reactivex.SingleEmitter.onError] throwable; - * @param T Type of emitted by single items; + * @param onSuccess Action which will raise on every [io.reactivex.SingleEmitter.onSuccess] item; + * @param onError Action which will raise on every [io.reactivex.SingleEmitter.onError] throwable; * @return [Disposable] which is wrapping source single to unsubscribe from it onDestroy. */ fun Single.untilDestroy( - onSuccessAction: (T) -> Unit = Functions.emptyConsumer()::accept, - onErrorAction: (Throwable) -> Unit = getActionThrowableForAssertion(Lc.getCodePoint(this, 2)) + onSuccess: (T) -> Unit = Functions.emptyConsumer()::accept, + onError: (Throwable) -> Unit = getActionThrowableForAssertion(Lc.getCodePoint(this, 2)) ): Disposable /** @@ -84,14 +80,13 @@ interface Destroyable { * It is automatically subscribing to the completable and calls onCompletedAction and onErrorAction on completable events. * Don't forget to process errors if completable can emit them. * - * @param completable [Completable] to subscribe until onDestroy; - * @param onCompletedAction Action which will raise on every [io.reactivex.CompletableEmitter.onComplete] item; - * @param onErrorAction Action which will raise on every [io.reactivex.CompletableEmitter.onError] throwable; + * @param onComplete Action which will raise on every [io.reactivex.CompletableEmitter.onComplete] item; + * @param onError Action which will raise on every [io.reactivex.CompletableEmitter.onError] throwable; * @return [Disposable] which is wrapping source completable to unsubscribe from it onDestroy. */ fun Completable.untilDestroy( - onCompletedAction: () -> Unit = Functions.EMPTY_ACTION::run, - onErrorAction: (Throwable) -> Unit = getActionThrowableForAssertion(Lc.getCodePoint(this, 2)) + onComplete: () -> Unit = Functions.EMPTY_ACTION::run, + onError: (Throwable) -> Unit = getActionThrowableForAssertion(Lc.getCodePoint(this, 2)) ): Disposable /** @@ -99,15 +94,15 @@ interface Destroyable { * It is automatically subscribing to the maybe and calls onSuccessAction and onErrorAction on maybe events. * Don't forget to process errors if completable can emit them. * - * @param maybe [Maybe] to subscribe until onDestroy; - * @param onSuccessAction Action which will raise on every [io.reactivex.MaybeEmitter.onSuccess] ()} item; - * @param onErrorAction Action which will raise on every [io.reactivex.MaybeEmitter.onError] throwable; + * @param onSuccess Action which will raise on every [io.reactivex.MaybeEmitter.onSuccess] ()} item; + * @param onError Action which will raise on every [io.reactivex.MaybeEmitter.onError] throwable; + * @param onComplete Action which will raise on every [io.reactivex.MaybeEmitter.onComplete] item; * @return [Disposable] which is wrapping source maybe to unsubscribe from it onDestroy. */ fun Maybe.untilDestroy( - onSuccessAction: (T) -> Unit = Functions.emptyConsumer()::accept, - onErrorAction: (Throwable) -> Unit = getActionThrowableForAssertion(Lc.getCodePoint(this, 2)), - onCompletedAction: () -> Unit = Functions.EMPTY_ACTION::run + onSuccess: (T) -> Unit = Functions.emptyConsumer()::accept, + onError: (Throwable) -> Unit = getActionThrowableForAssertion(Lc.getCodePoint(this, 2)), + onComplete: () -> Unit = Functions.EMPTY_ACTION::run ): Disposable } From 7d377614cb10a067b8c334f8ec2dbeb29169482f Mon Sep 17 00:00:00 2001 From: Unknown Date: Fri, 13 Jul 2018 12:39:02 +0300 Subject: [PATCH 80/95] Added onViewStateRestored callback to viewcontroller --- .../navigation/fragments/ViewControllerFragment.java | 8 ++++++++ .../navigation/viewcontrollers/ViewController.java | 9 +++++++++ 2 files changed, 17 insertions(+) diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java b/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java index e334a04..aaa5496 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java +++ b/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java @@ -234,6 +234,14 @@ public class ViewControllerFragment Date: Fri, 13 Jul 2018 13:13:04 +0300 Subject: [PATCH 81/95] fix non final Bundle --- .../components/navigation/fragments/ViewControllerFragment.java | 2 +- .../components/navigation/viewcontrollers/ViewController.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java b/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java index aaa5496..74f290d 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java +++ b/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java @@ -235,7 +235,7 @@ public class ViewControllerFragment Date: Tue, 31 Jul 2018 15:57:24 +0300 Subject: [PATCH 82/95] fix hideSoftInput onstart --- .../components/navigation/viewcontrollers/ViewController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java b/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java index 2679d5d..363e628 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java +++ b/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java @@ -337,7 +337,7 @@ public class ViewController Date: Mon, 6 Aug 2018 01:39:16 +0300 Subject: [PATCH 83/95] Components refactor --- .gitignore | 36 +- build.gradle | 54 +- gradle.properties | 13 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54708 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 172 +++++++ gradlew.bat | 84 ++++ logging/.gitignore | 1 + logging/build.gradle | 18 + logging/src/main/AndroidManifest.xml | 1 + .../core/log/ConsoleLogProcessor.java | 63 +++ .../java/ru/touchin/roboswag/core/log/Lc.java | 277 ++++++++++ .../ru/touchin/roboswag/core/log/LcGroup.java | 237 +++++++++ .../ru/touchin/roboswag/core/log/LcLevel.java | 63 +++ .../roboswag/core/log/LogProcessor.java | 61 +++ .../core/utils/ShouldNotHappenException.java | 49 ++ .../roboswag/core/utils/ThreadLocalValue.java | 61 +++ navigation/.gitignore | 1 + navigation/build.gradle | 23 + navigation/src/main/AndroidManifest.xml | 3 + .../navigation/FragmentNavigation.kt | 0 .../navigation/OnFragmentStartedListener.java | 2 +- .../navigation/SerializableBundle.java | 0 .../SimpleActionBarDrawerToggle.java | 0 .../navigation/activities/BaseActivity.java | 20 +- .../fragments/ViewControllerFragment.java | 13 +- .../navigation/fragments/ViewFragment.java | 2 +- .../viewcontrollers/DefaultViewController.kt | 0 .../navigation/viewcontrollers/EmptyState.kt | 0 .../viewcontrollers/ViewController.java | 41 +- .../ViewControllerNavigation.kt | 0 sample/.gitignore | 1 + sample/build.gradle | 26 + sample/proguard-rules.pro | 21 + sample/src/main/AndroidManifest.xml | 22 + .../roboswag/components/MainActivity.kt | 12 + .../drawable-v24/ic_launcher_foreground.xml | 35 ++ .../res/drawable/ic_launcher_background.xml | 171 +++++++ sample/src/main/res/layout/activity_main.xml | 19 + .../res/mipmap-anydpi-v26/ic_launcher.xml | 6 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 6 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3056 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 5024 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2096 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2858 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4569 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 7098 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 6464 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 10676 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 9250 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 15523 bytes sample/src/main/res/values/colors.xml | 6 + sample/src/main/res/values/strings.xml | 3 + sample/src/main/res/values/styles.xml | 11 + settings.gradle | 1 + src/main/AndroidManifest.xml | 1 - .../components/adapters/AdapterDelegate.java | 96 ---- .../components/adapters/DelegatesManager.kt | 52 -- .../adapters/DelegationListAdapter.kt | 89 ---- .../adapters/ItemAdapterDelegate.java | 85 ---- .../adapters/OffsetAdapterUpdateCallback.kt | 24 - .../adapters/PositionAdapterDelegate.java | 68 --- .../components/extensions/Delegates.kt | 24 - .../roboswag/components/extensions/View.kt | 20 - .../components/extensions/ViewHolder.kt | 32 -- .../utils/audio/HeadsetStateObserver.java | 119 ----- .../utils/audio/VolumeController.java | 132 ----- .../components/utils/images/BlurUtils.java | 323 ------------ .../components/views/MaterialLoadingBar.java | 120 ----- .../views/MaterialProgressDrawable.java | 292 ----------- .../components/views/TypefacedEditText.java | 345 ------------- .../components/views/TypefacedTextView.java | 467 ----------------- .../views/internal/AttributesUtils.java | 164 ------ ...agment_slide_in_left_paralax_animation.xml | 6 - .../fragment_slide_in_right_animation.xml | 6 - ...gment_slide_out_left_paralax_animation.xml | 6 - .../fragment_slide_out_right_animation.xml | 6 - .../res/anim/global_fade_in_animation.xml | 16 - .../res/anim/global_fade_out_animation.xml | 16 - .../anim/global_slide_in_left_animation.xml | 17 - .../anim/global_slide_in_right_animation.xml | 17 - .../anim/global_slide_out_left_animation.xml | 17 - .../anim/global_slide_out_right_animation.xml | 17 - .../res/drawable-v21/global_dark_selector.xml | 9 - .../drawable-v21/global_light_selector.xml | 9 - .../res/drawable/global_dark_selector.xml | 4 - .../res/drawable/global_light_selector.xml | 4 - src/main/res/values/attrs.xml | 29 -- src/main/res/values/integers.xml | 4 - storable/.gitignore | 1 + storable/build.gradle | 24 + storable/src/main/AndroidManifest.xml | 1 + .../utils/storables/PreferenceStore.java | 1 - .../utils/storables/PreferenceUtils.java | 184 ++++--- .../ObservableRefCountWithCacheTime.java | 299 +++++++++++ .../observables/storable/BaseStorable.java | 472 ++++++++++++++++++ .../core/observables/storable/Converter.java | 74 +++ .../core/observables/storable/Migration.java | 165 ++++++ .../core/observables/storable/Migrator.java | 97 ++++ .../observables/storable/NonNullStorable.java | 145 ++++++ .../storable/SameTypesConverter.java | 27 + .../core/observables/storable/Storable.java | 147 ++++++ .../core/observables/storable/Store.java | 70 +++ utils/.gitignore | 1 + utils/build.gradle | 18 + utils/src/main/AndroidManifest.xml | 1 + .../roboswag/components/utils/UiUtils.java | 11 - .../utils/destroyable/BaseDestroyable.kt | 0 .../utils/destroyable/Destroyable.kt | 0 .../utils/spans/ColoredUrlSpan.java | 0 .../components/utils/spans/PhoneSpan.java | 4 +- .../components/utils/spans/TypefaceSpan.java | 0 .../roboswag/core/utils/BiConsumer.java | 19 + .../roboswag/core/utils/ObjectUtils.java | 177 +++++++ .../touchin/roboswag/core/utils/Optional.java | 71 +++ .../roboswag/core/utils/ServiceBinder.java | 70 +++ .../roboswag/core/utils/StringUtils.java | 60 +++ .../src}/main/res/values/common_resources.xml | 4 +- 118 files changed, 3609 insertions(+), 2811 deletions(-) create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 logging/.gitignore create mode 100644 logging/build.gradle create mode 100644 logging/src/main/AndroidManifest.xml create mode 100644 logging/src/main/java/ru/touchin/roboswag/core/log/ConsoleLogProcessor.java create mode 100644 logging/src/main/java/ru/touchin/roboswag/core/log/Lc.java create mode 100644 logging/src/main/java/ru/touchin/roboswag/core/log/LcGroup.java create mode 100644 logging/src/main/java/ru/touchin/roboswag/core/log/LcLevel.java create mode 100644 logging/src/main/java/ru/touchin/roboswag/core/log/LogProcessor.java create mode 100644 logging/src/main/java/ru/touchin/roboswag/core/utils/ShouldNotHappenException.java create mode 100644 logging/src/main/java/ru/touchin/roboswag/core/utils/ThreadLocalValue.java create mode 100644 navigation/.gitignore create mode 100644 navigation/build.gradle create mode 100644 navigation/src/main/AndroidManifest.xml rename {src => navigation/src}/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.kt (100%) rename {src => navigation/src}/main/java/ru/touchin/roboswag/components/navigation/OnFragmentStartedListener.java (99%) rename {src => navigation/src}/main/java/ru/touchin/roboswag/components/navigation/SerializableBundle.java (100%) rename {src => navigation/src}/main/java/ru/touchin/roboswag/components/navigation/SimpleActionBarDrawerToggle.java (100%) rename {src => navigation/src}/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java (83%) rename {src => navigation/src}/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java (95%) rename {src => navigation/src}/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewFragment.java (99%) rename {src => navigation/src}/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/DefaultViewController.kt (100%) rename {src => navigation/src}/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/EmptyState.kt (100%) rename {src => navigation/src}/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java (93%) rename {src => navigation/src}/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewControllerNavigation.kt (100%) create mode 100644 sample/.gitignore create mode 100644 sample/build.gradle create mode 100644 sample/proguard-rules.pro create mode 100644 sample/src/main/AndroidManifest.xml create mode 100644 sample/src/main/java/ru/touchin/roboswag/components/MainActivity.kt create mode 100644 sample/src/main/res/drawable-v24/ic_launcher_foreground.xml create mode 100644 sample/src/main/res/drawable/ic_launcher_background.xml create mode 100644 sample/src/main/res/layout/activity_main.xml create mode 100644 sample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 sample/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 sample/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 sample/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 sample/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 sample/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 sample/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 sample/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 sample/src/main/res/values/colors.xml create mode 100644 sample/src/main/res/values/strings.xml create mode 100644 sample/src/main/res/values/styles.xml create mode 100644 settings.gradle delete mode 100644 src/main/AndroidManifest.xml delete mode 100644 src/main/java/ru/touchin/roboswag/components/adapters/AdapterDelegate.java delete mode 100644 src/main/java/ru/touchin/roboswag/components/adapters/DelegatesManager.kt delete mode 100644 src/main/java/ru/touchin/roboswag/components/adapters/DelegationListAdapter.kt delete mode 100644 src/main/java/ru/touchin/roboswag/components/adapters/ItemAdapterDelegate.java delete mode 100644 src/main/java/ru/touchin/roboswag/components/adapters/OffsetAdapterUpdateCallback.kt delete mode 100644 src/main/java/ru/touchin/roboswag/components/adapters/PositionAdapterDelegate.java delete mode 100644 src/main/java/ru/touchin/roboswag/components/extensions/Delegates.kt delete mode 100644 src/main/java/ru/touchin/roboswag/components/extensions/View.kt delete mode 100644 src/main/java/ru/touchin/roboswag/components/extensions/ViewHolder.kt delete mode 100644 src/main/java/ru/touchin/roboswag/components/utils/audio/HeadsetStateObserver.java delete mode 100644 src/main/java/ru/touchin/roboswag/components/utils/audio/VolumeController.java delete mode 100644 src/main/java/ru/touchin/roboswag/components/utils/images/BlurUtils.java delete mode 100644 src/main/java/ru/touchin/roboswag/components/views/MaterialLoadingBar.java delete mode 100644 src/main/java/ru/touchin/roboswag/components/views/MaterialProgressDrawable.java delete mode 100644 src/main/java/ru/touchin/roboswag/components/views/TypefacedEditText.java delete mode 100644 src/main/java/ru/touchin/roboswag/components/views/TypefacedTextView.java delete mode 100644 src/main/java/ru/touchin/roboswag/components/views/internal/AttributesUtils.java delete mode 100644 src/main/res/anim/fragment_slide_in_left_paralax_animation.xml delete mode 100644 src/main/res/anim/fragment_slide_in_right_animation.xml delete mode 100644 src/main/res/anim/fragment_slide_out_left_paralax_animation.xml delete mode 100644 src/main/res/anim/fragment_slide_out_right_animation.xml delete mode 100644 src/main/res/anim/global_fade_in_animation.xml delete mode 100644 src/main/res/anim/global_fade_out_animation.xml delete mode 100644 src/main/res/anim/global_slide_in_left_animation.xml delete mode 100644 src/main/res/anim/global_slide_in_right_animation.xml delete mode 100644 src/main/res/anim/global_slide_out_left_animation.xml delete mode 100644 src/main/res/anim/global_slide_out_right_animation.xml delete mode 100644 src/main/res/drawable-v21/global_dark_selector.xml delete mode 100644 src/main/res/drawable-v21/global_light_selector.xml delete mode 100644 src/main/res/drawable/global_dark_selector.xml delete mode 100644 src/main/res/drawable/global_light_selector.xml delete mode 100644 src/main/res/values/attrs.xml delete mode 100644 src/main/res/values/integers.xml create mode 100644 storable/.gitignore create mode 100644 storable/build.gradle create mode 100644 storable/src/main/AndroidManifest.xml rename {src => storable/src}/main/java/ru/touchin/roboswag/components/utils/storables/PreferenceStore.java (99%) rename {src => storable/src}/main/java/ru/touchin/roboswag/components/utils/storables/PreferenceUtils.java (61%) create mode 100644 storable/src/main/java/ru/touchin/roboswag/core/observables/ObservableRefCountWithCacheTime.java create mode 100644 storable/src/main/java/ru/touchin/roboswag/core/observables/storable/BaseStorable.java create mode 100644 storable/src/main/java/ru/touchin/roboswag/core/observables/storable/Converter.java create mode 100644 storable/src/main/java/ru/touchin/roboswag/core/observables/storable/Migration.java create mode 100644 storable/src/main/java/ru/touchin/roboswag/core/observables/storable/Migrator.java create mode 100644 storable/src/main/java/ru/touchin/roboswag/core/observables/storable/NonNullStorable.java create mode 100644 storable/src/main/java/ru/touchin/roboswag/core/observables/storable/SameTypesConverter.java create mode 100644 storable/src/main/java/ru/touchin/roboswag/core/observables/storable/Storable.java create mode 100644 storable/src/main/java/ru/touchin/roboswag/core/observables/storable/Store.java create mode 100644 utils/.gitignore create mode 100644 utils/build.gradle create mode 100644 utils/src/main/AndroidManifest.xml rename {src => utils/src}/main/java/ru/touchin/roboswag/components/utils/UiUtils.java (96%) rename {src => utils/src}/main/java/ru/touchin/roboswag/components/utils/destroyable/BaseDestroyable.kt (100%) rename {src => utils/src}/main/java/ru/touchin/roboswag/components/utils/destroyable/Destroyable.kt (100%) rename {src => utils/src}/main/java/ru/touchin/roboswag/components/utils/spans/ColoredUrlSpan.java (100%) rename {src => utils/src}/main/java/ru/touchin/roboswag/components/utils/spans/PhoneSpan.java (93%) rename {src => utils/src}/main/java/ru/touchin/roboswag/components/utils/spans/TypefaceSpan.java (100%) create mode 100644 utils/src/main/java/ru/touchin/roboswag/core/utils/BiConsumer.java create mode 100644 utils/src/main/java/ru/touchin/roboswag/core/utils/ObjectUtils.java create mode 100644 utils/src/main/java/ru/touchin/roboswag/core/utils/Optional.java create mode 100644 utils/src/main/java/ru/touchin/roboswag/core/utils/ServiceBinder.java create mode 100644 utils/src/main/java/ru/touchin/roboswag/core/utils/StringUtils.java rename {src => utils/src}/main/res/values/common_resources.xml (96%) diff --git a/.gitignore b/.gitignore index 63e899b..09b993d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,30 +1,8 @@ -# Built application files -*.apk -*.ap_ - -# Files for the Dalvik VM -*.dex - -# Java class files -*.class - -# Generated files -bin/ -gen/ - -# Gradle files -.gradle/ -build/ -/*/build/ - -# Local configuration file (sdk path, etc) -local.properties - -# Log Files -*.log - -.gradle -.idea -.DS_Store -/captures *.iml +.gradle +/local.properties +/.idea +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/build.gradle b/build.gradle index 0a7ccae..7accccb 100644 --- a/build.gradle +++ b/build.gradle @@ -1,28 +1,36 @@ -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' - -android { - compileSdkVersion compileSdk - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 +buildscript { + ext.kotlin_version = '1.2.60' + repositories { + google() + jcenter() } - - defaultConfig { - minSdkVersion 16 + dependencies { + classpath 'com.android.tools.build:gradle:3.1.3' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } -dependencies { - api project(':libraries:core') - - compileOnly "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - - compileOnly "com.android.support:appcompat-v7:$supportLibraryVersion" - compileOnly "com.android.support:design:$supportLibraryVersion" - compileOnly "com.android.support:recyclerview-v7:$supportLibraryVersion" - - compileOnly "io.reactivex.rxjava2:rxandroid:$rxAndroidVersion" - compileOnly "io.reactivex.rxjava2:rxjava:$rxJavaVersion" +allprojects { + repositories { + google() + jcenter() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} + +ext { + versions = [ + compileSdk : 27, + minSdk : 19, + supportLibrary: '27.1.1', + navigation : '1.0.0-alpha04', + lifecycle : '1.1.1', + dagger : '2.16', + retrofit : '2.4.0', + rxJava : '2.1.17', + rxAndroid : '2.0.2' + ] } diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..e30af81 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,13 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1536m +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +org.gradle.parallel=true diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..7a3265ee94c0ab25cf079ac8ccdf87f41d455d42 GIT binary patch literal 54708 zcmagFV|ZrKvM!pAZQHhO+qP}9lTNj?q^^Y^VFp)SH8qbSJ)2BQ2girk4u zvO<3q)c?v~^Z#E_K}1nTQbJ9gQ9<%vVRAxVj)8FwL5_iTdUB>&m3fhE=kRWl;g`&m z!W5kh{WsV%fO*%je&j+Lv4xxK~zsEYQls$Q-p&dwID|A)!7uWtJF-=Tm1{V@#x*+kUI$=%KUuf2ka zjiZ{oiL1MXE2EjciJM!jrjFNwCh`~hL>iemrqwqnX?T*MX;U>>8yRcZb{Oy+VKZos zLiFKYPw=LcaaQt8tj=eoo3-@bG_342HQ%?jpgAE?KCLEHC+DmjxAfJ%Og^$dpC8Xw zAcp-)tfJm}BPNq_+6m4gBgBm3+CvmL>4|$2N$^Bz7W(}fz1?U-u;nE`+9`KCLuqg} zwNstNM!J4Uw|78&Y9~9>MLf56to!@qGkJw5Thx%zkzj%Ek9Nn1QA@8NBXbwyWC>9H z#EPwjMNYPigE>*Ofz)HfTF&%PFj$U6mCe-AFw$U%-L?~-+nSXHHKkdgC5KJRTF}`G zE_HNdrE}S0zf4j{r_f-V2imSqW?}3w-4=f@o@-q+cZgaAbZ((hn))@|eWWhcT2pLpTpL!;_5*vM=sRL8 zqU##{U#lJKuyqW^X$ETU5ETeEVzhU|1m1750#f}38_5N9)B_2|v@1hUu=Kt7-@dhA zq_`OMgW01n`%1dB*}C)qxC8q;?zPeF_r;>}%JYmlER_1CUbKa07+=TV45~symC*g8 zW-8(gag#cAOuM0B1xG8eTp5HGVLE}+gYTmK=`XVVV*U!>H`~j4+ROIQ+NkN$LY>h4 zqpwdeE_@AX@PL};e5vTn`Ro(EjHVf$;^oiA%@IBQq>R7_D>m2D4OwwEepkg}R_k*M zM-o;+P27087eb+%*+6vWFCo9UEGw>t&WI17Pe7QVuoAoGHdJ(TEQNlJOqnjZ8adCb zI`}op16D@v7UOEo%8E-~m?c8FL1utPYlg@m$q@q7%mQ4?OK1h%ODjTjFvqd!C z-PI?8qX8{a@6d&Lb_X+hKxCImb*3GFemm?W_du5_&EqRq!+H?5#xiX#w$eLti-?E$;Dhu`{R(o>LzM4CjO>ICf z&DMfES#FW7npnbcuqREgjPQM#gs6h>`av_oEWwOJZ2i2|D|0~pYd#WazE2Bbsa}X@ zu;(9fi~%!VcjK6)?_wMAW-YXJAR{QHxrD5g(ou9mR6LPSA4BRG1QSZT6A?kelP_g- zH(JQjLc!`H4N=oLw=f3{+WmPA*s8QEeEUf6Vg}@!xwnsnR0bl~^2GSa5vb!Yl&4!> zWb|KQUsC$lT=3A|7vM9+d;mq=@L%uWKwXiO9}a~gP4s_4Yohc!fKEgV7WbVo>2ITbE*i`a|V!^p@~^<={#?Gz57 zyPWeM2@p>D*FW#W5Q`1`#5NW62XduP1XNO(bhg&cX`-LYZa|m-**bu|>}S;3)eP8_ zpNTnTfm8 ze+7wDH3KJ95p)5tlwk`S7mbD`SqHnYD*6`;gpp8VdHDz%RR_~I_Ar>5)vE-Pgu7^Y z|9Px+>pi3!DV%E%4N;ii0U3VBd2ZJNUY1YC^-e+{DYq+l@cGtmu(H#Oh%ibUBOd?C z{y5jW3v=0eV0r@qMLgv1JjZC|cZ9l9Q)k1lLgm))UR@#FrJd>w^`+iy$c9F@ic-|q zVHe@S2UAnc5VY_U4253QJxm&Ip!XKP8WNcnx9^cQ;KH6PlW8%pSihSH2(@{2m_o+m zr((MvBja2ctg0d0&U5XTD;5?d?h%JcRJp{_1BQW1xu&BrA3(a4Fh9hon-ly$pyeHq zG&;6q?m%NJ36K1Sq_=fdP(4f{Hop;_G_(i?sPzvB zDM}>*(uOsY0I1j^{$yn3#U(;B*g4cy$-1DTOkh3P!LQ;lJlP%jY8}Nya=h8$XD~%Y zbV&HJ%eCD9nui-0cw!+n`V~p6VCRqh5fRX z8`GbdZ@73r7~myQLBW%db;+BI?c-a>Y)m-FW~M=1^|<21_Sh9RT3iGbO{o-hpN%d6 z7%++#WekoBOP^d0$$|5npPe>u3PLvX_gjH2x(?{&z{jJ2tAOWTznPxv-pAv<*V7r$ z6&glt>7CAClWz6FEi3bToz-soY^{ScrjwVPV51=>n->c(NJngMj6TyHty`bfkF1hc zkJS%A@cL~QV0-aK4>Id!9dh7>0IV;1J9(myDO+gv76L3NLMUm9XyPauvNu$S<)-|F zZS}(kK_WnB)Cl`U?jsdYfAV4nrgzIF@+%1U8$poW&h^c6>kCx3;||fS1_7JvQT~CV zQ8Js+!p)3oW>Df(-}uqC`Tcd%E7GdJ0p}kYj5j8NKMp(KUs9u7?jQ94C)}0rba($~ zqyBx$(1ae^HEDG`Zc@-rXk1cqc7v0wibOR4qpgRDt#>-*8N3P;uKV0CgJE2SP>#8h z=+;i_CGlv+B^+$5a}SicVaSeaNn29K`C&=}`=#Nj&WJP9Xhz4mVa<+yP6hkrq1vo= z1rX4qg8dc4pmEvq%NAkpMK>mf2g?tg_1k2%v}<3`$6~Wlq@ItJ*PhHPoEh1Yi>v57 z4k0JMO)*=S`tKvR5gb-(VTEo>5Y>DZJZzgR+j6{Y`kd|jCVrg!>2hVjz({kZR z`dLlKhoqT!aI8=S+fVp(5*Dn6RrbpyO~0+?fy;bm$0jmTN|t5i6rxqr4=O}dY+ROd zo9Et|x}!u*xi~>-y>!M^+f&jc;IAsGiM_^}+4|pHRn{LThFFpD{bZ|TA*wcGm}XV^ zr*C6~@^5X-*R%FrHIgo-hJTBcyQ|3QEj+cSqp#>&t`ZzB?cXM6S(lRQw$I2?m5=wd z78ki`R?%;o%VUhXH?Z#(uwAn9$m`npJ=cA+lHGk@T7qq_M6Zoy1Lm9E0UUysN)I_x zW__OAqvku^>`J&CB=ie@yNWsaFmem}#L3T(x?a`oZ+$;3O-icj2(5z72Hnj=9Z0w% z<2#q-R=>hig*(t0^v)eGq2DHC%GymE-_j1WwBVGoU=GORGjtaqr0BNigOCqyt;O(S zKG+DoBsZU~okF<7ahjS}bzwXxbAxFfQAk&O@>LsZMsZ`?N?|CDWM(vOm%B3CBPC3o z%2t@%H$fwur}SSnckUm0-k)mOtht`?nwsDz=2#v=RBPGg39i#%odKq{K^;bTD!6A9 zskz$}t)sU^=a#jLZP@I=bPo?f-L}wpMs{Tc!m7-bi!Ldqj3EA~V;4(dltJmTXqH0r z%HAWKGutEc9vOo3P6Q;JdC^YTnby->VZ6&X8f{obffZ??1(cm&L2h7q)*w**+sE6dG*;(H|_Q!WxU{g)CeoT z(KY&bv!Usc|m+Fqfmk;h&RNF|LWuNZ!+DdX*L=s-=_iH=@i` z?Z+Okq^cFO4}_n|G*!)Wl_i%qiMBaH8(WuXtgI7EO=M>=i_+;MDjf3aY~6S9w0K zUuDO7O5Ta6+k40~xh~)D{=L&?Y0?c$s9cw*Ufe18)zzk%#ZY>Tr^|e%8KPb0ht`b( zuP@8#Ox@nQIqz9}AbW0RzE`Cf>39bOWz5N3qzS}ocxI=o$W|(nD~@EhW13Rj5nAp; zu2obEJa=kGC*#3=MkdkWy_%RKcN=?g$7!AZ8vBYKr$ePY(8aIQ&yRPlQ=mudv#q$q z4%WzAx=B{i)UdLFx4os?rZp6poShD7Vc&mSD@RdBJ=_m^&OlkEE1DFU@csgKcBifJ zz4N7+XEJhYzzO=86 z#%eBQZ$Nsf2+X0XPHUNmg#(sNt^NW1Y0|M(${e<0kW6f2q5M!2YE|hSEQ*X-%qo(V zHaFwyGZ0on=I{=fhe<=zo{=Og-_(to3?cvL4m6PymtNsdDINsBh8m>a%!5o3s(en) z=1I z6O+YNertC|OFNqd6P=$gMyvmfa`w~p9*gKDESFqNBy(~Zw3TFDYh}$iudn)9HxPBi zdokK@o~nu?%imcURr5Y~?6oo_JBe}t|pU5qjai|#JDyG=i^V~7+a{dEnO<(y>ahND#_X_fcEBNiZ)uc&%1HVtx8Ts z*H_Btvx^IhkfOB#{szN*n6;y05A>3eARDXslaE>tnLa>+`V&cgho?ED+&vv5KJszf zG4@G;7i;4_bVvZ>!mli3j7~tPgybF5|J6=Lt`u$D%X0l}#iY9nOXH@(%FFJLtzb%p zzHfABnSs;v-9(&nzbZytLiqqDIWzn>JQDk#JULcE5CyPq_m#4QV!}3421haQ+LcfO*>r;rg6K|r#5Sh|y@h1ao%Cl)t*u`4 zMTP!deC?aL7uTxm5^nUv#q2vS-5QbBKP|drbDXS%erB>fYM84Kpk^au99-BQBZR z7CDynflrIAi&ahza+kUryju5LR_}-Z27g)jqOc(!Lx9y)e z{cYc&_r947s9pteaa4}dc|!$$N9+M38sUr7h(%@Ehq`4HJtTpA>B8CLNO__@%(F5d z`SmX5jbux6i#qc}xOhumzbAELh*Mfr2SW99=WNOZRZgoCU4A2|4i|ZVFQt6qEhH#B zK_9G;&h*LO6tB`5dXRSBF0hq0tk{2q__aCKXYkP#9n^)@cq}`&Lo)1KM{W+>5mSed zKp~=}$p7>~nK@va`vN{mYzWN1(tE=u2BZhga5(VtPKk(*TvE&zmn5vSbjo zZLVobTl%;t@6;4SsZ>5+U-XEGUZGG;+~|V(pE&qqrp_f~{_1h@5ZrNETqe{bt9ioZ z#Qn~gWCH!t#Ha^n&fT2?{`}D@s4?9kXj;E;lWV9Zw8_4yM0Qg-6YSsKgvQ*fF{#Pq z{=(nyV>#*`RloBVCs;Lp*R1PBIQOY=EK4CQa*BD0MsYcg=opP?8;xYQDSAJBeJpw5 zPBc_Ft9?;<0?pBhCmOtWU*pN*;CkjJ_}qVic`}V@$TwFi15!mF1*m2wVX+>5p%(+R zQ~JUW*zWkalde{90@2v+oVlkxOZFihE&ZJ){c?hX3L2@R7jk*xjYtHi=}qb+4B(XJ z$gYcNudR~4Kz_WRq8eS((>ALWCO)&R-MXE+YxDn9V#X{_H@j616<|P(8h(7z?q*r+ zmpqR#7+g$cT@e&(%_|ipI&A%9+47%30TLY(yuf&*knx1wNx|%*H^;YB%ftt%5>QM= z^i;*6_KTSRzQm%qz*>cK&EISvF^ovbS4|R%)zKhTH_2K>jP3mBGn5{95&G9^a#4|K zv+!>fIsR8z{^x4)FIr*cYT@Q4Z{y}};rLHL+atCgHbfX*;+k&37DIgENn&=k(*lKD zG;uL-KAdLn*JQ?@r6Q!0V$xXP=J2i~;_+i3|F;_En;oAMG|I-RX#FwnmU&G}w`7R{ z788CrR-g1DW4h_`&$Z`ctN~{A)Hv_-Bl!%+pfif8wN32rMD zJDs$eVWBYQx1&2sCdB0!vU5~uf)=vy*{}t{2VBpcz<+~h0wb7F3?V^44*&83Z2#F` z32!rd4>uc63rQP$3lTH3zb-47IGR}f)8kZ4JvX#toIpXH`L%NnPDE~$QI1)0)|HS4 zVcITo$$oWWwCN@E-5h>N?Hua!N9CYb6f8vTFd>h3q5Jg-lCI6y%vu{Z_Uf z$MU{{^o~;nD_@m2|E{J)q;|BK7rx%`m``+OqZAqAVj-Dy+pD4-S3xK?($>wn5bi90CFAQ+ACd;&m6DQB8_o zjAq^=eUYc1o{#+p+ zn;K<)Pn*4u742P!;H^E3^Qu%2dM{2slouc$AN_3V^M7H_KY3H)#n7qd5_p~Za7zAj|s9{l)RdbV9e||_67`#Tu*c<8!I=zb@ z(MSvQ9;Wrkq6d)!9afh+G`!f$Ip!F<4ADdc*OY-y7BZMsau%y?EN6*hW4mOF%Q~bw z2==Z3^~?q<1GTeS>xGN-?CHZ7a#M4kDL zQxQr~1ZMzCSKFK5+32C%+C1kE#(2L=15AR!er7GKbp?Xd1qkkGipx5Q~FI-6zt< z*PTpeVI)Ngnnyaz5noIIgNZtb4bQdKG{Bs~&tf)?nM$a;7>r36djllw%hQxeCXeW^ z(i6@TEIuxD<2ulwLTt|&gZP%Ei+l!(%p5Yij6U(H#HMkqM8U$@OKB|5@vUiuY^d6X zW}fP3;Kps6051OEO(|JzmVU6SX(8q>*yf*x5QoxDK={PH^F?!VCzES_Qs>()_y|jg6LJlJWp;L zKM*g5DK7>W_*uv}{0WUB0>MHZ#oJZmO!b3MjEc}VhsLD~;E-qNNd?x7Q6~v zR=0$u>Zc2Xr}>x_5$-s#l!oz6I>W?lw;m9Ae{Tf9eMX;TI-Wf_mZ6sVrMnY#F}cDd z%CV*}fDsXUF7Vbw>PuDaGhu631+3|{xp<@Kl|%WxU+vuLlcrklMC!Aq+7n~I3cmQ! z`e3cA!XUEGdEPSu``&lZEKD1IKO(-VGvcnSc153m(i!8ohi`)N2n>U_BemYJ`uY>8B*Epj!oXRLV}XK}>D*^DHQ7?NY*&LJ9VSo`Ogi9J zGa;clWI8vIQqkngv2>xKd91K>?0`Sw;E&TMg&6dcd20|FcTsnUT7Yn{oI5V4@Ow~m zz#k~8TM!A9L7T!|colrC0P2WKZW7PNj_X4MfESbt<-soq*0LzShZ}fyUx!(xIIDwx zRHt^_GAWe0-Vm~bDZ(}XG%E+`XhKpPlMBo*5q_z$BGxYef8O!ToS8aT8pmjbPq)nV z%x*PF5ZuSHRJqJ!`5<4xC*xb2vC?7u1iljB_*iUGl6+yPyjn?F?GOF2_KW&gOkJ?w z3e^qc-te;zez`H$rsUCE0<@7PKGW?7sT1SPYWId|FJ8H`uEdNu4YJjre`8F*D}6Wh z|FQ`xf7yiphHIAkU&OYCn}w^ilY@o4larl?^M7&8YI;hzBIsX|i3UrLsx{QDKwCX< zy;a>yjfJ6!sz`NcVi+a!Fqk^VE^{6G53L?@Tif|j!3QZ0fk9QeUq8CWI;OmO-Hs+F zuZ4sHLA3{}LR2Qlyo+{d@?;`tpp6YB^BMoJt?&MHFY!JQwoa0nTSD+#Ku^4b{5SZVFwU9<~APYbaLO zu~Z)nS#dxI-5lmS-Bnw!(u15by(80LlC@|ynj{TzW)XcspC*}z0~8VRZq>#Z49G`I zgl|C#H&=}n-ajxfo{=pxPV(L*7g}gHET9b*s=cGV7VFa<;Htgjk>KyW@S!|z`lR1( zGSYkEl&@-bZ*d2WQ~hw3NpP=YNHF^XC{TMG$Gn+{b6pZn+5=<()>C!N^jncl0w6BJ zdHdnmSEGK5BlMeZD!v4t5m7ct7{k~$1Ie3GLFoHjAH*b?++s<|=yTF+^I&jT#zuMx z)MLhU+;LFk8bse|_{j+d*a=&cm2}M?*arjBPnfPgLwv)86D$6L zLJ0wPul7IenMvVAK$z^q5<^!)7aI|<&GGEbOr=E;UmGOIa}yO~EIr5xWU_(ol$&fa zR5E(2vB?S3EvJglTXdU#@qfDbCYs#82Yo^aZN6`{Ex#M)easBTe_J8utXu(fY1j|R z9o(sQbj$bKU{IjyhosYahY{63>}$9_+hWxB3j}VQkJ@2$D@vpeRSldU?&7I;qd2MF zSYmJ>zA(@N_iK}m*AMPIJG#Y&1KR)6`LJ83qg~`Do3v^B0>fU&wUx(qefuTgzFED{sJ65!iw{F2}1fQ3= ziFIP{kezQxmlx-!yo+sC4PEtG#K=5VM9YIN0z9~c4XTX?*4e@m;hFM!zVo>A`#566 z>f&3g94lJ{r)QJ5m7Xe3SLau_lOpL;A($wsjHR`;xTXgIiZ#o&vt~ zGR6KdU$FFbLfZCC3AEu$b`tj!9XgOGLSV=QPIYW zjI!hSP#?8pn0@ezuenOzoka8!8~jXTbiJ6+ZuItsWW03uzASFyn*zV2kIgPFR$Yzm zE<$cZlF>R8?Nr2_i?KiripBc+TGgJvG@vRTY2o?(_Di}D30!k&CT`>+7ry2!!iC*X z<@=U0_C#16=PN7bB39w+zPwDOHX}h20Ap);dx}kjXX0-QkRk=cr};GYsjSvyLZa-t zzHONWddi*)RDUH@RTAsGB_#&O+QJaaL+H<<9LLSE+nB@eGF1fALwjVOl8X_sdOYme z0lk!X=S(@25=TZHR7LlPp}fY~yNeThMIjD}pd9+q=j<_inh0$>mIzWVY+Z9p<{D^#0Xk+b_@eNSiR8;KzSZ#7lUsk~NGMcB8C2c=m2l5paHPq`q{S(kdA7Z1a zyfk2Y;w?^t`?@yC5Pz9&pzo}Hc#}mLgDmhKV|PJ3lKOY(Km@Fi2AV~CuET*YfUi}u zfInZnqDX(<#vaS<^fszuR=l)AbqG{}9{rnyx?PbZz3Pyu!eSJK`uwkJU!ORQXy4x83r!PNgOyD33}}L=>xX_93l6njNTuqL8J{l%*3FVn3MG4&Fv*`lBXZ z?=;kn6HTT^#SrPX-N)4EZiIZI!0ByXTWy;;J-Tht{jq1mjh`DSy7yGjHxIaY%*sTx zuy9#9CqE#qi>1misx=KRWm=qx4rk|}vd+LMY3M`ow8)}m$3Ggv&)Ri*ON+}<^P%T5 z_7JPVPfdM=Pv-oH<tecoE}(0O7|YZc*d8`Uv_M*3Rzv7$yZnJE6N_W=AQ3_BgU_TjA_T?a)U1csCmJ&YqMp-lJe`y6>N zt++Bi;ZMOD%%1c&-Q;bKsYg!SmS^#J@8UFY|G3!rtyaTFb!5@e(@l?1t(87ln8rG? z--$1)YC~vWnXiW3GXm`FNSyzu!m$qT=Eldf$sMl#PEfGmzQs^oUd=GIQfj(X=}dw+ zT*oa0*oS%@cLgvB&PKIQ=Ok?>x#c#dC#sQifgMwtAG^l3D9nIg(Zqi;D%807TtUUCL3_;kjyte#cAg?S%e4S2W>9^A(uy8Ss0Tc++ZTjJw1 z&Em2g!3lo@LlDyri(P^I8BPpn$RE7n*q9Q-c^>rfOMM6Pd5671I=ZBjAvpj8oIi$! zl0exNl(>NIiQpX~FRS9UgK|0l#s@#)p4?^?XAz}Gjb1?4Qe4?j&cL$C8u}n)?A@YC zfmbSM`Hl5pQFwv$CQBF=_$Sq zxsV?BHI5bGZTk?B6B&KLdIN-40S426X3j_|ceLla*M3}3gx3(_7MVY1++4mzhH#7# zD>2gTHy*%i$~}mqc#gK83288SKp@y3wz1L_e8fF$Rb}ex+`(h)j}%~Ld^3DUZkgez zOUNy^%>>HHE|-y$V@B}-M|_{h!vXpk01xaD%{l{oQ|~+^>rR*rv9iQen5t?{BHg|% zR`;S|KtUb!X<22RTBA4AAUM6#M?=w5VY-hEV)b`!y1^mPNEoy2K)a>OyA?Q~Q*&(O zRzQI~y_W=IPi?-OJX*&&8dvY0zWM2%yXdFI!D-n@6FsG)pEYdJbuA`g4yy;qrgR?G z8Mj7gv1oiWq)+_$GqqQ$(ZM@#|0j7})=#$S&hZwdoijFI4aCFLVI3tMH5fLreZ;KD zqA`)0l~D2tuIBYOy+LGw&hJ5OyE+@cnZ0L5+;yo2pIMdt@4$r^5Y!x7nHs{@>|W(MzJjATyWGNwZ^4j+EPU0RpAl-oTM@u{lx*i0^yyWPfHt6QwPvYpk9xFMWfBFt!+Gu6TlAmr zeQ#PX71vzN*_-xh&__N`IXv6`>CgV#eA_%e@7wjgkj8jlKzO~Ic6g$cT`^W{R{606 zCDP~+NVZ6DMO$jhL~#+!g*$T!XW63#(ngDn#Qwy71yj^gazS{e;3jGRM0HedGD@pt z?(ln3pCUA(ekqAvvnKy0G@?-|-dh=eS%4Civ&c}s%wF@0K5Bltaq^2Os1n6Z3%?-Q zAlC4goQ&vK6TpgtzkHVt*1!tBYt-`|5HLV1V7*#45Vb+GACuU+QB&hZ=N_flPy0TY zR^HIrdskB#<$aU;HY(K{a3(OQa$0<9qH(oa)lg@Uf>M5g2W0U5 zk!JSlhrw8quBx9A>RJ6}=;W&wt@2E$7J=9SVHsdC?K(L(KACb#z)@C$xXD8^!7|uv zZh$6fkq)aoD}^79VqdJ!Nz-8$IrU(_-&^cHBI;4 z^$B+1aPe|LG)C55LjP;jab{dTf$0~xbXS9!!QdcmDYLbL^jvxu2y*qnx2%jbL%rB z{aP85qBJe#(&O~Prk%IJARcdEypZ)vah%ZZ%;Zk{eW(U)Bx7VlzgOi8)x z`rh4l`@l_Ada7z&yUK>ZF;i6YLGwI*Sg#Fk#Qr0Jg&VLax(nNN$u-XJ5=MsP3|(lEdIOJ7|(x3iY;ea)5#BW*mDV%^=8qOeYO&gIdJVuLLN3cFaN=xZtFB=b zH{l)PZl_j^u+qx@89}gAQW7ofb+k)QwX=aegihossZq*+@PlCpb$rpp>Cbk9UJO<~ zDjlXQ_Ig#W0zdD3&*ei(FwlN#3b%FSR%&M^ywF@Fr>d~do@-kIS$e%wkIVfJ|Ohh=zc zF&Rnic^|>@R%v?@jO}a9;nY3Qrg_!xC=ZWUcYiA5R+|2nsM*$+c$TOs6pm!}Z}dfM zGeBhMGWw3$6KZXav^>YNA=r6Es>p<6HRYcZY)z{>yasbC81A*G-le8~QoV;rtKnkx z;+os8BvEe?0A6W*a#dOudsv3aWs?d% z0oNngyVMjavLjtjiG`!007#?62ClTqqU$@kIY`=x^$2e>iqIy1>o|@Tw@)P)B8_1$r#6>DB_5 zmaOaoE~^9TolgDgooKFuEFB#klSF%9-~d2~_|kQ0Y{Ek=HH5yq9s zDq#1S551c`kSiWPZbweN^A4kWiP#Qg6er1}HcKv{fxb1*BULboD0fwfaNM_<55>qM zETZ8TJDO4V)=aPp_eQjX%||Ud<>wkIzvDlpNjqW>I}W!-j7M^TNe5JIFh#-}zAV!$ICOju8Kx)N z0vLtzDdy*rQN!7r>Xz7rLw8J-(GzQlYYVH$WK#F`i_i^qVlzTNAh>gBWKV@XC$T-` z3|kj#iCquDhiO7NKum07i|<-NuVsX}Q}mIP$jBJDMfUiaWR3c|F_kWBMw0_Sr|6h4 zk`_r5=0&rCR^*tOy$A8K;@|NqwncjZ>Y-75vlpxq%Cl3EgH`}^^~=u zoll6xxY@a>0f%Ddpi;=cY}fyG!K2N-dEyXXmUP5u){4VnyS^T4?pjN@Ot4zjL(Puw z_U#wMH2Z#8Pts{olG5Dy0tZj;N@;fHheu>YKYQU=4Bk|wcD9MbA`3O4bj$hNRHwzb zSLcG0SLV%zywdbuwl(^E_!@&)TdXge4O{MRWk2RKOt@!8E{$BU-AH(@4{gxs=YAz9LIob|Hzto0}9cWoz6Tp2x0&xi#$ zHh$dwO&UCR1Ob2w00-2eG7d4=cN(Y>0R#$q8?||q@iTi+7-w-xR%uMr&StFIthC<# zvK(aPduwuNB}oJUV8+Zl)%cnfsHI%4`;x6XW^UF^e4s3Z@S<&EV8?56Wya;HNs0E> z`$0dgRdiUz9RO9Au3RmYq>K#G=X%*_dUbSJHP`lSfBaN8t-~@F>)BL1RT*9I851A3 z<-+Gb#_QRX>~av#Ni<#zLswtu-c6{jGHR>wflhKLzC4P@b%8&~u)fosoNjk4r#GvC zlU#UU9&0Hv;d%g72Wq?Ym<&&vtA3AB##L}=ZjiTR4hh7J)e>ei} zt*u+>h%MwN`%3}b4wYpV=QwbY!jwfIj#{me)TDOG`?tI!%l=AwL2G@9I~}?_dA5g6 zCKgK(;6Q0&P&K21Tx~k=o6jwV{dI_G+Ba*Zts|Tl6q1zeC?iYJTb{hel*x>^wb|2RkHkU$!+S4OU4ZOKPZjV>9OVsqNnv5jK8TRAE$A&^yRwK zj-MJ3Pl?)KA~fq#*K~W0l4$0=8GRx^9+?w z!QT8*-)w|S^B0)ZeY5gZPI2G(QtQf?DjuK(s^$rMA!C%P22vynZY4SuOE=wX2f8$R z)A}mzJi4WJnZ`!bHG1=$lwaxm!GOnRbR15F$nRC-M*H<*VfF|pQw(;tbSfp({>9^5 zw_M1-SJ9eGF~m(0dvp*P8uaA0Yw+EkP-SWqu zqal$hK8SmM7#Mrs0@OD+%_J%H*bMyZiWAZdsIBj#lkZ!l2c&IpLu(5^T0Ge5PHzR} zn;TXs$+IQ_&;O~u=Jz+XE0wbOy`=6>m9JVG} zJ~Kp1e5m?K3x@@>!D)piw^eMIHjD4RebtR`|IlckplP1;r21wTi8v((KqNqn%2CB< zifaQc&T}*M&0i|LW^LgdjIaX|o~I$`owHolRqeH_CFrqCUCleN130&vH}dK|^kC>) z-r2P~mApHotL4dRX$25lIcRh_*kJaxi^%ZN5-GAAMOxfB!6flLPY-p&QzL9TE%ho( zRwftE3sy5<*^)qYzKkL|rE>n@hyr;xPqncY6QJ8125!MWr`UCWuC~A#G1AqF1@V$kv>@NBvN&2ygy*{QvxolkRRb%Ui zsmKROR%{*g*WjUUod@@cS^4eF^}yQ1>;WlGwOli z+Y$(8I`0(^d|w>{eaf!_BBM;NpCoeem2>J}82*!em=}}ymoXk>QEfJ>G(3LNA2-46 z5PGvjr)Xh9>aSe>vEzM*>xp{tJyZox1ZRl}QjcvX2TEgNc^(_-hir@Es>NySoa1g^ zFow_twnHdx(j?Q_3q51t3XI7YlJ4_q&(0#)&a+RUy{IcBq?)eaWo*=H2UUVIqtp&lW9JTJiP&u zw8+4vo~_IJXZIJb_U^&=GI1nSD%e;P!c{kZALNCm5c%%oF+I3DrA63_@4)(v4(t~JiddILp7jmoy+>cD~ivwoctFfEL zP*#2Rx?_&bCpX26MBgp^4G>@h`Hxc(lnqyj!*t>9sOBcXN(hTwEDpn^X{x!!gPX?1 z*uM$}cYRwHXuf+gYTB}gDTcw{TXSOUU$S?8BeP&sc!Lc{{pEv}x#ELX>6*ipI1#>8 zKes$bHjiJ1OygZge_ak^Hz#k;=od1wZ=o71ba7oClBMq>Uk6hVq|ePPt)@FM5bW$I z;d2Or@wBjbTyZj|;+iHp%Bo!Vy(X3YM-}lasMItEV_QrP-Kk_J4C>)L&I3Xxj=E?| zsAF(IfVQ4w+dRRnJ>)}o^3_012YYgFWE)5TT=l2657*L8_u1KC>Y-R{7w^ShTtO;VyD{dezY;XD@Rwl_9#j4Uo!1W&ZHVe0H>f=h#9k>~KUj^iUJ%@wU{Xuy z3FItk0<;}6D02$u(RtEY#O^hrB>qgxnOD^0AJPGC9*WXw_$k%1a%-`>uRIeeAIf3! zbx{GRnG4R$4)3rVmg63gW?4yIWW_>;t3>4@?3}&ct0Tk}<5ljU>jIN1 z&+mzA&1B6`v(}i#vAzvqWH~utZzQR;fCQGLuCN|p0hey7iCQ8^^dr*hi^wC$bTk`8M(JRKtQuXlSf$d(EISvuY0dM z7&ff;p-Ym}tT8^MF5ACG4sZmAV!l;0h&Mf#ZPd--_A$uv2@3H!y^^%_&Iw$*p79Uc5@ZXLGK;edg%)6QlvrN`U7H@e^P*0Atd zQB%>4--B1!9yeF(3vk;{>I8+2D;j`zdR8gd8dHuCQ_6|F(5-?gd&{YhLeyq_-V--4 z(SP#rP=-rsSHJSHDpT1{dMAb7-=9K1-@co_!$dG^?c(R-W&a_C5qy2~m3@%vBGhgnrw|H#g9ABb7k{NE?m4xD?;EV+fPdE>S2g$U(&_zGV+TPvaot>W_ zf8yY@)yP8k$y}UHVgF*uxtjW2zX4Hc3;W&?*}K&kqYpi%FHarfaC$ETHpSoP;A692 zR*LxY1^BO1ry@7Hc9p->hd==U@cuo*CiTnozxen;3Gct=?{5P94TgQ(UJoBb`7z@BqY z;q&?V2D1Y%n;^Dh0+eD)>9<}=A|F5{q#epBu#sf@lRs`oFEpkE%mrfwqJNFCpJC$| zy6#N;GF8XgqX(m2yMM2yq@TxStIR7whUIs2ar$t%Avh;nWLwElVBSI#j`l2$lb-!y zK|!?0hJ1T-wL{4uJhOFHp4?@28J^Oh61DbeTeSWub(|dL-KfxFCp0CjQjV`WaPW|U z=ev@VyC>IS@{ndzPy||b3z-bj5{Y53ff}|TW8&&*pu#?qs?)#&M`ACfb;%m+qX{Or zb+FNNHU}mz!@!EdrxmP_6eb3Cah!mL0ArL#EA1{nCY-!jL8zzz7wR6wAw(8K|IpW; zUvH*b1wbuRlwlUt;dQhx&pgsvJcUpm67rzkNc}2XbC6mZAgUn?VxO6YYg=M!#e=z8 zjX5ZLyMyz(VdPVyosL0}ULO!Mxu>hh`-MItnGeuQ;wGaU0)gIq3ZD=pDc(Qtk}APj z#HtA;?idVKNF)&0r|&w#l7DbX%b91b2;l2=L8q#}auVdk{RuYn3SMDo1%WW0tD*62 zaIj65Y38;?-~@b82AF!?Nra2;PU)t~qYUhl!GDK3*}%@~N0GQH7zflSpfP-ydOwNe zOK~w((+pCD&>f!b!On);5m+zUBFJtQ)mV^prS3?XgPybC2%2LiE5w+S4B|lP z+_>3$`g=%P{IrN|1Oxz30R{kI`}ZL!r|)RS@8Do;ZD3_=PbBrrP~S@EdsD{V+`!4v z{MSF}j!6odl33rA+$odIMaK%ersg%xMz>JQ^R+!qNq$5S{KgmGN#gAApX*3ib)TDsVVi>4ypIX|Ik4d6E}v z=8+hs9J=k3@Eiga^^O|ESMQB-O6i+BL*~*8coxjGs{tJ9wXjGZ^Vw@j93O<&+bzAH z9+N^ALvDCV<##cGoo5fX;wySGGmbH zHsslio)cxlud=iP2y=nM>v8vBn*hJ0KGyNOy7dr8yJKRh zywBOa4Lhh58y06`5>ESYXqLt8ZM1axd*UEp$wl`APU}C9m1H8-ModG!(wfSUQ%}rT3JD*ud~?WJdM}x>84)Cra!^J9wGs6^G^ze~eV(d&oAfm$ z_gwq4SHe=<#*FN}$5(0d_NumIZYaqs|MjFtI_rJb^+ZO?*XQ*47mzLNSL7~Nq+nw8 zuw0KwWITC43`Vx9eB!0Fx*CN9{ea$xjCvtjeyy>yf!ywxvv6<*h0UNXwkEyRxX{!e$TgHZ^db3r;1qhT)+yt@|_!@ zQG2aT`;lj>qjY`RGfQE?KTt2mn=HmSR>2!E38n8PlFs=1zsEM}AMICb z86Dbx(+`!hl$p=Z)*W~+?_HYp+CJacrCS-Fllz!7E>8*!E(yCh-cWbKc7)mPT6xu= zfKpF3I+p%yFXkMIq!ALiXF89-aV{I6v+^k#!_xwtQ*Nl#V|hKg=nP=fG}5VB8Ki7) z;19!on-iq&Xyo#AowvpA)RRgF?YBdDc$J8*)2Wko;Y?V6XMOCqT(4F#U2n1jg*4=< z8$MfDYL|z731iEKB3WW#kz|c3qh7AXjyZ}wtSg9xA(ou-pLoxF{4qk^KS?!d3J0!! zqE#R9NYGUyy>DEs%^xW;oQ5Cs@fomcrsN}rI2Hg^6y9kwLPF`K3llX00aM_r)c?ay zevlHA#N^8N+AI=)vx?4(=?j^ba^{umw140V#g58#vtnh8i7vRs*UD=lge;T+I zl1byCNr5H%DF58I2(rk%8hQ;zuCXs=sipbQy?Hd;umv4!fav@LE4JQ^>J{aZ=!@Gc~p$JudMy%0{=5QY~S8YVP zaP6gRqfZ0>q9nR3p+Wa8icNyl0Zn4k*bNto-(+o@-D8cd1Ed7`}dN3%wezkFxj_#_K zyV{msOOG;n+qbU=jBZk+&S$GEwJ99zSHGz8hF1`Xxa^&l8aaD8OtnIVsdF0cz=Y)? zP$MEdfKZ}_&#AC)R%E?G)tjrKsa-$KW_-$QL}x$@$NngmX2bHJQG~77D1J%3bGK!- zl!@kh5-uKc@U4I_Er;~epL!gej`kdX>tSXVFP-BH#D-%VJOCpM(-&pOY+b#}lOe)Z z0MP5>av1Sy-dfYFy%?`p`$P|`2yDFlv(8MEsa++Qv5M?7;%NFQK0E`Ggf3@2aUwtBpCoh`D}QLY%QAnJ z%qcf6!;cjOTYyg&2G27K(F8l^RgdV-V!~b$G%E=HP}M*Q*%xJV3}I8UYYd)>*nMvw zemWg`K6Rgy+m|y!8&*}=+`STm(dK-#b%)8nLsL&0<8Zd^|# z;I2gR&e1WUS#v!jX`+cuR;+yi(EiDcRCouW0AHNd?;5WVnC_Vg#4x56#0FOwTH6_p z#GILFF0>bb_tbmMM0|sd7r%l{U!fI0tGza&?65_D7+x9G zf3GA{c|mnO(|>}y(}%>|2>p0X8wRS&Eb0g)rcICIctfD_I9Wd+hKuEqv?gzEZBxG-rG~e!-2hqaR$Y$I@k{rLyCccE}3d)7Fn3EvfsEhA|bnJ374&pZDq&i zr(9#eq(g8^tG??ZzVk(#jU+-ce`|yiQ1dgrJ)$|wk?XLEqv&M+)I*OZ*oBCizjHuT zjZ|mW=<1u$wPhyo#&rIO;qH~pu4e3X;!%BRgmX%?&KZ6tNl386-l#a>ug5nHU2M~{fM2jvY*Py< zbR&^o&!T19G6V-pV@CB)YnEOfmrdPG%QByD?=if99ihLxP6iA8$??wUPWzptC{u5H z38Q|!=IW`)5Gef4+pz|9fIRXt>nlW)XQvUXBO8>)Q=$@gtwb1iEkU4EOWI4`I4DN5 zTC-Pk6N>2%7Hikg?`Poj5lkM0T_i zoCXfXB&}{TG%IB)ENSfI_Xg3=lxYc6-P059>oK;L+vGMy_h{y9soj#&^q5E!pl(Oq zl)oCBi56u;YHkD)d`!iOAhEJ0A^~T;uE9~Yp0{E%G~0q|9f34F!`P56-ZF{2hSaWj zio%9RR%oe~he22r@&j_d(y&nAUL*ayBY4#CWG&gZ8ybs#UcF?8K#HzziqOYM-<`C& z1gD?j)M0bp1w*U>X_b1@ag1Fx=d*wlr zEAcpmI#5LtqcX95LeS=LXlzh*l;^yPl_6MKk)zPuTz_p8ynQ5;oIOUAoPED=+M6Q( z8YR!DUm#$zTM9tbNhxZ4)J0L&Hpn%U>wj3z<=g;`&c_`fGufS!o|1%I_sA&;14bRC z3`BtzpAB-yl!%zM{Aiok8*X%lDNrPiAjBnzHbF0=Ua*3Lxl(zN3Thj2x6nWi^H7Jlwd2fxIvnI-SiC%*j z2~wIWWKT^5fYipo-#HSrr;(RkzzCSt?THVEH2EPvV-4c#Gu4&1X% z<1zTAM7ZM(LuD@ZPS?c30Ur`;2w;PXPVevxT)Ti25o}1JL>MN5i1^(aCF3 zbp>RI?X(CkR9*Hnv!({Ti@FBm;`Ip%e*D2tWEOc62@$n7+gWb;;j}@G()~V)>s}Bd zw+uTg^ibA(gsp*|&m7Vm=heuIF_pIukOedw2b_uO8hEbM4l=aq?E-7M_J`e(x9?{5 zpbgu7h}#>kDQAZL;Q2t?^pv}Y9Zlu=lO5e18twH&G&byq9XszEeXt$V93dQ@Fz2DV zs~zm*L0uB`+o&#{`uVYGXd?)Fv^*9mwLW4)IKoOJ&(8uljK?3J`mdlhJF1aK;#vlc zJdTJc2Q>N*@GfafVw45B03)Ty8qe>Ou*=f#C-!5uiyQ^|6@Dzp9^n-zidp*O`YuZ|GO28 zO0bqi;)fspT0dS2;PLm(&nLLV&&=Ingn(0~SB6Fr^AxPMO(r~y-q2>gRWv7{zYW6c zfiuqR)Xc41A7Eu{V7$-yxYT-opPtqQIJzMVkxU)cV~N0ygub%l9iHT3eQtB>nH0c` zFy}Iwd9vocxlm!P)eh0GwKMZ(fEk92teSi*fezYw3qRF_E-EcCh-&1T)?beW?9Q_+pde8&UW*(avPF4P}M#z*t~KlF~#5TT!&nu z>FAKF8vQl>Zm(G9UKi4kTqHj`Pf@Z@Q(bmZkseb1^;9k*`a9lKXceKX#dMd@ds`t| z2~UPsbn2R0D9Nm~G*oc@(%oYTD&yK)scA?36B7mndR9l*hNg!3?6>CR+tF1;6sr?V zzz8FBrZ@g4F_!O2igIGZcWd zRe_0*{d6cyy9QQ(|Ct~WTM1pC3({5qHahk*M*O}IPE6icikx48VZ?!0Oc^FVoq`}eu~ zpRq0MYHaBA-`b_BVID}|oo-bem76;B2zo7j7yz(9JiSY6JTjKz#+w{9mc{&#x}>E? zSS3mY$_|scfP3Mo_F5x;r>y&Mquy*Q1b3eF^*hg3tap~%?@ASeyodYa=dF&k=ZyWy z3C+&C95h|9TAVM~-8y(&xcy0nvl}6B*)j0FOlSz%+bK-}S4;F?P`j55*+ZO0Ogk7D z5q30zE@Nup4lqQoG`L%n{T?qn9&WC94%>J`KU{gHIq?n_L;75kkKyib;^?yXUx6BO zju%DyU(l!Vj(3stJ>!pMZ*NZFd60%oSAD1JUXG0~2GCXpB0Am(YPyhzQda-e)b^+f zzFaEZdVTJRJXPJo%w z$?T;xq^&(XjmO>0bNGsT|1{1UqGHHhasPC;H!oX52(AQ7h9*^npOIRdQbNrS0X5#5G?L4V}WsAYcpq-+JNXhSl)XbxZ)L@5Q+?wm{GAU z9a7X8hAjAo;4r_eOdZfXGL@YpmT|#qECEcPTQ;nsjIkQ;!0}g?T>Zr*Fg}%BZVA)4 zCAzvWr?M&)KEk`t9eyFi_GlPV9a2kj9G(JgiZadd_&Eb~#DyZ%2Zcvrda_A47G&uW z^6TnBK|th;wHSo8ivpScU?AM5HDu2+ayzExMJc@?4{h-c`!b($ExB`ro#vkl<;=BA z961c*n(4OR!ebT*7UV7sqL;rZ3+Z)BYs<1I|9F|TOKebtLPxahl|ZXxj4j!gjj!3*+iSb5Zni&EKVt$S{0?2>A}d@3PSF3LUu)5 z*Y#a1uD6Y!$=_ghsPrOqX!OcIP`IW};tZzx1)h_~mgl;0=n zdP|Te_7)~R?c9s>W(-d!@nzQyxqakrME{Tn@>0G)kqV<4;{Q?Z-M)E-|IFLTc}WQr z1Qt;u@_dN2kru_9HMtz8MQx1aDYINH&3<+|HA$D#sl3HZ&YsjfQBv~S>4=u z7gA2*X6_cI$2}JYLIq`4NeXTz6Q3zyE717#>RD&M?0Eb|KIyF;xj;+3#DhC-xOj~! z$-Kx#pQ)_$eHE3Zg?V>1z^A%3jW0JBnd@z`kt$p@lch?A9{j6hXxt$(3|b>SZiBxOjA%LsIPii{=o(B`yRJ>OK;z_ELTi8xHX)il z--qJ~RWsZ%9KCNuRNUypn~<2+mQ=O)kd59$Lul?1ev3c&Lq5=M#I{ zJby%%+Top_ocqv!jG6O6;r0Xwb%vL6SP{O(hUf@8riADSI<|y#g`D)`x^vHR4!&HY`#TQMqM`Su}2(C|KOmG`wyK>uh@3;(prdL{2^7T3XFGznp{-sNLLJH@mh* z^vIyicj9yH9(>~I-Ev7p=yndfh}l!;3Q65}K}()(jp|tC;{|Ln1a+2kbctWEX&>Vr zXp5=#pw)@-O6~Q|><8rd0>H-}0Nsc|J6TgCum{XnH2@hFB09FsoZ_ow^Nv@uGgz3# z<6dRDt1>>-!kN58&K1HFrgjTZ^q<>hNI#n8=hP&pKAL4uDcw*J66((I?!pE0fvY6N zu^N=X8lS}(=w$O_jlE(;M9F={-;4R(K5qa=P#ZVW>}J&s$d0?JG8DZJwZcx3{CjLg zJA>q-&=Ekous)vT9J>fbnZYNUtvox|!Rl@e^a6ue_4-_v=(sNB^I1EPtHCFEs!>kK6B@-MS!(B zST${=v9q6q8YdSwk4}@c6cm$`qZ86ipntH8G~51qIlsYQ)+2_Fg1@Y-ztI#aa~tFD_QUxb zU-?g5B}wU@`tnc_l+B^mRogRghXs!7JZS=A;In1|f(1T(+xfIi zvjccLF$`Pkv2w|c5BkSj>>k%`4o6#?ygojkV78%zzz`QFE6nh{(SSJ9NzVdq>^N>X zpg6+8u7i(S>c*i*cO}poo7c9%i^1o&3HmjY!s8Y$5aO(!>u1>-eai0;rK8hVzIh8b zL53WCXO3;=F4_%CxMKRN^;ggC$;YGFTtHtLmX%@MuMxvgn>396~ zEp>V(dbfYjBX^!8CSg>P2c5I~HItbe(dl^Ax#_ldvCh;D+g6-%WD|$@S6}Fvv*eHc zaKxji+OG|_KyMe2D*fhP<3VP0J1gTgs6JZjE{gZ{SO-ryEhh;W237Q0 z{yrDobsM6S`bPMUzr|lT|99m6XDI$RzW4tQ$|@C2RjhBYPliEXFV#M*5G4;Kb|J8E z0IH}-d^S-53kFRZ)ZFrd2%~Sth-6BN?hnMa_PC4gdWyW3q-xFw&L^x>j<^^S$y_3_ zdZxouw%6;^mg#jG@7L!g9Kdw}{w^X9>TOtHgxLLIbfEG^Qf;tD=AXozE6I`XmOF=# zGt$Wl+7L<8^VI-eSK%F%dqXieK^b!Z3yEA$KL}X@>fD9)g@=DGt|=d(9W%8@Y@!{PI@`Nd zyF?Us(0z{*u6|X?D`kKSa}}Q*HP%9BtDEA^buTlI5ihwe)CR%OR46b+>NakH3SDbZmB2X>c8na&$lk zYg$SzY+EXtq2~$Ep_x<~+YVl<-F&_fbayzTnf<7?Y-un3#+T~ahT+eW!l83sofNt; zZY`eKrGqOux)+RMLgGgsJdcA3I$!#zy!f<$zL0udm*?M5w=h$Boj*RUk8mDPVUC1RC8A`@7PgoBIU+xjB7 z25vky+^7k_|1n1&jKNZkBWUu1VCmS}a|6_+*;fdUZAaIR4G!wv=bAZEXBhcjch6WH zdKUr&>z^P%_LIx*M&x{!w|gij?nigT8)Ol3VicXRL0tU}{vp2fi!;QkVc#I38op3O z=q#WtNdN{x)OzmH;)j{cor)DQ;2%m>xMu_KmTisaeCC@~rQwQTfMml7FZ_ zU2AR8yCY_CT$&IAn3n#Acf*VKzJD8-aphMg(12O9cv^AvLQ9>;f!4mjyxq_a%YH2+{~=3TMNE1 z#r3@ynnZ#p?RCkPK36?o{ILiHq^N5`si(T_cKvO9r3^4pKG0AgDEB@_72(2rvU^-; z%&@st2+HjP%H)u50t81p>(McL{`dTq6u-{JM|d=G1&h-mtjc2{W0%*xuZVlJpUSP-1=U6@5Q#g(|nTVN0icr-sdD~DWR=s}`$#=Wa zt5?|$`5`=TWZevaY9J9fV#Wh~Fw@G~0vP?V#Pd=|nMpSmA>bs`j2e{)(827mU7rxM zJ@ku%Xqhq!H)It~yXm=)6XaPk=$Rpk*4i4*aSBZe+h*M%w6?3&0>>|>GHL>^e4zR!o%aGzUn40SR+TdN%=Dbn zsRfXzGcH#vjc-}7v6yRhl{V5PhE-r~)dnmNz=sDt?*1knNZ>xI5&vBwrosF#qRL-Y z;{W)4W&cO0XMKy?{^d`Xh(2B?j0ioji~G~p5NQJyD6vouyoFE9w@_R#SGZ1DR4GnN z{b=sJ^8>2mq3W;*u2HeCaKiCzK+yD!^i6QhTU5npwO+C~A#5spF?;iuOE>o&p3m1C zmT$_fH8v+5u^~q^ic#pQN_VYvU>6iv$tqx#Sulc%|S7f zshYrWq7IXCiGd~J(^5B1nGMV$)lo6FCTm1LshfcOrGc?HW7g>pV%#4lFbnt#94&Rg{%Zbg;Rh?deMeOP(du*)HryI zCdhO$3|SeaWK<>(jSi%qst${Z(q@{cYz7NA^QO}eZ$K@%YQ^Dt4CXzmvx~lLG{ef8 zyckIVSufk>9^e_O7*w2z>Q$8me4T~NQDq=&F}Ogo#v1u$0xJV~>YS%mLVYqEf~g*j zGkY#anOI9{(f4^v21OvYG<(u}UM!-k;ziH%GOVU1`$0VuO@Uw2N{$7&5MYjTE?Er) zr?oZAc~Xc==KZx-pmoh9KiF_JKU7u0#b_}!dWgC>^fmbVOjuiP2FMq5OD9+4TKg^2 z>y6s|sQhI`=fC<>BnQYV433-b+jBi+N6unz%6EQR%{8L#=4sktI>*3KhX+qAS>+K#}y5KnJ8YuOuzG(Ea5;$*1P$-9Z+V4guyJ#s) zRPH(JPN;Es;H72%c8}(U)CEN}Xm>HMn{n!d(=r*YP0qo*^APwwU5YTTeHKy#85Xj< zEboiH=$~uIVMPg!qbx~0S=g&LZ*IyTJG$hTN zv%2>XF``@S9lnLPC?|myt#P)%7?%e_j*aU4TbTyxO|3!h%=Udp;THL+^oPp<6;TLlIOa$&xeTG_a*dbRDy+(&n1T=MU z+|G5{2UprrhN^AqODLo$9Z2h(3^wtdVIoSk@}wPajVgIoZipRft}^L)2Y@mu;X-F{LUw|s7AQD-0!otW#W9M@A~08`o%W;Bq-SOQavG*e-sy8) zwtaucR0+64B&Pm++-m56MQ$@+t{_)7l-|`1kT~1s!swfc4D9chbawUt`RUOdoxU|j z$NE$4{Ysr@2Qu|K8pD37Yv&}>{_I5N49a@0<@rGHEs}t zwh_+9T0oh@ptMbjy*kbz<&3>LGR-GNsT8{x1g{!S&V7{5tPYX(GF>6qZh>O&F)%_I zkPE-pYo3dayjNQAG+xrI&yMZy590FA1unQ*k*Zfm#f9Z5GljOHBj-B83KNIP1a?<^1vOhDJkma0o- zs(TP=@e&s6fRrU(R}{7eHL*(AElZ&80>9;wqj{|1YQG=o2Le-m!UzUd?Xrn&qd8SJ0mmEYtW;t(;ncW_j6 zGWh4y|KMK^s+=p#%fWxjXo434N`MY<8W`tNH-aM6x{@o?D3GZM&+6t4V3I*3fZd{a z0&D}DI?AQl{W*?|*%M^D5{E>V%;=-r&uQ>*e)cqVY52|F{ptA*`!iS=VKS6y4iRP6 zKUA!qpElT5vZvN}U5k-IpeNOr6KF`-)lN1r^c@HnT#RlZbi(;yuvm9t-Noh5AfRxL@j5dU-X37(?S)hZhRDbf5cbhDO5nSX@WtApyp` zT$5IZ*4*)h8wShkPI45stQH2Y7yD*CX^Dh@B%1MJSEn@++D$AV^ttKXZdQMU`rxiR z+M#45Z2+{N#uR-hhS&HAMFK@lYBWOzU^Xs-BlqQDyN4HwRtP2$kks@UhAr@wlJii%Rq?qy25?Egs z*a&iAr^rbJWlv+pYAVUq9lor}#Cm|D$_ev2d2Ko}`8kuP(ljz$nv3OCDc7zQp|j6W zbS6949zRvj`bhbO(LN3}Pq=$Ld3a_*9r_24u_n)1)}-gRq?I6pdHPYHgIsn$#XQi~ z%&m_&nnO9BKy;G%e~fa7i9WH#MEDNQ8WCXhqqI+oeE5R7hLZT_?7RWVzEGZNz4*Po ze&*a<^Q*ze72}UM&$c%FuuEIN?EQ@mnILwyt;%wV-MV+|d%>=;3f0(P46;Hwo|Wr0 z>&FS9CCb{?+lDpJMs`95)C$oOQ}BSQEv0Dor%-Qj0@kqlIAm1-qSY3FCO2j$br7_w zlpRfAWz3>Gh~5`Uh?ER?@?r0cXjD0WnTx6^AOFii;oqM?|M9QjHd*GK3WwA}``?dK15`ZvG>_nB2pSTGc{n2hYT6QF^+&;(0c`{)*u*X7L_ zaxqyvVm$^VX!0YdpSNS~reC+(uRqF2o>jqIJQkC&X>r8|mBHvLaduM^Mh|OI60<;G zDHx@&jUfV>cYj5+fAqvv(XSmc(nd@WhIDvpj~C#jhZ6@M3cWF2HywB1yJv2#=qoY| zIiaxLsSQa7w;4YE?7y&U&e6Yp+2m(sb5q4AZkKtey{904rT08pJpanm->Z75IdvW^ z!kVBy|CIUZn)G}92_MgoLgHa?LZJDp_JTbAEq8>6a2&uKPF&G!;?xQ*+{TmNB1H)_ z-~m@CTxDry_-rOM2xwJg{fcZ41YQDh{DeI$4!m8c;6XtFkFyf`fOsREJ`q+Bf4nS~ zKDYs4AE7Gugv?X)tu4<-M8ag{`4pfQ14z<(8MYQ4u*fl*DCpq66+Q1-gxNCQ!c$me zyTrmi7{W-MGP!&S-_qJ%9+e08_9`wWGG{i5yLJ;8qbt-n_0*Q371<^u@tdz|;>fPW zE=&q~;wVD_4IQ^^jyYX;2shIMiYdvIpIYRT>&I@^{kL9Ka2ECG>^l>Ae!GTn{r~o= z|I9=J#wNe)zYRqGZ7Q->L{dfewyC$ZYcLaoNormZ3*gfM=da*{heC)&46{yTS!t10 zn_o0qUbQOs$>YuY>YHi|NG^NQG<_@jD&WnZcW^NTC#mhVE7rXlZ=2>mZkx{bc=~+2 z{zVH=Xs0`*K9QAgq9cOtfQ^BHh-yr=qX8hmW*0~uCup89IJMvWy%#yt_nz@6dTS)L{O3vXye< zW4zUNb6d|Tx`XIVwMMgqnyk?c;Kv`#%F0m^<$9X!@}rI##T{iXFC?(ui{;>_9Din8 z7;(754q!Jx(~sb!6+6Lf*l{fqD7GW*v{>3wp+)@wq2abADBK!kI8To}7zooF%}g-z zJ1-1lp-lQI6w^bov9EfhpxRI}`$PTpJI3uo@ZAV729JJ2Hs68{r$C0U=!d$Bm+s(p z8Kgc(Ixf4KrN%_jjJjTx5`&`Ak*Il%!}D_V)GM1WF!k$rDJ-SudXd_Xhl#NWnET&e-P!rH~*nNZTzxj$?^oo3VWc-Ay^`Phze3(Ft!aNW-f_ zeMy&BfNCP^-FvFzR&rh!w(pP5;z1$MsY9Voozmpa&A}>|a{eu}>^2s)So>&kmi#7$ zJS_-DVT3Yi(z+ruKbffNu`c}s`Uo`ORtNpUHa6Q&@a%I%I;lm@ea+IbCLK)IQ~)JY zp`kdQ>R#J*i&Ljer3uz$m2&Un9?W=Ue|hHv?xlM`I&*-M;2{@so--0OAiraN1TLra z>EYQu#)Q@UszfJj&?kr%RraFyi*eG+HD_(!AWB;hPgB5Gd-#VDRxxv*VWMY0hI|t- zR=;TL%EKEg*oet7GtmkM zgH^y*1bfJ*af(_*S1^PWqBVVbejFU&#m`_69IwO!aRW>Rcp~+7w^ptyu>}WFYUf;) zZrgs;EIN9$Immu`$umY%$I)5INSb}aV-GDmPp!d_g_>Ar(^GcOY%2M)Vd7gY9llJR zLGm*MY+qLzQ+(Whs8-=ty2l)G9#82H*7!eo|B6B$q%ak6eCN%j?{SI9|K$u3)ORoz zw{bAGaWHrMb|X^!UL~_J{jO?l^}lI^|7jIn^p{n%JUq9{tC|{GM5Az3SrrPkuCt_W zq#u0JfDw{`wAq`tAJmq~sz`D_P-8qr>kmms>I|);7Tn zLl^n*Ga7l=U)bQmgnSo5r_&#Pc=eXm~W75X9Cyy0WDO|fbSn5 zLgpFAF4fa90T-KyR4%%iOq6$6BNs@3ZV<~B;7V=u zdlB8$lpe`w-LoS;0NXFFu@;^^bc?t@r3^XTe*+0;o2dt&>eMQeDit(SfDxYxuA$uS z**)HYK7j!vJVRNfrcokVc@&(ke5kJzvi};Lyl7@$!`~HM$T!`O`~MQ1k~ZH??fQr zNP)33uBWYnTntKRUT*5lu&8*{fv>syNgxVzEa=qcKQ86Vem%Lpae2LM=TvcJLs?`=o9%5Mh#k*_7zQD|U7;A%=xo^_4+nX{~b1NJ6@ z*=55;+!BIj1nI+)TA$fv-OvydVQB=KK zrGWLUS_Chm$&yoljugU=PLudtJ2+tM(xj|E>Nk?c{-RD$sGYNyE|i%yw>9gPItE{ zD|BS=M>V^#m8r?-3swQofD8j$h-xkg=F+KM%IvcnIvc)y zl?R%u48Jeq7E*26fqtLe_b=9NC_z|axW#$e0adI#r(Zsui)txQ&!}`;;Z%q?y2Kn! zXzFNe+g7+>>`9S0K1rmd)B_QVMD?syc3e0)X*y6(RYH#AEM9u?V^E0GHlAAR)E^4- zjKD+0K=JKtf5DxqXSQ!j?#2^ZcQoG5^^T+JaJa3GdFeqIkm&)dj76WaqGukR-*&`13ls8lU2ayVIR%;79HYAr5aEhtYa&0}l}eAw~qKjUyz4v*At z?})QplY`3cWB6rl7MI5mZx&#%I0^iJm3;+J9?RA(!JXjl?(XgmA-D#2cY-^?g1c*Q z3GVLh!8Jhe;QqecbMK#XIJxKMb=6dcs?1vbb?@ov-raj`hnYO92y8pv@>RVr=9Y-F zv`BK)9R6!m4Pfllu4uy0WBL+ZaUFFzbZZtI@J8{OoQ^wL-b$!FpGT)jYS-=vf~b-@ zIiWs7j~U2yI=G5;okQz%gh6}tckV5wN;QDbnu|5%%I(#)8Q#)wTq8YYt$#f9=id;D zJbC=CaLUyDIPNOiDcV9+=|$LE9v2;Qz;?L+lG{|g&iW9TI1k2_H;WmGH6L4tN1WL+ zYfSVWq(Z_~u~U=g!RkS|YYlWpKfZV!X%(^I3gpV%HZ_{QglPSy0q8V+WCC2opX&d@eG2BB#(5*H!JlUzl$DayI5_J-n zF@q*Fc-nlp%Yt;$A$i4CJ_N8vyM5fNN`N(CN53^f?rtya=p^MJem>JF2BEG|lW|E) zxf)|L|H3Oh7mo=9?P|Y~|6K`B3>T)Gw`0ESP9R`yKv}g|+qux(nPnU(kQ&&x_JcYg9+6`=; z-EI_wS~l{T3K~8}8K>%Ke`PY!kNt415_x?^3QOvX(QUpW&$LXKdeZM-pCI#%EZ@ta zv(q-(xXIwvV-6~(Jic?8<7ain4itN>7#AqKsR2y(MHMPeL)+f+v9o8Nu~p4ve*!d3 z{Lg*NRTZsi;!{QJknvtI&QtQM_9Cu%1QcD0f!Fz+UH4O#8=hvzS+^(e{iG|Kt7C#u zKYk7{LFc+9Il>d6)blAY-9nMd(Ff0;AKUo3B0_^J&ESV@4UP8PO0no7G6Gp_;Z;YnzW4T-mCE6ZfBy(Y zXOq^Of&?3#Ra?khzc7IJT3!%IKK8P(N$ST47Mr=Gv@4c!>?dQ-&uZihAL1R<_(#T8Y`Ih~soL6fi_hQmI%IJ5qN995<{<@_ z;^N8AGQE+?7#W~6X>p|t<4@aYC$-9R^}&&pLo+%Ykeo46-*Yc(%9>X>eZpb8(_p{6 zwZzYvbi%^F@)-}5%d_z^;sRDhjqIRVL3U3yK0{Q|6z!PxGp?|>!%i(!aQODnKUHsk^tpeB<0Qt7`ZBlzRIxZMWR+|+ z3A}zyRZ%0Ck~SNNov~mN{#niO**=qc(faGz`qM16H+s;Uf`OD1{?LlH!K!+&5xO%6 z5J80-41C{6)j8`nFvDaeSaCu_f`lB z_Y+|LdJX=YYhYP32M556^^Z9MU}ybL6NL15ZTV?kfCFfpt*Pw5FpHp#2|ccrz#zoO zhs=+jQI4fk*H0CpG?{fpaSCmXzU8bB`;kCLB8T{_3t>H&DWj0q0b9B+f$WG=e*89l zzUE)b9a#aWsEpgnJqjVQETpp~R7gn)CZd$1B8=F*tl+(iPH@s9jQtE33$dBDOOr=% ziOpR8R|1eLI?Rn*d+^;_U#d%bi$|#obe0(-HdB;K>=Y=mg{~jTA_WpChe8QquhF`N z>hJ}uV+pH`l_@d>%^KQNm*$QNJ(lufH>zv9M`f+C-y*;hAH(=h;kp@eL=qPBeXrAo zE7my75EYlFB30h9sdt*Poc9)2sNP9@K&4O7QVPQ^m$e>lqzz)IFJWpYrpJs)Fcq|P z5^(gnntu!+oujqGpqgY_o0V&HL72uOF#13i+ngg*YvPcqpk)Hoecl$dx>C4JE4DWp z-V%>N7P-}xWv%9Z73nn|6~^?w$5`V^xSQbZceV<_UMM&ijOoe{Y^<@3mLSq_alz8t zr>hXX;zTs&k*igKAen1t1{pj94zFB;AcqFwV)j#Q#Y8>hYF_&AZ?*ar1u%((E2EfZ zcRsy@s%C0({v=?8oP=DML`QsPgzw3|9|C22Y>;=|=LHSm7~+wQyI|;^WLG0_NSfrf zamq!5%EzdQ&6|aTP2>X=Z^Jl=w6VHEZ@=}n+@yeu^ke2Yurrkg9up3g$0SI8_O-WQu$bCsKc(juv|H;vz6}%7ONww zKF%!83W6zO%0X(1c#BM}2l^ddrAu^*`9g&1>P6m%x{gYRB)}U`40r>6YmWSH(|6Ic zH~QNgxlH*;4jHg;tJiKia;`$n_F9L~M{GiYW*sPmMq(s^OPOKm^sYbBK(BB9dOY`0 z{0!=03qe*Sf`rcp5Co=~pfQyqx|umPHj?a6;PUnO>EZGb!pE(YJgNr{j;s2+nNV(K zDi#@IJ|To~Zw)vqGnFwb2}7a2j%YNYxe2qxLk)VWJIux$BC^oII=xv-_}h@)Vkrg1kpKokCmX({u=lSR|u znu_fA0PhezjAW{#Gu0Mdhe8F4`!0K|lEy+<1v;$ijSP~A9w%q5-4Ft|(l7UqdtKao zs|6~~nmNYS>fc?Nc=yzcvWNp~B0sB5ForO5SsN(z=0uXxl&DQsg|Y?(zS)T|X``&8 z*|^p?~S!vk8 zg>$B{oW}%rYkgXepmz;iqCKY{R@%@1rcjuCt}%Mia@d8Vz5D@LOSCbM{%JU#cmIp! z^{4a<3m%-p@JZ~qg)Szb-S)k{jv92lqB(C&KL(jr?+#ES5=pUH$(;CO9#RvDdErmW z3(|f{_)dcmF-p*D%qUa^yYngNP&Dh2gq5hr4J!B5IrJ?ODsw@*!0p6Fm|(ebRT%l) z#)l22@;4b9RDHl1ys$M2qFc;4BCG-lp2CN?Ob~Be^2wQJ+#Yz}LP#8fmtR%o7DYzoo1%4g4D+=HonK7b!3nvL0f1=oQp93dPMTsrjZRI)HX-T}ApZ%B#B;`s? z9Kng{|G?yw7rxo(T<* z1+O`)GNRmXq3uc(4SLX?fPG{w*}xDCn=iYo2+;5~vhWUV#e5e=Yfn4BoS@3SrrvV9 zrM-dPU;%~+3&>(f3sr$Rcf4>@nUGG*vZ~qnxJznDz0irB(wcgtyATPd&gSuX^QK@+ z)7MGgxj!RZkRnMSS&ypR94FC$;_>?8*{Q110XDZ)L);&SA8n>72s1#?6gL>gydPs` zM4;ert4-PBGB@5E` zBaWT=CJUEYV^kV%@M#3(E8>g8Eg|PXg`D`;K8(u{?}W`23?JgtNcXkUxrH}@H_4qN zw_Pr@g%;CKkgP(`CG6VTIS4ZZ`C22{LO{tGi6+uPvvHkBFK|S6WO{zo1MeK$P zUBe}-)3d{55lM}mDVoU@oGtPQ+a<=wwDol}o=o1z*)-~N!6t09du$t~%MlhM9B5~r zy|zs^LmEF#yWpXZq!+Nt{M;bE%Q8z7L8QJDLie^5MKW|I1jo}p)YW(S#oLf(sWn~* zII>pocNM5#Z+-n2|495>?H?*oyr0!SJIl(}q-?r`Q;Jbqqr4*_G8I7agO298VUr9x z8ZcHdCMSK)ZO@Yr@c0P3{`#GVVdZ{zZ$WTO zuvO4ukug&& ze#AopTVY3$B>c3p8z^Yyo8eJ+(@FqyDWlR;uxy0JnSe`gevLF`+ZN6OltYr>oN(ZV z>76nIiVoll$rDNkck6_eh%po^u16tD)JXcii|#Nn(7=R9mA45jz>v}S%DeMc(%1h> zoT2BlF9OQ080gInWJ3)bO9j$ z`h6OqF0NL4D3Kz?PkE8nh;oxWqz?<3_!TlN_%qy*T7soZ>Pqik?hWWuya>T$55#G9 zxJv=G&=Tm4!|p1#!!hsf*uQe}zWTKJg`hkuj?ADST2MX6fl_HIDL7w`5Dw1Btays1 zz*aRwd&>4*H%Ji2bt-IQE$>sbCcI1Poble0wL`LAhedGRZp>%>X6J?>2F*j>`BX|P zMiO%!VFtr_OV!eodgp-WgcA-S=kMQ^zihVAZc!vdx*YikuDyZdHlpy@Y3i!r%JI85$-udM6|7*?VnJ!R)3Qfm4mMm~Z#cvNrGUy|i0u zb|(7WsYawjBK0u1>@lLhMn}@X>gyDlx|SMXQo|yzkg-!wIcqfGrA!|t<3NC2k` zq;po50dzvvHD>_mG~>W0iecTf@3-)<$PM5W@^yMcu@U;)(^eu@e4jAX7~6@XrSbIE zVG6v2miWY^g8bu5YH$c2QDdLkg2pU8xHnh`EUNT+g->Q8Tp4arax&1$?CH($1W&*} zW&)FQ>k5aCim$`Ph<9Zt?=%|pz&EX@_@$;3lQT~+;EoD(ho|^nSZDh*M0Z&&@9T+e zHYJ;xB*~UcF^*7a_T)9iV5}VTYKda8n*~PSy@>h7c(mH~2AH@qz{LMQCb+-enMhX} z2k0B1JQ+6`?Q3Lx&(*CBQOnLBcq;%&Nf<*$CX2<`8MS9c5zA!QEbUz1;|(Ua%CiuL zF2TZ>@t7NKQ->O#!;0s;`tf$veXYgq^SgG>2iU9tCm5&^&B_aXA{+fqKVQ*S9=58y zddWqy1lc$Y@VdB?E~_B5w#so`r552qhPR649;@bf63_V@wgb!>=ij=%ptnsq&zl8^ zQ|U^aWCRR3TnoKxj0m0QL2QHM%_LNJ(%x6aK?IGlO=TUoS%7YRcY{!j(oPcUq{HP=eR1>0o^(KFl-}WdxGRjsT);K8sGCkK0qVe{xI`# z@f+_kTYmLbOTxRv@wm2TNBKrl+&B>=VaZbc(H`WWLQhT=5rPtHf)#B$Q6m1f8We^)f6ylbO=t?6Y;{?&VL|j$VXyGV!v8eceRk zl>yOWPbk%^wv1t63Zd8X^Ck#12$*|yv`v{OA@2;-5Mj5sk#ptfzeX(PrCaFgn{3*hau`-a+nZhuJxO;Tis51VVeKAwFML#hF9g26NjfzLs8~RiM_MFl1mgDOU z=ywk!Qocatj1Q1yPNB|FW>!dwh=aJxgb~P%%7(Uydq&aSyi?&b@QCBiA8aP%!nY@c z&R|AF@8}p7o`&~>xq9C&X6%!FAsK8gGhnZ$TY06$7_s%r*o;3Y7?CenJUXo#V-Oag z)T$d-V-_O;H)VzTM&v8^Uk7hmR8v0)fMquWHs6?jXYl^pdM#dY?T5XpX z*J&pnyJ<^n-d<0@wm|)2SW9e73u8IvTbRx?Gqfy_$*LI_Ir9NZt#(2T+?^AorOv$j zcsk+t<#!Z!eC|>!x&#l%**sSAX~vFU0|S<;-ei}&j}BQ#ekRB-;c9~vPDIdL5r{~O zMiO3g0&m-O^gB}<$S#lCRxX@c3g}Yv*l)Hh+S^my28*fGImrl<-nbEpOw-BZ;WTHL zgHoq&ftG|~ouV<>grxRO6Z%{!O+j`Cw_4~BIzrjpkdA5jH40{1kDy|pEq#7`$^m*? zX@HxvW`e}$O$mJvm+65Oc4j7W@iVe)rF&-}R>KKz>rF&*Qi3%F0*tz!vNtl@m8L9= zyW3%|X}0KsW&!W<@tRNM-R>~~QHz?__kgnA(G`jWOMiEaFjLzCdRrqzKlP1vYLG`Y zh6_knD3=9$weMn4tBD|5=3a9{sOowXHu(z5y^RYrxJK z|L>TUvbDuO?3=YJ55N5}Kj0lC(PI*Te0>%eLNWLnawD54geX5>8AT(oT6dmAacj>o zC`Bgj-RV0m3Dl2N=w3e0>wWWG5!mcal`Xu<(1=2$b{k(;kC(2~+B}a(w;xaHPk^@V zGzDR|pt%?(1xwNxV!O6`JLCM!MnvpbLoHzKziegT_2LLWAi4}UHIo6uegj#WTQLet z9Dbjyr{8NAk+$(YCw~_@Az9N|iqsliRYtR7Q|#ONIV|BZ7VKcW$phH9`ZAlnMTW&9 zIBqXYuv*YY?g*cJRb(bXG}ts-t0*|HXId4fpnI>$9A?+BTy*FG8f8iRRKYRd*VF_$ zoo$qc+A(d#Lx0@`ck>tt5c$L1y7MWohMnZd$HX++I9sHoj5VXZRZkrq`v@t?dfvC} z>0h!c4HSb8%DyeF#zeU@rJL2uhZ^8dt(s+7FNHJeY!TZJtyViS>a$~XoPOhHsdRH* zwW+S*rIgW0qSPzE6w`P$Jv^5dsyT6zoby;@z=^yWLG^x;e557RnndY>ph!qCF;ov$ ztSW1h3@x{zm*IMRx|3lRWeI3znjpbS-0*IL4LwwkWyPF1CRpQK|s42dJ{ddA#BDDqio-Y+mF-XcP-z4bi zAhfXa2=>F0*b;F0ftEPm&O+exD~=W^qjtv&>|%(4q#H=wbA>7QorDK4X3~bqeeXv3 zV1Q<>_Fyo!$)fD`fd@(7(%6o-^x?&+s=)jjbQ2^XpgyYq6`}ISX#B?{I$a&cRcW?X zhx(i&HWq{=8pxlA2w~7521v-~lu1M>4wL~hDA-j(F2;9ICMg+6;Zx2G)ulp7j;^O_ zQJIRUWQam(*@?bYiRTKR<;l_Is^*frjr-Dj3(fuZtK{Sn8F;d*t*t{|_lnlJ#e=hx zT9?&_n?__2mN5CRQ}B1*w-2Ix_=CF@SdX-cPjdJN+u4d-N4ir*AJn&S(jCpTxiAms zzI5v(&#_#YrKR?B?d~ge1j*g<2yI1kp`Lx>8Qb;aq1$HOX4cpuN{2ti!2dXF#`AG{ zp<iD=Z#qN-yEwLwE7%8w8&LB<&6{WO$#MB-|?aEc@S1a zt%_p3OA|kE&Hs47Y8`bdbt_ua{-L??&}uW zmwE7X4Y%A2wp-WFYPP_F5uw^?&f zH%NCcbw_LKx!c!bMyOBrHDK1Wzzc5n7A7C)QrTj_Go#Kz7%+y^nONjnnM1o5Sw(0n zxU&@41(?-faq?qC^kO&H301%|F9U-Qm(EGd3}MYTFdO+SY8%fCMTPMU3}bY7ML1e8 zrdOF?E~1uT)v?UX(XUlEIUg3*UzuT^g@QAxEkMb#N#q0*;r zF6ACHP{ML*{Q{M;+^4I#5bh#c)xDGaIqWc#ka=0fh*_Hlu%wt1rBv$B z%80@8%MhIwa0Zw$1`D;Uj1Bq`lsdI^g_18yZ9XUz2-u6&{?Syd zHGEh-3~HH-vO<)_2^r|&$(q7wG{@Q~un=3)Nm``&2T99L(P+|aFtu1sTy+|gwL*{z z)WoC4rsxoWhz0H$rG|EwhDT z0zcOAod_k_Ql&Y`YV!#&Mjq{2ln|;LMuF$-G#jX_2~oNioTHb4GqFatn@?_KgsA7T z(ouy$cGKa!m}6$=C1Wmb;*O2p*@g?wi-}X`v|QA4bNDU*4(y8*jZy-Ku)S3iBN(0r ztfLyPLfEPqj6EV}xope=?b0Nyf*~vDz-H-Te@B`{ib?~F<*(MmG+8zoYS77$O*3vayg#1kkKN+Bu9J9;Soev<%2S&J zr8*_PKV4|?RVfb#SfNQ;TZC$8*9~@GR%xFl1 z3MD?%`1PxxupvVO>2w#8*zV<-!m&Lis&B>)pHahPQ@I_;rY~Z$1+!4V1jde&L8y0! zha7@F+rOENF{~0$+a~oId0R|_!PhO=8)$>LcO)ca6YeOQs?ZG;`4O`x=Pd??Bl?Qf zgkaNj7X5@3_==zlQ-u6?omteA!_e-6gfDtw6CBnP2o1wo-7U!Y@89rU1HFb|bIr!I z=qIz=AW(}L^m z=I9RiS{DRtTYS6jsnvt1zs)W;kSVFOK|WMyZ@dxs+8{*W9-aTmS79J4R{Cis>EIqS zw+~gJqwz)(!z>)KDyhS{lM*xQ-8mNvo$A=IwGu+iS564tgX`|MeEuis!aN-=7!L&e zhNs;g1MBqDyx{y@AI&{_)+-?EEg|5C*!=OgD#$>HklRVU+R``HYZZq5{F9C0KKo!d z$bE2XC(G=I^YUxYST+Hk>0T;JP_iAvCObcrPV1Eau865w6d^Wh&B?^#h2@J#!M2xp zLGAxB^i}4D2^?RayxFqBgnZ-t`j+~zVqr+9Cz9Rqe%1a)c*keP#r54AaR2*TH^}7j zmJ48DN);^{7+5|+GmbvY2v#qJy>?$B(lRlS#kyodlxA&Qj#9-y4s&|eq$5} zgI;4u$cZWKWj`VU%UY#SH2M$8?PjO-B-rNPMr=8d=-D(iLW#{RWJ}@5#Z#EK=2(&LvfW&{P4_jsDr^^rg9w#B7h`mBwdL9y)Ni;= zd$jFDxnW7n-&ptjnk#<0zmNNt{;_30vbQW!5CQ7SuEjR1be!vxvO53!30iOermrU1 zXhXaen8=4Q(574KO_h$e$^1khO&tQL59=)Dc^8iPxz8+tC3`G$w|yUzkGd%Wg4(3u zJ<&7r^HAaEfG?F8?2I64j4kPpsNQk7qBJa9_hFT;*j;A%H%;QI@QWqJaiOl=;u>G8 zG`5Ow4K5ifd=OS|7F;EFc1+GzLld0RCQxG>Fn?~5Wl5VHJ=$DeR-2zwBgzSrQsGG0 zBqrILuB+_SgLxh~S~^QNHWW(2P;Z?d!Rd1lnEM=z23xPzyrbO_L0k43zruDkrJO*D zlzN(peBMLji`xfgYUirul-7c#3t(*=x6A^KSU-L|$(0pp9A*43#=Q!cu%9ZHP!$J| zSk8k=Z8cl811Vvn(4p8xx+EdKQV(sjC4_mEvlWeuIfwEVcF2LiC{H!oW)LSW=0ul| zT?$5PCc(pf-zKzUH`p7I7coVvCK;Dv-3_c?%~bPz`#ehbfrSrFf{RAz0I5e*W1S)kTW{0gf5X2v2k=S=W{>pr44tQ?o` zih8gE29VGR_SL~YJtcA)lRLozPg!<3Mh(`Hp)5{bclb)reTScXzJ>7{?i^yR@{(^% z#=$BYXPIX%fhgsofP-T`3b<5#V(TTS)^$vlhV&Kn=(LXOTAADIR1v8UqmW5c`n`S% zC8SOW$e?>&0dwKD%Jt{+67PfCLnqX0{8K^(q_^^2#puPYPkJsyXWMa~?V?p5{flYi z-1!uqI2x%puPG)r7b8y+Pc0Z5C%aA6`Q1_?W9k!YbiVVJVJwGLL?)P0M&vo{^IgEE zrX3eTgrJl_AeXYmiciYX9OP?NPN%-7Ji%z3U`-iXX=T~OI0M=ek|5IvIsvXM$%S&v zKw{`Kj(JVc+Pp^?vLKEyoycfnk)Hd>et78P^Z*{#rBY~_>V7>{gtB$0G99nbNBt+r zyXvEg_2=#jjK+YX1A>cj5NsFz9rjB_LB%hhx4-2I73gr~CW_5pD=H|e`?#CQ2)p4& z^v?Dlxm-_j6bO5~eeYFZGjW3@AGkIxY=XB*{*ciH#mjQ`dgppNk4&AbaRYKKY-1CT z>)>?+ME)AcCM7RRZQsH5)db7y!&jY-qHp%Ex9N|wKbN$!86i>_LzaD=f4JFc6Dp(a z%z>%=q(sXlJ=w$y^|tcTy@j%AP`v1n0oAt&XC|1kA`|#jsW(gwI0vi3a_QtKcL+yh z1Y=`IRzhiUvKeZXH6>>TDej)?t_V8Z7;WrZ_7@?Z=HRhtXY+{hlY?x|;7=1L($?t3 z6R$8cmez~LXopZ^mH9=^tEeAhJV!rGGOK@sN_Zc-vmEr;=&?OBEN)8aI4G&g&gdOb zfRLZ~dVk3194pd;=W|Z*R|t{}Evk&jw?JzVERk%JNBXbMDX82q~|bv%!2%wFP9;~-H?={C1sZ( zuDvY5?M8gGX*DyN?nru)UvdL|Rr&mXzgZ;H<^KYvzIlet!aeFM@I?JduKj=!(+ zM7`37KYhd*^MrKID^Y1}*sZ#6akDBJyKna%xK%vLlBqzDxjQ3}jx8PBOmXkvf@B{@ zc#J;~wQ<6{B;``j+B!#7s$zONYdXunbuKvl@zvaWq;`v2&iCNF2=V9Kl|77-mpCp= z2$SxhcN=pZ?V{GW;t6s)?-cNPAyTi&8O0QMGo#DcdRl#+px!h3ayc*(VOGR95*Anj zL0YaiVN2mifzZ){X+fl`Z^P=_(W@=*cIe~BJd&n@HD@;lRmu8cx7K8}wPbIK)GjF> zQGQ2h#21o6b2FZI1sPl}9_(~R|2lE^h}UyM5A0bJQk2~Vj*O)l-4WC4$KZ>nVZS|d zZv?`~2{uPYkc?254B9**q6tS|>We?uJ&wK3KIww|zzSuj>ncI4D~K z1Y6irVFE{?D-|R{!rLhZxAhs+Ka9*-(ltIUgC;snNek4_5xhO}@+r9Sl*5=7ztnXO zAVZLm$Kdh&rqEtdxxrE9hw`aXW1&sTE%aJ%3VL3*<7oWyz|--A^qvV3!FHBu9B-Jj z4itF)3dufc&2%V_pZsjUnN=;s2B9<^Zc83>tzo)a_Q$!B9jTjS->%_h`ZtQPz@{@z z5xg~s*cz`Tj!ls3-hxgnX}LDGQp$t7#d3E}>HtLa12z&06$xEQfu#k=(4h{+p%aCg zzeudlLc$=MVT+|43#CXUtRR%h5nMchy}EJ;n7oHfTq6wN6PoalAy+S~2l}wK;qg9o zcf#dX>ke;z^13l%bwm4tZcU1RTXnDhf$K3q-cK576+TCwgHl&?9w>>_(1Gxt@jXln zt3-Qxo3ITr&sw1wP%}B>J$Jy>^-SpO#3e=7iZrXCa2!N69GDlD{97|S*og)3hG)Lk zuqxK|PkkhxV$FP45%z*1Z?(LVy+ruMkZx|(@1R(0CoS6`7FWfr4-diailmq&Q#ehn zc)b&*&Ub;7HRtFVjL%((d$)M=^6BV@Kiusmnr1_2&&aEGBpbK7OWs;+(`tRLF8x?n zfKJB3tB^F~N`_ak3^exe_3{=aP)3tuuK2a-IriHcWv&+u7p z_yXsd6kyLV@k=(QoSs=NRiKNYZ>%4wAF;2#iu1p^!6>MZUPd;=2LY~l2ydrx10b#OSAlltILY%OKTp{e{ zzNogSk~SJBqi<_wRa#JqBW8Ok=6vb%?#H(hG}Dv98{JST5^SSh>_GQ@UK-0J`6l#E za}X#ud0W?cp-NQE@jAx>NUv65U~%YYS%BC0Cr$5|2_A)0tW;(nqoGJUHG5R`!-{1M-4T{<^pOE!Dvyuu1x7?Wt#YIgq zA$Vwj`St+M#ZxJXXGkepIF6`xL&XPu^qiFlZcX+@fOAdQ9d(h{^xCiAWJ0Ixp~3&E z(WwdT$O$7ez?pw>Jf{`!T-205_zJv+y~$w@XmQ;CiL8d*-x_z~0@vo4|3xUermJ;Q z9KgxjkN8Vh)xZ2xhX0N@{~@^d@BLoYFW%Uys83=`15+YZ%KecmWXjVV2}YbjBonSh zVOwOfI7^gvlC~Pq$QDHMQ6_Pd10OV{q_Zai^Yg({5XysuT`3}~3K*8u>a2FLBQ%#_YT6$4&6(?ZGwDE*C-p8>bM?hj*XOIoj@C!L5) zH1y!~wZ^dX5N&xExrKV>rEJJjkJDq*$K>qMi`Lrq08l4bQW~!Fbxb>m4qMHu6weTiV6_9(a*mZ23kr9AM#gCGE zBXg8#m8{ad@214=#w0>ylE7qL$4`xm!**E@pw484-VddzN}DK2qg&W~?%hcv3lNHx zg(CE<2)N=p!7->aJ4=1*eB%fbAGJcY65f3=cKF4WOoCgVelH$qh0NpIka5J-6+sY* zBg<5!R=I*5hk*CR@$rY6a8M%yX%o@D%{q1Jn=8wAZ;;}ol>xFv5nXvjFggCQ_>N2} zXHiC~pCFG*oEy!h_sqF$^NJIpQzXhtRU`LR0yU;MqrYUG0#iFW4mbHe)zN&4*Wf)G zV6(WGOq~OpEoq##E{rC?!)8ygAaAaA0^`<8kXmf%uIFfNHAE|{AuZd!HW9C^4$xW; zmIcO#ti!~)YlIU4sH(h&s6}PH-wSGtDOZ+%H2gAO(%2Ppdec9IMViuwwWW)qnqblH9xe1cPQ@C zS4W|atjGDGKKQAQlPUVUi1OvGC*Gh2i&gkh0up%u-9ECa7(Iw}k~0>r*WciZyRC%l z7NX3)9WBXK{mS|=IK5mxc{M}IrjOxBMzFbK59VI9k8Yr$V4X_^wI#R^~RFcme2)l!%kvUa zJ{zpM;;=mz&>jLvON5j>*cOVt1$0LWiV>x)g)KKZnhn=%1|2E|TWNfRQ&n?vZxQh* zG+YEIf33h%!tyVBPj>|K!EB{JZU{+k`N9c@x_wxD7z~eFVw%AyU9htoH6hmo0`%kb z55c#c80D%0^*6y|9xdLG$n4Hn%62KIp`Md9Jhyp8)%wkB8<%RlPEwC&FL z;hrH(yRr(Ke$%TZ09J=gGMC3L?bR2F4ZU!}pu)*8@l(d9{v^^(j>y+GF*nGran5*M z{pl5ig0CVsG1etMB8qlF4MDFRkLAg4N=l{Sc*F>K_^AZQc{dSXkvonBI)qEN1*U&? zKqMr?Wu)q9c>U~CZUG+-ImNrU#c`bS?RpvVgWXqSsOJrCK#HNIJ+k_1Iq^QNr(j|~ z-rz67Lf?}jj^9Ik@VIMBU2tN{Ts>-O%5f?=T^LGl-?iC%vfx{}PaoP7#^EH{6HP!( zG%3S1oaiR;OmlKhLy@yLNns`9K?60Zg7~NyT0JF(!$jPrm^m_?rxt~|J2)*P6tdTU z25JT~k4RH9b_1H3-y?X4=;6mrBxu$6lsb@xddPGKA*6O`Cc^>Ul`f9c&$SHFhHN!* zjj=(Jb`P}R%5X@cC%+1ICCRh1^G&u548#+3NpYTVr54^SbFhjTuO-yf&s%r4VIU!lE!j(JzHSc9zRD_fw@CP0pkL(WX6 zn+}LarmQP9ZGF9So^+jr<(LGLlOxGiCsI^SnuC{xE$S;DA+|z+cUk=j^0ipB(WTZ} zR0osv{abBd)HOjc(SAV&pcP@37SLnsbtADj?bT#cPZq|?W1Ar;4Vg5m!l{@{TA~|g zXYOeU`#h-rT@(#msh%%kH>D=`aN}2Rysez?E@R6|@SB(_gS0}HC>83pE`obNA9vsH zSu^r>6W-FSxJA}?oTuH>-y9!pQg|*<7J$09tH=nq4GTx+5($$+IGlO^bptmxy#=)e zuz^beIPpUB_YK^?eb@gu(D%pJJwj3QUk6<3>S>RN^0iO|DbTZNheFX?-jskc5}Nho zf&1GCbE^maIL$?i=nXwi)^?NiK`Khb6A*kmen^*(BI%Kw&Uv4H;<3ib-2UwG{7M&* zn$qyi8wD9cKOuxWhRmFupwLuFn!G5Vj6PZ#GCNJLlTQuQ?bqAYd7Eva5YR~OBbIim zf(6yXS4pei1Bz4w4rrB6Ke~gKYErlC=l9sm*Zp_vwJe7<+N&PaZe|~kYVO%uChefr%G4-=0eSPS{HNf=vB;p~ z5b9O1R?WirAZqcdRn9wtct>$FU2T8p=fSp;E^P~zR!^C!)WHe=9N$5@DHk6(L|7s@ zcXQ6NM9Q~fan1q-u8{ez;RADoIqwkf4|6LfsMZK6h{ZUGYo>vD%JpY<@w;oIN-*sK zxp4@+d{zxe>Z-pH#_)%|d(AC`fa!@Jq)5K8hd71!;CEG|ZI{I2XI`X~n|ae;B!q{I zJDa#T+fRviR&wAN^Sl{z8Ar1LQOF&$rDs18h0{yMh^pZ#hG?c5OL8v07qRZ-Lj5(0 zjFY(S4La&`3IjOT%Jqx4z~08($iVS;M10d@q~*H=Py)xnKt(+G-*o33c7S3bJ8cmwgj45` zU|b7xCoozC!-7CPOR194J-m9N*g`30ToBo!Io?m>T)S{CusNZx0J^Hu6hOmvv;0~W zFHRYJgyRhP1sM_AQ%pkD!X-dPu_>)`8HunR4_v$4T78~R<})-@K2LBt03PBLnjHzuYY)AK?>0TJe9 zmmOjwSL%CTaLYvYlJ~|w?vc*R+$@vEAYghtgGhZ2LyF+UdOn+v^yvD9R%xbU$fUjK{{VQ4VL&&UqAFa>CZuX4kX zJ)njewLWfKXneB+r}Y$`ezzwDoRT3r{9(@=I3-z>8tT)n3whDyi(r*lAnxQJefj_x z-8lc=r!Vua{b}v;LT)oXW>~6Q03~RAp~R}TZq9sGbeUBMS)?ZrJqiu|E&ZE)uN1uL zXcAj3#aEz zzbcCF)+;Hia#OGBvOatkPQfE{*RtBlO1QFVhi+3q0HeuFa*p+Dj)#8Mq9yGtIx%0A znV5EmN(j!&b%kNz4`Vr-)mX_?$ng&M^a6loFO(G3SA!~eBUEY!{~>C|Ht1Q4cw)X5~dPiEYQJNg?B2&P>bU7N(#e5cr8qc7A{a7J9cdMcRx)N|?;$L~O|E)p~ zIC}oi3iLZKb>|@=ApsDAfa_<$0Nm<3nOPdr+8Y@dnb|u2S<7CUmTGKd{G57JR*JTo zb&?qrusnu}jb0oKHTzh42P00C{i^`v+g=n|Q6)iINjWk4mydBo zf0g=ikV*+~{rIUr%MXdz|9ebUP)<@zR8fgeR_rChk0<^^3^?rfr;-A=x3M?*8|RPz z@}DOF`aXXuZGih9PyAbp|DULSw8PJ`54io)ga6JG@Hgg@_Zo>OfJ)8+TIfgqu%877 z@aFykK*+|%@rSs-t*oAzH6Whyr=TpuQ}B0ptSsMg9p8@ZE5A6LfMk1qdsf8T^zkdC3rUhB$`s zBdanX%L3tF7*YZ4^A8MvOvhfr&B)QOWCLJ^02kw5;P%n~5e`sa6MG{E2N^*2ZX@ge zI2>ve##O?I}sWX)UqK^_bRz@;5HWp5{ziyg?QuEjXfMP!j zpr(McSAQz>ME?M-3NSoCn$91#_iNnULp6tD0NN7Z0s#G~-~xWZFWN-%KUVi^yz~-` zn;AeGvjLJ~{1p#^?$>zM4vu=3mjBI$(_tC~NC0o@6<{zS_*3nGfUsHr3Gdgn%XedF zQUP=j5Mb>9=#f7aPl;cm$=I0u*WP}aVE!lCYw2Ht{Z_j9mp1h>dHGKkEZP6f^6O@J zndJ2+rWjxp|3#<2oO=8v!oHMX{|Vb|^G~pU_A6=ckBQvt>o+dpgYy(D=VCj65GE&jJj{&-*iq?z)PHNee&-@Mie~#LD*={ex8h(-)<@|55 zUr(}L?mz#;d|mrD%zrh<-*=;5*7K$B`zPjJ%m2pwr*G6tf8tN%a

_x$+l{{cH8$W#CT literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..190eeaa --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Sun Aug 05 23:37:20 MSK 2018 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..cccdd3d --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..f955316 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/logging/.gitignore b/logging/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/logging/.gitignore @@ -0,0 +1 @@ +/build diff --git a/logging/build.gradle b/logging/build.gradle new file mode 100644 index 0000000..217c17b --- /dev/null +++ b/logging/build.gradle @@ -0,0 +1,18 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion versions.compileSdk + + defaultConfig { + minSdkVersion 16 + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + implementation "com.android.support:support-annotations:$versions.supportLibrary" +} diff --git a/logging/src/main/AndroidManifest.xml b/logging/src/main/AndroidManifest.xml new file mode 100644 index 0000000..8ce26b3 --- /dev/null +++ b/logging/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/logging/src/main/java/ru/touchin/roboswag/core/log/ConsoleLogProcessor.java b/logging/src/main/java/ru/touchin/roboswag/core/log/ConsoleLogProcessor.java new file mode 100644 index 0000000..0c05b84 --- /dev/null +++ b/logging/src/main/java/ru/touchin/roboswag/core/log/ConsoleLogProcessor.java @@ -0,0 +1,63 @@ +/* + * 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.roboswag.core.log; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.Log; + +/** + * Created by Gavriil Sitnikov on 13/11/2015. + * Simple {@link LogProcessor} implementation which is logging messages to console (logcat). + */ +public class ConsoleLogProcessor extends LogProcessor { + + private static final int MAX_LOG_LENGTH = 4000; + + public ConsoleLogProcessor(@NonNull final LcLevel lclevel) { + super(lclevel); + } + + @NonNull + private String normalize(@NonNull final String message) { + return message.replace("\r\n", "\n").replace("\0", ""); + } + + @Override + @SuppressWarnings({"WrongConstant", "LogConditional"}) + //WrongConstant, LogConditional: level.getPriority() is not wrong constant! + public void processLogMessage(@NonNull final LcGroup group, @NonNull final LcLevel level, + @NonNull final String tag, @NonNull final String message, @Nullable final Throwable throwable) { + final String messageToLog = normalize(message + (throwable != null ? '\n' + Log.getStackTraceString(throwable) : "")); + final int length = messageToLog.length(); + for (int i = 0; i < length; i++) { + int newline = messageToLog.indexOf('\n', i); + newline = newline != -1 ? newline : length; + do { + final int end = Math.min(newline, i + MAX_LOG_LENGTH); + Log.println(level.getPriority(), tag, messageToLog.substring(i, end)); + i = end; + } + while (i < newline); + } + + } + +} \ No newline at end of file diff --git a/logging/src/main/java/ru/touchin/roboswag/core/log/Lc.java b/logging/src/main/java/ru/touchin/roboswag/core/log/Lc.java new file mode 100644 index 0000000..3d46668 --- /dev/null +++ b/logging/src/main/java/ru/touchin/roboswag/core/log/Lc.java @@ -0,0 +1,277 @@ +/* + * 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.roboswag.core.log; + +import android.annotation.SuppressLint; +import android.os.Handler; +import android.os.Looper; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.text.TextUtils; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import ru.touchin.roboswag.core.utils.ShouldNotHappenException; + +/** + * Created by Gavriil Sitnikov on 13/11/2015. + * General logging utility of RoboSwag library. + * You can initialize {@link LogProcessor} to intercept log messages and make decision how to show them. + * Also you can specify assertions behavior to manually make application more stable in production but intercept illegal states in some + * third-party tool to fix them later but not crash in production. + */ +@SuppressWarnings({"checkstyle:methodname", "PMD.ShortMethodName", "PMD.ShortClassName"}) +//MethodNameCheck,ShortMethodName: log methods better be 1-symbol +public final class Lc { + + public static final LcGroup GENERAL_LC_GROUP = new LcGroup("GENERAL"); + + public static final int STACK_TRACE_CODE_DEPTH; + + private static boolean crashOnAssertions = true; + @NonNull + private static LogProcessor logProcessor = new ConsoleLogProcessor(LcLevel.ERROR); + + static { + final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); + int stackDepth; + for (stackDepth = 0; stackDepth < stackTrace.length; stackDepth++) { + if (stackTrace[stackDepth].getClassName().equals(Lc.class.getName())) { + break; + } + } + STACK_TRACE_CODE_DEPTH = stackDepth + 1; + } + + /** + * Flag to crash application or pass it to {@link LogProcessor#processLogMessage(LcGroup, LcLevel, String, String, Throwable)} + * on specific {@link LcGroup#assertion(Throwable)} points of code. + * + * @return True if application should crash on assertion. + */ + public static boolean isCrashOnAssertions() { + return crashOnAssertions; + } + + /** + * Returns {@link LogProcessor} object to intercept incoming log messages (by default it returns {@link ConsoleLogProcessor}). + * + * @return Specific {@link LogProcessor}. + */ + @NonNull + public static LogProcessor getLogProcessor() { + return logProcessor; + } + + /** + * Initialize general logging behavior. + * + * @param logProcessor {@link LogProcessor} to intercept all log messages; + * @param crashOnAssertions Flag to crash application + * or pass it to {@link LogProcessor#processLogMessage(LcGroup, LcLevel, String, String, Throwable)} + * on specific {@link LcGroup#assertion(Throwable)} points of code. + */ + public static void initialize(@NonNull final LogProcessor logProcessor, final boolean crashOnAssertions) { + Lc.crashOnAssertions = crashOnAssertions; + Lc.logProcessor = logProcessor; + } + + /** + * Logs debug message via {@link #GENERAL_LC_GROUP}. + * + * @param message Message or format of message to log; + * @param args Arguments of formatted message. + */ + public static void d(@NonNull final String message, @NonNull final Object... args) { + GENERAL_LC_GROUP.d(message, args); + } + + /** + * Logs debug message via {@link #GENERAL_LC_GROUP}. + * + * @param throwable Exception to log; + * @param message Message or format of message to log; + * @param args Arguments of formatted message. + */ + public static void d(@NonNull final Throwable throwable, @NonNull final String message, @NonNull final Object... args) { + GENERAL_LC_GROUP.d(throwable, message, args); + } + + /** + * Logs info message via {@link #GENERAL_LC_GROUP}. + * + * @param message Message or format of message to log; + * @param args Arguments of formatted message. + */ + public static void i(@NonNull final String message, @NonNull final Object... args) { + GENERAL_LC_GROUP.i(message, args); + } + + /** + * Logs info message via {@link #GENERAL_LC_GROUP}. + * + * @param throwable Exception to log; + * @param message Message or format of message to log; + * @param args Arguments of formatted message. + */ + public static void i(@NonNull final Throwable throwable, @NonNull final String message, @NonNull final Object... args) { + GENERAL_LC_GROUP.i(throwable, message, args); + } + + /** + * Logs warning message via {@link #GENERAL_LC_GROUP}. + * + * @param message Message or format of message to log; + * @param args Arguments of formatted message. + */ + public static void w(@NonNull final String message, @NonNull final Object... args) { + GENERAL_LC_GROUP.w(message, args); + } + + /** + * Logs warning message via {@link #GENERAL_LC_GROUP}. + * + * @param throwable Exception to log; + * @param message Message or format of message to log; + * @param args Arguments of formatted message. + */ + public static void w(@NonNull final Throwable throwable, @NonNull final String message, @NonNull final Object... args) { + GENERAL_LC_GROUP.w(throwable, message, args); + } + + /** + * Logs error message via {@link #GENERAL_LC_GROUP}. + * + * @param message Message or format of message to log; + * @param args Arguments of formatted message. + */ + public static void e(@NonNull final String message, @NonNull final Object... args) { + GENERAL_LC_GROUP.e(message, args); + } + + /** + * Logs error message via {@link #GENERAL_LC_GROUP}. + * + * @param throwable Exception to log; + * @param message Message or format of message to log; + * @param args Arguments of formatted message. + */ + public static void e(@NonNull final Throwable throwable, @NonNull final String message, @NonNull final Object... args) { + GENERAL_LC_GROUP.e(throwable, message, args); + } + + /** + * Processes assertion. Normally it will throw {@link ShouldNotHappenException} and crash app. + * If it should crash or not is specified at {@link Lc#isCrashOnAssertions()}. + * In some cases crash on assertions should be switched off and assertion should be processed in {@link LogProcessor}. + * It is useful for example to not crash but log it as handled crash in Crashlitycs in production build. + * + * @param message Message that is describing assertion. + */ + public static void assertion(@NonNull final String message) { + GENERAL_LC_GROUP.assertion(message); + } + + /** + * Processes assertion. Normally it will throw {@link ShouldNotHappenException} and crash app. + * If it should crash or not is specified at {@link Lc#isCrashOnAssertions()}. + * In some cases crash on assertions should be switched off and assertion should be processed in {@link LogProcessor}. + * It is useful for example to not crash but log it as handled crash in Crashlitycs in production build. + * + * @param throwable Exception that is describing assertion. + */ + public static void assertion(@NonNull final Throwable throwable) { + GENERAL_LC_GROUP.assertion(throwable); + } + + /** + * Throws assertion on main thread (to avoid Rx exceptions e.g.) and cuts top causes by type of exception class. + * + * @param assertion Source throwable; + * @param exceptionsClassesToCut Classes which will be cut from top of causes stack of source throwable. + */ + @SafeVarargs + public static void cutAssertion(@NonNull final Throwable assertion, @NonNull final Class... exceptionsClassesToCut) { + new Handler(Looper.getMainLooper()).post(() -> { + final List processedExceptions = new ArrayList<>(); + Throwable result = assertion; + boolean exceptionAssignableFromIgnores; + do { + exceptionAssignableFromIgnores = false; + processedExceptions.add(result); + for (final Class exceptionClass : exceptionsClassesToCut) { + if (result.getClass().isAssignableFrom(exceptionClass)) { + exceptionAssignableFromIgnores = true; + result = result.getCause(); + break; + } + } + } + while (exceptionAssignableFromIgnores && result != null && !processedExceptions.contains(result)); + Lc.assertion(result != null ? result : assertion); + }); + } + + /** + * Returns line of code from where this method called. + * + * @param caller Object who is calling for code point; + * @return String represents code point. + */ + @NonNull + public static String getCodePoint(@Nullable final Object caller) { + return getCodePoint(caller, 1); + } + + /** + * Returns line of code from where this method called. + * + * @param caller Object who is calling for code point; + * @param stackShift caller Shift of stack (e.g. 2 means two elements deeper); + * @return String represents code point. + */ + @NonNull + public static String getCodePoint(@Nullable final Object caller, final int stackShift) { + final StackTraceElement traceElement = Thread.currentThread().getStackTrace()[STACK_TRACE_CODE_DEPTH + stackShift]; + return traceElement.getMethodName() + '(' + traceElement.getFileName() + ':' + traceElement.getLineNumber() + ')' + + (caller != null ? " of object " + caller.getClass().getSimpleName() + '(' + Integer.toHexString(caller.hashCode()) + ')' : ""); + } + + /** + * Prints stacktrace in log with specified tag. + * + * @param tag Tag to be shown in logs. + */ + + @SuppressLint("LogConditional") + public static void printStackTrace(@NonNull final String tag) { + final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); + if (Log.isLoggable(tag, Log.DEBUG)) { + Log.d(tag, TextUtils.join("\n", Arrays.copyOfRange(stackTrace, STACK_TRACE_CODE_DEPTH, stackTrace.length))); + } + } + + private Lc() { + } + +} diff --git a/logging/src/main/java/ru/touchin/roboswag/core/log/LcGroup.java b/logging/src/main/java/ru/touchin/roboswag/core/log/LcGroup.java new file mode 100644 index 0000000..3ccf1fa --- /dev/null +++ b/logging/src/main/java/ru/touchin/roboswag/core/log/LcGroup.java @@ -0,0 +1,237 @@ +/* + * 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.roboswag.core.log; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.text.SimpleDateFormat; +import java.util.Locale; + +import ru.touchin.roboswag.core.utils.ShouldNotHappenException; +import ru.touchin.roboswag.core.utils.ThreadLocalValue; + +/** + * Created by Gavriil Sitnikov on 14/05/2016. + * Group of log messages with specific tag prefix (name of group). + * It could be used in specific {@link LogProcessor} to filter messages by group. + */ +@SuppressWarnings({"checkstyle:methodname", "PMD.ShortMethodName"}) +//MethodNameCheck,ShortMethodName: log methods better be 1-symbol +public class LcGroup { + + /** + * Logging group to log UI metrics (like inflation or layout time etc.). + */ + public static final LcGroup UI_METRICS = new LcGroup("UI_METRICS"); + /** + * Logging group to log UI lifecycle (onCreate, onStart, onResume etc.). + */ + public static final LcGroup UI_LIFECYCLE = new LcGroup("UI_LIFECYCLE"); + + private static final ThreadLocalValue DATE_TIME_FORMATTER + = new ThreadLocalValue<>(() -> new SimpleDateFormat("HH:mm:ss.SSS", Locale.getDefault())); + + @NonNull + private final String name; + private boolean disabled; + + public LcGroup(@NonNull final String name) { + this.name = name; + } + + /** + * Disables logging of this group. + */ + public void disable() { + disabled = true; + } + + /** + * Enables logging of this group. + */ + public void enable() { + disabled = false; + } + + @NonNull + private String createLogTag() { + final StackTraceElement trace = Thread.currentThread().getStackTrace()[Lc.STACK_TRACE_CODE_DEPTH + 3]; + return trace.getFileName() + ':' + trace.getLineNumber(); + } + + @SuppressWarnings("PMD.AvoidCatchingThrowable") + //AvoidCatchingThrowable: it is needed to safety format message + @Nullable + private String createFormattedMessage(@Nullable final String message, @NonNull final Object... args) { + try { + if (args.length > 0 && message == null) { + throw new ShouldNotHappenException("Args are not empty but format message is null"); + } + return message != null ? (args.length > 0 ? String.format(message, args) : message) : null; + } catch (final Throwable formattingException) { + Lc.assertion(formattingException); + return null; + } + } + + @NonNull + private String createLogMessage(@Nullable final String formattedMessage) { + return DATE_TIME_FORMATTER.get().format(System.currentTimeMillis()) + + ' ' + Thread.currentThread().getName() + + ' ' + name + + (formattedMessage != null ? (' ' + formattedMessage) : ""); + } + + private void logMessage(@NonNull final LcLevel logLevel, @Nullable final String message, + @Nullable final Throwable throwable, @NonNull final Object... args) { + if (disabled || logLevel.lessThan(Lc.getLogProcessor().getMinLogLevel())) { + return; + } + + if (throwable == null && args.length > 0 && args[0] instanceof Throwable) { + Lc.w("Maybe you've misplaced exception with first format arg? format: %s; arg: %s", message, args[0]); + } + + final String formattedMessage = createFormattedMessage(message, args); + if (logLevel == LcLevel.ASSERT && Lc.isCrashOnAssertions()) { + throw createAssertion(formattedMessage, throwable); + } + + Lc.getLogProcessor().processLogMessage(this, logLevel, createLogTag(), createLogMessage(formattedMessage), throwable); + } + + @NonNull + private ShouldNotHappenException createAssertion(@Nullable final String message, @Nullable final Throwable exception) { + return exception != null + ? (message != null ? new ShouldNotHappenException(message, exception) + : (exception instanceof ShouldNotHappenException ? (ShouldNotHappenException) exception : new ShouldNotHappenException(exception))) + : (message != null ? new ShouldNotHappenException(message) : new ShouldNotHappenException()); + } + + /** + * Logs debug message. + * + * @param message Message or format of message to log; + * @param args Arguments of formatted message. + */ + public void d(@NonNull final String message, @NonNull final Object... args) { + logMessage(LcLevel.DEBUG, message, null, args); + } + + /** + * Logs debug message. + * + * @param throwable Exception to log; + * @param message Message or format of message to log; + * @param args Arguments of formatted message. + */ + public void d(@NonNull final Throwable throwable, @NonNull final String message, @NonNull final Object... args) { + logMessage(LcLevel.DEBUG, message, throwable, args); + } + + /** + * Logs info message. + * + * @param message Message or format of message to log; + * @param args Arguments of formatted message. + */ + public void i(@NonNull final String message, @NonNull final Object... args) { + logMessage(LcLevel.INFO, message, null, args); + } + + /** + * Logs info message. + * + * @param throwable Exception to log; + * @param message Message or format of message to log; + * @param args Arguments of formatted message. + */ + public void i(@NonNull final Throwable throwable, @NonNull final String message, @NonNull final Object... args) { + logMessage(LcLevel.INFO, message, throwable, args); + } + + /** + * Logs warning message. + * + * @param message Message or format of message to log; + * @param args Arguments of formatted message. + */ + public void w(@NonNull final String message, @NonNull final Object... args) { + logMessage(LcLevel.WARN, message, null, args); + } + + /** + * Logs warning message. + * + * @param throwable Exception to log; + * @param message Message or format of message to log; + * @param args Arguments of formatted message. + */ + public void w(@NonNull final Throwable throwable, @NonNull final String message, @NonNull final Object... args) { + logMessage(LcLevel.WARN, message, throwable, args); + } + + /** + * Logs error message. + * + * @param message Message or format of message to log; + * @param args Arguments of formatted message. + */ + public void e(@NonNull final String message, @NonNull final Object... args) { + logMessage(LcLevel.ERROR, message, null, args); + } + + /** + * Logs error message. + * + * @param throwable Exception to log; + * @param message Message or format of message to log; + * @param args Arguments of formatted message. + */ + public void e(@NonNull final Throwable throwable, @NonNull final String message, @NonNull final Object... args) { + logMessage(LcLevel.ERROR, message, throwable, args); + } + + /** + * Processes assertion. Normally it will throw {@link ShouldNotHappenException} and crash app. + * If it should crash or not is specified at {@link Lc#isCrashOnAssertions()}. + * In some cases crash on assertions should be switched off and assertion should be processed in {@link LogProcessor}. + * It is useful for example to not crash but log it as handled crash in Crashlitycs in production build. + * + * @param message Message that is describing assertion. + */ + public void assertion(@NonNull final String message) { + logMessage(LcLevel.ASSERT, "Assertion appears at %s with message: %s", null, Lc.getCodePoint(null, 2), message); + } + + /** + * Processes assertion. Normally it will throw {@link ShouldNotHappenException} and crash app. + * If it should crash or not is specified at {@link Lc#isCrashOnAssertions()}. + * In some cases crash on assertions should be switched off and assertion should be processed in {@link LogProcessor}. + * It is useful for example to not crash but log it as handled crash in Crashlitycs in production build. + * + * @param throwable Exception that is describing assertion. + */ + public void assertion(@NonNull final Throwable throwable) { + logMessage(LcLevel.ASSERT, "Assertion appears at %s", throwable, Lc.getCodePoint(null, 2)); + } + +} diff --git a/logging/src/main/java/ru/touchin/roboswag/core/log/LcLevel.java b/logging/src/main/java/ru/touchin/roboswag/core/log/LcLevel.java new file mode 100644 index 0000000..9dbd5e5 --- /dev/null +++ b/logging/src/main/java/ru/touchin/roboswag/core/log/LcLevel.java @@ -0,0 +1,63 @@ +/* + * 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.roboswag.core.log; + +import android.support.annotation.NonNull; +import android.util.Log; + +/** + * Created by Gavriil Sitnikov on 14/05/2016. + * Level of log message. + */ +public enum LcLevel { + + VERBOSE(Log.VERBOSE), + DEBUG(Log.DEBUG), + INFO(Log.INFO), + WARN(Log.WARN), + ERROR(Log.ERROR), + ASSERT(Log.ASSERT); + + private final int priority; + + LcLevel(final int priority) { + this.priority = priority; + } + + /** + * Standard {@link Log} integer value of level represents priority of message. + * + * @return Integer level. + */ + public int getPriority() { + return priority; + } + + /** + * Compares priorities of LcLevels and returns if current is less than another. + * + * @param logLevel {@link LcLevel} to compare priority with; + * @return True if current level priority less than level passed as parameter. + */ + public boolean lessThan(@NonNull final LcLevel logLevel) { + return this.priority < logLevel.priority; + } + +} diff --git a/logging/src/main/java/ru/touchin/roboswag/core/log/LogProcessor.java b/logging/src/main/java/ru/touchin/roboswag/core/log/LogProcessor.java new file mode 100644 index 0000000..6cb34a9 --- /dev/null +++ b/logging/src/main/java/ru/touchin/roboswag/core/log/LogProcessor.java @@ -0,0 +1,61 @@ +/* + * 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.roboswag.core.log; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +/** + * Created by Gavriil Sitnikov on 13/11/2015. + * Abstract object to intercept log messages coming from {@link LcGroup} and {@link Lc} log methods. + */ +public abstract class LogProcessor { + + @NonNull + private final LcLevel minLogLevel; + + public LogProcessor(@NonNull final LcLevel minLogLevel) { + this.minLogLevel = minLogLevel; + } + + /** + * Minimum logging level. + * Any messages with lower priority won't be passed into {@link #processLogMessage(LcGroup, LcLevel, String, String, Throwable)}. + * + * @return Minimum log level represented by {@link LcLevel} object. + */ + @NonNull + public LcLevel getMinLogLevel() { + return minLogLevel; + } + + /** + * Core method to process any incoming log messages from {@link LcGroup} and {@link Lc} with level higher or equals {@link #getMinLogLevel()}. + * + * @param group {@link LcGroup} where log message came from; + * @param level {@link LcLevel} level (priority) of message; + * @param tag String mark of message; + * @param message Message to log; + * @param throwable Exception to log. + */ + public abstract void processLogMessage(@NonNull final LcGroup group, @NonNull final LcLevel level, + @NonNull final String tag, @NonNull final String message, @Nullable final Throwable throwable); + +} diff --git a/logging/src/main/java/ru/touchin/roboswag/core/utils/ShouldNotHappenException.java b/logging/src/main/java/ru/touchin/roboswag/core/utils/ShouldNotHappenException.java new file mode 100644 index 0000000..b3f11f0 --- /dev/null +++ b/logging/src/main/java/ru/touchin/roboswag/core/utils/ShouldNotHappenException.java @@ -0,0 +1,49 @@ +/* + * 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.roboswag.core.utils; + +import android.support.annotation.NonNull; + +/** + * Created by Gavriil Sitnikov on 13/11/2015. + * Exception that should be threw when some unexpected code reached. + * E.g. if some value null but it is not legal or in default case in switch if all specific cases should be processed. + */ +public class ShouldNotHappenException extends RuntimeException { + + private static final long serialVersionUID = 0; + + public ShouldNotHappenException() { + super(); + } + + public ShouldNotHappenException(@NonNull final String detailMessage) { + super(detailMessage); + } + + public ShouldNotHappenException(@NonNull final String detailMessage, @NonNull final Throwable throwable) { + super(detailMessage, throwable); + } + + public ShouldNotHappenException(@NonNull final Throwable throwable) { + super(throwable); + } + +} diff --git a/logging/src/main/java/ru/touchin/roboswag/core/utils/ThreadLocalValue.java b/logging/src/main/java/ru/touchin/roboswag/core/utils/ThreadLocalValue.java new file mode 100644 index 0000000..62cf778 --- /dev/null +++ b/logging/src/main/java/ru/touchin/roboswag/core/utils/ThreadLocalValue.java @@ -0,0 +1,61 @@ +/* + * 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.roboswag.core.utils; + +import android.support.annotation.NonNull; + +/** + * Created by Gavriil Sitnikov on 13/11/2015. + * Thread local value with specified creator of value per thread. + */ +public class ThreadLocalValue extends ThreadLocal { + + @NonNull + private final Fabric fabric; + + public ThreadLocalValue(@NonNull final Fabric fabric) { + super(); + this.fabric = fabric; + } + + @NonNull + @Override + protected T initialValue() { + return fabric.create(); + } + + /** + * Fabric of thread-local objects. + * + * @param Type of objects. + */ + public interface Fabric { + + /** + * Creates object. + * + * @return new instance of object. + */ + @NonNull + T create(); + + } + +} diff --git a/navigation/.gitignore b/navigation/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/navigation/.gitignore @@ -0,0 +1 @@ +/build diff --git a/navigation/build.gradle b/navigation/build.gradle new file mode 100644 index 0000000..26d4258 --- /dev/null +++ b/navigation/build.gradle @@ -0,0 +1,23 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion versions.compileSdk + + defaultConfig { + minSdkVersion 16 + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + api project(":utils") + api project(":logging") + + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + + implementation "com.android.support:appcompat-v7:$versions.supportLibrary" +} diff --git a/navigation/src/main/AndroidManifest.xml b/navigation/src/main/AndroidManifest.xml new file mode 100644 index 0000000..bd2d3ee --- /dev/null +++ b/navigation/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.kt b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.kt similarity index 100% rename from src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.kt rename to navigation/src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.kt diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/OnFragmentStartedListener.java b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/OnFragmentStartedListener.java similarity index 99% rename from src/main/java/ru/touchin/roboswag/components/navigation/OnFragmentStartedListener.java rename to navigation/src/main/java/ru/touchin/roboswag/components/navigation/OnFragmentStartedListener.java index 2dfb580..8d68057 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/OnFragmentStartedListener.java +++ b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/OnFragmentStartedListener.java @@ -36,4 +36,4 @@ public interface OnFragmentStartedListener { */ void onFragmentStarted(@NonNull Fragment fragment); -} \ No newline at end of file +} diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/SerializableBundle.java b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/SerializableBundle.java similarity index 100% rename from src/main/java/ru/touchin/roboswag/components/navigation/SerializableBundle.java rename to navigation/src/main/java/ru/touchin/roboswag/components/navigation/SerializableBundle.java diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/SimpleActionBarDrawerToggle.java b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/SimpleActionBarDrawerToggle.java similarity index 100% rename from src/main/java/ru/touchin/roboswag/components/navigation/SimpleActionBarDrawerToggle.java rename to navigation/src/main/java/ru/touchin/roboswag/components/navigation/SimpleActionBarDrawerToggle.java diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java similarity index 83% rename from src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java rename to navigation/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java index 7a3f0b7..ee39486 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java +++ b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java @@ -29,8 +29,8 @@ import android.view.MenuItem; import java.util.Set; -import ru.touchin.roboswag.components.utils.UiUtils; import ru.touchin.roboswag.core.log.Lc; +import ru.touchin.roboswag.core.log.LcGroup; /** * Created by Gavriil Sitnikov on 08/03/2016. @@ -44,54 +44,54 @@ public abstract class BaseActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable final Bundle savedInstanceState) { super.onCreate(savedInstanceState); - UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this)); + LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(this)); } @Override protected void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) { super.onActivityResult(requestCode, resultCode, data); - UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this) + " requestCode: " + requestCode + "; resultCode: " + resultCode); + LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(this) + " requestCode: " + requestCode + "; resultCode: " + resultCode); } @Override protected void onStart() { super.onStart(); - UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this)); + LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(this)); } @Override protected void onResume() { super.onResume(); - UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this)); + LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(this)); } @Override protected void onPause() { - UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this)); + LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(this)); super.onPause(); } @Override protected void onSaveInstanceState(@NonNull final Bundle stateToSave) { super.onSaveInstanceState(stateToSave); - UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this)); + LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(this)); } @Override public void onLowMemory() { super.onLowMemory(); - UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this)); + LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(this)); } @Override protected void onStop() { - UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this)); + LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(this)); super.onStop(); } @Override protected void onDestroy() { - UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this)); + LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(this)); super.onDestroy(); } diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java similarity index 95% rename from src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java rename to navigation/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java index 74f290d..065f06b 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java +++ b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java @@ -30,7 +30,6 @@ import android.os.SystemClock; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.FragmentActivity; -import android.support.v4.view.ViewCompat; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -42,10 +41,9 @@ import android.widget.FrameLayout; import java.lang.reflect.Constructor; -import ru.touchin.roboswag.components.R; import ru.touchin.roboswag.components.navigation.viewcontrollers.ViewController; -import ru.touchin.roboswag.components.utils.UiUtils; import ru.touchin.roboswag.core.log.Lc; +import ru.touchin.roboswag.core.log.LcGroup; import ru.touchin.roboswag.core.utils.ShouldNotHappenException; /** @@ -182,7 +180,7 @@ public class ViewControllerFragment acceptableUiCalculationTime) { - UiUtils.UI_METRICS_LC_GROUP.w("Creation of %s took too much: %dms", viewControllerClass, creationPeriod); + LcGroup.UI_METRICS.w("Creation of %s took too much: %dms", viewControllerClass, creationPeriod); } } } @@ -212,11 +210,6 @@ public class ViewControllerFragment 0) { final long layoutTime = SystemClock.uptimeMillis() - lastMeasureTime; if (layoutTime > acceptableUiCalculationTime) { - UiUtils.UI_METRICS_LC_GROUP.w("Measure and layout of %s took too much: %dms", tagName, layoutTime); + LcGroup.UI_METRICS.w("Measure and layout of %s took too much: %dms", tagName, layoutTime); } lastMeasureTime = 0; } diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewFragment.java b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewFragment.java similarity index 99% rename from src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewFragment.java rename to navigation/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewFragment.java index 5cd873f..bca1f28 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewFragment.java +++ b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewFragment.java @@ -30,9 +30,9 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import io.reactivex.functions.BiConsumer; import ru.touchin.roboswag.components.navigation.OnFragmentStartedListener; import ru.touchin.roboswag.core.log.Lc; +import ru.touchin.roboswag.core.utils.BiConsumer; /** * Created by Gavriil Sitnikov on 21/10/2015. diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/DefaultViewController.kt b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/DefaultViewController.kt similarity index 100% rename from src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/DefaultViewController.kt rename to navigation/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/DefaultViewController.kt diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/EmptyState.kt b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/EmptyState.kt similarity index 100% rename from src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/EmptyState.kt rename to navigation/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/EmptyState.kt diff --git a/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java similarity index 93% rename from src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java rename to navigation/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java index 363e628..8e4c7d0 100644 --- a/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java +++ b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java @@ -46,18 +46,19 @@ import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; - import android.view.animation.Animation; + import ru.touchin.roboswag.components.navigation.fragments.ViewControllerFragment; import ru.touchin.roboswag.components.utils.UiUtils; import ru.touchin.roboswag.core.log.Lc; +import ru.touchin.roboswag.core.log.LcGroup; /** * Created by Gavriil Sitnikov on 21/10/2015. * Class to control view of specific fragment, activity and application by logic bridge. * * @param Type of activity where such {@link ViewController} could be; - * @param Type of state; + * @param Type of state; */ public class ViewController implements LifecycleOwner { @@ -221,7 +222,7 @@ public class ViewController *

Starting in {@link android.os.Build.VERSION_CODES#M}, the returned * color state list will be styled for the specified Context's theme. * @@ -275,7 +276,7 @@ public class ViewController + + + + + + + + + + + + + \ No newline at end of file diff --git a/sample/src/main/java/ru/touchin/roboswag/components/MainActivity.kt b/sample/src/main/java/ru/touchin/roboswag/components/MainActivity.kt new file mode 100644 index 0000000..ebf42b3 --- /dev/null +++ b/sample/src/main/java/ru/touchin/roboswag/components/MainActivity.kt @@ -0,0 +1,12 @@ +package ru.touchin.roboswag.components + +import android.support.v7.app.AppCompatActivity +import android.os.Bundle + +class MainActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + } +} diff --git a/sample/src/main/res/drawable-v24/ic_launcher_foreground.xml b/sample/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..3e810c0 --- /dev/null +++ b/sample/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + diff --git a/sample/src/main/res/drawable/ic_launcher_background.xml b/sample/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..5713f34 --- /dev/null +++ b/sample/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,171 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sample/src/main/res/layout/activity_main.xml b/sample/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..7539a01 --- /dev/null +++ b/sample/src/main/res/layout/activity_main.xml @@ -0,0 +1,19 @@ + + + + + + \ No newline at end of file diff --git a/sample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/sample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..f4bc9d9 --- /dev/null +++ b/sample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/sample/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/sample/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..f4bc9d9 --- /dev/null +++ b/sample/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/sample/src/main/res/mipmap-hdpi/ic_launcher.png b/sample/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..a2f5908281d070150700378b64a84c7db1f97aa1 GIT binary patch literal 3056 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 axPAxe%7rDusX+w*00006jj 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⁣ zwiG+x#%j}{0gU~J8;<|!B1@-XaB@{KORFwrYg_8rOv({b0EO#DbeQRm;B6_9=mXGf z-x|VL{zd`)#@yN}HkCSJbjbNlE|zL3Wm9Q8HY`sV)}3%pgN>cL^67{Z;PPL(*wT8N zUjXU{@|*hvm}({wsAC=x0^ok0%UAz0;sogW{B!nDqk|JJ5x~4NfTDgP49^zeu`csl?5mY@JdQdISc zFs!E{^grmkLnUk9 zny~m)1vws@5BFI<-0Tuo2JWX(0v`W|t(wg;s--L47WTvTMz-8l#TL^=OJNRS2?_Qj z3AKT+gvbyBi#H*-tJ%tWD|>EV3wy|8qxfzS!5RW;Jpl5*zo&^UBU=fG#2}UvRyNkK zA06Dy9;K1ca@r2T>yThYgI!ont$(G{6q#2QT+00r_x0(b)gsE`lBB?2gr55gq^D3Fi&p%E(p9>U%bv zkg1Jco(RbyTX7FDHOnl7-O@ zI$AaIl?9NJKPm(WiBP`1-#CB1QzU>&hKm)fpa5DKE{2$X0hGz-0uZ?cyTk(YC!Y&| zL=1VrNERSA5NA2jq7FACfX4JfPyj5XXl1yv0>~s;eF7L2$>&oMqeTFT2m$y7FlkON z_yurD1yIOvA;5C6016pyxBznGUt0kJ&k5r#;&>Jow`r)sp9R~PmK~lz$3xH%LT*1U zJdOyABZ3!FvNoR*vN$5ykHS8f`jA4zV+|L}i1C4`B2c{R0;UdYxaU|H)2avz@ z=mEYc|2S<+(B2Tj+FkX+2D+yFI!k9lWMA61DJ{)e;lum$(;O87?vGJJe!KtK04+N_ zI*P~t@dUb>9Xh{dbyl{-ZQ(UMgz7$|QfL5XSPkskt^NgctYC#;4WcZB1@%@wy@2t3 z2z0DI7&%b$*Aw~abe?GxE`ez@+6hOh-6*8fHRV{1os$EL@}uUZeG4h1&Be`98q*7j z=3-v+lhIjfWVo12!<>%V^a6lTgW3+_#W6n|p*~==zOH7z$0{LSZk(Tpd7EaD04hnA zL;#fxS0aD{`5^&D`}>0Uq?byDD-l2=!wm_bLcUl4gc(% za1p|itVANvFF>hghAS07Im1;IK;|b*W)}VDyI;BIp2=K*yu2a)j?B|f<44NI$NbmJ z#dE0>jI$fMr&@>4kN8MLFb4&2O9fEKaQg%(QO$4_1rVQywG^CmBLh#}_7gKW3vd?| z2?1^&KWq8}8I^_S0|)MowU_pw$q@nl@Nkn$z>BQq_KA^9yaR`(R3u{{Ig;cwt z@AJ^{ODQCm^neroM9nKNUAXi9RCK`OsP_LuR0PUR(YZCCX5dNF6VzcoK&=b^r`W?ltt|*F zpkoae%ZT{C1h~EcFui~b7fF`vb<<~j_VquuUA$}QqIKYELPp#;{u?q8Dz}WAG-(3; zjrm$i%7UbyZMM(Y{>!uJ#vNB?R~B{6Htp=>e*<{fQQ5W7V(1coCWlOON!MzZxhum| ztZBQpGR z;~#ur^&PockKdV{Q6R>o`Pl{0x!DEbpZ7y9Y;*ZvE!*gU`V1W3znva{f=?WO5I&>B z&hw6}tjECtaghm5z|C#%M;Yf_*pI^};h}Vl=^r9EN=tVDj86D;C$jIJ?K7VP+00000NkvXXu0mjf D5i!M* literal 0 HcmV?d00001 diff --git a/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..459ca609d3ae0d3943ab44cdc27feef9256dc6d7 GIT binary patch literal 7098 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 literal 0 HcmV?d00001 diff --git a/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png b/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..8ca12fe024be86e868d14e91120a6902f8e88ac6 GIT binary patch literal 6464 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 B0*L?s literal 0 HcmV?d00001 diff --git a/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..8e19b410a1b15ff180f3dacac19395fe3046cdec GIT binary patch literal 10676 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 literal 0 HcmV?d00001 diff --git a/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..4c19a13c239cb67b8a2134ddd5f325db1d2d5bee GIT binary patch literal 15523 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 BIP(Ai literal 0 HcmV?d00001 diff --git a/sample/src/main/res/values/colors.xml b/sample/src/main/res/values/colors.xml new file mode 100644 index 0000000..3ab3e9c --- /dev/null +++ b/sample/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #3F51B5 + #303F9F + #FF4081 + diff --git a/sample/src/main/res/values/strings.xml b/sample/src/main/res/values/strings.xml new file mode 100644 index 0000000..f6470ae --- /dev/null +++ b/sample/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Components + diff --git a/sample/src/main/res/values/styles.xml b/sample/src/main/res/values/styles.xml new file mode 100644 index 0000000..5885930 --- /dev/null +++ b/sample/src/main/res/values/styles.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..9e1f446 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +include ':sample', ':utils', ':logging', ':navigation', ':storable' diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml deleted file mode 100644 index c76023c..0000000 --- a/src/main/AndroidManifest.xml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/AdapterDelegate.java b/src/main/java/ru/touchin/roboswag/components/adapters/AdapterDelegate.java deleted file mode 100644 index 25be0c9..0000000 --- a/src/main/java/ru/touchin/roboswag/components/adapters/AdapterDelegate.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (c) 2017 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.roboswag.components.adapters; - -import android.support.annotation.NonNull; -import android.support.v4.view.ViewCompat; -import android.support.v7.widget.RecyclerView; -import android.view.ViewGroup; - -import java.util.List; - -/** - * Objects of such class controls creation and binding of specific type of RecyclerView's ViewHolders. - * Default {@link #getItemViewType} is generating on construction of object. - * - * @param Type of {@link RecyclerView.ViewHolder} of delegate. - */ -public abstract class AdapterDelegate { - - private final int defaultItemViewType = ViewCompat.generateViewId(); - - /** - * Unique ID of AdapterDelegate. - * - * @return Unique ID. - */ - public int getItemViewType() { - return defaultItemViewType; - } - - /** - * Returns if object is processable by this delegate. - * - * @param items Items to check; - * @param adapterPosition Position of item in adapter; - * @param collectionPosition Position of item in collection; - * @return True if item is processable by this delegate. - */ - public abstract boolean isForViewType(@NonNull final List items, final int adapterPosition, final int collectionPosition); - - /** - * Returns unique ID of item to support stable ID's logic of RecyclerView's adapter. - * - * @param items Items in adapter; - * @param adapterPosition Position of item in adapter; - * @param collectionPosition Position of item in collection; - * @return Unique item ID. - */ - public long getItemId(@NonNull final List items, final int adapterPosition, final int collectionPosition) { - return 0; - } - - /** - * Creates ViewHolder to bind item to it later. - * - * @param parent Container of ViewHolder's view. - * @return New ViewHolder. - */ - @NonNull - public abstract TViewHolder onCreateViewHolder(@NonNull final ViewGroup parent); - - /** - * Binds item to created by this object ViewHolder. - * - * @param holder ViewHolder to bind item to; - * @param items Items in adapter; - * @param adapterPosition Position of item in adapter; - * @param collectionPosition Position of item in collection that contains item; - * @param payloads Payloads; - */ - public abstract void onBindViewHolder( - @NonNull final RecyclerView.ViewHolder holder, - @NonNull final List items, - final int adapterPosition, - final int collectionPosition, - @NonNull final List payloads - ); - -} diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/DelegatesManager.kt b/src/main/java/ru/touchin/roboswag/components/adapters/DelegatesManager.kt deleted file mode 100644 index e3ba14f..0000000 --- a/src/main/java/ru/touchin/roboswag/components/adapters/DelegatesManager.kt +++ /dev/null @@ -1,52 +0,0 @@ -package ru.touchin.roboswag.components.adapters - -import android.support.v7.widget.RecyclerView -import android.util.SparseArray -import android.view.ViewGroup - -/** - * Manager for delegation callbacks from [RecyclerView.Adapter] to delegates. - */ -class DelegatesManager { - - private val delegates = SparseArray>() - - fun getItemViewType(items: List<*>, adapterPosition: Int, collectionPosition: Int): Int { - for (index in 0 until delegates.size()) { - val delegate = delegates.valueAt(index) - if (delegate.isForViewType(items, adapterPosition, collectionPosition)) { - return delegate.itemViewType - } - } - throw IllegalStateException("Delegate not found for adapterPosition: $adapterPosition") - } - - fun getItemId(items: List<*>, adapterPosition: Int, collectionPosition: Int): Long { - val delegate = getDelegate(getItemViewType(items, adapterPosition, collectionPosition)) - return delegate.getItemId(items, adapterPosition, collectionPosition) - } - - fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder = getDelegate(viewType).onCreateViewHolder(parent) - - fun onBindViewHolder(holder: RecyclerView.ViewHolder, items: List<*>, adapterPosition: Int, collectionPosition: Int, payloads: List) { - val delegate = getDelegate(getItemViewType(items, adapterPosition, collectionPosition)) - delegate.onBindViewHolder(holder, items, adapterPosition, collectionPosition, payloads) - } - - /** - * Adds [PositionAdapterDelegate] to adapter. - * - * @param delegate Delegate to add. - */ - fun addDelegate(delegate: AdapterDelegate<*>) = delegates.put(delegate.itemViewType, delegate) - - /** - * Removes [AdapterDelegate] from adapter. - * - * @param delegate Delegate to remove. - */ - fun removeDelegate(delegate: AdapterDelegate<*>) = delegates.remove(delegate.itemViewType) - - private fun getDelegate(viewType: Int) = delegates[viewType] ?: throw IllegalStateException("No AdapterDelegate added for view type: $viewType") - -} diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/DelegationListAdapter.kt b/src/main/java/ru/touchin/roboswag/components/adapters/DelegationListAdapter.kt deleted file mode 100644 index 094b298..0000000 --- a/src/main/java/ru/touchin/roboswag/components/adapters/DelegationListAdapter.kt +++ /dev/null @@ -1,89 +0,0 @@ -package ru.touchin.roboswag.components.adapters - -import android.support.v7.recyclerview.extensions.AsyncDifferConfig -import android.support.v7.recyclerview.extensions.AsyncListDiffer -import android.support.v7.util.DiffUtil -import android.support.v7.widget.RecyclerView -import android.view.ViewGroup -import ru.touchin.roboswag.components.extensions.setOnRippleClickListener - -/** - * Base adapter with delegation and diff computing on background thread. - */ -open class DelegationListAdapter(config: AsyncDifferConfig) : RecyclerView.Adapter() { - - constructor(diffCallback: DiffUtil.ItemCallback) : this(AsyncDifferConfig.Builder(diffCallback).build()) - - var itemClickListener: ((TItem, RecyclerView.ViewHolder) -> Unit)? = null - - private val delegatesManager = DelegatesManager() - private var differ = AsyncListDiffer(OffsetAdapterUpdateCallback(this, ::getHeadersCount), config) - - open fun getHeadersCount() = 0 - - open fun getFootersCount() = 0 - - override fun getItemCount() = getHeadersCount() + getList().size + getFootersCount() - - override fun getItemViewType(position: Int) = delegatesManager.getItemViewType(getList(), position, getCollectionPosition(position)) - - override fun getItemId(position: Int) = delegatesManager.getItemId(getList(), position, getCollectionPosition(position)) - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = delegatesManager.onCreateViewHolder(parent, viewType) - - override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int, payloads: List) { - val collectionPosition = getCollectionPosition(position) - if (collectionPosition in 0 until getList().size) { - if (itemClickListener != null) { - holder.itemView.setOnRippleClickListener { - itemClickListener?.invoke(getList()[getCollectionPosition(holder.adapterPosition)], holder) - } - } else { - holder.itemView.setOnClickListener(null) - } - } - delegatesManager.onBindViewHolder(holder, getList(), position, collectionPosition, payloads) - } - - final override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) = Unit - - /** - * Adds [AdapterDelegate] to adapter. - * - * @param delegate Delegate to add. - */ - fun addDelegate(delegate: AdapterDelegate<*>) = delegatesManager.addDelegate(delegate) - - /** - * Removes [AdapterDelegate] from adapter. - * - * @param delegate Delegate to remove. - */ - fun removeDelegate(delegate: AdapterDelegate<*>) = delegatesManager.removeDelegate(delegate) - - /** - * Submits a new list to be diffed, and displayed. - * - * If a list is already being displayed, a diff will be computed on a background thread, which - * will dispatch Adapter.notifyItem events on the main thread. - * - * @param list The new list to be displayed. - */ - fun submitList(list: List) = differ.submitList(list) - - /** - * Get the current List - any diffing to present this list has already been computed and - * dispatched via the ListUpdateCallback. - *

- * If a null List, or no List has been submitted, an empty list will be returned. - *

- * The returned list may not be mutated - mutations to content must be done through - * {@link #submitList(List)}. - * - * @return current List. - */ - fun getList(): List = differ.currentList - - fun getCollectionPosition(adapterPosition: Int) = adapterPosition - getHeadersCount() - -} diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/ItemAdapterDelegate.java b/src/main/java/ru/touchin/roboswag/components/adapters/ItemAdapterDelegate.java deleted file mode 100644 index 324bcf4..0000000 --- a/src/main/java/ru/touchin/roboswag/components/adapters/ItemAdapterDelegate.java +++ /dev/null @@ -1,85 +0,0 @@ -package ru.touchin.roboswag.components.adapters; - -import android.support.annotation.NonNull; -import android.support.v7.widget.RecyclerView; - -import java.util.List; - -/** - * Objects of such class controls creation and binding of specific type of RecyclerView's ViewHolders. - * Such delegates are creating and binding ViewHolders for specific items. - * Default {@link #getItemViewType} is generating on construction of object. - * - * @param Type of {@link RecyclerView.ViewHolder} of delegate; - * @param Type of items to bind to {@link RecyclerView.ViewHolder}s. - */ -public abstract class ItemAdapterDelegate extends AdapterDelegate { - - @Override - public boolean isForViewType(@NonNull final List items, final int adapterPosition, final int collectionPosition) { - return collectionPosition >= 0 - && collectionPosition < items.size() - && isForViewType(items.get(collectionPosition), adapterPosition, collectionPosition); - } - - /** - * Returns if object is processable by this delegate. - * This item will be casted to {@link TItem} and passes to {@link #onBindViewHolder(TViewHolder, TItem, int, int, List)}. - * - * @param item Item to check; - * @param adapterPosition Position of item in adapter; - * @param collectionPosition Position of item in collection that contains item; - * @return True if item is processable by this delegate. - */ - public boolean isForViewType(@NonNull final Object item, final int adapterPosition, final int collectionPosition) { - return true; - } - - @Override - public long getItemId(@NonNull final List items, final int adapterPosition, final int collectionPosition) { - //noinspection unchecked - return getItemId((TItem) items.get(collectionPosition), adapterPosition, collectionPosition); - } - - /** - * Returns unique ID of item to support stable ID's logic of RecyclerView's adapter. - * - * @param item Item in adapter; - * @param adapterPosition Position of item in adapter; - * @param collectionPosition Position of item in collection that contains item; - * @return Unique item ID. - */ - public long getItemId(@NonNull final TItem item, final int adapterPosition, final int collectionPosition) { - return 0; - } - - @Override - public void onBindViewHolder( - @NonNull final RecyclerView.ViewHolder holder, - @NonNull final List items, - final int adapterPosition, - final int collectionPosition, - @NonNull final List payloads - ) { - //noinspection unchecked - onBindViewHolder((TViewHolder) holder, (TItem) items.get(collectionPosition), adapterPosition, collectionPosition, payloads); - } - - /** - * Binds item with payloads to created by this object ViewHolder. - * - * @param holder ViewHolder to bind item to; - * @param item Item in adapter; - * @param adapterPosition Position of item in adapter; - * @param collectionPosition Position of item in collection that contains item; - * @param payloads Payloads; - */ - public abstract void onBindViewHolder( - @NonNull final TViewHolder holder, - @NonNull final TItem item, - final int adapterPosition, - final int collectionPosition, - @NonNull final List payloads - ); - -} diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/OffsetAdapterUpdateCallback.kt b/src/main/java/ru/touchin/roboswag/components/adapters/OffsetAdapterUpdateCallback.kt deleted file mode 100644 index 9715eb2..0000000 --- a/src/main/java/ru/touchin/roboswag/components/adapters/OffsetAdapterUpdateCallback.kt +++ /dev/null @@ -1,24 +0,0 @@ -package ru.touchin.roboswag.components.adapters - -import android.support.v7.util.ListUpdateCallback -import android.support.v7.widget.RecyclerView - -class OffsetAdapterUpdateCallback(private val adapter: RecyclerView.Adapter<*>, private val offsetProvider: () -> Int) : ListUpdateCallback { - - override fun onInserted(position: Int, count: Int) { - adapter.notifyItemRangeInserted(position + offsetProvider(), count) - } - - override fun onRemoved(position: Int, count: Int) { - adapter.notifyItemRangeRemoved(position + offsetProvider(), count) - } - - override fun onMoved(fromPosition: Int, toPosition: Int) { - adapter.notifyItemMoved(fromPosition + offsetProvider(), toPosition + offsetProvider()) - } - - override fun onChanged(position: Int, count: Int, payload: Any?) { - adapter.notifyItemRangeChanged(position + offsetProvider(), count, payload) - } - -} diff --git a/src/main/java/ru/touchin/roboswag/components/adapters/PositionAdapterDelegate.java b/src/main/java/ru/touchin/roboswag/components/adapters/PositionAdapterDelegate.java deleted file mode 100644 index ada8888..0000000 --- a/src/main/java/ru/touchin/roboswag/components/adapters/PositionAdapterDelegate.java +++ /dev/null @@ -1,68 +0,0 @@ -package ru.touchin.roboswag.components.adapters; - -import android.support.annotation.NonNull; -import android.support.v7.widget.RecyclerView; - -import java.util.List; - -/** - * Objects of such class controls creation and binding of specific type of RecyclerView's ViewHolders. - * Such delegates are creating and binding ViewHolders by position in adapter. - * Default {@link #getItemViewType} is generating on construction of object. - * - * @param Type of {@link RecyclerView.ViewHolder} of delegate. - */ -public abstract class PositionAdapterDelegate extends AdapterDelegate { - - @Override - public boolean isForViewType(@NonNull final List items, final int adapterPosition, final int collectionPosition) { - return isForViewType(adapterPosition); - } - - /** - * Returns if object is processable by this delegate. - * - * @param adapterPosition Position of item in adapter; - * @return True if item is processable by this delegate. - */ - public abstract boolean isForViewType(final int adapterPosition); - - @Override - public long getItemId(@NonNull final List objects, final int adapterPosition, final int itemsOffset) { - return getItemId(adapterPosition); - } - - /** - * Returns unique ID of item to support stable ID's logic of RecyclerView's adapter. - * - * @param adapterPosition Position of item in adapter; - * @return Unique item ID. - */ - public long getItemId(final int adapterPosition) { - return 0; - } - - @Override - public void onBindViewHolder( - @NonNull final RecyclerView.ViewHolder holder, - @NonNull final List items, - final int adapterPosition, - final int collectionPosition, - @NonNull final List payloads - ) { - //noinspection unchecked - onBindViewHolder((TViewHolder) holder, adapterPosition, payloads); - } - - /** - * Binds position with payloads to ViewHolder. - * - * @param holder ViewHolder to bind position to; - * @param adapterPosition Position of item in adapter; - * @param payloads Payloads. - */ - public void onBindViewHolder(@NonNull final TViewHolder holder, final int adapterPosition, @NonNull final List payloads) { - //do nothing by default - } - -} diff --git a/src/main/java/ru/touchin/roboswag/components/extensions/Delegates.kt b/src/main/java/ru/touchin/roboswag/components/extensions/Delegates.kt deleted file mode 100644 index 050bd6b..0000000 --- a/src/main/java/ru/touchin/roboswag/components/extensions/Delegates.kt +++ /dev/null @@ -1,24 +0,0 @@ -package ru.touchin.roboswag.components.extensions - -import kotlin.properties.Delegates -import kotlin.properties.ObservableProperty -import kotlin.properties.ReadWriteProperty -import kotlin.reflect.KProperty - -/** - * Simple observable delegate only for notification of new value. - */ -inline fun Delegates.observable( - initialValue: T, - crossinline onChange: (newValue: T) -> Unit -): ReadWriteProperty = object : ObservableProperty(initialValue) { - override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = onChange(newValue) -} - -inline fun Delegates.distinctUntilChanged( - initialValue: T, - crossinline onChange: (newValue: T) -> Unit -): ReadWriteProperty = object : ObservableProperty(initialValue) { - override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = - if (newValue != null && oldValue != newValue) onChange(newValue) else Unit -} diff --git a/src/main/java/ru/touchin/roboswag/components/extensions/View.kt b/src/main/java/ru/touchin/roboswag/components/extensions/View.kt deleted file mode 100644 index 9f4ddca..0000000 --- a/src/main/java/ru/touchin/roboswag/components/extensions/View.kt +++ /dev/null @@ -1,20 +0,0 @@ -package ru.touchin.roboswag.components.extensions - -import android.os.Build -import android.view.View - -private const val RIPPLE_EFFECT_DELAY = 150L - -/** - * Sets click listener to view. On click it will call something after delay. - * - * @param delay Delay after which click listener will be called; - * @param listener Click listener. - */ -fun View.setOnRippleClickListener(delay: Long = RIPPLE_EFFECT_DELAY, listener: (View) -> Unit) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - setOnClickListener { view -> postDelayed({ if (hasWindowFocus()) listener(view) }, delay) } - } else { - setOnClickListener(listener) - } -} diff --git a/src/main/java/ru/touchin/roboswag/components/extensions/ViewHolder.kt b/src/main/java/ru/touchin/roboswag/components/extensions/ViewHolder.kt deleted file mode 100644 index db7165f..0000000 --- a/src/main/java/ru/touchin/roboswag/components/extensions/ViewHolder.kt +++ /dev/null @@ -1,32 +0,0 @@ -package ru.touchin.roboswag.components.extensions - -import android.content.Context -import android.content.res.ColorStateList -import android.graphics.drawable.Drawable -import android.support.annotation.ColorInt -import android.support.annotation.ColorRes -import android.support.annotation.DrawableRes -import android.support.annotation.IdRes -import android.support.annotation.StringRes -import android.support.v4.content.ContextCompat -import android.support.v7.widget.RecyclerView -import android.view.View - -fun RecyclerView.ViewHolder.findViewById(@IdRes resId: Int): T = itemView.findViewById(resId) - -val RecyclerView.ViewHolder.context: Context - get() = itemView.context - -fun RecyclerView.ViewHolder.getText(@StringRes resId: Int): CharSequence = context.getText(resId) - -fun RecyclerView.ViewHolder.getString(@StringRes resId: Int): String = context.getString(resId) - -@SuppressWarnings("SpreadOperator") // it's OK for small arrays -fun RecyclerView.ViewHolder.getString(@StringRes resId: Int, vararg args: Any): String = context.getString(resId, *args) - -@ColorInt -fun RecyclerView.ViewHolder.getColor(@ColorRes resId: Int): Int = ContextCompat.getColor(context, resId) - -fun RecyclerView.ViewHolder.getColorStateList(@ColorRes resId: Int): ColorStateList? = ContextCompat.getColorStateList(context, resId) - -fun RecyclerView.ViewHolder.getDrawable(@DrawableRes resId: Int): Drawable? = ContextCompat.getDrawable(context, resId) diff --git a/src/main/java/ru/touchin/roboswag/components/utils/audio/HeadsetStateObserver.java b/src/main/java/ru/touchin/roboswag/components/utils/audio/HeadsetStateObserver.java deleted file mode 100644 index 346e52b..0000000 --- a/src/main/java/ru/touchin/roboswag/components/utils/audio/HeadsetStateObserver.java +++ /dev/null @@ -1,119 +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.roboswag.components.utils.audio; - -import android.bluetooth.BluetoothA2dp; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.media.AudioManager; -import android.support.annotation.NonNull; - -import io.reactivex.Observable; -import io.reactivex.subjects.BehaviorSubject; - -/** - * Created by Gavriil Sitnikov on 02/11/2015. - * Simple observer of wired or wireless (bluetooth A2DP) headsets state (connected or not). - *
You require android.permission.BLUETOOTH and API level >= 11 if want to observe wireless headset state - */ -public final class HeadsetStateObserver { - - @NonNull - private final AudioManager audioManager; - @NonNull - private final Observable connectedObservable; - - public HeadsetStateObserver(@NonNull final Context context) { - audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); - connectedObservable = Observable - .fromCallable(() -> new IsConnectedReceiver(audioManager)) - .switchMap(isConnectedReceiver -> Observable.combineLatest(isConnectedReceiver.isWiredConnectedChangedEvent, - isConnectedReceiver.isWirelessConnectedChangedEvent, - (isWiredConnected, isWirelessConnected) -> isWiredConnected || isWirelessConnected) - .distinctUntilChanged() - .doOnSubscribe(disposable -> { - final IntentFilter headsetStateIntentFilter = new IntentFilter(Intent.ACTION_HEADSET_PLUG); - headsetStateIntentFilter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); - context.registerReceiver(isConnectedReceiver, headsetStateIntentFilter); - }) - .doOnDispose(() -> context.unregisterReceiver(isConnectedReceiver))) - .replay(1) - .refCount(); - } - - /** - * Returns if wired or wireless headset is connected. - * - * @return True if headset is connected. - */ - @SuppressWarnings("deprecation") - public boolean isConnected() { - return audioManager.isWiredHeadsetOn() || audioManager.isBluetoothA2dpOn(); - } - - /** - * Observes connection state of headset. - * - * @return Returns observable which will provide current connection state and any of it's udpdate. - */ - @NonNull - public Observable observeIsConnected() { - return connectedObservable; - } - - private static class IsConnectedReceiver extends BroadcastReceiver { - - @NonNull - private final BehaviorSubject isWiredConnectedChangedEvent; - @NonNull - private final BehaviorSubject isWirelessConnectedChangedEvent; - - @SuppressWarnings("deprecation") - public IsConnectedReceiver(@NonNull final AudioManager audioManager) { - super(); - isWiredConnectedChangedEvent = BehaviorSubject.createDefault(audioManager.isWiredHeadsetOn()); - isWirelessConnectedChangedEvent = BehaviorSubject.createDefault(audioManager.isBluetoothA2dpOn()); - } - - @Override - public void onReceive(@NonNull final Context context, @NonNull final Intent intent) { - if (Intent.ACTION_HEADSET_PLUG.equals(intent.getAction()) && !isInitialStickyBroadcast()) { - isWiredConnectedChangedEvent.onNext(intent.getIntExtra("state", 0) != 0); - } - if (BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) { - final int bluetoothState = intent.getIntExtra(BluetoothA2dp.EXTRA_STATE, BluetoothA2dp.STATE_DISCONNECTED); - switch (bluetoothState) { - case BluetoothA2dp.STATE_DISCONNECTED: - isWirelessConnectedChangedEvent.onNext(false); - break; - case BluetoothA2dp.STATE_CONNECTED: - isWirelessConnectedChangedEvent.onNext(true); - break; - default: - break; - } - } - } - - } - -} diff --git a/src/main/java/ru/touchin/roboswag/components/utils/audio/VolumeController.java b/src/main/java/ru/touchin/roboswag/components/utils/audio/VolumeController.java deleted file mode 100644 index c0853de..0000000 --- a/src/main/java/ru/touchin/roboswag/components/utils/audio/VolumeController.java +++ /dev/null @@ -1,132 +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.roboswag.components.utils.audio; - -import android.content.Context; -import android.database.ContentObserver; -import android.media.AudioManager; -import android.os.Handler; -import android.os.Looper; -import android.provider.Settings; -import android.support.annotation.NonNull; - -import io.reactivex.Observable; -import io.reactivex.subjects.PublishSubject; -import ru.touchin.roboswag.core.log.Lc; -import ru.touchin.roboswag.core.utils.ShouldNotHappenException; - -/** - * Created by Gavriil Sitnikov on 02/11/2015. - * Simple class to control and observe volume of specific stream type (phone call, music etc.). - */ -public final class VolumeController { - - @NonNull - private final AudioManager audioManager; - private final int maxVolume; - @NonNull - private final Observable volumeObservable; - @NonNull - private final PublishSubject selfVolumeChangedEvent = PublishSubject.create(); - - public VolumeController(@NonNull final Context context) { - this(context, AudioManager.STREAM_MUSIC); - } - - public VolumeController(@NonNull final Context context, final int streamType) { - audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); - maxVolume = audioManager.getStreamMaxVolume(streamType); - volumeObservable = Observable - .fromCallable(VolumeObserver::new) - .switchMap(volumeObserver -> selfVolumeChangedEvent - .mergeWith(volumeObserver.systemVolumeChangedEvent - .map(event -> getVolume()) - .doOnSubscribe(disposable -> context.getContentResolver() - .registerContentObserver(Settings.System.CONTENT_URI, true, volumeObserver)) - .doOnDispose(() -> context.getContentResolver() - .unregisterContentObserver(volumeObserver))) - .startWith(getVolume())) - .distinctUntilChanged() - .replay(1) - .refCount(); - } - - /** - * Max volume amount to set. - * - * @return max volume. - */ - public int getMaxVolume() { - return maxVolume; - } - - /** - * Sets volume. - * - * @param volume Volume value to set from 0 to {@link #getMaxVolume()}. - */ - public void setVolume(final int volume) { - if (volume < 0 || volume > maxVolume) { - Lc.assertion(new ShouldNotHappenException("Volume: " + volume + " out of bounds [0," + maxVolume + ']')); - return; - } - if (getVolume() != volume) { - audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0); - selfVolumeChangedEvent.onNext(volume); - } - } - - /** - * Returns volume. - * - * @return Returns volume value from 0 to {@link #getMaxVolume()}. - */ - public int getVolume() { - return audioManager.getStreamVolume(AudioManager.STREAM_MUSIC); - } - - /** - * Observes current volume. - * - * @return Observable which will provide current volume and then it's updates. - */ - @NonNull - public Observable observeVolume() { - return volumeObservable; - } - - private static class VolumeObserver extends ContentObserver { - - @NonNull - private final PublishSubject systemVolumeChangedEvent = PublishSubject.create(); - - public VolumeObserver() { - super(new Handler(Looper.getMainLooper())); - } - - @Override - public void onChange(final boolean selfChange) { - super.onChange(selfChange); - systemVolumeChangedEvent.onNext(null); - } - - } - -} diff --git a/src/main/java/ru/touchin/roboswag/components/utils/images/BlurUtils.java b/src/main/java/ru/touchin/roboswag/components/utils/images/BlurUtils.java deleted file mode 100644 index 6f3837d..0000000 --- a/src/main/java/ru/touchin/roboswag/components/utils/images/BlurUtils.java +++ /dev/null @@ -1,323 +0,0 @@ -/** - * Copyright (C) 2015 Wasabeef - * 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.roboswag.components.utils.images; - -import android.annotation.TargetApi; -import android.content.Context; -import android.graphics.Bitmap; -import android.os.Build; -import android.renderscript.Allocation; -import android.renderscript.Element; -import android.renderscript.RSRuntimeException; -import android.renderscript.RenderScript; -import android.renderscript.ScriptIntrinsicBlur; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -public final class BlurUtils { - - @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) - @Nullable - public static Bitmap blurRenderscript(@NonNull final Context context, @NonNull final Bitmap bitmap, final int radius) throws RSRuntimeException { - RenderScript rs = null; - Allocation input = null; - Allocation output = null; - ScriptIntrinsicBlur blur = null; - try { - rs = RenderScript.create(context); - rs.setMessageHandler(new RenderScript.RSMessageHandler()); - input = Allocation.createFromBitmap(rs, bitmap, Allocation.MipmapControl.MIPMAP_NONE, - Allocation.USAGE_SCRIPT); - output = Allocation.createTyped(rs, input.getType()); - blur = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs)); - - blur.setInput(input); - blur.setRadius(radius); - blur.forEach(output); - output.copyTo(bitmap); - } finally { - if (rs != null) { - rs.destroy(); - } - if (input != null) { - input.destroy(); - } - if (output != null) { - output.destroy(); - } - if (blur != null) { - blur.destroy(); - } - } - - return bitmap; - } - - @Nullable - @SuppressWarnings({"PMD.ExcessiveMethodLength", "PMD.CyclomaticComplexity", "PMD.ModifiedCyclomaticComplexity", "PMD.StdCyclomaticComplexity", - "PMD.NcssMethodCount", "PMD.NPathComplexity", "checkstyle:MethodLength", "checkstyle:LocalFinalVariableName", - "checkstyle:ArrayTypeStyle", "checkstyle:InnerAssignment", "checkstyle:LocalVariableName"}) - public static Bitmap blurFast(@NonNull final Bitmap sentBitmap, final int radius, final boolean canReuseInBitmap) { - - // Stack Blur v1.0 from - // http://www.quasimondo.com/StackBlurForCanvas/StackBlurDemo.html - // - // Java Author: Mario Klingemann - // http://incubator.quasimondo.com - // created Feburary 29, 2004 - // Android port : Yahel Bouaziz - // http://www.kayenko.com - // ported april 5th, 2012 - - // This is a compromise between Gaussian Blur and Box blur - // It creates much better looking blurs than Box Blur, but is - // 7x faster than my Gaussian Blur implementation. - // - // I called it Stack Blur because this describes best how this - // filter works internally: it creates a kind of moving stack - // of colors whilst scanning through the image. Thereby it - // just has to add one new block of color to the right side - // of the stack and remove the leftmost color. The remaining - // colors on the topmost layer of the stack are either added on - // or reduced by one, depending on if they are on the right or - // on the left side of the stack. - // - // If you are using this algorithm in your code please add - // the following line: - // - // Stack Blur Algorithm by Mario Klingemann - - final Bitmap bitmap; - if (canReuseInBitmap) { - bitmap = sentBitmap; - } else { - bitmap = sentBitmap.copy(sentBitmap.getConfig(), true); - } - - if (radius < 1) { - return null; - } - - final int w = bitmap.getWidth(); - final int h = bitmap.getHeight(); - - final int[] pix = new int[w * h]; - bitmap.getPixels(pix, 0, w, 0, 0, w, h); - - final int wm = w - 1; - final int hm = h - 1; - final int wh = w * h; - final int div = radius + radius + 1; - - final int r[] = new int[wh]; - final int g[] = new int[wh]; - final int b[] = new int[wh]; - int rsum; - int gsum; - int bsum; - int x; - int i; - int p; - int yp; - int yi; - int yw; - final int vmin[] = new int[Math.max(w, h)]; - - int divsum = (div + 1) >> 1; - divsum *= divsum; - final int dv[] = new int[256 * divsum]; - for (i = 0; i < 256 * divsum; i++) { - dv[i] = i / divsum; - } - - yw = yi = 0; - - final int[][] stack = new int[div][3]; - int stackpointer; - int stackstart; - int[] sir; - int rbs; - final int r1 = radius + 1; - int routsum; - int goutsum; - int boutsum; - int rinsum; - int ginsum; - int binsum; - - int y; - - for (y = 0; y < h; y++) { - rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0; - for (i = -radius; i <= radius; i++) { - p = pix[yi + Math.min(wm, Math.max(i, 0))]; - sir = stack[i + radius]; - sir[0] = (p & 0xff0000) >> 16; - sir[1] = (p & 0x00ff00) >> 8; - sir[2] = p & 0x0000ff; - rbs = r1 - Math.abs(i); - rsum += sir[0] * rbs; - gsum += sir[1] * rbs; - bsum += sir[2] * rbs; - if (i > 0) { - rinsum += sir[0]; - ginsum += sir[1]; - binsum += sir[2]; - } else { - routsum += sir[0]; - goutsum += sir[1]; - boutsum += sir[2]; - } - } - stackpointer = radius; - - for (x = 0; x < w; x++) { - - r[yi] = dv[rsum]; - g[yi] = dv[gsum]; - b[yi] = dv[bsum]; - - rsum -= routsum; - gsum -= goutsum; - bsum -= boutsum; - - stackstart = stackpointer - radius + div; - sir = stack[stackstart % div]; - - routsum -= sir[0]; - goutsum -= sir[1]; - boutsum -= sir[2]; - - if (y == 0) { - vmin[x] = Math.min(x + radius + 1, wm); - } - p = pix[yw + vmin[x]]; - - sir[0] = (p & 0xff0000) >> 16; - sir[1] = (p & 0x00ff00) >> 8; - sir[2] = p & 0x0000ff; - - rinsum += sir[0]; - ginsum += sir[1]; - binsum += sir[2]; - - rsum += rinsum; - gsum += ginsum; - bsum += binsum; - - stackpointer = (stackpointer + 1) % div; - sir = stack[stackpointer % div]; - - routsum += sir[0]; - goutsum += sir[1]; - boutsum += sir[2]; - - rinsum -= sir[0]; - ginsum -= sir[1]; - binsum -= sir[2]; - - yi++; - } - yw += w; - } - for (x = 0; x < w; x++) { - rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0; - yp = -radius * w; - for (i = -radius; i <= radius; i++) { - yi = Math.max(0, yp) + x; - - sir = stack[i + radius]; - - sir[0] = r[yi]; - sir[1] = g[yi]; - sir[2] = b[yi]; - - rbs = r1 - Math.abs(i); - - rsum += r[yi] * rbs; - gsum += g[yi] * rbs; - bsum += b[yi] * rbs; - - if (i > 0) { - rinsum += sir[0]; - ginsum += sir[1]; - binsum += sir[2]; - } else { - routsum += sir[0]; - goutsum += sir[1]; - boutsum += sir[2]; - } - - if (i < hm) { - yp += w; - } - } - yi = x; - stackpointer = radius; - for (y = 0; y < h; y++) { - // Preserve alpha channel: ( 0xff000000 & pix[yi] ) - pix[yi] = (0xff000000 & pix[yi]) | (dv[rsum] << 16) | (dv[gsum] << 8) | dv[bsum]; - - rsum -= routsum; - gsum -= goutsum; - bsum -= boutsum; - - stackstart = stackpointer - radius + div; - sir = stack[stackstart % div]; - - routsum -= sir[0]; - goutsum -= sir[1]; - boutsum -= sir[2]; - - if (x == 0) { - vmin[y] = Math.min(y + r1, hm) * w; - } - p = x + vmin[y]; - - sir[0] = r[p]; - sir[1] = g[p]; - sir[2] = b[p]; - - rinsum += sir[0]; - ginsum += sir[1]; - binsum += sir[2]; - - rsum += rinsum; - gsum += ginsum; - bsum += binsum; - - stackpointer = (stackpointer + 1) % div; - sir = stack[stackpointer]; - - routsum += sir[0]; - goutsum += sir[1]; - boutsum += sir[2]; - - rinsum -= sir[0]; - ginsum -= sir[1]; - binsum -= sir[2]; - - yi += w; - } - } - - bitmap.setPixels(pix, 0, w, 0, 0, w, h); - - return bitmap; - } - - private BlurUtils() { - } - -} diff --git a/src/main/java/ru/touchin/roboswag/components/views/MaterialLoadingBar.java b/src/main/java/ru/touchin/roboswag/components/views/MaterialLoadingBar.java deleted file mode 100644 index 9ee131d..0000000 --- a/src/main/java/ru/touchin/roboswag/components/views/MaterialLoadingBar.java +++ /dev/null @@ -1,120 +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.roboswag.components.views; - -import android.content.Context; -import android.content.res.TypedArray; -import android.os.Build; -import android.support.annotation.ColorInt; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v7.widget.AppCompatImageView; -import android.util.AttributeSet; -import android.util.TypedValue; - -import ru.touchin.roboswag.components.R; -import ru.touchin.roboswag.components.utils.UiUtils; - -/** - * Created by Ilia Kurtov on 07/12/2016. - * Simple endless progress bar view in material (round circle) style. - * It is able to setup size, stroke width and color. - * See MaterialLoadingBar Attributes: - * R.styleable#MaterialLoadingBar_size - * R.styleable#MaterialLoadingBar_strokeWidth - * R.styleable#MaterialLoadingBar_color - * Use - * R.styleable#MaterialLoadingBar_materialLoadingBarStyle - * to set default style of MaterialLoadingBar in your Theme. - * Sample: - * - * - */ -public class MaterialLoadingBar extends AppCompatImageView { - - private static int getPrimaryColor(@NonNull final Context context) { - final int colorAttr; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - colorAttr = android.R.attr.colorPrimary; - } else { - colorAttr = context.getResources().getIdentifier("colorPrimary", "attr", context.getPackageName()); - } - final TypedValue outValue = new TypedValue(); - context.getTheme().resolveAttribute(colorAttr, outValue, true); - return outValue.data; - } - - private MaterialProgressDrawable progressDrawable; - - public MaterialLoadingBar(@NonNull final Context context) { - this(context, null); - } - - public MaterialLoadingBar(@NonNull final Context context, @Nullable final AttributeSet attrs) { - this(context, attrs, R.attr.materialLoadingBarStyle); - } - - public MaterialLoadingBar(@NonNull final Context context, @Nullable final AttributeSet attrs, final int defStyleAttr) { - super(context, attrs, defStyleAttr); - - final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MaterialLoadingBar, - defStyleAttr, - 0); - final int size = (int) typedArray.getDimension(R.styleable.MaterialLoadingBar_size, UiUtils.OfMetrics.dpToPixels(context, 48)); - final int color = typedArray.getColor(R.styleable.MaterialLoadingBar_color, getPrimaryColor(context)); - final float strokeWidth = typedArray.getDimension(R.styleable.MaterialLoadingBar_strokeWidth, - UiUtils.OfMetrics.dpToPixels(context, 4)); - typedArray.recycle(); - - progressDrawable = new MaterialProgressDrawable(context, size); - setColor(color); - progressDrawable.setStrokeWidth(strokeWidth); - setScaleType(ScaleType.CENTER); - setImageDrawable(progressDrawable); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - progressDrawable.start(); - } - - @Override - protected void onDetachedFromWindow() { - progressDrawable.stop(); - super.onDetachedFromWindow(); - } - - /** - * Set color of loader. - * - * @param colorInt Color of loader to be set. - */ - public void setColor(@ColorInt final int colorInt) { - progressDrawable.setColor(colorInt); - } - -} \ No newline at end of file diff --git a/src/main/java/ru/touchin/roboswag/components/views/MaterialProgressDrawable.java b/src/main/java/ru/touchin/roboswag/components/views/MaterialProgressDrawable.java deleted file mode 100644 index a152cb4..0000000 --- a/src/main/java/ru/touchin/roboswag/components/views/MaterialProgressDrawable.java +++ /dev/null @@ -1,292 +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.roboswag.components.views; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.ColorFilter; -import android.graphics.Paint; -import android.graphics.PixelFormat; -import android.graphics.Rect; -import android.graphics.RectF; -import android.graphics.drawable.Animatable; -import android.graphics.drawable.Drawable; -import android.os.SystemClock; -import android.support.annotation.ColorInt; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import ru.touchin.roboswag.components.utils.UiUtils; - -/** - * Created by Gavriil Sitnikov on 01/03/16. - * Simple realization of endless progress bar which is looking material-like. - */ -public class MaterialProgressDrawable extends Drawable implements Runnable, Animatable { - - private static final int UPDATE_INTERVAL = 1000 / 60; - - private static final float DEFAULT_STROKE_WIDTH_DP = 4.5f; - private static final Parameters DEFAULT_PARAMETERS = new Parameters(20, 270, 4, 12, 4, 8); - - private final int size; - @NonNull - private final Paint paint; - @NonNull - private Parameters parameters = DEFAULT_PARAMETERS; - @NonNull - private final RectF arcBounds = new RectF(); - - private float rotationAngle; - private float arcSize; - private boolean running; - - public MaterialProgressDrawable(@NonNull final Context context) { - this(context, -1); - } - - public MaterialProgressDrawable(@NonNull final Context context, final int size) { - super(); - - this.size = size; - paint = new Paint(Paint.ANTI_ALIAS_FLAG); - paint.setStyle(Paint.Style.STROKE); - paint.setStrokeWidth(UiUtils.OfMetrics.dpToPixels(context, DEFAULT_STROKE_WIDTH_DP)); - paint.setColor(Color.BLACK); - } - - @Override - public int getIntrinsicWidth() { - return size; - } - - @Override - public int getIntrinsicHeight() { - return size; - } - - /** - * Returns width of arc. - * - * @return Width. - */ - public float getStrokeWidth() { - return paint.getStrokeWidth(); - } - - /** - * Sets width of arc. - * - * @param strokeWidth Width. - */ - public void setStrokeWidth(final float strokeWidth) { - paint.setStrokeWidth(strokeWidth); - updateArcBounds(); - invalidateSelf(); - } - - /** - * Sets color of arc. - * - * @param color Color. - */ - public void setColor(@ColorInt final int color) { - paint.setColor(color); - invalidateSelf(); - } - - /** - * Returns magic parameters of spinning. - * - * @return Parameters of spinning. - */ - @NonNull - public Parameters getParameters() { - return parameters; - } - - /** - * Sets magic parameters of spinning. - * - * @param parameters Parameters of spinning. - */ - public void setParameters(@NonNull final Parameters parameters) { - this.parameters = parameters; - invalidateSelf(); - } - - @Override - protected void onBoundsChange(@NonNull final Rect bounds) { - super.onBoundsChange(bounds); - updateArcBounds(); - } - - private void updateArcBounds() { - arcBounds.set(getBounds()); - //HACK: + 1 as anti-aliasing drawing bug workaround - final int inset = (int) (paint.getStrokeWidth() / 2) + 1; - arcBounds.inset(inset, inset); - } - - @SuppressWarnings("PMD.NPathComplexity") - @Override - public void draw(@NonNull final Canvas canvas) { - final boolean isGrowingCycle = (((int) (arcSize / parameters.maxAngle)) % 2) == 0; - final float angle = arcSize % parameters.maxAngle; - final float shift = (angle / parameters.maxAngle) * parameters.gapAngle; - canvas.drawArc(arcBounds, isGrowingCycle ? rotationAngle + shift : rotationAngle + parameters.gapAngle - shift, - isGrowingCycle ? angle + parameters.gapAngle : parameters.maxAngle - angle + parameters.gapAngle, false, paint); - //TODO: compute based on animation start time - rotationAngle += isGrowingCycle ? parameters.rotationMagicNumber1 : parameters.rotationMagicNumber2; - arcSize += isGrowingCycle ? parameters.arcMagicNumber1 : parameters.arcMagicNumber2; - if (arcSize < 0) { - arcSize = 0; - } - if (isRunning()) { - scheduleSelf(this, SystemClock.uptimeMillis() + UPDATE_INTERVAL); - } - } - - @Override - public void setAlpha(final int alpha) { - paint.setAlpha(alpha); - invalidateSelf(); - } - - @Override - public void setColorFilter(@Nullable final ColorFilter colorFilter) { - paint.setColorFilter(colorFilter); - invalidateSelf(); - } - - @Override - public int getOpacity() { - return PixelFormat.TRANSLUCENT; - } - - @Override - public void start() { - if (!running) { - running = true; - run(); - } - } - - @Override - public void stop() { - if (running) { - unscheduleSelf(this); - running = false; - } - } - - @Override - public boolean isRunning() { - return running; - } - - @Override - public void run() { - if (running) { - invalidateSelf(); - } - } - - /** - * Some parameters which are using to spin progress bar. - */ - public static class Parameters { - - private final float gapAngle; - private final float maxAngle; - private final float rotationMagicNumber1; - private final float rotationMagicNumber2; - private final float arcMagicNumber1; - private final float arcMagicNumber2; - - public Parameters(final float gapAngle, final float maxAngle, - final float rotationMagicNumber1, final float rotationMagicNumber2, - final float arcMagicNumber1, final float arcMagicNumber2) { - this.gapAngle = gapAngle; - this.maxAngle = maxAngle; - this.rotationMagicNumber1 = rotationMagicNumber1; - this.rotationMagicNumber2 = rotationMagicNumber2; - this.arcMagicNumber1 = arcMagicNumber1; - this.arcMagicNumber2 = arcMagicNumber2; - } - - /** - * Returns angle of gap of arc. - * - * @return Angle of gap. - */ - public float getGapAngle() { - return gapAngle; - } - - /** - * Returns maximum angle of arc. - * - * @return Maximum angle of arc. - */ - public float getMaxAngle() { - return maxAngle; - } - - /** - * Magic parameter 1. - * - * @return Magic. - */ - public float getRotationMagicNumber1() { - return rotationMagicNumber1; - } - - /** - * Magic parameter 2. - * - * @return Magic. - */ - public float getRotationMagicNumber2() { - return rotationMagicNumber2; - } - - /** - * Magic parameter 3. - * - * @return Magic. - */ - public float getArcMagicNumber1() { - return arcMagicNumber1; - } - - /** - * Magic parameter 4. - * - * @return Magic. - */ - public float getArcMagicNumber2() { - return arcMagicNumber2; - } - - } - -} \ No newline at end of file diff --git a/src/main/java/ru/touchin/roboswag/components/views/TypefacedEditText.java b/src/main/java/ru/touchin/roboswag/components/views/TypefacedEditText.java deleted file mode 100644 index d29b60f..0000000 --- a/src/main/java/ru/touchin/roboswag/components/views/TypefacedEditText.java +++ /dev/null @@ -1,345 +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.roboswag.components.views; - -import android.content.Context; -import android.content.res.TypedArray; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.design.widget.TextInputLayout; -import android.support.v7.widget.AppCompatEditText; -import android.text.Editable; -import android.text.InputType; -import android.text.TextUtils; -import android.text.TextWatcher; -import android.text.method.SingleLineTransformationMethod; -import android.text.method.TransformationMethod; -import android.util.AttributeSet; -import android.view.View; -import android.view.ViewParent; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputConnection; - -import java.util.ArrayList; -import java.util.List; - -import ru.touchin.roboswag.components.R; -import ru.touchin.roboswag.components.views.internal.AttributesUtils; -import ru.touchin.roboswag.core.log.Lc; - -/** - * Created by Gavriil Sitnikov on 18/07/2014. - * TextView that supports fonts from Typefaces class - */ - -/** - * Created by Gavriil Sitnikov on 18/07/2014. - * EditText that supports custom typeface and forces developer to specify if this view multiline or not. - * Also in debug mode it has common checks for popular bugs. - */ -@SuppressWarnings("PMD.ConstructorCallsOverridableMethod") -//ConstructorCallsOverridableMethod: it's ok as we need to setTypeface -public class TypefacedEditText extends AppCompatEditText { - - private static boolean inDebugMode; - - /** - * Enables debugging features like checking attributes on inflation. - */ - public static void setInDebugMode() { - inDebugMode = true; - } - - private boolean multiline; - private boolean constructed; - - @Nullable - private OnTextChangedListener onTextChangedListener; - - public TypefacedEditText(@NonNull final Context context) { - super(context); - initialize(context, null); - } - - public TypefacedEditText(@NonNull final Context context, @Nullable final AttributeSet attrs) { - super(context, attrs); - initialize(context, attrs); - } - - public TypefacedEditText(@NonNull final Context context, @Nullable final AttributeSet attrs, final int defStyle) { - super(context, attrs, defStyle); - initialize(context, attrs); - } - - private void initialize(@NonNull final Context context, @Nullable final AttributeSet attrs) { - constructed = true; - super.setIncludeFontPadding(false); - initializeTextChangedListener(); - if (attrs != null) { - final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.TypefacedEditText); - final boolean multiline = typedArray.getBoolean(R.styleable.TypefacedEditText_isMultiline, false); - if (multiline) { - setMultiline(AttributesUtils.getMaxLinesFromAttrs(context, attrs)); - } else { - setSingleLine(); - } - typedArray.recycle(); - if (inDebugMode) { - checkAttributes(context, attrs); - } - } - } - - @Nullable - public InputConnection onCreateInputConnection(@NonNull final EditorInfo attrs) { - final InputConnection inputConnection = super.onCreateInputConnection(attrs); - if (inputConnection != null && attrs.hintText == null) { - for (ViewParent parent = getParent(); parent instanceof View; parent = parent.getParent()) { - if (parent instanceof TextInputLayout) { - attrs.hintText = ((TextInputLayout) parent).getHint(); - break; - } - } - } - - return inputConnection; - } - - private void checkAttributes(@NonNull final Context context, @NonNull final AttributeSet attrs) { - final List errors = new ArrayList<>(); - Boolean multiline = null; - TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.TypefacedEditText); - AttributesUtils.checkAttribute(typedArray, errors, R.styleable.TypefacedEditText_isMultiline, true, - "isMultiline required parameter"); - if (typedArray.hasValue(R.styleable.TypefacedEditText_isMultiline)) { - multiline = typedArray.getBoolean(R.styleable.TypefacedEditText_isMultiline, false); - } - typedArray.recycle(); - - try { - final Class androidRes = Class.forName("com.android.internal.R$styleable"); - - typedArray = context.obtainStyledAttributes(attrs, AttributesUtils.getField(androidRes, "TextView")); - AttributesUtils.checkRegularTextViewAttributes(typedArray, androidRes, errors, "isMultiline"); - checkEditTextSpecificAttributes(typedArray, androidRes, errors); - if (multiline != null) { - checkMultilineAttributes(typedArray, androidRes, errors, multiline); - } - } catch (final Exception exception) { - Lc.e(exception, "Error during checking attributes"); - } - AttributesUtils.handleErrors(this, errors); - typedArray.recycle(); - } - - private void checkEditTextSpecificAttributes(@NonNull final TypedArray typedArray, @NonNull final Class androidRes, - @NonNull final List errors) - throws NoSuchFieldException, IllegalAccessException { - AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_singleLine"), false, - "remove singleLine and use isMultiline"); - AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_includeFontPadding"), false, - "includeFontPadding forbid parameter"); - AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_ellipsize"), false, - "ellipsize forbid parameter"); - - if (typedArray.hasValue(AttributesUtils.getField(androidRes, "TextView_hint"))) { - AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_textColorHint"), true, - "textColorHint required parameter if hint is not null"); - } - - AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_textSize"), true, - "textSize required parameter. If it's dynamic then use '0sp'"); - AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_inputType"), true, - "inputType required parameter"); - - final int inputType = typedArray.getInt(AttributesUtils.getField(androidRes, "TextView_inputType"), -1); - if (AttributesUtils.isNumberInputType(inputType)) { - errors.add("use inputType phone instead of number"); - } - - AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_imeOptions"), true, - "imeOptions required parameter"); - } - - private void checkMultilineAttributes(@NonNull final TypedArray typedArray, @NonNull final Class androidRes, - @NonNull final List errors, final boolean multiline) - throws NoSuchFieldException, IllegalAccessException { - if (multiline) { - if (typedArray.getInt(AttributesUtils.getField(androidRes, "TextView_lines"), -1) == 1) { - errors.add("lines should be more than 1 if isMultiline is true"); - } - if (typedArray.getInt(AttributesUtils.getField(androidRes, "TextView_maxLines"), -1) == 1) { - errors.add("maxLines should be more than 1 if isMultiline is true"); - } - if (!typedArray.hasValue(AttributesUtils.getField(androidRes, "TextView_maxLines")) - && !typedArray.hasValue(AttributesUtils.getField(androidRes, "TextView_maxLength"))) { - errors.add("specify maxLines or maxLength if isMultiline is true"); - } - } else { - AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_lines"), false, - "remove lines and use isMultiline"); - AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_maxLines"), false, - "maxLines remove and use isMultiline"); - AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_minLines"), false, - "minLines remove and use isMultiline"); - AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_maxLength"), true, - "maxLength required parameter if isMultiline is false"); - } - } - - private void initializeTextChangedListener() { - addTextChangedListener(new TextWatcher() { - - @Override - public void beforeTextChanged(@NonNull final CharSequence oldText, final int start, final int count, final int after) { - //do nothing - } - - @Override - public void onTextChanged(@NonNull final CharSequence inputText, final int start, final int before, final int count) { - if (onTextChangedListener != null) { - onTextChangedListener.onTextChanged(inputText); - } - } - - @Override - public void afterTextChanged(@NonNull final Editable editable) { - //do nothing - } - - }); - } - - /** - * Sets if view supports multiline text alignment. - * - * @param maxLines Maximum lines to be set. - */ - public void setMultiline(final int maxLines) { - if (maxLines <= 1) { - Lc.assertion("Wrong maxLines: " + maxLines); - return; - } - multiline = true; - final TransformationMethod transformationMethod = getTransformationMethod(); - super.setSingleLine(false); - super.setMaxLines(maxLines); - if (!(transformationMethod instanceof SingleLineTransformationMethod)) { - setTransformationMethod(transformationMethod); - } - } - - @Override - public void setSingleLine(final boolean singleLine) { - if (singleLine) { - setSingleLine(); - } else { - setMultiline(Integer.MAX_VALUE); - } - } - - @Override - public void setSingleLine() { - final TransformationMethod transformationMethod = getTransformationMethod(); - super.setSingleLine(true); - if (transformationMethod != null) { - /*DEBUG if (!(transformationMethod instanceof SingleLineTransformationMethod)) { - Lc.w("SingleLineTransformationMethod method ignored because of previous transformation method: " + transformationMethod); - }*/ - setTransformationMethod(transformationMethod); - } - setLines(1); - multiline = false; - } - - @Override - public void setLines(final int lines) { - if (constructed && multiline && lines == 1) { - Lc.assertion(new IllegalStateException(AttributesUtils.viewError(this, "lines = 1 is illegal if multiline is set to true"))); - return; - } - super.setLines(lines); - } - - @Override - public void setMaxLines(final int maxLines) { - if (constructed && !multiline && maxLines > 1) { - Lc.assertion(new IllegalStateException(AttributesUtils.viewError(this, "maxLines > 1 is illegal if multiline is set to false"))); - return; - } - super.setMaxLines(maxLines); - } - - @Override - public void setMinLines(final int minLines) { - if (constructed && !multiline && minLines > 1) { - Lc.assertion(new IllegalStateException(AttributesUtils.viewError(this, "minLines > 1 is illegal if multiline is set to false"))); - return; - } - super.setMinLines(minLines); - } - - @Override - public final void setIncludeFontPadding(final boolean includeFontPadding) { - if (!constructed) { - return; - } - Lc.assertion(new IllegalStateException( - AttributesUtils.viewError(this, "Do not specify font padding as it is hard to make pixel-perfect design with such option"))); - } - - @Override - public void setEllipsize(@NonNull final TextUtils.TruncateAt ellipsis) { - if (!constructed) { - return; - } - Lc.assertion(new IllegalStateException(AttributesUtils.viewError(this, "Do not specify ellipsize for EditText"))); - } - - @Override - public void setInputType(final int type) { - if (AttributesUtils.isNumberInputType(type)) { - Lc.assertion(new IllegalStateException(AttributesUtils.viewError(this, - "Do not specify number InputType for EditText, use phone instead"))); - super.setInputType(InputType.TYPE_CLASS_PHONE); - return; - } - super.setInputType(type); - } - - public void setOnTextChangedListener(@Nullable final OnTextChangedListener onTextChangedListener) { - this.onTextChangedListener = onTextChangedListener; - } - - /** - * Simplified variant of {@link TextWatcher}. - */ - public interface OnTextChangedListener { - - /** - * Calls when text have changed. - * - * @param text New text. - */ - void onTextChanged(@NonNull CharSequence text); - - } - -} \ No newline at end of file diff --git a/src/main/java/ru/touchin/roboswag/components/views/TypefacedTextView.java b/src/main/java/ru/touchin/roboswag/components/views/TypefacedTextView.java deleted file mode 100644 index b8fddb2..0000000 --- a/src/main/java/ru/touchin/roboswag/components/views/TypefacedTextView.java +++ /dev/null @@ -1,467 +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.roboswag.components.views; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.res.TypedArray; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v7.widget.AppCompatTextView; -import android.text.TextUtils; -import android.text.method.TransformationMethod; -import android.util.AttributeSet; -import android.util.TypedValue; - -import java.util.ArrayList; -import java.util.List; - -import ru.touchin.roboswag.components.R; -import ru.touchin.roboswag.components.utils.UiUtils; -import ru.touchin.roboswag.components.views.internal.AttributesUtils; -import ru.touchin.roboswag.core.log.Lc; - -/** - * Created by Gavriil Sitnikov on 18/07/2014. - * TextView that supports custom typeface and forces developer to specify {@link LineStrategy}. - * Also in debug mode it has common checks for popular bugs. - */ -@SuppressWarnings("PMD.ConstructorCallsOverridableMethod") -//ConstructorCallsOverridableMethod: it's ok as we need to setTypeface -public class TypefacedTextView extends AppCompatTextView { - - private static final int SIZE_THRESHOLD = 10000; - - private static final int UNSPECIFIED_MEASURE_SPEC = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); - private static final int START_SCALABLE_DIFFERENCE = 4; - - private static boolean inDebugMode; - - /** - * Enables debugging features like checking attributes on inflation. - */ - public static void setInDebugMode() { - inDebugMode = true; - } - - private boolean constructed; - @NonNull - private LineStrategy lineStrategy = LineStrategy.SINGLE_LINE_ELLIPSIZE; - - public TypefacedTextView(@NonNull final Context context) { - super(context); - initialize(context, null); - } - - public TypefacedTextView(@NonNull final Context context, @Nullable final AttributeSet attrs) { - super(context, attrs); - initialize(context, attrs); - } - - public TypefacedTextView(@NonNull final Context context, @Nullable final AttributeSet attrs, final int defStyle) { - super(context, attrs, defStyle); - initialize(context, attrs); - } - - private void initialize(@NonNull final Context context, @Nullable final AttributeSet attrs) { - constructed = true; - super.setIncludeFontPadding(false); - if (attrs != null) { - final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.TypefacedTextView); - final LineStrategy lineStrategy = LineStrategy - .byResIndex(typedArray.getInt(R.styleable.TypefacedTextView_lineStrategy, LineStrategy.MULTILINE_ELLIPSIZE.ordinal())); - if (lineStrategy.multiline) { - setLineStrategy(lineStrategy, AttributesUtils.getMaxLinesFromAttrs(context, attrs)); - } else { - setLineStrategy(lineStrategy); - } - typedArray.recycle(); - if (inDebugMode) { - checkAttributes(context, attrs); - } - } - } - - private void checkAttributes(@NonNull final Context context, @NonNull final AttributeSet attrs) { - final List errors = new ArrayList<>(); - LineStrategy lineStrategy = null; - TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.TypefacedTextView); - AttributesUtils.checkAttribute(typedArray, errors, R.styleable.TypefacedTextView_lineStrategy, true, - "lineStrategy required parameter"); - if (typedArray.hasValue(R.styleable.TypefacedTextView_lineStrategy)) { - lineStrategy = LineStrategy.byResIndex(typedArray.getInt(R.styleable.TypefacedTextView_lineStrategy, -1)); - } - typedArray.recycle(); - - try { - final Class androidRes = Class.forName("com.android.internal.R$styleable"); - - typedArray = context.obtainStyledAttributes(attrs, AttributesUtils.getField(androidRes, "TextView")); - AttributesUtils.checkRegularTextViewAttributes(typedArray, androidRes, errors, "lineStrategy"); - checkTextViewSpecificAttributes(typedArray, androidRes, errors); - - if (lineStrategy != null) { - checkLineStrategyAttributes(typedArray, androidRes, errors, lineStrategy); - } - } catch (final Exception exception) { - Lc.e(exception, "Error during checking attributes"); - } - AttributesUtils.handleErrors(this, errors); - typedArray.recycle(); - } - - private void checkTextViewSpecificAttributes(@NonNull final TypedArray typedArray, @NonNull final Class androidRes, - @NonNull final List errors) - throws NoSuchFieldException, IllegalAccessException { - AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_phoneNumber"), false, - "phoneNumber forbid parameter"); - AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_password"), false, - "password forbid parameter"); - AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_numeric"), false, - "numeric forbid parameter"); - AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_inputType"), false, - "inputType forbid parameter"); - AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_imeOptions"), false, - "imeOptions forbid parameter"); - AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_imeActionId"), false, - "imeActionId forbid parameter"); - AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_imeActionLabel"), false, - "imeActionLabel forbid parameter"); - AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_hint"), false, - "hint forbid parameter"); - AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_editable"), false, - "editable forbid parameter"); - AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_digits"), false, - "digits forbid parameter"); - AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_cursorVisible"), false, - "cursorVisible forbid parameter"); - } - - private void checkLineStrategyAttributes(@NonNull final TypedArray typedArray, @NonNull final Class androidRes, - @NonNull final List errors, @NonNull final LineStrategy lineStrategy) - throws NoSuchFieldException, IllegalAccessException { - if (!lineStrategy.scalable) { - AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_textSize"), true, - "textSize required parameter. If it's dynamic then use '0sp'"); - } - if (lineStrategy.multiline) { - if (typedArray.getInt(AttributesUtils.getField(androidRes, "TextView_lines"), -1) == 1) { - errors.add("lines should be more than 1 if lineStrategy is true"); - } - if (typedArray.getInt(AttributesUtils.getField(androidRes, "TextView_maxLines"), -1) == 1) { - errors.add("maxLines should be more than 1 if lineStrategy is true"); - } - } else { - AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_lines"), false, - "remove lines and use lineStrategy"); - AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_maxLines"), false, - "remove maxLines and use lineStrategy"); - AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_minLines"), false, - "remove minLines and use lineStrategy"); - AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_textAllCaps"), false, - "remove textAllCaps and use app:textAllCaps"); - } - } - - /** - * Sets behavior of text if there is no space for it in one line. - * - * @param lineStrategy Specific {@link LineStrategy}. - */ - public void setLineStrategy(@NonNull final LineStrategy lineStrategy) { - setLineStrategy(lineStrategy, Integer.MAX_VALUE); - } - - /** - * Sets behavior of text if there is no space for it in one line. - * - * @param lineStrategy Specific {@link LineStrategy}; - * @param maxLines Max lines if line strategy is multiline. - */ - public void setLineStrategy(@NonNull final LineStrategy lineStrategy, final int maxLines) { - this.lineStrategy = lineStrategy; - final TransformationMethod transformationMethod = getTransformationMethod(); - super.setSingleLine(!lineStrategy.multiline); - if (transformationMethod != null) { - /*DEBUG if (!(transformationMethod instanceof SingleLineTransformationMethod)) { - Lc.w("SingleLineTransformationMethod method ignored because of previous transformation method: " + transformationMethod); - }*/ - setTransformationMethod(transformationMethod); - } - if (lineStrategy.multiline) { - super.setMaxLines(maxLines); - } - switch (lineStrategy) { - case SINGLE_LINE_ELLIPSIZE: - case MULTILINE_ELLIPSIZE: - super.setEllipsize(TextUtils.TruncateAt.END); - break; - case SINGLE_LINE_MARQUEE: - case MULTILINE_MARQUEE: - super.setEllipsize(TextUtils.TruncateAt.MARQUEE); - break; - case SINGLE_LINE_AUTO_SCALE: - super.setEllipsize(null); - break; - case SINGLE_LINE_ELLIPSIZE_MIDDLE: - case MULTILINE_ELLIPSIZE_MIDDLE: - super.setEllipsize(TextUtils.TruncateAt.MIDDLE); - break; - default: - Lc.assertion("Unknown line strategy: " + lineStrategy); - break; - } - if (lineStrategy.scalable) { - requestLayout(); - } - } - - /** - * Returns behavior of text if there is no space for it in one line. - * - * @return Specific {@link LineStrategy}. - */ - @NonNull - public LineStrategy getLineStrategy() { - return lineStrategy; - } - - @Override - public void setSingleLine() { - if (!constructed) { - return; - } - Lc.assertion(new IllegalStateException(AttributesUtils.viewError(this, "Do not specify setSingleLine use setLineStrategy instead"))); - } - - @Override - public void setSingleLine(final boolean singleLine) { - if (!constructed) { - return; - } - Lc.assertion(new IllegalStateException(AttributesUtils.viewError(this, "Do not specify setSingleLine use setLineStrategy instead"))); - } - - @Override - public void setLines(final int lines) { - if (constructed && lineStrategy.multiline && lines == 1) { - Lc.assertion(new IllegalStateException(AttributesUtils.viewError(this, "lines = 1 is illegal if lineStrategy is multiline"))); - return; - } - super.setLines(lines); - } - - @Override - public void setMaxLines(final int maxLines) { - if (constructed && !lineStrategy.multiline && maxLines > 1) { - Lc.assertion(new IllegalStateException( - AttributesUtils.viewError(this, "maxLines > 1 is illegal if lineStrategy is single line"))); - return; - } - super.setMaxLines(maxLines); - } - - @Override - public final void setIncludeFontPadding(final boolean includeFontPadding) { - if (!constructed) { - return; - } - Lc.assertion(new IllegalStateException( - AttributesUtils.viewError(this, "Do not specify font padding as it is hard to make pixel-perfect design with such option"))); - } - - @Override - public void setMinLines(final int minLines) { - if (constructed && !lineStrategy.multiline && minLines > 1) { - Lc.assertion(new IllegalStateException( - AttributesUtils.viewError(this, "minLines > 1 is illegal if lineStrategy is single line"))); - return; - } - super.setMinLines(minLines); - } - - @Override - public void setEllipsize(@NonNull final TextUtils.TruncateAt ellipsize) { - if (!constructed) { - return; - } - Lc.assertion(new IllegalStateException(AttributesUtils.viewError(this, "Do not specify ellipsize use setLineStrategy instead"))); - } - - @Override - public void setText(@Nullable final CharSequence text, @Nullable final BufferType type) { - super.setText(text, type); - if (constructed && lineStrategy.scalable) { - requestLayout(); - } - } - - @Override - public void setTextSize(final float size) { - if (constructed && lineStrategy.scalable) { - Lc.cutAssertion(new IllegalStateException(AttributesUtils.viewError(this, "textSize call is illegal if lineStrategy is scalable"))); - return; - } - super.setTextSize(size); - } - - @Override - public void setTextSize(final int unit, final float size) { - if (constructed && lineStrategy.scalable) { - Lc.assertion(new IllegalStateException(AttributesUtils.viewError(this, "textSize call is illegal if lineStrategy is scalable"))); - return; - } - super.setTextSize(unit, size); - } - - @SuppressLint("WrongCall") - //WrongCall: actually this method is always calling from onMeasure - private void computeScalableTextSize(final int maxWidth, final int maxHeight) { - final int minDifference = (int) UiUtils.OfMetrics.dpToPixels(getContext(), 1); - int difference = (int) UiUtils.OfMetrics.dpToPixels(getContext(), START_SCALABLE_DIFFERENCE); - ScaleAction scaleAction = ScaleAction.DO_NOTHING; - ScaleAction previousScaleAction = ScaleAction.DO_NOTHING; - do { - switch (scaleAction) { - case SCALE_DOWN: - if (difference > minDifference) { - difference -= minDifference; - super.setTextSize(TypedValue.COMPLEX_UNIT_PX, Math.max(0, getTextSize() - difference)); - } else { - super.setTextSize(TypedValue.COMPLEX_UNIT_PX, Math.max(0, getTextSize() - minDifference)); - if (previousScaleAction == ScaleAction.SCALE_UP) { - return; - } - } - break; - case SCALE_UP: - if (difference > minDifference) { - difference -= minDifference; - super.setTextSize(TypedValue.COMPLEX_UNIT_PX, Math.max(0, getTextSize() + difference)); - } else { - if (previousScaleAction == ScaleAction.SCALE_DOWN) { - return; - } - super.setTextSize(TypedValue.COMPLEX_UNIT_PX, Math.max(0, getTextSize() + minDifference)); - } - break; - case DO_NOTHING: - default: - break; - } - super.onMeasure(UNSPECIFIED_MEASURE_SPEC, UNSPECIFIED_MEASURE_SPEC); - previousScaleAction = scaleAction; - scaleAction = computeScaleAction(maxWidth, maxHeight); - } - while (scaleAction != ScaleAction.DO_NOTHING); - } - - @NonNull - private ScaleAction computeScaleAction(final int maxWidth, final int maxHeight) { - ScaleAction result = ScaleAction.DO_NOTHING; - if (maxWidth < getMeasuredWidth()) { - result = ScaleAction.SCALE_DOWN; - } else if (maxWidth > getMeasuredWidth()) { - result = ScaleAction.SCALE_UP; - } - - if (maxHeight < getMeasuredHeight()) { - result = ScaleAction.SCALE_DOWN; - } else if (maxHeight > getMeasuredHeight() && result != ScaleAction.SCALE_DOWN) { - result = ScaleAction.SCALE_UP; - } - return result; - } - - @Override - protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { - final int maxWidth = MeasureSpec.getSize(widthMeasureSpec); - final int maxHeight = MeasureSpec.getSize(heightMeasureSpec); - if (!constructed || !lineStrategy.scalable || (maxWidth <= 0 && maxHeight <= 0) || TextUtils.isEmpty(getText())) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - return; - } - - computeScalableTextSize(MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED ? maxWidth : SIZE_THRESHOLD, - MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.UNSPECIFIED ? maxHeight : SIZE_THRESHOLD); - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - - private enum ScaleAction { - SCALE_DOWN, - SCALE_UP, - DO_NOTHING - } - - /** - * Specific behavior, mostly based on combination of {@link #getEllipsize()} and {@link #getMaxLines()} to specify how view should show text - * if there is no space for it on one line. - */ - public enum LineStrategy { - - /** - * Not more than one line and ellipsize text with dots at the end. - */ - SINGLE_LINE_ELLIPSIZE(false, false), - /** - * Not more than one line and ellipsize text with marquee at the end. - */ - SINGLE_LINE_MARQUEE(false, false), - /** - * Not more than one line and scale text to maximum possible size. - */ - SINGLE_LINE_AUTO_SCALE(false, true), - /** - * More than one line and ellipsize text with dots at the end. - */ - MULTILINE_ELLIPSIZE(true, false), - /** - * More than one line and ellipsize text with marquee at the end. - */ - MULTILINE_MARQUEE(true, false), - /** - * Not more than one line and ellipsize text with dots in the middle. - */ - SINGLE_LINE_ELLIPSIZE_MIDDLE(false, false), - /** - * More than one line and ellipsize text with dots in the middle. - */ - MULTILINE_ELLIPSIZE_MIDDLE(true, false); - - @NonNull - public static LineStrategy byResIndex(final int resIndex) { - if (resIndex < 0 || resIndex >= values().length) { - Lc.assertion("Unexpected resIndex " + resIndex); - return MULTILINE_ELLIPSIZE; - } - return values()[resIndex]; - } - - private final boolean multiline; - private final boolean scalable; - - LineStrategy(final boolean multiline, final boolean scalable) { - this.multiline = multiline; - this.scalable = scalable; - } - - } - -} diff --git a/src/main/java/ru/touchin/roboswag/components/views/internal/AttributesUtils.java b/src/main/java/ru/touchin/roboswag/components/views/internal/AttributesUtils.java deleted file mode 100644 index 6965e72..0000000 --- a/src/main/java/ru/touchin/roboswag/components/views/internal/AttributesUtils.java +++ /dev/null @@ -1,164 +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.roboswag.components.views.internal; - -import android.content.Context; -import android.content.res.TypedArray; -import android.support.annotation.NonNull; -import android.support.annotation.StyleableRes; -import android.text.InputType; -import android.text.TextUtils; -import android.util.AttributeSet; -import android.view.View; - -import java.lang.reflect.Field; -import java.util.Collection; - -import ru.touchin.roboswag.components.utils.UiUtils; -import ru.touchin.roboswag.core.log.Lc; -import ru.touchin.roboswag.core.utils.ShouldNotHappenException; - -/** - * Created by Gavriil Sitnikov on 13/06/2016. - * Bunch of inner helper library methods to validate attributes of custom views. - */ -public final class AttributesUtils { - - /** - * Gets static field of class. - * - * @param resourcesClass Class to get field from; - * @param fieldName name of field; - * @param Type of object that is stored in field; - * @return Field value; - * @throws NoSuchFieldException Throws on reflection call; - * @throws IllegalAccessException Throws on reflection call. - */ - @NonNull - @SuppressWarnings("unchecked") - public static T getField(@NonNull final Class resourcesClass, @NonNull final String fieldName) - throws NoSuchFieldException, IllegalAccessException { - final Field field = resourcesClass.getDeclaredField(fieldName); - field.setAccessible(true); - return (T) field.get(null); - } - - /** - * Checks if attribute is in array or not and collecterror if attribute missed. - * - * @param typedArray Array of attributes; - * @param errors Errors to collect into; - * @param resourceId Id of attribute; - * @param required Is parameter have to be in array OR it have not to be in; - * @param description Description of error. - */ - public static void checkAttribute(@NonNull final TypedArray typedArray, - @NonNull final Collection errors, - @StyleableRes final int resourceId, - final boolean required, - @NonNull final String description) { - if ((required && typedArray.hasValue(resourceId)) - || (!required && !typedArray.hasValue(resourceId))) { - return; - } - errors.add(description); - } - - /** - * Collects regular {@link android.widget.TextView} errors. - * - * @param typedArray Array of attributes; - * @param androidRes Class of styleable attributes; - * @param errors Errors to collect into; - * @param lineStrategyParameterName name of line strategy parameter; - * @throws NoSuchFieldException Throws during getting attribute values through reflection; - * @throws IllegalAccessException Throws during getting attribute values through reflection. - */ - public static void checkRegularTextViewAttributes(@NonNull final TypedArray typedArray, @NonNull final Class androidRes, - @NonNull final Collection errors, @NonNull final String lineStrategyParameterName) - throws NoSuchFieldException, IllegalAccessException { - checkAttribute(typedArray, errors, getField(androidRes, "TextView_fontFamily"), true, "fontFamily required parameter"); - checkAttribute(typedArray, errors, getField(androidRes, "TextView_includeFontPadding"), false, "includeFontPadding forbid parameter"); - checkAttribute(typedArray, errors, getField(androidRes, "TextView_singleLine"), false, - "remove singleLine and use " + lineStrategyParameterName); - checkAttribute(typedArray, errors, getField(androidRes, "TextView_ellipsize"), false, - "remove ellipsize and use " + lineStrategyParameterName); - checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_textColor"), true, - "textColor required parameter. If it's dynamic then use 'android:color/transparent'"); - } - - /** - * Inner helper library method to merge errors in string and assert it. - * - * @param view View with errors; - * @param errors Errors of view. - */ - public static void handleErrors(@NonNull final View view, @NonNull final Collection errors) { - if (!errors.isEmpty()) { - final String exceptionText = viewError(view, TextUtils.join("\n", errors)); - Lc.cutAssertion(new ShouldNotHappenException(exceptionText)); - } - } - - /** - * Returns max lines attribute value for views extended from {@link android.widget.TextView}. - * - * @param context Context of attributes; - * @param attrs TextView based attributes; - * @return Max lines value. - */ - public static int getMaxLinesFromAttrs(@NonNull final Context context, @NonNull final AttributeSet attrs) { - try { - final Class androidRes = Class.forName("com.android.internal.R$styleable"); - final TypedArray typedArray = context.obtainStyledAttributes(attrs, AttributesUtils.getField(androidRes, "TextView")); - final int result = typedArray.getInt(AttributesUtils.getField(androidRes, "TextView_maxLines"), Integer.MAX_VALUE); - typedArray.recycle(); - return result; - } catch (final Exception exception) { - return Integer.MAX_VALUE; - } - } - - /** - * Creates readable view error. - * - * @param view View of error; - * @param errorText Text of error; - * @return Readable error string. - */ - @NonNull - public static String viewError(@NonNull final View view, @NonNull final String errorText) { - return "Errors for view id=" + UiUtils.OfViews.getViewIdString(view) + ":\n" + errorText; - } - - /** - * Returns true if input type equals number input type. - * - * @param inputType Input type to check; - * @return true if input type equals number input type. - */ - public static boolean isNumberInputType(final int inputType) { - return inputType == InputType.TYPE_CLASS_NUMBER || inputType == InputType.TYPE_DATETIME_VARIATION_NORMAL; - } - - private AttributesUtils() { - } - -} diff --git a/src/main/res/anim/fragment_slide_in_left_paralax_animation.xml b/src/main/res/anim/fragment_slide_in_left_paralax_animation.xml deleted file mode 100644 index 2a638ac..0000000 --- a/src/main/res/anim/fragment_slide_in_left_paralax_animation.xml +++ /dev/null @@ -1,6 +0,0 @@ - diff --git a/src/main/res/anim/fragment_slide_in_right_animation.xml b/src/main/res/anim/fragment_slide_in_right_animation.xml deleted file mode 100644 index 76be496..0000000 --- a/src/main/res/anim/fragment_slide_in_right_animation.xml +++ /dev/null @@ -1,6 +0,0 @@ - diff --git a/src/main/res/anim/fragment_slide_out_left_paralax_animation.xml b/src/main/res/anim/fragment_slide_out_left_paralax_animation.xml deleted file mode 100644 index 67b35b0..0000000 --- a/src/main/res/anim/fragment_slide_out_left_paralax_animation.xml +++ /dev/null @@ -1,6 +0,0 @@ - diff --git a/src/main/res/anim/fragment_slide_out_right_animation.xml b/src/main/res/anim/fragment_slide_out_right_animation.xml deleted file mode 100644 index 9757e23..0000000 --- a/src/main/res/anim/fragment_slide_out_right_animation.xml +++ /dev/null @@ -1,6 +0,0 @@ - diff --git a/src/main/res/anim/global_fade_in_animation.xml b/src/main/res/anim/global_fade_in_animation.xml deleted file mode 100644 index 200a2bf..0000000 --- a/src/main/res/anim/global_fade_in_animation.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - \ No newline at end of file diff --git a/src/main/res/anim/global_fade_out_animation.xml b/src/main/res/anim/global_fade_out_animation.xml deleted file mode 100644 index 472546a..0000000 --- a/src/main/res/anim/global_fade_out_animation.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - \ No newline at end of file diff --git a/src/main/res/anim/global_slide_in_left_animation.xml b/src/main/res/anim/global_slide_in_left_animation.xml deleted file mode 100644 index 9ddb859..0000000 --- a/src/main/res/anim/global_slide_in_left_animation.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - \ No newline at end of file diff --git a/src/main/res/anim/global_slide_in_right_animation.xml b/src/main/res/anim/global_slide_in_right_animation.xml deleted file mode 100644 index ac7f27a..0000000 --- a/src/main/res/anim/global_slide_in_right_animation.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - \ No newline at end of file diff --git a/src/main/res/anim/global_slide_out_left_animation.xml b/src/main/res/anim/global_slide_out_left_animation.xml deleted file mode 100644 index 5bc1921..0000000 --- a/src/main/res/anim/global_slide_out_left_animation.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - \ No newline at end of file diff --git a/src/main/res/anim/global_slide_out_right_animation.xml b/src/main/res/anim/global_slide_out_right_animation.xml deleted file mode 100644 index 0839498..0000000 --- a/src/main/res/anim/global_slide_out_right_animation.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - \ No newline at end of file diff --git a/src/main/res/drawable-v21/global_dark_selector.xml b/src/main/res/drawable-v21/global_dark_selector.xml deleted file mode 100644 index 847128c..0000000 --- a/src/main/res/drawable-v21/global_dark_selector.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/src/main/res/drawable-v21/global_light_selector.xml b/src/main/res/drawable-v21/global_light_selector.xml deleted file mode 100644 index b0932fd..0000000 --- a/src/main/res/drawable-v21/global_light_selector.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/src/main/res/drawable/global_dark_selector.xml b/src/main/res/drawable/global_dark_selector.xml deleted file mode 100644 index 1a825ed..0000000 --- a/src/main/res/drawable/global_dark_selector.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/src/main/res/drawable/global_light_selector.xml b/src/main/res/drawable/global_light_selector.xml deleted file mode 100644 index 39c33f0..0000000 --- a/src/main/res/drawable/global_light_selector.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/src/main/res/values/attrs.xml b/src/main/res/values/attrs.xml deleted file mode 100644 index 7320080..0000000 --- a/src/main/res/values/attrs.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/res/values/integers.xml b/src/main/res/values/integers.xml deleted file mode 100644 index 93ec0fc..0000000 --- a/src/main/res/values/integers.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - 250 - diff --git a/storable/.gitignore b/storable/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/storable/.gitignore @@ -0,0 +1 @@ +/build diff --git a/storable/build.gradle b/storable/build.gradle new file mode 100644 index 0000000..2a6be21 --- /dev/null +++ b/storable/build.gradle @@ -0,0 +1,24 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion versions.compileSdk + + defaultConfig { + minSdkVersion 16 + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + api project(":utils") + api project(":logging") + + implementation "com.android.support:support-annotations:$versions.supportLibrary" + + implementation "io.reactivex.rxjava2:rxjava:$versions.rxJava" + implementation "io.reactivex.rxjava2:rxandroid:$versions.rxAndroid" +} diff --git a/storable/src/main/AndroidManifest.xml b/storable/src/main/AndroidManifest.xml new file mode 100644 index 0000000..fd2722a --- /dev/null +++ b/storable/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/src/main/java/ru/touchin/roboswag/components/utils/storables/PreferenceStore.java b/storable/src/main/java/ru/touchin/roboswag/components/utils/storables/PreferenceStore.java similarity index 99% rename from src/main/java/ru/touchin/roboswag/components/utils/storables/PreferenceStore.java rename to storable/src/main/java/ru/touchin/roboswag/components/utils/storables/PreferenceStore.java index caa718a..73d90bf 100644 --- a/src/main/java/ru/touchin/roboswag/components/utils/storables/PreferenceStore.java +++ b/storable/src/main/java/ru/touchin/roboswag/components/utils/storables/PreferenceStore.java @@ -31,7 +31,6 @@ import ru.touchin.roboswag.core.utils.Optional; import io.reactivex.Completable; import io.reactivex.Single; - /** * Created by Gavriil Sitnikov on 18/03/16. * Store based on {@link SharedPreferences} for {@link ru.touchin.roboswag.core.observables.storable.Storable}. diff --git a/src/main/java/ru/touchin/roboswag/components/utils/storables/PreferenceUtils.java b/storable/src/main/java/ru/touchin/roboswag/components/utils/storables/PreferenceUtils.java similarity index 61% rename from src/main/java/ru/touchin/roboswag/components/utils/storables/PreferenceUtils.java rename to storable/src/main/java/ru/touchin/roboswag/components/utils/storables/PreferenceUtils.java index 2ee1db9..5ae4521 100644 --- a/src/main/java/ru/touchin/roboswag/components/utils/storables/PreferenceUtils.java +++ b/storable/src/main/java/ru/touchin/roboswag/components/utils/storables/PreferenceUtils.java @@ -45,9 +45,13 @@ public final class PreferenceUtils { */ @NonNull public static Storable stringStorable(@NonNull final String name, @NonNull final SharedPreferences preferences) { - return new Storable.Builder(name, String.class, - String.class, new PreferenceStore<>(preferences), new SameTypesConverter<>()) - .build(); + return new Storable.Builder( + name, + String.class, + String.class, + new PreferenceStore(preferences), + new SameTypesConverter<>() + ).build(); } /** @@ -59,13 +63,18 @@ public final class PreferenceUtils { * @return {@link Storable} for string. */ @NonNull - public static NonNullStorable stringStorable(@NonNull final String name, - @NonNull final SharedPreferences preferences, - @NonNull final String defaultValue) { - return new Storable.Builder(name, String.class, - String.class, new PreferenceStore<>(preferences), new SameTypesConverter<>()) - .setDefaultValue(defaultValue) - .build(); + public static NonNullStorable stringStorable( + @NonNull final String name, + @NonNull final SharedPreferences preferences, + @NonNull final String defaultValue + ) { + return new Storable.Builder( + name, + String.class, + String.class, + new PreferenceStore(preferences), + new SameTypesConverter<>() + ).setDefaultValue(defaultValue).build(); } /** @@ -77,9 +86,13 @@ public final class PreferenceUtils { */ @NonNull public static Storable longStorable(@NonNull final String name, @NonNull final SharedPreferences preferences) { - return new Storable.Builder(name, Long.class, - Long.class, new PreferenceStore<>(preferences), new SameTypesConverter<>()) - .build(); + return new Storable.Builder( + name, + Long.class, + Long.class, + new PreferenceStore(preferences), + new SameTypesConverter<>() + ).build(); } /** @@ -91,13 +104,18 @@ public final class PreferenceUtils { * @return {@link Storable} for long. */ @NonNull - public static NonNullStorable longStorable(@NonNull final String name, - @NonNull final SharedPreferences preferences, - final long defaultValue) { - return new Storable.Builder(name, Long.class, - Long.class, new PreferenceStore<>(preferences), new SameTypesConverter<>()) - .setDefaultValue(defaultValue) - .build(); + public static NonNullStorable longStorable( + @NonNull final String name, + @NonNull final SharedPreferences preferences, + final long defaultValue + ) { + return new Storable.Builder( + name, + Long.class, + Long.class, + new PreferenceStore(preferences), + new SameTypesConverter<>() + ).setDefaultValue(defaultValue).build(); } /** @@ -109,9 +127,13 @@ public final class PreferenceUtils { */ @NonNull public static Storable booleanStorable(@NonNull final String name, @NonNull final SharedPreferences preferences) { - return new Storable.Builder(name, Boolean.class, - Boolean.class, new PreferenceStore<>(preferences), new SameTypesConverter<>()) - .build(); + return new Storable.Builder( + name, + Boolean.class, + Boolean.class, + new PreferenceStore(preferences), + new SameTypesConverter<>() + ).build(); } /** @@ -123,13 +145,18 @@ public final class PreferenceUtils { * @return {@link Storable} for boolean. */ @NonNull - public static NonNullStorable booleanStorable(@NonNull final String name, - @NonNull final SharedPreferences preferences, - final boolean defaultValue) { - return new Storable.Builder(name, Boolean.class, - Boolean.class, new PreferenceStore<>(preferences), new SameTypesConverter<>()) - .setDefaultValue(defaultValue) - .build(); + public static NonNullStorable booleanStorable( + @NonNull final String name, + @NonNull final SharedPreferences preferences, + final boolean defaultValue + ) { + return new Storable.Builder( + name, + Boolean.class, + Boolean.class, + new PreferenceStore(preferences), + new SameTypesConverter<>() + ).setDefaultValue(defaultValue).build(); } /** @@ -141,9 +168,13 @@ public final class PreferenceUtils { */ @NonNull public static Storable integerStorable(@NonNull final String name, @NonNull final SharedPreferences preferences) { - return new Storable.Builder(name, Integer.class, - Integer.class, new PreferenceStore<>(preferences), new SameTypesConverter<>()) - .build(); + return new Storable.Builder( + name, + Integer.class, + Integer.class, + new PreferenceStore(preferences), + new SameTypesConverter<>() + ).build(); } /** @@ -155,13 +186,18 @@ public final class PreferenceUtils { * @return {@link Storable} for integer. */ @NonNull - public static NonNullStorable integerStorable(@NonNull final String name, - @NonNull final SharedPreferences preferences, - final int defaultValue) { - return new Storable.Builder(name, Integer.class, - Integer.class, new PreferenceStore<>(preferences), new SameTypesConverter<>()) - .setDefaultValue(defaultValue) - .build(); + public static NonNullStorable integerStorable( + @NonNull final String name, + @NonNull final SharedPreferences preferences, + final int defaultValue + ) { + return new Storable.Builder( + name, + Integer.class, + Integer.class, + new PreferenceStore(preferences), + new SameTypesConverter<>() + ).setDefaultValue(defaultValue).build(); } /** @@ -173,9 +209,13 @@ public final class PreferenceUtils { */ @NonNull public static Storable floatStorable(@NonNull final String name, @NonNull final SharedPreferences preferences) { - return new Storable.Builder(name, Float.class, - Float.class, new PreferenceStore<>(preferences), new SameTypesConverter<>()) - .build(); + return new Storable.Builder( + name, + Float.class, + Float.class, + new PreferenceStore(preferences), + new SameTypesConverter<>() + ).build(); } /** @@ -187,13 +227,18 @@ public final class PreferenceUtils { * @return {@link Storable} for float. */ @NonNull - public static NonNullStorable floatStorable(@NonNull final String name, - @NonNull final SharedPreferences preferences, - final float defaultValue) { - return new Storable.Builder(name, Float.class, - Float.class, new PreferenceStore<>(preferences), new SameTypesConverter<>()) - .setDefaultValue(defaultValue) - .build(); + public static NonNullStorable floatStorable( + @NonNull final String name, + @NonNull final SharedPreferences preferences, + final float defaultValue + ) { + return new Storable.Builder( + name, + Float.class, + Float.class, + new PreferenceStore(preferences), + new SameTypesConverter<>() + ).setDefaultValue(defaultValue).build(); } /** @@ -204,12 +249,18 @@ public final class PreferenceUtils { * @return {@link Storable} for enum. */ @NonNull - public static > Storable enumStorable(@NonNull final String name, - @NonNull final Class enumClass, - @NonNull final SharedPreferences preferences) { - return new Storable.Builder(name, enumClass, - String.class, new PreferenceStore<>(preferences), new EnumToStringConverter<>()) - .build(); + public static > Storable enumStorable( + @NonNull final String name, + @NonNull final Class enumClass, + @NonNull final SharedPreferences preferences + ) { + return new Storable.Builder( + name, + enumClass, + String.class, + new PreferenceStore(preferences), + new EnumToStringConverter<>() + ).build(); } /** @@ -221,14 +272,19 @@ public final class PreferenceUtils { * @return {@link Storable} for enum. */ @NonNull - public static > NonNullStorable enumStorable(@NonNull final String name, - @NonNull final Class enumClass, - @NonNull final SharedPreferences preferences, - @NonNull final T defaultValue) { - return new Storable.Builder(name, enumClass, - String.class, new PreferenceStore<>(preferences), new EnumToStringConverter<>()) - .setDefaultValue(defaultValue) - .build(); + public static > NonNullStorable enumStorable( + @NonNull final String name, + @NonNull final Class enumClass, + @NonNull final SharedPreferences preferences, + @NonNull final T defaultValue + ) { + return new Storable.Builder( + name, + enumClass, + String.class, + new PreferenceStore(preferences), + new EnumToStringConverter<>() + ).setDefaultValue(defaultValue).build(); } private PreferenceUtils() { diff --git a/storable/src/main/java/ru/touchin/roboswag/core/observables/ObservableRefCountWithCacheTime.java b/storable/src/main/java/ru/touchin/roboswag/core/observables/ObservableRefCountWithCacheTime.java new file mode 100644 index 0000000..6217204 --- /dev/null +++ b/storable/src/main/java/ru/touchin/roboswag/core/observables/ObservableRefCountWithCacheTime.java @@ -0,0 +1,299 @@ +/* + Copyright (c) 2016-present, RxJava Contributors. +

+ 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.roboswag.core.observables; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.ReentrantLock; + +import io.reactivex.Observable; +import io.reactivex.ObservableSource; +import io.reactivex.Observer; +import io.reactivex.Scheduler; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.disposables.Disposable; +import io.reactivex.disposables.Disposables; +import io.reactivex.functions.Consumer; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.fuseable.HasUpstreamObservableSource; +import io.reactivex.observables.ConnectableObservable; +import io.reactivex.schedulers.Schedulers; + +/** + * Returns an observable sequence that stays connected to the source as long as + * there is at least one subscription to the observable sequence. + * + * @param the value type + */ +@SuppressWarnings({"PMD.CompareObjectsWithEquals", "PMD.AvoidUsingVolatile"}) +//AvoidUsingVolatile: it's RxJava code +public final class ObservableRefCountWithCacheTime extends Observable implements HasUpstreamObservableSource { + + @NonNull + private final ConnectableObservable connectableSource; + @NonNull + private final ObservableSource actualSource; + + @NonNull + private volatile CompositeDisposable baseDisposable = new CompositeDisposable(); + + @NonNull + private final AtomicInteger subscriptionCount = new AtomicInteger(); + + /** + * Use this lock for every subscription and disconnect action. + */ + @NonNull + private final ReentrantLock lock = new ReentrantLock(); + + @NonNull + private final Scheduler scheduler = Schedulers.computation(); + private final long cacheTime; + @NonNull + private final TimeUnit cacheTimeUnit; + @Nullable + private Scheduler.Worker worker; + + /** + * Constructor. + * + * @param source observable to apply ref count to + */ + public ObservableRefCountWithCacheTime(@NonNull final ConnectableObservable source, + final long cacheTime, @NonNull final TimeUnit cacheTimeUnit) { + super(); + this.connectableSource = source; + this.actualSource = source; + this.cacheTime = cacheTime; + this.cacheTimeUnit = cacheTimeUnit; + } + + @NonNull + public ObservableSource source() { + return actualSource; + } + + private void cleanupWorker() { + if (worker != null) { + worker.dispose(); + worker = null; + } + } + + @Override + public void subscribeActual(@NonNull final Observer subscriber) { + + lock.lock(); + if (subscriptionCount.incrementAndGet() == 1) { + cleanupWorker(); + final AtomicBoolean writeLocked = new AtomicBoolean(true); + + try { + // need to use this overload of connect to ensure that + // baseDisposable is set in the case that source is a + // synchronous Observable + connectableSource.connect(onSubscribe(subscriber, writeLocked)); + } finally { + // need to cover the case where the source is subscribed to + // outside of this class thus preventing the Action1 passed + // to source.connect above being called + if (writeLocked.get()) { + // Action1 passed to source.connect was not called + lock.unlock(); + } + } + } else { + try { + // ready to subscribe to source so do it + doSubscribe(subscriber, baseDisposable); + } finally { + // release the read lock + lock.unlock(); + } + } + + } + + @NonNull + private Consumer onSubscribe(@NonNull final Observer observer, @NonNull final AtomicBoolean writeLocked) { + return new DisposeConsumer(observer, writeLocked); + } + + private void doSubscribe(@NonNull final Observer observer, @NonNull final CompositeDisposable currentBase) { + // handle disposing from the base CompositeDisposable + final Disposable disposable = disconnect(currentBase); + + final ConnectionObserver connectionObserver = new ConnectionObserver(observer, currentBase, disposable); + observer.onSubscribe(connectionObserver); + + connectableSource.subscribe(connectionObserver); + } + + @NonNull + private Disposable disconnect(@NonNull final CompositeDisposable current) { + return Disposables.fromRunnable(new DisposeTask(current)); + } + + private final class ConnectionObserver extends AtomicReference implements Observer, Disposable { + + private static final long serialVersionUID = 3813126992133394324L; + + @NonNull + private final Observer subscriber; + @NonNull + private final CompositeDisposable currentBase; + @NonNull + private final Disposable resource; + + public ConnectionObserver(@NonNull final Observer subscriber, @NonNull final CompositeDisposable currentBase, + @NonNull final Disposable resource) { + super(); + this.subscriber = subscriber; + this.currentBase = currentBase; + this.resource = resource; + } + + @Override + public void onSubscribe(@NonNull final Disposable disposable) { + DisposableHelper.setOnce(this, disposable); + } + + @Override + public void onError(@NonNull final Throwable throwable) { + cleanup(); + subscriber.onError(throwable); + } + + @Override + public void onNext(@NonNull final T item) { + subscriber.onNext(item); + } + + @Override + public void onComplete() { + cleanup(); + subscriber.onComplete(); + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + resource.dispose(); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + private void cleanup() { + // on error or completion we need to dispose the base CompositeDisposable + // and set the subscriptionCount to 0 + lock.lock(); + try { + if (baseDisposable == currentBase) { + cleanupWorker(); + if (connectableSource instanceof Disposable) { + ((Disposable) connectableSource).dispose(); + } + + baseDisposable.dispose(); + baseDisposable = new CompositeDisposable(); + subscriptionCount.set(0); + } + } finally { + lock.unlock(); + } + } + + } + + private final class DisposeConsumer implements Consumer { + + @NonNull + private final Observer observer; + @NonNull + private final AtomicBoolean writeLocked; + + public DisposeConsumer(@NonNull final Observer observer, @NonNull final AtomicBoolean writeLocked) { + this.observer = observer; + this.writeLocked = writeLocked; + } + + @Override + public void accept(@NonNull final Disposable subscription) { + try { + baseDisposable.add(subscription); + // ready to subscribe to source so do it + doSubscribe(observer, baseDisposable); + } finally { + // release the write lock + lock.unlock(); + writeLocked.set(false); + } + } + + } + + private final class DisposeTask implements Runnable { + + @NonNull + private final CompositeDisposable current; + + public DisposeTask(@NonNull final CompositeDisposable current) { + this.current = current; + } + + @Override + public void run() { + lock.lock(); + try { + if (baseDisposable == current && subscriptionCount.decrementAndGet() == 0) { + if (worker != null) { + worker.dispose(); + } else { + worker = scheduler.createWorker(); + } + worker.schedule(() -> { + lock.lock(); + try { + if (subscriptionCount.get() == 0) { + cleanupWorker(); + if (connectableSource instanceof Disposable) { + ((Disposable) connectableSource).dispose(); + } + + baseDisposable.dispose(); + // need a new baseDisposable because once + // disposed stays that way + baseDisposable = new CompositeDisposable(); + } + } finally { + lock.unlock(); + } + }, cacheTime, cacheTimeUnit); + } + } finally { + lock.unlock(); + } + } + + } + +} diff --git a/storable/src/main/java/ru/touchin/roboswag/core/observables/storable/BaseStorable.java b/storable/src/main/java/ru/touchin/roboswag/core/observables/storable/BaseStorable.java new file mode 100644 index 0000000..390a024 --- /dev/null +++ b/storable/src/main/java/ru/touchin/roboswag/core/observables/storable/BaseStorable.java @@ -0,0 +1,472 @@ +/* + * 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.roboswag.core.observables.storable; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.lang.reflect.Type; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import io.reactivex.Completable; +import io.reactivex.Observable; +import io.reactivex.Scheduler; +import io.reactivex.Single; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.schedulers.Schedulers; +import io.reactivex.subjects.PublishSubject; +import ru.touchin.roboswag.core.log.LcGroup; +import ru.touchin.roboswag.core.observables.ObservableRefCountWithCacheTime; +import ru.touchin.roboswag.core.utils.ObjectUtils; +import ru.touchin.roboswag.core.utils.Optional; + +/** + * Created by Gavriil Sitnikov on 04/10/2015. + * Base class allows to async access to some store. + * Supports conversion between store and actual value. If it is not needed then use {@link SameTypesConverter} + * Supports migration from specific version to latest by {@link Migration} object. + * Allows to set default value which will be returned if actual value is null. + * Allows to declare specific {@link ObserveStrategy}. + * Also specific {@link Scheduler} could be specified to not create new scheduler per storable. + * + * @param Type of key to identify object; + * @param Type of actual object; + * @param Type of store object. Could be same as {@link TObject}; + * @param Type of actual value operating by Storable. Could be same as {@link TObject}. + */ +public abstract class BaseStorable { + + public static final LcGroup STORABLE_LC_GROUP = new LcGroup("STORABLE"); + + private static final long DEFAULT_CACHE_TIME_MILLIS = TimeUnit.SECONDS.toMillis(5); + + @NonNull + private static ObserveStrategy getDefaultObserveStrategyFor(@NonNull final Type objectType, @NonNull final Type storeObjectType) { + if (objectType instanceof Class && ObjectUtils.isSimpleClass((Class) objectType)) { + return ObserveStrategy.CACHE_ACTUAL_VALUE; + } + if (objectType instanceof Class && ObjectUtils.isSimpleClass((Class) storeObjectType)) { + return ObserveStrategy.CACHE_STORE_VALUE; + } + return ObserveStrategy.NO_CACHE; + } + + @NonNull + private final TKey key; + @NonNull + private final Type objectType; + @NonNull + private final Type storeObjectType; + @NonNull + private final Store store; + @NonNull + private final Converter converter; + @NonNull + private final PublishSubject> newStoreValueEvent = PublishSubject.create(); + @NonNull + private final Observable> storeValueObservable; + @NonNull + private final Observable> valueObservable; + @NonNull + private final Scheduler scheduler; + + public BaseStorable(@NonNull final BuilderCore builderCore) { + this(builderCore.key, builderCore.objectType, builderCore.storeObjectType, + builderCore.store, builderCore.converter, builderCore.observeStrategy, + builderCore.migration, builderCore.defaultValue, builderCore.storeScheduler, builderCore.cacheTimeMillis); + } + + @SuppressWarnings("PMD.ExcessiveParameterList") + //ExcessiveParameterList: that's why we are using builder to create it + private BaseStorable(@NonNull final TKey key, + @NonNull final Type objectType, + @NonNull final Type storeObjectType, + @NonNull final Store store, + @NonNull final Converter converter, + @Nullable final ObserveStrategy observeStrategy, + @Nullable final Migration migration, + @Nullable final TObject defaultValue, + @Nullable final Scheduler storeScheduler, + final long cacheTimeMillis) { + this.key = key; + this.objectType = objectType; + this.storeObjectType = storeObjectType; + this.store = store; + this.converter = converter; + + final ObserveStrategy nonNullObserveStrategy + = observeStrategy != null ? observeStrategy : getDefaultObserveStrategyFor(objectType, storeObjectType); + scheduler = storeScheduler != null ? storeScheduler : Schedulers.from(Executors.newSingleThreadExecutor()); + storeValueObservable + = createStoreValueObservable(nonNullObserveStrategy, migration, defaultValue, cacheTimeMillis); + valueObservable = createValueObservable(storeValueObservable, nonNullObserveStrategy, cacheTimeMillis); + } + + @Nullable + private Optional returnDefaultValueIfNull(@NonNull final Optional storeObject, @Nullable final TObject defaultValue) + throws Converter.ConversionException { + if (storeObject.get() != null || defaultValue == null) { + return storeObject; + } + + try { + return new Optional<>(converter.toStoreObject(objectType, storeObjectType, defaultValue)); + } catch (final Converter.ConversionException exception) { + STORABLE_LC_GROUP.w(exception, "Exception while converting default value of '%s' from '%s' from store %s", + key, defaultValue, store); + throw exception; + } + } + + @NonNull + private Observable> createStoreInitialLoadingObservable(@Nullable final Migration migration) { + final Single> loadObservable = store.loadObject(storeObjectType, key) + .doOnError(throwable -> STORABLE_LC_GROUP.w(throwable, "Exception while trying to load value of '%s' from store %s", key, store)); + return (migration != null ? migration.migrateToLatestVersion(key).andThen(loadObservable) : loadObservable) + .subscribeOn(scheduler) + .observeOn(scheduler) + .toObservable() + .replay(1) + .refCount() + .take(1); + } + + @NonNull + private Observable> createStoreValueObservable(@NonNull final ObserveStrategy observeStrategy, + @Nullable final Migration migration, + @Nullable final TObject defaultValue, + final long cacheTimeMillis) { + final Observable> storeInitialLoadingObservable = createStoreInitialLoadingObservable(migration); + final Observable> result = storeInitialLoadingObservable + .concatWith(newStoreValueEvent) + .map(storeObject -> returnDefaultValueIfNull(storeObject, defaultValue)); + return observeStrategy == ObserveStrategy.CACHE_STORE_VALUE || observeStrategy == ObserveStrategy.CACHE_STORE_AND_ACTUAL_VALUE + ? RxJavaPlugins.onAssembly(new ObservableRefCountWithCacheTime<>(result.replay(1), cacheTimeMillis, TimeUnit.MILLISECONDS)) + : result; + } + + @NonNull + private Observable> createValueObservable(@NonNull final Observable> storeValueObservable, + @NonNull final ObserveStrategy observeStrategy, + final long cacheTimeMillis) { + final Observable> result = storeValueObservable + .map(storeObject -> { + try { + return new Optional<>(converter.toObject(objectType, storeObjectType, storeObject.get())); + } catch (final Converter.ConversionException exception) { + STORABLE_LC_GROUP.w(exception, "Exception while trying to converting value of '%s' from store %s by %s", + key, storeObject, store, converter); + throw exception; + } + }); + return observeStrategy == ObserveStrategy.CACHE_ACTUAL_VALUE || observeStrategy == ObserveStrategy.CACHE_STORE_AND_ACTUAL_VALUE + ? RxJavaPlugins.onAssembly(new ObservableRefCountWithCacheTime<>(result.replay(1), cacheTimeMillis, TimeUnit.MILLISECONDS)) + : result; + } + + /** + * Returns key of value. + * + * @return Unique key. + */ + @NonNull + public TKey getKey() { + return key; + } + + /** + * Returns type of actual object. + * + * @return Type of actual object. + */ + @NonNull + public Type getObjectType() { + return objectType; + } + + /** + * Returns type of store object. + * + * @return Type of store object. + */ + @NonNull + public Type getStoreObjectType() { + return storeObjectType; + } + + /** + * Returns {@link Store} where store class representation of object is storing. + * + * @return Store. + */ + @NonNull + public Store getStore() { + return store; + } + + /** + * Returns {@link Converter} to convert values from store class to actual and back. + * + * @return Converter. + */ + @NonNull + public Converter getConverter() { + return converter; + } + + @NonNull + private Completable internalSet(@Nullable final TObject newValue, final boolean checkForEqualityBeforeSet) { + return (checkForEqualityBeforeSet ? storeValueObservable.firstOrError() : Single.just(new Optional<>(null))) + .observeOn(scheduler) + .flatMapCompletable(oldStoreValue -> { + final TStoreObject newStoreValue; + try { + newStoreValue = converter.toStoreObject(objectType, storeObjectType, newValue); + } catch (final Converter.ConversionException exception) { + STORABLE_LC_GROUP.w(exception, "Exception while trying to store value of '%s' from store %s by %s", + key, newValue, store, converter); + return Completable.error(exception); + } + if (checkForEqualityBeforeSet && ObjectUtils.equals(newStoreValue, oldStoreValue.get())) { + return Completable.complete(); + } + return store.storeObject(storeObjectType, key, newStoreValue) + .doOnError(throwable -> STORABLE_LC_GROUP.w(throwable, + "Exception while trying to store value of '%s' from store %s by %s", + key, newValue, store, converter)) + .observeOn(scheduler) + .andThen(Completable.fromAction(() -> { + newStoreValueEvent.onNext(new Optional<>(newStoreValue)); + if (checkForEqualityBeforeSet) { + STORABLE_LC_GROUP.i("Value of '%s' changed from '%s' to '%s'", key, oldStoreValue, newStoreValue); + } else { + STORABLE_LC_GROUP.i("Value of '%s' force changed to '%s'", key, newStoreValue); + } + })); + }); + } + + /** + * Creates observable which is async setting value to store. + * It is not checking if stored value equals new value. + * In result it will be faster to not get value from store and compare but it will emit item to {@link #observe()} subscribers. + * NOTE: It could emit ONLY completed and errors events. It is not providing onNext event! + * + * @param newValue Value to set; + * @return Observable of setting process. + */ + @NonNull + public Completable forceSet(@Nullable final TObject newValue) { + return internalSet(newValue, false); + } + + /** + * Creates observable which is async setting value to store. + * It is checking if stored value equals new value. + * In result it will take time to get value from store and compare + * but it won't emit item to {@link #observe()} subscribers if stored value equals new value. + * NOTE: It could emit ONLY completed and errors events. It is not providing onNext event! + * + * @param newValue Value to set; + * @return Observable of setting process. + */ + @NonNull + public Completable set(@Nullable final TObject newValue) { + return internalSet(newValue, true); + } + + /** + * Sets value synchronously. You should NOT use this method normally. Use {@link #set(Object)} asynchronously instead. + * + * @param newValue Value to set; + */ + @Deprecated + //deprecation: it should be used for debug only and in very rare cases. + public void setSync(@Nullable final TObject newValue) { + set(newValue).blockingAwait(); + } + + @NonNull + protected Observable> observeOptionalValue() { + return valueObservable; + } + + /** + * Returns Observable which is emitting item on subscribe and every time when someone have changed value. + * It could emit next and error events but not completed. + * + * @return Returns observable of value. + */ + @NonNull + public abstract Observable observe(); + + /** + * Returns Observable which is emitting only one item on subscribe. + * It could emit next and error events but not completed. + * + * @return Returns observable of value. + */ + @NonNull + public Single get() { + return observe().firstOrError(); + } + + /** + * Gets value synchronously. You should NOT use this method normally. Use {@link #get()} or {@link #observe()} asynchronously instead. + * + * @return Returns value; + */ + @Deprecated + //deprecation: it should be used for debug only and in very rare cases. + @Nullable + public TReturnObject getSync() { + return get().blockingGet(); + } + + /** + * Enum that is representing strategy of observing item from store. + */ + public enum ObserveStrategy { + + /** + * Not caching value so on every {@link #get()} emit it will get value from {@link #getStore()} and converts it with {@link #getConverter()}. + */ + NO_CACHE, + /** + * Caching only store value so on every {@link #get()} emit it will converts it with {@link #getConverter()}. + * Do not use such strategy if store object could be big (like byte-array of file). + */ + CACHE_STORE_VALUE, + /** + * Caching value so it won't spend time for getting value from {@link #getStore()} and converts it by {@link #getConverter()}. + * But it will take time for getting value from {@link #getStore()} to set value. + * Do not use such strategy if object could be big (like Bitmap or long string). + * Do not use such strategy if object is mutable because multiple subscribers could then change it's state. + */ + CACHE_ACTUAL_VALUE, + /** + * Caching value so it won't spend time for getting value from {@link #getStore()} and converts it by {@link #getConverter()}. + * It won't take time or getting value from {@link #getStore()} to set value. + * Do not use such strategy if store object could be big (like byte-array of file). + * Do not use such strategy if object could be big (like Bitmap or long string). + * Do not use such strategy if object is mutable because multiple subscribers could then change it's state. + */ + CACHE_STORE_AND_ACTUAL_VALUE + + } + + /** + * Helper class to create various builders. + * + * @param Type of key to identify object; + * @param Type of actual object; + * @param Type of store object. Could be same as {@link TObject}. + */ + public static class BuilderCore { + + @NonNull + protected final TKey key; + @NonNull + protected final Type objectType; + @NonNull + private final Type storeObjectType; + @NonNull + private final Store store; + @NonNull + private final Converter converter; + @Nullable + private ObserveStrategy observeStrategy; + @Nullable + private Migration migration; + @Nullable + private TObject defaultValue; + @Nullable + private Scheduler storeScheduler; + private long cacheTimeMillis; + + protected BuilderCore(@NonNull final TKey key, + @NonNull final Type objectType, + @NonNull final Type storeObjectType, + @NonNull final Store store, + @NonNull final Converter converter) { + this(key, objectType, storeObjectType, store, converter, null, null, null, null, DEFAULT_CACHE_TIME_MILLIS); + } + + protected BuilderCore(@NonNull final BuilderCore sourceBuilder) { + this(sourceBuilder.key, sourceBuilder.objectType, sourceBuilder.storeObjectType, + sourceBuilder.store, sourceBuilder.converter, sourceBuilder.observeStrategy, + sourceBuilder.migration, sourceBuilder.defaultValue, sourceBuilder.storeScheduler, sourceBuilder.cacheTimeMillis); + } + + @SuppressWarnings({"PMD.ExcessiveParameterList", "CPD-START"}) + //CPD: it is same code as constructor of Storable + //ExcessiveParameterList: that's why we are using builder to create it + private BuilderCore(@NonNull final TKey key, + @NonNull final Type objectType, + @NonNull final Type storeObjectType, + @NonNull final Store store, + @NonNull final Converter converter, + @Nullable final ObserveStrategy observeStrategy, + @Nullable final Migration migration, + @Nullable final TObject defaultValue, + @Nullable final Scheduler storeScheduler, + final long cacheTimeMillis) { + this.key = key; + this.objectType = objectType; + this.storeObjectType = storeObjectType; + this.store = store; + this.converter = converter; + this.observeStrategy = observeStrategy; + this.migration = migration; + this.defaultValue = defaultValue; + this.storeScheduler = storeScheduler; + this.cacheTimeMillis = cacheTimeMillis; + } + + @SuppressWarnings("CPD-END") + protected void setStoreSchedulerInternal(@Nullable final Scheduler storeScheduler) { + this.storeScheduler = storeScheduler; + } + + protected void setObserveStrategyInternal(@Nullable final ObserveStrategy observeStrategy) { + this.observeStrategy = observeStrategy; + } + + protected void setMigrationInternal(@NonNull final Migration migration) { + this.migration = migration; + } + + protected void setCacheTimeInternal(final long cacheTime, @NonNull final TimeUnit timeUnit) { + this.cacheTimeMillis = timeUnit.toMillis(cacheTime); + } + + @Nullable + protected TObject getDefaultValue() { + return defaultValue; + } + + protected void setDefaultValueInternal(@NonNull final TObject defaultValue) { + this.defaultValue = defaultValue; + } + + } + +} diff --git a/storable/src/main/java/ru/touchin/roboswag/core/observables/storable/Converter.java b/storable/src/main/java/ru/touchin/roboswag/core/observables/storable/Converter.java new file mode 100644 index 0000000..5991932 --- /dev/null +++ b/storable/src/main/java/ru/touchin/roboswag/core/observables/storable/Converter.java @@ -0,0 +1,74 @@ +/* + * 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.roboswag.core.observables.storable; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.lang.reflect.Type; + +/** + * Created by Gavriil Sitnikov on 04/10/2015. + * Interface that is providing logic to convert value from specific type to type allowed to store in {@link Store} object and back. + * + * @param Type of original objects; + * @param Type of objects in store. + */ +public interface Converter { + + /** + * Converts specific object of objectType to object of storeObjectClass allowed to store. + * + * @param objectType Type of object; + * @param storeObjectType Type of store object allowed to store; + * @param object Object to be converted to store object; + * @return Object that is allowed to store into specific {@link Store}; + * @throws ConversionException Exception during conversion. Usually it indicates illegal state. + */ + @Nullable + TStoreObject toStoreObject(@NonNull Type objectType, @NonNull Type storeObjectType, @Nullable TObject object) + throws ConversionException; + + /** + * Converts specific store object of storeObjectClass to object of objectType. + * + * @param objectType Type of object; + * @param storeObjectType Type of store object allowed to store; + * @param storeObject Object from specific {@link Store}; + * @return Object converted from store object; + * @throws ConversionException Exception during conversion. Usually it indicates illegal state. + */ + @Nullable + TObject toObject(@NonNull Type objectType, @NonNull Type storeObjectType, @Nullable TStoreObject storeObject) + throws ConversionException; + + class ConversionException extends Exception { + + public ConversionException(@NonNull final String message) { + super(message); + } + + public ConversionException(@NonNull final String message, @NonNull final Throwable throwable) { + super(message, throwable); + } + + } + +} diff --git a/storable/src/main/java/ru/touchin/roboswag/core/observables/storable/Migration.java b/storable/src/main/java/ru/touchin/roboswag/core/observables/storable/Migration.java new file mode 100644 index 0000000..3fa2475 --- /dev/null +++ b/storable/src/main/java/ru/touchin/roboswag/core/observables/storable/Migration.java @@ -0,0 +1,165 @@ +/* + * 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.roboswag.core.observables.storable; + +import android.support.annotation.NonNull; + +import java.util.Arrays; +import java.util.List; + +import io.reactivex.Completable; +import io.reactivex.Flowable; +import io.reactivex.Single; + +/** + * Created by Gavriil Sitnikov on 06/10/2015. + * Object that allows to migrate some store objects from one version to another by migrators passed into constructor. + * Migrating objects should have same types of store key. + * + * @param Type of key of store objects. + */ +public class Migration { + + public static final long DEFAULT_VERSION = -1L; + + private final long latestVersion; + @NonNull + private final Store versionsStore; + @NonNull + private final List> migrators; + + @SafeVarargs + public Migration(@NonNull final Store versionsStore, + final long latestVersion, + @NonNull final Migrator... migrators) { + this.versionsStore = versionsStore; + this.latestVersion = latestVersion; + this.migrators = Arrays.asList(migrators); + } + + @NonNull + private Single loadCurrentVersion(@NonNull final TKey key) { + return versionsStore.loadObject(Long.class, key) + .map(version -> version.get() != null ? version.get() : DEFAULT_VERSION) + .onErrorResumeNext(throwable + -> Single.error(new MigrationException(String.format("Can't get version of '%s' from %s", key, versionsStore), throwable))); + } + + @NonNull + private Single makeMigrationChain(@NonNull final TKey key, @NonNull final VersionUpdater versionUpdater) { + Single chain = Single.fromCallable(() -> versionUpdater.initialVersion); + for (final Migrator migrator : migrators) { + chain = chain.flatMap(updatedVersion -> + migrator.canMigrate(key, updatedVersion) + .flatMap(canMigrate -> canMigrate + ? migrator.migrate(key, updatedVersion) + .doOnSuccess(newVersion + -> versionUpdater.updateVersion(newVersion, latestVersion, migrator)) + : Single.just(updatedVersion))); + } + return chain; + } + + /** + * Migrates some object by key to latest version. + * + * @param key Key of object to migrate. + */ + @NonNull + public Completable migrateToLatestVersion(@NonNull final TKey key) { + return loadCurrentVersion(key) + .flatMap(currentVersion -> { + final VersionUpdater versionUpdater = new VersionUpdater<>(key, versionsStore, currentVersion); + return makeMigrationChain(key, versionUpdater) + .doOnSuccess(lastUpdatedVersion -> { + if (lastUpdatedVersion < latestVersion) { + throw new NextLoopMigrationException(); + } + if (versionUpdater.initialVersion == versionUpdater.oldVersion) { + throw new MigrationException(String.format("Version of '%s' not updated from %s", + key, versionUpdater.initialVersion)); + } + }) + .retryWhen(attempts -> attempts + .switchMap(throwable -> throwable instanceof NextLoopMigrationException + ? Flowable.just(new Object()) : Flowable.error(throwable))); + }) + .toCompletable() + .andThen(versionsStore.storeObject(Long.class, key, latestVersion)) + .onErrorResumeNext(throwable -> { + if (throwable instanceof MigrationException) { + return Completable.error(throwable); + } + return Completable.error(new MigrationException(String.format("Can't migrate '%s'", key), throwable)); + }); + } + + private static class VersionUpdater { + + @NonNull + private final TKey key; + @NonNull + private final Store versionsStore; + private long oldVersion; + private long initialVersion; + + public VersionUpdater(@NonNull final TKey key, @NonNull final Store versionsStore, final long initialVersion) { + this.key = key; + this.versionsStore = versionsStore; + this.oldVersion = initialVersion; + this.initialVersion = initialVersion; + } + + public void updateVersion(final long updateVersion, final long latestVersion, @NonNull final Migrator migrator) { + if (initialVersion > updateVersion) { + throw new MigrationException(String.format("Version of '%s' downgraded from %s to %s [from %s by %s]", + key, initialVersion, updateVersion, versionsStore, migrator)); + } + if (updateVersion > latestVersion) { + throw new MigrationException( + String.format("Version of '%s' is %s and higher than latest version %s [from %s by %s]", + key, initialVersion, updateVersion, versionsStore, migrator)); + } + if (updateVersion == initialVersion) { + throw new MigrationException(String.format("Update version of '%s' equals current version '%s' [from %s by %s]", + key, updateVersion, versionsStore, migrator)); + } + oldVersion = initialVersion; + initialVersion = updateVersion; + } + + } + + private static class NextLoopMigrationException extends Exception { + } + + public static class MigrationException extends RuntimeException { + + public MigrationException(@NonNull final String message) { + super(message); + } + + public MigrationException(@NonNull final String message, @NonNull final Throwable throwable) { + super(message, throwable); + } + + } + +} diff --git a/storable/src/main/java/ru/touchin/roboswag/core/observables/storable/Migrator.java b/storable/src/main/java/ru/touchin/roboswag/core/observables/storable/Migrator.java new file mode 100644 index 0000000..41a3c53 --- /dev/null +++ b/storable/src/main/java/ru/touchin/roboswag/core/observables/storable/Migrator.java @@ -0,0 +1,97 @@ +/* + * 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.roboswag.core.observables.storable; + +import android.support.annotation.NonNull; + +import io.reactivex.Single; + +/** + * Created by Gavriil Sitnikov on 05/10/2015. + * Abstract class of objects which are able to migrate some values from one version to another. + * Also it is able to move objects from one store to another. + * + * @param Type of keys of migrating values; + * @param Type of values from current store; + * @param Type of values from new store. Could be same as {@link TOldStoreObject}. + */ +public abstract class Migrator { + + @NonNull + private final Store oldStore; + @NonNull + private final Store newStore; + + public Migrator(@NonNull final Store oldStore, + @NonNull final Store newStore) { + this.oldStore = oldStore; + this.newStore = newStore; + } + + /** + * Returns if this migrator can migrate from specific version to some new version. + * + * @param version Version to migrate from; + * @return True if migrator supports migration from this version. + */ + public abstract boolean supportsMigrationFor(long version); + + /** + * Returns {@link Single} that emits if specific object with key of specific version could be migrated by this migrator. + * + * @param key Key of object to migrate; + * @param version Current version of object; + * @return {@link Single} that emits true if object with such key and version could be migrated. + */ + @NonNull + public Single canMigrate(@NonNull final TKey key, final long version) { + return supportsMigrationFor(version) ? oldStore.contains(key) : Single.just(false); + } + + /** + * Single that migrates object with specific key from some version to migrator's version. + * + * @param key Key of object to migrate; + * @param version Current version of object; + * @return {@link Single} that emits new version of object after migration process. + */ + @NonNull + public Single migrate(@NonNull final TKey key, final long version) { + return supportsMigrationFor(version) + ? migrateInternal(key, version, oldStore, newStore) + : Single.error(new Migration.MigrationException(String.format("Version %s of '%s' is not supported by %s", version, key, this))); + } + + /** + * Single that represents internal migration logic specified by implementation. + * + * @param key Key of object to migrate; + * @param version Current version of object; + * @param oldStore Old store of object; + * @param newStore new store of object; + * @return {@link Single} that emits new version of object after migration process. + */ + @NonNull + protected abstract Single migrateInternal(@NonNull TKey key, + long version, + @NonNull Store oldStore, + @NonNull Store newStore); + +} diff --git a/storable/src/main/java/ru/touchin/roboswag/core/observables/storable/NonNullStorable.java b/storable/src/main/java/ru/touchin/roboswag/core/observables/storable/NonNullStorable.java new file mode 100644 index 0000000..3373ae6 --- /dev/null +++ b/storable/src/main/java/ru/touchin/roboswag/core/observables/storable/NonNullStorable.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2016 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.roboswag.core.observables.storable; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.util.concurrent.TimeUnit; + +import io.reactivex.Observable; +import io.reactivex.Scheduler; +import ru.touchin.roboswag.core.utils.ShouldNotHappenException; + +/** + * Created by Gavriil Sitnikov on 04/10/2015. + * {@link Storable} that should return not null value on get. + * If this rule is violated then it will throw {@link ShouldNotHappenException}. + * + * @param Type of key to identify object; + * @param Type of actual object; + * @param Type of store object. Could be same as {@link TObject}. + */ +public class NonNullStorable extends BaseStorable { + + public NonNullStorable(@NonNull final Builder builderCore) { + super(builderCore); + } + + @NonNull + @Override + public Observable observe() { + return observeOptionalValue() + .map(optional -> { + if (optional.get() == null) { + throw new ShouldNotHappenException(); + } + return optional.get(); + }); + } + + /** + * Created by Gavriil Sitnikov on 15/05/2016. + * Builder that is already contains not null default value. + * + * @param Type of key to identify object; + * @param Type of actual object; + * @param Type of store object. Could be same as {@link TObject}. + */ + @SuppressWarnings("CPD-START") + //CPD: it is same code as Builder of Storable because it's methods returning this and can't be inherited + public static class Builder extends BuilderCore { + + public Builder(@NonNull final Storable.Builder sourceBuilder, + @NonNull final TObject defaultValue) { + super(sourceBuilder); + if (defaultValue == null) { + throw new ShouldNotHappenException(); + } + setDefaultValueInternal(defaultValue); + } + + /** + * Sets specific {@link Scheduler} to store/load/convert values on it. + * + * @param storeScheduler Scheduler; + * @return Builder that allows to specify other fields. + */ + @NonNull + public Builder setStoreScheduler(@Nullable final Scheduler storeScheduler) { + setStoreSchedulerInternal(storeScheduler); + return this; + } + + /** + * Sets specific {@link ObserveStrategy} to cache value in memory in specific way. + * + * @param observeStrategy ObserveStrategy; + * @return Builder that allows to specify other fields. + */ + @NonNull + public Builder setObserveStrategy(@Nullable final ObserveStrategy observeStrategy) { + setObserveStrategyInternal(observeStrategy); + return this; + } + + /** + * Sets cache time for while value that cached by {@link #setObserveStrategy(ObserveStrategy)} + * will be in memory after everyone unsubscribe. + * It is important for example for cases when user switches between screens and hide/open app very fast. + * + * @param cacheTime Cache time value; + * @param timeUnit Cache time units. + * @return Builder that allows to specify other fields. + */ + @NonNull + public Builder setCacheTime(final long cacheTime, @NonNull final TimeUnit timeUnit) { + setCacheTimeInternal(cacheTime, timeUnit); + return this; + } + + /** + * Sets specific {@link Migration} to migrate values from specific version to latest version. + * + * @param migration Migration; + * @return Builder that allows to specify other fields. + */ + @NonNull + public Builder setMigration(@NonNull final Migration migration) { + setMigrationInternal(migration); + return this; + } + + /** + * Building {@link NonNullStorable} object. + * + * @return New {@link NonNullStorable}. + */ + @NonNull + @SuppressWarnings("CPD-END") + public NonNullStorable build() { + if (getDefaultValue() == null) { + throw new ShouldNotHappenException(); + } + return new NonNullStorable<>(this); + } + + } +} diff --git a/storable/src/main/java/ru/touchin/roboswag/core/observables/storable/SameTypesConverter.java b/storable/src/main/java/ru/touchin/roboswag/core/observables/storable/SameTypesConverter.java new file mode 100644 index 0000000..9bfa228 --- /dev/null +++ b/storable/src/main/java/ru/touchin/roboswag/core/observables/storable/SameTypesConverter.java @@ -0,0 +1,27 @@ +package ru.touchin.roboswag.core.observables.storable; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.lang.reflect.Type; + +/** + * Simple safe converter that is doing nothing on conversion. + * + * @param Same type. + */ +public class SameTypesConverter implements Converter { + + @Nullable + @Override + public T toStoreObject(@NonNull final Type type1, @NonNull final Type type2, @Nullable final T object) { + return object; + } + + @Nullable + @Override + public T toObject(@NonNull final Type type1, @NonNull final Type type2, @Nullable final T object) { + return object; + } + +} diff --git a/storable/src/main/java/ru/touchin/roboswag/core/observables/storable/Storable.java b/storable/src/main/java/ru/touchin/roboswag/core/observables/storable/Storable.java new file mode 100644 index 0000000..b891c0c --- /dev/null +++ b/storable/src/main/java/ru/touchin/roboswag/core/observables/storable/Storable.java @@ -0,0 +1,147 @@ +/* + * 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.roboswag.core.observables.storable; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.lang.reflect.Type; +import java.util.concurrent.TimeUnit; + +import io.reactivex.Observable; +import io.reactivex.Scheduler; +import ru.touchin.roboswag.core.utils.Optional; + +/** + * Created by Gavriil Sitnikov on 04/10/2015. + * Base class allows to async access to some store. + * Supports conversion between store and actual value. If it is not needed then use {@link SameTypesConverter} + * Supports migration from specific version to latest by {@link Migration} object. + * Allows to set default value which will be returned if actual value is null. + * Allows to declare specific {@link ObserveStrategy}. + * Also specific {@link Scheduler} could be specified to not create new scheduler per storable. + * + * @param Type of key to identify object; + * @param Type of actual object; + * @param Type of store object. Could be same as {@link TObject}. + */ +public class Storable extends BaseStorable> { + + public Storable(@NonNull final BuilderCore builderCore) { + super(builderCore); + } + + @NonNull + @Override + public Observable> observe() { + return observeOptionalValue(); + } + + /** + * Helper class to build {@link Storable}. + * + * @param Type of key to identify object; + * @param Type of actual object; + * @param Type of store object. Could be same as {@link TObject}. + */ + public static class Builder extends BuilderCore { + + public Builder(@NonNull final TKey key, + @NonNull final Type objectType, + @NonNull final Type storeObjectType, + @NonNull final Store store, + @NonNull final Converter converter) { + super(key, objectType, storeObjectType, store, converter); + } + + /** + * Sets specific {@link Scheduler} to store/load/convert values on it. + * + * @param storeScheduler Scheduler; + * @return Builder that allows to specify other fields. + */ + @NonNull + public Builder setStoreScheduler(@Nullable final Scheduler storeScheduler) { + setStoreSchedulerInternal(storeScheduler); + return this; + } + + /** + * Sets specific {@link ObserveStrategy} to cache value in memory in specific way. + * + * @param observeStrategy ObserveStrategy; + * @return Builder that allows to specify other fields. + */ + @NonNull + public Builder setObserveStrategy(@Nullable final ObserveStrategy observeStrategy) { + setObserveStrategyInternal(observeStrategy); + return this; + } + + /** + * Sets cache time for while value that cached by {@link #setObserveStrategy(ObserveStrategy)} will be in memory after everyone unsubscribe. + * It is important for example for cases when user switches between screens and hide/open app very fast. + * + * @param cacheTime Cache time value; + * @param timeUnit Cache time units. + * @return Builder that allows to specify other fields. + */ + @NonNull + public Builder setCacheTime(final long cacheTime, @NonNull final TimeUnit timeUnit) { + setCacheTimeInternal(cacheTime, timeUnit); + return this; + } + + /** + * Sets specific {@link Migration} to migrate values from specific version to latest version. + * + * @param migration Migration; + * @return Builder that allows to specify other fields. + */ + @NonNull + public Builder setMigration(@NonNull final Migration migration) { + setMigrationInternal(migration); + return this; + } + + /** + * Sets value which will be returned instead of null. + * + * @param defaultValue Default value; + * @return Builder that allows to specify other fields. + */ + @NonNull + public NonNullStorable.Builder setDefaultValue(@NonNull final TObject defaultValue) { + return new NonNullStorable.Builder<>(this, defaultValue); + } + + /** + * Building {@link Storable} object. + * + * @return New {@link Storable}. + */ + @NonNull + public Storable build() { + return new Storable<>(this); + } + + } + +} diff --git a/storable/src/main/java/ru/touchin/roboswag/core/observables/storable/Store.java b/storable/src/main/java/ru/touchin/roboswag/core/observables/storable/Store.java new file mode 100644 index 0000000..c6c3a3b --- /dev/null +++ b/storable/src/main/java/ru/touchin/roboswag/core/observables/storable/Store.java @@ -0,0 +1,70 @@ +/* + * 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.roboswag.core.observables.storable; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.lang.reflect.Type; + +import io.reactivex.Completable; +import io.reactivex.Single; +import ru.touchin.roboswag.core.utils.Optional; + +/** + * Created by Gavriil Sitnikov on 04/10/2015. + * Interface that is providing access to abstract object which can store (e.g. in file, database, remote store) + * some type of objects (e.g. String, byte[], Integer) by key. + * + * @param Type of keys for values; + * @param Type of values stored in store. + */ +public interface Store { + + /** + * Returns if store contains specific key related to some value. + * + * @param key Key which is finding in store; + * @return True if key have found in store. + */ + @NonNull + Single contains(@NonNull TKey key); + + /** + * Stores object to store with related key. + * + * @param storeObjectType Type of object to store; + * @param key Key related to object; + * @param storeObject Object to store; + */ + @NonNull + Completable storeObject(@NonNull Type storeObjectType, @NonNull TKey key, @Nullable TStoreObject storeObject); + + /** + * Loads object from store by key. + * + * @param storeObjectType Type of object to store; + * @param key Key related to object; + * @return Object from store found by key; + */ + @NonNull + Single> loadObject(@NonNull Type storeObjectType, @NonNull TKey key); + +} diff --git a/utils/.gitignore b/utils/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/utils/.gitignore @@ -0,0 +1 @@ +/build diff --git a/utils/build.gradle b/utils/build.gradle new file mode 100644 index 0000000..217c17b --- /dev/null +++ b/utils/build.gradle @@ -0,0 +1,18 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion versions.compileSdk + + defaultConfig { + minSdkVersion 16 + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + implementation "com.android.support:support-annotations:$versions.supportLibrary" +} diff --git a/utils/src/main/AndroidManifest.xml b/utils/src/main/AndroidManifest.xml new file mode 100644 index 0000000..6d76123 --- /dev/null +++ b/utils/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.java b/utils/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.java similarity index 96% rename from src/main/java/ru/touchin/roboswag/components/utils/UiUtils.java rename to utils/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.java index b6eb724..a80ee72 100644 --- a/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.java +++ b/utils/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.java @@ -38,23 +38,12 @@ import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.inputmethod.InputMethodManager; -import ru.touchin.roboswag.core.log.LcGroup; - /** * Created by Gavriil Sitnikov on 13/11/2015. * General utilities related to UI (Inflation, Views, Metrics, Activities etc.). */ public final class UiUtils { - /** - * Logging group to log UI metrics (like inflation or layout time etc.). - */ - public static final LcGroup UI_METRICS_LC_GROUP = new LcGroup("UI_METRICS"); - /** - * Logging group to log UI lifecycle (onCreate, onStart, onResume etc.). - */ - public static final LcGroup UI_LIFECYCLE_LC_GROUP = new LcGroup("UI_LIFECYCLE"); - /** * Method to inflate view with right layout parameters based on container and add inflated view as a child to it. * diff --git a/src/main/java/ru/touchin/roboswag/components/utils/destroyable/BaseDestroyable.kt b/utils/src/main/java/ru/touchin/roboswag/components/utils/destroyable/BaseDestroyable.kt similarity index 100% rename from src/main/java/ru/touchin/roboswag/components/utils/destroyable/BaseDestroyable.kt rename to utils/src/main/java/ru/touchin/roboswag/components/utils/destroyable/BaseDestroyable.kt diff --git a/src/main/java/ru/touchin/roboswag/components/utils/destroyable/Destroyable.kt b/utils/src/main/java/ru/touchin/roboswag/components/utils/destroyable/Destroyable.kt similarity index 100% rename from src/main/java/ru/touchin/roboswag/components/utils/destroyable/Destroyable.kt rename to utils/src/main/java/ru/touchin/roboswag/components/utils/destroyable/Destroyable.kt diff --git a/src/main/java/ru/touchin/roboswag/components/utils/spans/ColoredUrlSpan.java b/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/ColoredUrlSpan.java similarity index 100% rename from src/main/java/ru/touchin/roboswag/components/utils/spans/ColoredUrlSpan.java rename to utils/src/main/java/ru/touchin/roboswag/components/utils/spans/ColoredUrlSpan.java diff --git a/src/main/java/ru/touchin/roboswag/components/utils/spans/PhoneSpan.java b/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/PhoneSpan.java similarity index 93% rename from src/main/java/ru/touchin/roboswag/components/utils/spans/PhoneSpan.java rename to utils/src/main/java/ru/touchin/roboswag/components/utils/spans/PhoneSpan.java index 5f84f91..b350912 100644 --- a/src/main/java/ru/touchin/roboswag/components/utils/spans/PhoneSpan.java +++ b/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/PhoneSpan.java @@ -8,8 +8,6 @@ import android.text.TextPaint; import android.text.style.URLSpan; import android.view.View; -import ru.touchin.roboswag.core.log.Lc; - /** * Created by Gavriil Sitnikov on 14/11/2015. * Span that is opening phone call intent. @@ -28,7 +26,7 @@ public class PhoneSpan extends URLSpan { intent.setData(Uri.parse(getURL())); widget.getContext().startActivity(intent); } catch (final ActivityNotFoundException exception) { - Lc.assertion(exception); + // Do nothing } } diff --git a/src/main/java/ru/touchin/roboswag/components/utils/spans/TypefaceSpan.java b/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/TypefaceSpan.java similarity index 100% rename from src/main/java/ru/touchin/roboswag/components/utils/spans/TypefaceSpan.java rename to utils/src/main/java/ru/touchin/roboswag/components/utils/spans/TypefaceSpan.java diff --git a/utils/src/main/java/ru/touchin/roboswag/core/utils/BiConsumer.java b/utils/src/main/java/ru/touchin/roboswag/core/utils/BiConsumer.java new file mode 100644 index 0000000..c13ac22 --- /dev/null +++ b/utils/src/main/java/ru/touchin/roboswag/core/utils/BiConsumer.java @@ -0,0 +1,19 @@ +package ru.touchin.roboswag.core.utils; + +import android.support.annotation.Nullable; + +/** + * A functional interface (callback) that accepts two values (of possibly different types). + * @param the first value type + * @param the second value type + */ +public interface BiConsumer { + + /** + * Performs an operation on the given values. + * @param t1 the first value + * @param t2 the second value + * @throws Exception on error + */ + void accept(@Nullable T1 t1, @Nullable T2 t2) throws Exception; +} diff --git a/utils/src/main/java/ru/touchin/roboswag/core/utils/ObjectUtils.java b/utils/src/main/java/ru/touchin/roboswag/core/utils/ObjectUtils.java new file mode 100644 index 0000000..a8c2baf --- /dev/null +++ b/utils/src/main/java/ru/touchin/roboswag/core/utils/ObjectUtils.java @@ -0,0 +1,177 @@ +/* + * 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.roboswag.core.utils; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; + +/** + * Created by Gavriil Sitnikov on 04/10/2015. + * Some utilities related to objects. + */ +public final class ObjectUtils { + + /** + * Compares two objects if they are equals or not. If they are arrays then compare process same as {@link Arrays#deepEquals(Object[], Object[])}. + * + * @param object1 First object to compare; + * @param object2 Second object to compare; + * @return True if objects are equals. + */ + @SuppressWarnings("PMD.CompareObjectsWithEquals") + //CompareObjectsWithEquals: we need to compare if it's same object + public static boolean equals(@Nullable final Object object1, @Nullable final Object object2) { + if (object1 == object2) { + return true; + } + if (object1 == null || object2 == null) { + return false; + } + + final Class elementType1 = object1.getClass().getComponentType(); + final Class elementType2 = object2.getClass().getComponentType(); + + if (!(elementType1 == null ? elementType2 == null : elementType1.equals(elementType2))) { + return false; + } + if (elementType1 == null) { + return object1.equals(object2); + } + return isArraysEquals(object1, object2, elementType1); + } + + /** + * Compares two collections if their elements are equals or not. + * + * @param collection1 First object to compare; + * @param collection2 Second object to compare; + * @return True if collections are equals. + */ + @SuppressWarnings("PMD.CompareObjectsWithEquals") + //CompareObjectsWithEquals: we need to compare if it's same object + public static boolean isCollectionsEquals(@Nullable final Collection collection1, @Nullable final Collection collection2) { + if (collection1 == collection2) { + return true; + } + if (collection1 == null || collection2 == null) { + return false; + } + if (collection1.size() != collection2.size()) { + return false; + } + final Iterator collection2Iterator = collection2.iterator(); + for (final Object item1 : collection1) { + if (!equals(item1, collection2Iterator.next())) { + return false; + } + } + return true; + } + + /** + * Compares two maps if their elements are equals or not. + * + * @param map1 First object to compare; + * @param map2 Second object to compare; + * @return True if maps are equals. + */ + @SuppressWarnings("PMD.CompareObjectsWithEquals") + //CompareObjectsWithEquals: we need to compare if it's same object + public static boolean isMapsEquals(@Nullable final Map map1, @Nullable final Map map2) { + return map1 == map2 || !(map1 == null || map2 == null) + && map1.size() == map2.size() + && map1.entrySet().containsAll(map2.entrySet()) + && map2.entrySet().containsAll(map1.entrySet()); + } + + @SuppressWarnings("PMD.AvoidUsingShortType") + private static boolean isArraysEquals(@NonNull final Object object1, @Nullable final Object object2, @NonNull final Class elementType) { + if (object1 instanceof Object[]) { + return Arrays.deepEquals((Object[]) object1, (Object[]) object2); + } else if (elementType == int.class) { + return Arrays.equals((int[]) object1, (int[]) object2); + } else if (elementType == char.class) { + return Arrays.equals((char[]) object1, (char[]) object2); + } else if (elementType == boolean.class) { + return Arrays.equals((boolean[]) object1, (boolean[]) object2); + } else if (elementType == byte.class) { + return Arrays.equals((byte[]) object1, (byte[]) object2); + } else if (elementType == long.class) { + return Arrays.equals((long[]) object1, (long[]) object2); + } else if (elementType == float.class) { + return Arrays.equals((float[]) object1, (float[]) object2); + } else if (elementType == double.class) { + return Arrays.equals((double[]) object1, (double[]) object2); + } else { + return Arrays.equals((short[]) object1, (short[]) object2); + } + } + + /** + * Calculates hashCode() of several objects. + * + * @param objects Objects to combine hashCode() of; + * @return Calculated hashCode(). + */ + public static int hashCode(@Nullable final Object... objects) { + return Arrays.hashCode(objects); + } + + /** + * Returns if class is simple like primitive, enum or string. + * + * @param objectClass Class to check if it's simple class; + * @return True if class is simple. + */ + public static boolean isSimpleClass(@NonNull final Class objectClass) { + return objectClass.isPrimitive() || objectClass.getSuperclass() == Number.class + || objectClass.isEnum() || objectClass == Boolean.class + || objectClass == String.class || objectClass == Object.class; + } + + /** + * Returns true if collection is null or empty. + * + * @param collection Collection to check; + * @return True if collection is null or empty. + */ + public static boolean isNullOrEmpty(@Nullable final Collection collection) { + return collection == null || collection.isEmpty(); + } + + /** + * Returns true if map is null or empty. + * + * @param map Map to check; + * @return True if map is null or empty. + */ + public static boolean isNullOrEmpty(@Nullable final Map map) { + return map == null || map.isEmpty(); + } + + private ObjectUtils() { + } + +} diff --git a/utils/src/main/java/ru/touchin/roboswag/core/utils/Optional.java b/utils/src/main/java/ru/touchin/roboswag/core/utils/Optional.java new file mode 100644 index 0000000..86a8493 --- /dev/null +++ b/utils/src/main/java/ru/touchin/roboswag/core/utils/Optional.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2017 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.roboswag.core.utils; + +import android.support.annotation.Nullable; + +import java.io.Serializable; + +/** + * Created by Gavriil Sitnikov on 16/04/2017. + * Holds nullable objects inside. It is needed to implement RxJava2 non-null emitting logic. + * + * @param Type of object. + */ +public class Optional implements Serializable { + + private static final long serialVersionUID = 1L; + + @Nullable + private final T value; + + public Optional(@Nullable final T value) { + this.value = value; + } + + /** + * Returns holding nullable object. + * + * @return Holding object. + */ + @Nullable + public T get() { + return value; + } + + @Override + public boolean equals(@Nullable final Object object) { + if (this == object) { + return true; + } + if (object == null || getClass() != object.getClass()) { + return false; + } + + final Optional that = (Optional) object; + return ObjectUtils.equals(value, that.value); + } + + @Override + public int hashCode() { + return value != null ? value.hashCode() : 0; + } + +} diff --git a/utils/src/main/java/ru/touchin/roboswag/core/utils/ServiceBinder.java b/utils/src/main/java/ru/touchin/roboswag/core/utils/ServiceBinder.java new file mode 100644 index 0000000..82f8acf --- /dev/null +++ b/utils/src/main/java/ru/touchin/roboswag/core/utils/ServiceBinder.java @@ -0,0 +1,70 @@ +/* + * 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.roboswag.core.utils; + +import android.app.Service; +import android.os.Binder; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +/** + * Created by Gavriil Sitnikov on 03/10/2015. + * Basic binding to {@link Service} which holds service object inside. + */ +public class ServiceBinder extends Binder { + + @NonNull + private final TService service; + + public ServiceBinder(@NonNull final TService service) { + super(); + this.service = service; + } + + /** + * Returns service which created this binder. + * + * @return Returns service. + */ + @NonNull + public TService getService() { + return service; + } + + @Override + public boolean equals(@Nullable final Object object) { + if (this == object) { + return true; + } + if (object == null || getClass() != object.getClass()) { + return false; + } + + final ServiceBinder that = (ServiceBinder) object; + + return ObjectUtils.equals(service, that.service); + } + + @Override + public int hashCode() { + return service.hashCode(); + } + +} diff --git a/utils/src/main/java/ru/touchin/roboswag/core/utils/StringUtils.java b/utils/src/main/java/ru/touchin/roboswag/core/utils/StringUtils.java new file mode 100644 index 0000000..639a5ab --- /dev/null +++ b/utils/src/main/java/ru/touchin/roboswag/core/utils/StringUtils.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2016 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.roboswag.core.utils; + +import android.support.annotation.NonNull; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * Created by Gavriil Sitnikov on 29/08/2016. + * Utility class to providing some string-related helper methods. + */ +public final class StringUtils { + + /** + * Returns MD5 of string. + * + * @param string String to get MD5 from; + * @return MD5 of string. + */ + @NonNull + public static String md5(@NonNull final String string) throws NoSuchAlgorithmException, UnsupportedEncodingException { + final MessageDigest digest = MessageDigest.getInstance("MD5"); + digest.update(string.getBytes("UTF-8")); + final byte[] messageDigestArray = digest.digest(); + + final StringBuilder hexString = new StringBuilder(); + for (final byte messageDigest : messageDigestArray) { + final String hex = Integer.toHexString(0xFF & messageDigest); + if (hex.length() == 1) { + hexString.append('0'); + } + hexString.append(hex); + } + return hexString.toString(); + } + + private StringUtils() { + } + +} diff --git a/src/main/res/values/common_resources.xml b/utils/src/main/res/values/common_resources.xml similarity index 96% rename from src/main/res/values/common_resources.xml rename to utils/src/main/res/values/common_resources.xml index fff1084..f0221c8 100644 --- a/src/main/res/values/common_resources.xml +++ b/utils/src/main/res/values/common_resources.xml @@ -1,5 +1,5 @@ - + #0D000000 @@ -43,4 +43,4 @@ #E6FFFFFF #F3FFFFFF - \ No newline at end of file + From 4ea6ed857b609c02ccd330e0b13ec39f7aa07d1d Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Tue, 21 Aug 2018 12:17:05 +0300 Subject: [PATCH 84/95] Added modules: api-logansquare, lifecycle-common, lifecycle-rx --- api-logansquare/.gitignore | 1 + api-logansquare/build.gradle | 27 +++ api-logansquare/src/main/AndroidManifest.xml | 2 + .../logansquare/ConverterUtils.java | 22 ++ .../java/ru/touchin/templates/ApiModel.java | 137 +++++++++++ .../LoganSquareBigDecimalConverter.java | 70 ++++++ .../logansquare/LoganSquareEnum.java | 33 +++ .../logansquare/LoganSquareEnumConverter.java | 77 ++++++ .../LoganSquareJodaTimeConverter.java | 84 +++++++ .../logansquare/LoganSquareJsonFactory.java | 141 +++++++++++ .../logansquare/LoganSquareJsonModel.java | 45 ++++ .../logansquare/LoganSquarePreferences.java | 158 ++++++++++++ .../retrofit/JsonRequestBodyConverter.java | 63 +++++ .../retrofit/JsonResponseBodyConverter.java | 112 +++++++++ build.gradle | 6 +- lifecycle-common/.gitignore | 1 + lifecycle-common/build.gradle | 27 +++ lifecycle-common/src/main/AndroidManifest.xml | 2 + .../templates/livedata/SingleLiveEvent.kt | 25 ++ .../viewmodel/LifecycleViewModelProviders.kt | 49 ++++ .../templates/viewmodel/ViewModelFactory.kt | 17 ++ .../viewmodel/ViewModelFactoryProvider.kt | 7 + lifecycle-rx/.gitignore | 1 + lifecycle-rx/build.gradle | 24 ++ lifecycle-rx/src/main/AndroidManifest.xml | 3 + .../livedata}/destroyable/BaseDestroyable.kt | 2 +- .../livedata}/destroyable/Destroyable.kt | 2 +- .../dispatcher/BaseLiveDataDispatcher.kt | 57 +++++ .../livedata/dispatcher/LiveDataDispatcher.kt | 27 +++ .../livedata/event/CompletableEvent.kt | 14 ++ .../ru/touchin/livedata/event/MaybeEvent.kt | 16 ++ .../touchin/livedata/event/ObservableEvent.kt | 16 ++ .../ru/touchin/livedata/event/SingleEvent.kt | 14 ++ .../templates/viewmodel/RxViewModel.kt | 24 ++ lifecycle-rx/src/main/res/values/strings.xml | 3 + logging/build.gradle | 2 +- navigation/build.gradle | 3 +- .../navigation/OnFragmentStartedListener.java | 39 --- .../SimpleActionBarDrawerToggle.java | 3 +- .../navigation/activities/BaseActivity.java | 25 +- .../activities/OnBackPressedListener.java | 15 ++ .../fragments/ViewControllerFragment.java | 168 ++++++++----- .../navigation/fragments/ViewFragment.java | 229 ------------------ sample/build.gradle | 11 +- settings.gradle | 2 +- storable/build.gradle | 2 +- utils/build.gradle | 2 +- .../roboswag/core/utils/BiConsumer.java | 19 -- .../ru/touchin/templates/DeviceUtils.java | 146 +++++++++++ 49 files changed, 1585 insertions(+), 390 deletions(-) create mode 100644 api-logansquare/.gitignore create mode 100644 api-logansquare/build.gradle create mode 100644 api-logansquare/src/main/AndroidManifest.xml create mode 100644 api-logansquare/src/main/java/com/bluelinelabs/logansquare/ConverterUtils.java create mode 100644 api-logansquare/src/main/java/ru/touchin/templates/ApiModel.java create mode 100644 api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareBigDecimalConverter.java create mode 100644 api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareEnum.java create mode 100644 api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareEnumConverter.java create mode 100644 api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareJodaTimeConverter.java create mode 100644 api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareJsonFactory.java create mode 100644 api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareJsonModel.java create mode 100644 api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquarePreferences.java create mode 100644 api-logansquare/src/main/java/ru/touchin/templates/retrofit/JsonRequestBodyConverter.java create mode 100644 api-logansquare/src/main/java/ru/touchin/templates/retrofit/JsonResponseBodyConverter.java create mode 100644 lifecycle-common/.gitignore create mode 100644 lifecycle-common/build.gradle create mode 100644 lifecycle-common/src/main/AndroidManifest.xml create mode 100644 lifecycle-common/src/main/java/ru/touchin/templates/livedata/SingleLiveEvent.kt create mode 100644 lifecycle-common/src/main/java/ru/touchin/templates/viewmodel/LifecycleViewModelProviders.kt create mode 100644 lifecycle-common/src/main/java/ru/touchin/templates/viewmodel/ViewModelFactory.kt create mode 100644 lifecycle-common/src/main/java/ru/touchin/templates/viewmodel/ViewModelFactoryProvider.kt create mode 100644 lifecycle-rx/.gitignore create mode 100644 lifecycle-rx/build.gradle create mode 100644 lifecycle-rx/src/main/AndroidManifest.xml rename {utils/src/main/java/ru/touchin/roboswag/components/utils => lifecycle-rx/src/main/java/ru/touchin/livedata}/destroyable/BaseDestroyable.kt (97%) rename {utils/src/main/java/ru/touchin/roboswag/components/utils => lifecycle-rx/src/main/java/ru/touchin/livedata}/destroyable/Destroyable.kt (99%) create mode 100644 lifecycle-rx/src/main/java/ru/touchin/livedata/dispatcher/BaseLiveDataDispatcher.kt create mode 100644 lifecycle-rx/src/main/java/ru/touchin/livedata/dispatcher/LiveDataDispatcher.kt create mode 100644 lifecycle-rx/src/main/java/ru/touchin/livedata/event/CompletableEvent.kt create mode 100644 lifecycle-rx/src/main/java/ru/touchin/livedata/event/MaybeEvent.kt create mode 100644 lifecycle-rx/src/main/java/ru/touchin/livedata/event/ObservableEvent.kt create mode 100644 lifecycle-rx/src/main/java/ru/touchin/livedata/event/SingleEvent.kt create mode 100644 lifecycle-rx/src/main/java/ru/touchin/templates/viewmodel/RxViewModel.kt create mode 100644 lifecycle-rx/src/main/res/values/strings.xml delete mode 100644 navigation/src/main/java/ru/touchin/roboswag/components/navigation/OnFragmentStartedListener.java create mode 100644 navigation/src/main/java/ru/touchin/roboswag/components/navigation/activities/OnBackPressedListener.java delete mode 100644 navigation/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewFragment.java delete mode 100644 utils/src/main/java/ru/touchin/roboswag/core/utils/BiConsumer.java create mode 100644 utils/src/main/java/ru/touchin/templates/DeviceUtils.java diff --git a/api-logansquare/.gitignore b/api-logansquare/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/api-logansquare/.gitignore @@ -0,0 +1 @@ +/build diff --git a/api-logansquare/build.gradle b/api-logansquare/build.gradle new file mode 100644 index 0000000..9daa9da --- /dev/null +++ b/api-logansquare/build.gradle @@ -0,0 +1,27 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion versions.compileSdk + + defaultConfig { + minSdkVersion versions.minSdk + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +repositories { + maven { url "http://dl.bintray.com/touchin/touchin-tools" } +} + +dependencies { + api project(":storable") + api 'net.danlew:android.joda:2.9.9.4' + + implementation "com.android.support:support-annotations:$versions.supportLibrary" + implementation "com.squareup.retrofit2:retrofit:$versions.retrofit" + implementation 'ru.touchin:logansquare:1.4.3' +} diff --git a/api-logansquare/src/main/AndroidManifest.xml b/api-logansquare/src/main/AndroidManifest.xml new file mode 100644 index 0000000..d48ece2 --- /dev/null +++ b/api-logansquare/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + diff --git a/api-logansquare/src/main/java/com/bluelinelabs/logansquare/ConverterUtils.java b/api-logansquare/src/main/java/com/bluelinelabs/logansquare/ConverterUtils.java new file mode 100644 index 0000000..f8f7bba --- /dev/null +++ b/api-logansquare/src/main/java/com/bluelinelabs/logansquare/ConverterUtils.java @@ -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() { + } + +} diff --git a/api-logansquare/src/main/java/ru/touchin/templates/ApiModel.java b/api-logansquare/src/main/java/ru/touchin/templates/ApiModel.java new file mode 100644 index 0000000..5755445 --- /dev/null +++ b/api-logansquare/src/main/java/ru/touchin/templates/ApiModel.java @@ -0,0 +1,137 @@ +/* + * 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.CallSuper; +import android.support.annotation.NonNull; + +import java.io.IOException; +import java.io.Serializable; +import java.util.Collection; +import java.util.Iterator; + +import ru.touchin.roboswag.core.log.Lc; +import ru.touchin.roboswag.core.log.LcGroup; + +/** + * Created by Gavriil Sitnikov on 11/08/2016. + * Just model from getting from API. + */ +public abstract class ApiModel implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * Logging group to log API validation errors. + */ + public static final LcGroup API_VALIDATION_LC_GROUP = new LcGroup("API_VALIDATION"); + + /** + * Validates list of objects. Use it if objects in list extends {@link ApiModel}. + * + * @param collection Collection of items to check; + * @param collectionValidationRule Rule explaining what to do if invalid items found; + * @throws ValidationException Exception of validation. + */ + @SuppressWarnings({"PMD.PreserveStackTrace", "PMD.CyclomaticComplexity"}) + // PreserveStackTrace: it's ok - we are logging it on Lc.e() + public static void validateCollection(@NonNull final Collection collection, @NonNull final CollectionValidationRule collectionValidationRule) + throws ValidationException { + boolean haveValidItem = false; + int position = 0; + final Iterator iterator = collection.iterator(); + while (iterator.hasNext()) { + final Object item = iterator.next(); + if (!(item instanceof ApiModel)) { + if (item != null) { + // let's just think that all of items are not ApiModels + break; + } + continue; + } + + try { + ((ApiModel) item).validate(); + haveValidItem = true; + } catch (final ValidationException exception) { + switch (collectionValidationRule) { + case EXCEPTION_IF_ANY_INVALID: + throw exception; + case EXCEPTION_IF_ALL_INVALID: + iterator.remove(); + API_VALIDATION_LC_GROUP.e(exception, "Item %s is invalid at " + Lc.getCodePoint(null, 1), position); + if (!iterator.hasNext() && !haveValidItem) { + throw new ValidationException("Whole list is invalid at " + Lc.getCodePoint(null, 1)); + } + break; + case REMOVE_INVALID_ITEMS: + iterator.remove(); + API_VALIDATION_LC_GROUP.e(exception, "Item %s is invalid at " + Lc.getCodePoint(null, 1), position); + break; + default: + Lc.assertion("Unexpected rule " + collectionValidationRule); + break; + } + } + position++; + } + } + + /** + * Validates collection on emptiness. + * + * @param collection Collection to check; + * @throws ValidationException Exception of validation. + */ + protected static void validateCollectionNotEmpty(@NonNull final Collection collection) + throws ValidationException { + if (collection.isEmpty()) { + throw new ValidationException("List is empty at " + Lc.getCodePoint(null, 1)); + } + } + + /** + * Validates this object. Override it to write your own logic. + * + * @throws ValidationException Exception of validation. + */ + @CallSuper + public void validate() throws ValidationException { + //do nothing + } + + public enum CollectionValidationRule { + EXCEPTION_IF_ANY_INVALID, + EXCEPTION_IF_ALL_INVALID, + REMOVE_INVALID_ITEMS, + } + + /** + * Class of exceptions throws during {@link ApiModel} validation. + */ + public static class ValidationException extends IOException { + + public ValidationException(@NonNull final String reason) { + super(reason); + } + + } + +} diff --git a/api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareBigDecimalConverter.java b/api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareBigDecimalConverter.java new file mode 100644 index 0000000..9f08d6f --- /dev/null +++ b/api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareBigDecimalConverter.java @@ -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 { + + @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); + } + } + +} + diff --git a/api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareEnum.java b/api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareEnum.java new file mode 100644 index 0000000..a5d0963 --- /dev/null +++ b/api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareEnum.java @@ -0,0 +1,33 @@ +/* + * 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; + +/** + * Created by Gavriil Sitnikov. + * LoganSquare enum base class. + */ +public interface LoganSquareEnum { + + @NonNull + String getValueName(); + +} diff --git a/api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareEnumConverter.java b/api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareEnumConverter.java new file mode 100644 index 0000000..c274667 --- /dev/null +++ b/api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareEnumConverter.java @@ -0,0 +1,77 @@ +/* + * 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.StringBasedTypeConverter; + +import ru.touchin.roboswag.core.utils.ShouldNotHappenException; + +/** + * Created by Gavriil Sitnikov. + * LoganSquare converter from String to Enum. + */ +@SuppressWarnings("PMD.UseVarargs") +public class LoganSquareEnumConverter extends StringBasedTypeConverter { + + @NonNull + private final T[] enumValues; + @Nullable + private final T defaultValue; + + public LoganSquareEnumConverter(@NonNull final T[] enumValues) { + this(enumValues, null); + } + + public LoganSquareEnumConverter(@NonNull final T[] enumValues, @Nullable final T defaultValue) { + super(); + this.enumValues = enumValues; + this.defaultValue = defaultValue; + } + + @Nullable + @Override + public T getFromString(@Nullable final String string) { + if (string == null) { + return defaultValue; + } + for (final T value : enumValues) { + if (value.getValueName().equals(string)) { + return value; + } + } + if (defaultValue != null) { + return defaultValue; + } + throw new ShouldNotHappenException("Enum parsing exception for value: " + string); + } + + @Nullable + @Override + public String convertToString(@Nullable final T object) { + if (object == null) { + return null; + } + return object.getValueName(); + } + +} diff --git a/api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareJodaTimeConverter.java b/api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareJodaTimeConverter.java new file mode 100644 index 0000000..11f52b8 --- /dev/null +++ b/api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareJodaTimeConverter.java @@ -0,0 +1,84 @@ +/* + * 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 android.text.TextUtils; + +import com.bluelinelabs.logansquare.typeconverters.TypeConverter; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; + +import org.joda.time.DateTime; +import org.joda.time.format.DateTimeFormatter; + +import java.io.IOException; + +import ru.touchin.roboswag.core.log.Lc; + +/** + * LoganSquare converter for joda.time.DateTime + */ +public class LoganSquareJodaTimeConverter implements TypeConverter { + + @Nullable + private final DateTimeFormatter formatter; + + public LoganSquareJodaTimeConverter() { + this.formatter = null; + } + + public LoganSquareJodaTimeConverter(@Nullable final DateTimeFormatter formatter) { + this.formatter = formatter; + } + + @Nullable + @Override + public DateTime parse(@NonNull final JsonParser jsonParser) throws IOException { + final String dateString = jsonParser.getValueAsString(); + if (dateString == null || dateString.isEmpty()) { + return null; + } + try { + return DateTime.parse(dateString); + } catch (final RuntimeException exception) { + Lc.assertion(exception); + } + return null; + } + + @Override + public void serialize( + @Nullable final DateTime object, + @Nullable final String fieldName, + final boolean writeFieldNameForObject, + @NonNull final JsonGenerator jsonGenerator + ) throws IOException { + final String serializedValue = object != null ? object.toString(formatter) : null; + if (fieldName != null) { + jsonGenerator.writeStringField(fieldName, !TextUtils.isEmpty(serializedValue) ? serializedValue : null); + } else { + jsonGenerator.writeString(!TextUtils.isEmpty(serializedValue) ? serializedValue : null); + } + } + +} diff --git a/api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareJsonFactory.java b/api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareJsonFactory.java new file mode 100644 index 0000000..20c5b15 --- /dev/null +++ b/api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareJsonFactory.java @@ -0,0 +1,141 @@ +/* + * 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.ConverterUtils; +import com.bluelinelabs.logansquare.LoganSquare; +import com.fasterxml.jackson.core.JsonGenerator; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.StringWriter; +import java.lang.annotation.Annotation; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.List; +import java.util.Map; + +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. + * LoganSquareConverter class to use with {@link Retrofit} to parse and generate models based on Logan Square library. + */ +public class LoganSquareJsonFactory extends Converter.Factory { + + @NonNull + @Override + public Converter responseBodyConverter(@NonNull final Type type, + @NonNull final Annotation[] annotations, + @NonNull final Retrofit retrofit) { + return new LoganSquareJsonResponseBodyConverter<>(type); + } + + @NonNull + @Override + public Converter requestBodyConverter(@NonNull final Type type, + @NonNull final Annotation[] parameterAnnotations, + @NonNull final Annotation[] methodAnnotations, + @NonNull final Retrofit retrofit) { + return new LoganSquareRequestBodyConverter<>(); + } + + @Nullable + @Override + public Converter 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 extends JsonResponseBodyConverter { + + @NonNull + private final Type type; + + public LoganSquareJsonResponseBodyConverter(@NonNull final Type type) { + super(); + this.type = type; + } + + @SuppressWarnings("unchecked") + @NonNull + @Override + protected T parseResponse(@NonNull final ResponseBody value) throws IOException { + 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); + } + } + + } + + public static class LoganSquareRequestBodyConverter extends JsonRequestBodyConverter { + + @Override + protected void writeValueToByteArray(@NonNull final T value, @NonNull final ByteArrayOutputStream byteArrayOutputStream) throws IOException { + LoganSquare.serialize(value, byteArrayOutputStream); + } + + } + + public static class LoganSquareStringEnumConverter implements Converter { + + @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) value.getClass()).serialize(value, null, false, generator); + generator.close(); + return writer.toString().replaceAll("\"", ""); + } finally { + writer.close(); + } + } + + } + +} diff --git a/api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareJsonModel.java b/api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareJsonModel.java new file mode 100644 index 0000000..ea67fbe --- /dev/null +++ b/api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareJsonModel.java @@ -0,0 +1,45 @@ +/* + * 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.Nullable; + +import ru.touchin.roboswag.core.log.Lc; +import ru.touchin.templates.ApiModel; + +/** + * Created by Gavriil Sitnikov. + * Just model from getting from API via LoganSquare. + */ +public abstract class LoganSquareJsonModel extends ApiModel { + + /** + * 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 (object == null) { + throw new ValidationException("Not nullable object is null or missed at " + Lc.getCodePoint(null, 1)); + } + } + +} diff --git a/api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquarePreferences.java b/api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquarePreferences.java new file mode 100644 index 0000000..763ccf0 --- /dev/null +++ b/api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquarePreferences.java @@ -0,0 +1,158 @@ +/* + * 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.content.SharedPreferences; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.bluelinelabs.logansquare.LoganSquare; + +import java.io.IOException; +import java.lang.reflect.Type; +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.NonNullStorable; +import ru.touchin.roboswag.core.observables.storable.Storable; +import ru.touchin.roboswag.core.utils.ShouldNotHappenException; + +/** + * Created by Gavriil Sitnikov on 26/12/2016. + * Utility class to get {@link Storable} that is storing LoganSquare (Json) generated object into preferences. + */ +@SuppressWarnings("CPD-START") +//CPD: it is same code as in GoogleJsonPreferences +public final class LoganSquarePreferences { + + @NonNull + public static Storable jsonStorable(@NonNull final String name, + @NonNull final Class jsonClass, + @NonNull final SharedPreferences preferences) { + return new Storable.Builder(name, jsonClass, String.class, new PreferenceStore<>(preferences), new JsonConverter<>()) + .setObserveStrategy(Storable.ObserveStrategy.CACHE_ACTUAL_VALUE) + .build(); + } + + @NonNull + public static NonNullStorable jsonStorable(@NonNull final String name, + @NonNull final Class jsonClass, + @NonNull final SharedPreferences preferences, + @NonNull final T defaultValue) { + return new Storable.Builder(name, jsonClass, String.class, new PreferenceStore<>(preferences), new JsonConverter<>()) + .setObserveStrategy(Storable.ObserveStrategy.CACHE_ACTUAL_VALUE) + .setDefaultValue(defaultValue) + .build(); + } + + @NonNull + public static Storable, String> jsonListStorable(@NonNull final String name, + @NonNull final Class 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 NonNullStorable, String> jsonListStorable(@NonNull final String name, + @NonNull final Class jsonListItemClass, + @NonNull final SharedPreferences preferences, + @NonNull final List 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 LoganSquarePreferences() { + } + + public static class JsonConverter implements Converter { + + @Nullable + @Override + public String toStoreObject(@NonNull final Type jsonObjectType, @NonNull final Type stringType, @Nullable final TJsonObject object) { + if (object == null) { + return null; + } + try { + return LoganSquare.serialize(object); + } catch (final IOException exception) { + throw new ShouldNotHappenException(exception); + } + } + + @Nullable + @Override + @SuppressWarnings("unchecked") + public TJsonObject toObject(@NonNull final Type jsonObjectClass, @NonNull final Type storeObjectType, @Nullable final String storeValue) { + if (storeValue == null) { + return null; + } + try { + return LoganSquare.parse(storeValue, (Class) jsonObjectClass); + } catch (final IOException exception) { + throw new ShouldNotHappenException(exception); + } + } + + } + + public static class JsonListConverter implements Converter, String> { + + @NonNull + private final Class itemClass; + + public JsonListConverter(@NonNull final Class itemClass) { + this.itemClass = itemClass; + } + + @Nullable + @Override + @SuppressWarnings("unchecked") + public String toStoreObject(@NonNull final Type jsonObjectType, @NonNull final Type stringType, @Nullable final List object) { + if (object == null) { + return null; + } + try { + return LoganSquare.serialize(object, itemClass); + } catch (final IOException exception) { + throw new ShouldNotHappenException(exception); + } + } + + @Nullable + @Override + public List toObject(@NonNull final Type jsonObjectType, @NonNull final Type stringType, @Nullable final String storeValue) { + if (storeValue == null) { + return null; + } + try { + return LoganSquare.parseList(storeValue, itemClass); + } catch (final IOException exception) { + throw new ShouldNotHappenException(exception); + } + } + + } + +} diff --git a/api-logansquare/src/main/java/ru/touchin/templates/retrofit/JsonRequestBodyConverter.java b/api-logansquare/src/main/java/ru/touchin/templates/retrofit/JsonRequestBodyConverter.java new file mode 100644 index 0000000..e0ea113 --- /dev/null +++ b/api-logansquare/src/main/java/ru/touchin/templates/retrofit/JsonRequestBodyConverter.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2017 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.retrofit; + + +import android.support.annotation.NonNull; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import okhttp3.MediaType; +import okhttp3.RequestBody; +import retrofit2.Converter; +import ru.touchin.templates.ApiModel; + +/** + * Created by Gavriil Sitnikov on 14/02/2017. + * Object to serialize bodies of remote requests for Retrofit. + * + * @param Type of body object. + */ +public abstract class JsonRequestBodyConverter implements Converter { + + private static final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8"); + + @NonNull + @Override + public RequestBody convert(@NonNull final T value) throws IOException { + if (value instanceof ApiModel) { + ((ApiModel) value).validate(); + } + final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + writeValueToByteArray(value, byteArrayOutputStream); + return RequestBody.create(MEDIA_TYPE, byteArrayOutputStream.toByteArray()); + } + + /** + * Serializing value to byte stream. + * + * @param value Value to serialize; + * @param byteArrayOutputStream Byte stream to write serialized bytes; + * @throws IOException Throws on serialization. + */ + protected abstract void writeValueToByteArray(@NonNull T value, @NonNull ByteArrayOutputStream byteArrayOutputStream) throws IOException; + +} diff --git a/api-logansquare/src/main/java/ru/touchin/templates/retrofit/JsonResponseBodyConverter.java b/api-logansquare/src/main/java/ru/touchin/templates/retrofit/JsonResponseBodyConverter.java new file mode 100644 index 0000000..614c8e2 --- /dev/null +++ b/api-logansquare/src/main/java/ru/touchin/templates/retrofit/JsonResponseBodyConverter.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2017 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.retrofit; + +import android.support.annotation.NonNull; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.SocketException; +import java.util.Collection; +import java.util.Map; + +import javax.net.ssl.SSLException; + +import okhttp3.ResponseBody; +import okhttp3.internal.http2.StreamResetException; +import retrofit2.Converter; +import ru.touchin.roboswag.core.log.Lc; +import ru.touchin.templates.ApiModel; + +/** + * Created by Gavriil Sitnikov on 14/02/2017. + * Object to deserialize responses of remote requests from Retrofit. + * + * @param Type of response object. + */ +public abstract class JsonResponseBodyConverter implements Converter { + + @SuppressWarnings("PMD.AvoidInstanceofChecksInCatchClause") + //AvoidInstanceofChecksInCatchClause: we just don't need assertion on specific exceptions + @NonNull + @Override + public T convert(@NonNull final ResponseBody value) throws IOException { + final T result; + try { + result = parseResponse(value); + } catch (final IOException exception) { + if (!(exception instanceof SocketException) + && !(exception instanceof InterruptedIOException) + && !(exception instanceof SSLException) + && !(exception instanceof StreamResetException)) { + Lc.assertion(exception); + } + throw exception; + } finally { + value.close(); + } + + if (result instanceof ApiModel) { + validateModel((ApiModel) result); + } + if (result instanceof Collection) { + validateCollection((Collection) result); + } + if (result instanceof Map) { + validateCollection(((Map) result).values()); + } + + 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. + * + * @param value Response to parse; + * @return Parsed object; + * @throws IOException Throws during parsing. + */ + @NonNull + protected abstract T parseResponse(@NonNull ResponseBody value) throws IOException; + +} \ No newline at end of file diff --git a/build.gradle b/build.gradle index 7accccb..d489a4e 100644 --- a/build.gradle +++ b/build.gradle @@ -1,11 +1,11 @@ buildscript { - ext.kotlin_version = '1.2.60' + ext.kotlin_version = '1.2.61' repositories { google() jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.1.3' + classpath 'com.android.tools.build:gradle:3.1.4' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } @@ -24,7 +24,7 @@ task clean(type: Delete) { ext { versions = [ compileSdk : 27, - minSdk : 19, + minSdk : 16, supportLibrary: '27.1.1', navigation : '1.0.0-alpha04', lifecycle : '1.1.1', diff --git a/lifecycle-common/.gitignore b/lifecycle-common/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/lifecycle-common/.gitignore @@ -0,0 +1 @@ +/build diff --git a/lifecycle-common/build.gradle b/lifecycle-common/build.gradle new file mode 100644 index 0000000..e7ab339 --- /dev/null +++ b/lifecycle-common/build.gradle @@ -0,0 +1,27 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + compileSdkVersion versions.compileSdk + + defaultConfig { + minSdkVersion versions.minSdk + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + api project(":navigation") + + compileOnly "javax.inject:javax.inject:1" + + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + + implementation "com.android.support:appcompat-v7:$versions.supportLibrary" + + implementation "android.arch.lifecycle:extensions:$versions.lifecycle" +} diff --git a/lifecycle-common/src/main/AndroidManifest.xml b/lifecycle-common/src/main/AndroidManifest.xml new file mode 100644 index 0000000..f617b1f --- /dev/null +++ b/lifecycle-common/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + diff --git a/lifecycle-common/src/main/java/ru/touchin/templates/livedata/SingleLiveEvent.kt b/lifecycle-common/src/main/java/ru/touchin/templates/livedata/SingleLiveEvent.kt new file mode 100644 index 0000000..27b0a1d --- /dev/null +++ b/lifecycle-common/src/main/java/ru/touchin/templates/livedata/SingleLiveEvent.kt @@ -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 : MutableLiveData() { + + private val pending = AtomicBoolean(false) + + override fun observe(owner: LifecycleOwner, observer: Observer) { + super.observe(owner, Observer { value -> + if (pending.compareAndSet(true, false)) { + observer.onChanged(value) + } + }) + } + + override fun setValue(value: T) { + pending.set(true) + super.setValue(value) + } + +} diff --git a/lifecycle-common/src/main/java/ru/touchin/templates/viewmodel/LifecycleViewModelProviders.kt b/lifecycle-common/src/main/java/ru/touchin/templates/viewmodel/LifecycleViewModelProviders.kt new file mode 100644 index 0000000..41ee808 --- /dev/null +++ b/lifecycle-common/src/main/java/ru/touchin/templates/viewmodel/LifecycleViewModelProviders.kt @@ -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}. + *

+ * 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.") + } + +} diff --git a/lifecycle-common/src/main/java/ru/touchin/templates/viewmodel/ViewModelFactory.kt b/lifecycle-common/src/main/java/ru/touchin/templates/viewmodel/ViewModelFactory.kt new file mode 100644 index 0000000..8061ba6 --- /dev/null +++ b/lifecycle-common/src/main/java/ru/touchin/templates/viewmodel/ViewModelFactory.kt @@ -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, @JvmSuppressWildcards Provider> +) : ViewModelProvider.Factory { + + @Suppress("UNCHECKED_CAST") + override fun create(modelClass: Class): T + = (creators[modelClass]?.get() as? T) ?: throw IllegalArgumentException("Unknown model class $modelClass") + +} diff --git a/lifecycle-common/src/main/java/ru/touchin/templates/viewmodel/ViewModelFactoryProvider.kt b/lifecycle-common/src/main/java/ru/touchin/templates/viewmodel/ViewModelFactoryProvider.kt new file mode 100644 index 0000000..b1684e2 --- /dev/null +++ b/lifecycle-common/src/main/java/ru/touchin/templates/viewmodel/ViewModelFactoryProvider.kt @@ -0,0 +1,7 @@ +package ru.touchin.templates.viewmodel + +interface ViewModelFactoryProvider { + + val viewModelFactory: ViewModelFactory + +} diff --git a/lifecycle-rx/.gitignore b/lifecycle-rx/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/lifecycle-rx/.gitignore @@ -0,0 +1 @@ +/build diff --git a/lifecycle-rx/build.gradle b/lifecycle-rx/build.gradle new file mode 100644 index 0000000..0956030 --- /dev/null +++ b/lifecycle-rx/build.gradle @@ -0,0 +1,24 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + compileSdkVersion versions.compileSdk + + defaultConfig { + minSdkVersion versions.minSdk + } +} + +dependencies { + api project(":utils") + api project(":logging") + + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + + implementation "com.android.support:appcompat-v7:$versions.supportLibrary" + + implementation "android.arch.lifecycle:extensions:$versions.lifecycle" + + implementation "io.reactivex.rxjava2:rxjava:$versions.rxJava" + implementation "io.reactivex.rxjava2:rxandroid:$versions.rxAndroid" +} diff --git a/lifecycle-rx/src/main/AndroidManifest.xml b/lifecycle-rx/src/main/AndroidManifest.xml new file mode 100644 index 0000000..68016e6 --- /dev/null +++ b/lifecycle-rx/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + diff --git a/utils/src/main/java/ru/touchin/roboswag/components/utils/destroyable/BaseDestroyable.kt b/lifecycle-rx/src/main/java/ru/touchin/livedata/destroyable/BaseDestroyable.kt similarity index 97% rename from utils/src/main/java/ru/touchin/roboswag/components/utils/destroyable/BaseDestroyable.kt rename to lifecycle-rx/src/main/java/ru/touchin/livedata/destroyable/BaseDestroyable.kt index fafa855..cca546b 100644 --- a/utils/src/main/java/ru/touchin/roboswag/components/utils/destroyable/BaseDestroyable.kt +++ b/lifecycle-rx/src/main/java/ru/touchin/livedata/destroyable/BaseDestroyable.kt @@ -1,4 +1,4 @@ -package ru.touchin.roboswag.components.utils.destroyable +package ru.touchin.livedata.destroyable import io.reactivex.Completable import io.reactivex.Flowable diff --git a/utils/src/main/java/ru/touchin/roboswag/components/utils/destroyable/Destroyable.kt b/lifecycle-rx/src/main/java/ru/touchin/livedata/destroyable/Destroyable.kt similarity index 99% rename from utils/src/main/java/ru/touchin/roboswag/components/utils/destroyable/Destroyable.kt rename to lifecycle-rx/src/main/java/ru/touchin/livedata/destroyable/Destroyable.kt index 8ad3012..93dd9b8 100644 --- a/utils/src/main/java/ru/touchin/roboswag/components/utils/destroyable/Destroyable.kt +++ b/lifecycle-rx/src/main/java/ru/touchin/livedata/destroyable/Destroyable.kt @@ -1,4 +1,4 @@ -package ru.touchin.roboswag.components.utils.destroyable +package ru.touchin.livedata.destroyable import io.reactivex.Completable import io.reactivex.Flowable diff --git a/lifecycle-rx/src/main/java/ru/touchin/livedata/dispatcher/BaseLiveDataDispatcher.kt b/lifecycle-rx/src/main/java/ru/touchin/livedata/dispatcher/BaseLiveDataDispatcher.kt new file mode 100644 index 0000000..afcc376 --- /dev/null +++ b/lifecycle-rx/src/main/java/ru/touchin/livedata/dispatcher/BaseLiveDataDispatcher.kt @@ -0,0 +1,57 @@ +package ru.touchin.livedata.dispatcher + +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.livedata.destroyable.BaseDestroyable +import ru.touchin.livedata.destroyable.Destroyable +import ru.touchin.livedata.event.CompletableEvent +import ru.touchin.livedata.event.MaybeEvent +import ru.touchin.livedata.event.ObservableEvent +import ru.touchin.livedata.event.SingleEvent + +class BaseLiveDataDispatcher(private val destroyable: BaseDestroyable = BaseDestroyable()) : LiveDataDispatcher, Destroyable by destroyable { + + override fun Flowable.dispatchTo(liveData: MutableLiveData>): 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 Observable.dispatchTo(liveData: MutableLiveData>): 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 Single.dispatchTo(liveData: MutableLiveData>): 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): Disposable { + liveData.value = CompletableEvent.Loading + return untilDestroy( + { liveData.value = CompletableEvent.Completed }, + { throwable -> liveData.value = CompletableEvent.Error(throwable) }) + } + + override fun Maybe.dispatchTo(liveData: MutableLiveData>): 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) }) + } + +} diff --git a/lifecycle-rx/src/main/java/ru/touchin/livedata/dispatcher/LiveDataDispatcher.kt b/lifecycle-rx/src/main/java/ru/touchin/livedata/dispatcher/LiveDataDispatcher.kt new file mode 100644 index 0000000..e1d7d8f --- /dev/null +++ b/lifecycle-rx/src/main/java/ru/touchin/livedata/dispatcher/LiveDataDispatcher.kt @@ -0,0 +1,27 @@ +package ru.touchin.livedata.dispatcher + +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.livedata.event.CompletableEvent +import ru.touchin.livedata.event.MaybeEvent +import ru.touchin.livedata.event.ObservableEvent +import ru.touchin.livedata.event.SingleEvent + +interface LiveDataDispatcher { + + fun Flowable.dispatchTo(liveData: MutableLiveData>): Disposable + + fun Observable.dispatchTo(liveData: MutableLiveData>): Disposable + + fun Single.dispatchTo(liveData: MutableLiveData>): Disposable + + fun Completable.dispatchTo(liveData: MutableLiveData): Disposable + + fun Maybe.dispatchTo(liveData: MutableLiveData>): Disposable + +} diff --git a/lifecycle-rx/src/main/java/ru/touchin/livedata/event/CompletableEvent.kt b/lifecycle-rx/src/main/java/ru/touchin/livedata/event/CompletableEvent.kt new file mode 100644 index 0000000..d0832ef --- /dev/null +++ b/lifecycle-rx/src/main/java/ru/touchin/livedata/event/CompletableEvent.kt @@ -0,0 +1,14 @@ +package ru.touchin.livedata.event + +/** + * Event class that emits from [io.reactivex.Completable]. + */ +sealed class CompletableEvent { + + object Loading: CompletableEvent() + + object Completed: CompletableEvent() + + data class Error(val throwable: Throwable): CompletableEvent() + +} diff --git a/lifecycle-rx/src/main/java/ru/touchin/livedata/event/MaybeEvent.kt b/lifecycle-rx/src/main/java/ru/touchin/livedata/event/MaybeEvent.kt new file mode 100644 index 0000000..0f88077 --- /dev/null +++ b/lifecycle-rx/src/main/java/ru/touchin/livedata/event/MaybeEvent.kt @@ -0,0 +1,16 @@ +package ru.touchin.livedata.event + +/** + * Event class that emits from [io.reactivex.Maybe]. + */ +sealed class MaybeEvent(open val data: T?) { + + class Loading(data: T?): MaybeEvent(data) + + class Success(override val data: T): MaybeEvent(data) + + class Error(val throwable: Throwable, data: T?): MaybeEvent(data) + + class Completed(data: T?): MaybeEvent(data) + +} diff --git a/lifecycle-rx/src/main/java/ru/touchin/livedata/event/ObservableEvent.kt b/lifecycle-rx/src/main/java/ru/touchin/livedata/event/ObservableEvent.kt new file mode 100644 index 0000000..5a6b8e2 --- /dev/null +++ b/lifecycle-rx/src/main/java/ru/touchin/livedata/event/ObservableEvent.kt @@ -0,0 +1,16 @@ +package ru.touchin.livedata.event + +/** + * Event class that emits from [io.reactivex.Observable]. + */ +sealed class ObservableEvent(open val data: T?) { + + class Loading(data: T? = null): ObservableEvent(data) + + class Success(override val data: T): ObservableEvent(data) + + class Error(val throwable: Throwable, data: T? = null): ObservableEvent(data) + + class Completed(data: T? = null): ObservableEvent(data) + +} diff --git a/lifecycle-rx/src/main/java/ru/touchin/livedata/event/SingleEvent.kt b/lifecycle-rx/src/main/java/ru/touchin/livedata/event/SingleEvent.kt new file mode 100644 index 0000000..8b53f63 --- /dev/null +++ b/lifecycle-rx/src/main/java/ru/touchin/livedata/event/SingleEvent.kt @@ -0,0 +1,14 @@ +package ru.touchin.livedata.event + +/** + * Event class that emits from [io.reactivex.Single]. + */ +sealed class SingleEvent(open val data: T?) { + + class Loading(data: T?): SingleEvent(data) + + class Success(override val data: T): SingleEvent(data) + + class Error(val throwable: Throwable, data: T?): SingleEvent(data) + +} diff --git a/lifecycle-rx/src/main/java/ru/touchin/templates/viewmodel/RxViewModel.kt b/lifecycle-rx/src/main/java/ru/touchin/templates/viewmodel/RxViewModel.kt new file mode 100644 index 0000000..e27a860 --- /dev/null +++ b/lifecycle-rx/src/main/java/ru/touchin/templates/viewmodel/RxViewModel.kt @@ -0,0 +1,24 @@ +package ru.touchin.templates.viewmodel + +import android.arch.lifecycle.ViewModel +import android.support.annotation.CallSuper +import ru.touchin.livedata.dispatcher.BaseLiveDataDispatcher +import ru.touchin.livedata.dispatcher.LiveDataDispatcher +import ru.touchin.livedata.destroyable.BaseDestroyable +import ru.touchin.livedata.destroyable.Destroyable + +/** + * 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() + } + +} diff --git a/lifecycle-rx/src/main/res/values/strings.xml b/lifecycle-rx/src/main/res/values/strings.xml new file mode 100644 index 0000000..b340b3d --- /dev/null +++ b/lifecycle-rx/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + lifecycle-rx + diff --git a/logging/build.gradle b/logging/build.gradle index 217c17b..69333c1 100644 --- a/logging/build.gradle +++ b/logging/build.gradle @@ -4,7 +4,7 @@ android { compileSdkVersion versions.compileSdk defaultConfig { - minSdkVersion 16 + minSdkVersion versions.minSdk } compileOptions { diff --git a/navigation/build.gradle b/navigation/build.gradle index 26d4258..8a95e1c 100644 --- a/navigation/build.gradle +++ b/navigation/build.gradle @@ -1,10 +1,11 @@ apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' android { compileSdkVersion versions.compileSdk defaultConfig { - minSdkVersion 16 + minSdkVersion versions.minSdk } compileOptions { diff --git a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/OnFragmentStartedListener.java b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/OnFragmentStartedListener.java deleted file mode 100644 index 8d68057..0000000 --- a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/OnFragmentStartedListener.java +++ /dev/null @@ -1,39 +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.roboswag.components.navigation; - -import android.support.annotation.NonNull; -import android.support.v4.app.Fragment; - -/** - * Created by Gavriil Sitnikov on 08/10/2014. - * Base interface to listen child fragments start. - * Usually it helps to determine that fragment have showed on screen and we can change {@link android.app.Activity}'s navigation state for example. - */ -public interface OnFragmentStartedListener { - - /** - * Calls by child fragment (added via {@link android.support.v4.app.FragmentManager}) on it'sstart. - * - * @param fragment Child fragment which called this method. - */ - void onFragmentStarted(@NonNull Fragment fragment); - -} diff --git a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/SimpleActionBarDrawerToggle.java b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/SimpleActionBarDrawerToggle.java index e2d88f6..090f9c0 100644 --- a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/SimpleActionBarDrawerToggle.java +++ b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/SimpleActionBarDrawerToggle.java @@ -29,6 +29,7 @@ import android.view.MenuItem; import android.view.View; import ru.touchin.roboswag.components.navigation.activities.BaseActivity; +import ru.touchin.roboswag.components.navigation.activities.OnBackPressedListener; import ru.touchin.roboswag.components.utils.UiUtils; /** @@ -36,7 +37,7 @@ import ru.touchin.roboswag.components.utils.UiUtils; * Simple realization of one-side {@link ActionBarDrawerToggle}. */ public class SimpleActionBarDrawerToggle extends ActionBarDrawerToggle - implements FragmentManager.OnBackStackChangedListener, BaseActivity.OnBackPressedListener { + implements FragmentManager.OnBackStackChangedListener, OnBackPressedListener { @NonNull private final BaseActivity activity; diff --git a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java index ee39486..2e09da4 100644 --- a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java +++ b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java @@ -25,7 +25,6 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.util.ArraySet; import android.support.v7.app.AppCompatActivity; -import android.view.MenuItem; import java.util.Set; @@ -96,13 +95,9 @@ public abstract class BaseActivity extends AppCompatActivity { } @Override - public boolean onOptionsItemSelected(@NonNull final MenuItem item) { - if (item.getItemId() == android.R.id.home) { - onBackPressed(); - return true; - } else { - return super.onOptionsItemSelected(item); - } + public boolean onSupportNavigateUp() { + onBackPressed(); + return true; } public void addOnBackPressedListener(@NonNull final OnBackPressedListener onBackPressedListener) { @@ -123,18 +118,4 @@ public abstract class BaseActivity extends AppCompatActivity { super.onBackPressed(); } - /* - * Interface to be implemented for someone who want to intercept device back button pressing event. - */ - public interface OnBackPressedListener { - - /** - * Calls when user presses device back button. - * - * @return True if it is processed by this object. - */ - boolean onBackPressed(); - - } - } diff --git a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/activities/OnBackPressedListener.java b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/activities/OnBackPressedListener.java new file mode 100644 index 0000000..0f052f2 --- /dev/null +++ b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/activities/OnBackPressedListener.java @@ -0,0 +1,15 @@ +package ru.touchin.roboswag.components.navigation.activities; + +/** + * Interface to be implemented for someone who want to intercept device back button pressing event. + */ +public interface OnBackPressedListener { + + /** + * Calls when user presses device back button. + * + * @return True if it is processed by this object. + */ + boolean onBackPressed(); + +} diff --git a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java index 065f06b..34e4b32 100644 --- a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java +++ b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java @@ -20,6 +20,8 @@ package ru.touchin.roboswag.components.navigation.fragments; import android.animation.Animator; +import android.annotation.SuppressLint; +import android.arch.lifecycle.Lifecycle; import android.content.Context; import android.content.Intent; import android.graphics.Canvas; @@ -27,8 +29,10 @@ import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.os.SystemClock; +import android.support.annotation.CallSuper; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; import android.support.v4.app.FragmentActivity; import android.view.LayoutInflater; import android.view.Menu; @@ -41,6 +45,7 @@ import android.widget.FrameLayout; import java.lang.reflect.Constructor; +import ru.touchin.roboswag.components.navigation.BuildConfig; import ru.touchin.roboswag.components.navigation.viewcontrollers.ViewController; import ru.touchin.roboswag.core.log.Lc; import ru.touchin.roboswag.core.log.LcGroup; @@ -54,24 +59,15 @@ import ru.touchin.roboswag.core.utils.ShouldNotHappenException; * @param Type of {@link FragmentActivity} where fragment could be attached to. */ @SuppressWarnings("PMD.TooManyMethods") -public class ViewControllerFragment extends ViewFragment { +public class ViewControllerFragment extends Fragment { private static final String VIEW_CONTROLLER_CLASS_EXTRA = "VIEW_CONTROLLER_CLASS_EXTRA"; private static final String VIEW_CONTROLLER_STATE_EXTRA = "VIEW_CONTROLLER_STATE_EXTRA"; - private static boolean inDebugMode; private static long acceptableUiCalculationTime = 100; - /** - * Enables debugging features like serialization of {@link #getState()} every creation. - */ - public static void setInDebugMode() { - inDebugMode = true; - } - /** * Sets acceptable UI calculation time so there will be warnings in logs if ViewController's inflate/layout actions will take more than that time. - * Works only if {@link #setInDebugMode()} called. * It's 100ms by default. */ public static void setAcceptableUiCalculationTime(final long acceptableUiCalculationTime) { @@ -113,6 +109,8 @@ public class ViewControllerFragment>) getArguments().getSerializable(VIEW_CONTROLLER_CLASS_EXTRA); state = savedInstanceState != null ? savedInstanceState.getParcelable(VIEW_CONTROLLER_STATE_EXTRA) : (getArguments() != null ? getArguments().getParcelable(VIEW_CONTROLLER_STATE_EXTRA) : null); if (state != null) { - if (inDebugMode) { + if (BuildConfig.DEBUG) { state = reserialize(state); } } else { @@ -148,43 +146,6 @@ public class ViewControllerFragment constructor = viewControllerClass.getConstructors()[0]; - final ViewController.CreationContext creationContext = new ViewController.CreationContext(activity, this, view); - final long creationTime = inDebugMode ? SystemClock.elapsedRealtime() : 0; - try { - switch (constructor.getParameterTypes().length) { - case 2: - return (ViewController) constructor.newInstance(creationContext, savedInstanceState); - case 3: - return (ViewController) constructor.newInstance(this, creationContext, savedInstanceState); - default: - throw new ShouldNotHappenException("Wrong constructor parameters count: " + constructor.getParameterTypes().length); - } - } catch (@NonNull final Exception exception) { - throw new ShouldNotHappenException(exception); - } finally { - checkCreationTime(creationTime); - } - } - - private void checkCreationTime(final long creationTime) { - if (inDebugMode) { - final long creationPeriod = SystemClock.elapsedRealtime() - creationTime; - if (creationPeriod > acceptableUiCalculationTime) { - LcGroup.UI_METRICS.w("Creation of %s took too much: %dms", viewControllerClass, creationPeriod); - } - } - } - @NonNull @Override public final View onCreateView( @@ -199,7 +160,7 @@ public class ViewControllerFragment constructor = viewControllerClass.getConstructors()[0]; + final ViewController.CreationContext creationContext = new ViewController.CreationContext(activity, this, view); + final long creationTime = BuildConfig.DEBUG ? SystemClock.elapsedRealtime() : 0; + try { + switch (constructor.getParameterTypes().length) { + case 2: + return (ViewController) constructor.newInstance(creationContext, savedInstanceState); + case 3: + return (ViewController) constructor.newInstance(this, creationContext, savedInstanceState); + default: + throw new ShouldNotHappenException("Wrong constructor parameters count: " + constructor.getParameterTypes().length); + } + } catch (@NonNull final Exception exception) { + throw new ShouldNotHappenException(exception); + } finally { + checkCreationTime(creationTime); + } + } + + private void checkCreationTime(final long creationTime) { + if (BuildConfig.DEBUG) { + final long creationPeriod = SystemClock.elapsedRealtime() - creationTime; + if (creationPeriod > acceptableUiCalculationTime) { + LcGroup.UI_METRICS.w("Creation of %s took too much: %dms", viewControllerClass, creationPeriod); + } + } + } + private static class PlaceholderView extends FrameLayout { @NonNull @@ -353,7 +389,7 @@ public class ViewControllerFragment 0) { + if (BuildConfig.DEBUG && lastMeasureTime > 0) { final long layoutTime = SystemClock.uptimeMillis() - lastMeasureTime; if (layoutTime > acceptableUiCalculationTime) { LcGroup.UI_METRICS.w("Measure and layout of %s took too much: %dms", tagName, layoutTime); diff --git a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewFragment.java b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewFragment.java deleted file mode 100644 index bca1f28..0000000 --- a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewFragment.java +++ /dev/null @@ -1,229 +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.roboswag.components.navigation.fragments; - -import android.arch.lifecycle.Lifecycle; -import android.os.Bundle; -import android.support.annotation.CallSuper; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentActivity; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import ru.touchin.roboswag.components.navigation.OnFragmentStartedListener; -import ru.touchin.roboswag.core.log.Lc; -import ru.touchin.roboswag.core.utils.BiConsumer; - -/** - * Created by Gavriil Sitnikov on 21/10/2015. - * Non-background fragment that have specific activity as a parent. - * - * @param Type of activity which to such fragment could be attached. - */ -public abstract class ViewFragment extends Fragment implements OnFragmentStartedListener { - - private boolean appeared; - - /** - * Returns if fragment have parent fragment. - * - * @return Returns true if fragment is in some fragment's children stack. - */ - public boolean isChildFragment() { - return getParentFragment() != null; - } - - /** - * Returns specific activity which to this fragment could be attached. - * - * @return Returns parent activity. - */ - @SuppressWarnings("unchecked") - @Nullable - protected final TActivity getBaseActivity() { - if (getActivity() == null) { - return null; - } - - try { - return (TActivity) getActivity(); - } catch (final ClassCastException exception) { - Lc.assertion(exception); - return null; - } - } - - @NonNull - @Override - public View onCreateView(@NonNull final LayoutInflater inflater, - @Nullable final ViewGroup container, - @Nullable final Bundle savedInstanceState) { - throw new IllegalStateException("Method onCreateView() should be overridden"); - } - - @Override - @CallSuper - public void onFragmentStarted(@NonNull final Fragment fragment) { - //do nothing - } - - @Override - public void onActivityCreated(@Nullable final Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - if (getView() == null || getBaseActivity() == null) { - Lc.assertion("View and activity shouldn't be null"); - } - } - - private void callMethodAfterInstantiation(@NonNull final BiConsumer action) { - if (getView() == null || getBaseActivity() == null) { - Lc.assertion("View and activity shouldn't be null"); - return; - } - try { - action.accept(getView(), getBaseActivity()); - } catch (final Exception exception) { - Lc.assertion(exception); - } - } - - @Deprecated - @Override - public void onStart() { - super.onStart(); - callMethodAfterInstantiation(this::onStart); - } - - /** - * Replacement of {@link #onStart} with non null activity as first parameter. - * - * @param view Instantiated view. - * @param activity Activity which fragment attached to. - */ - @CallSuper - @SuppressWarnings("RestrictedApi") - //RestrictedApi: we need isMenuVisible() to check analytics rightly! - protected void onStart(@NonNull final View view, @NonNull final TActivity activity) { - if (getParentFragment() instanceof OnFragmentStartedListener) { - ((OnFragmentStartedListener) getParentFragment()).onFragmentStarted(this); - } else if (activity instanceof OnFragmentStartedListener) { - ((OnFragmentStartedListener) activity).onFragmentStarted(this); - } - if (!appeared && isMenuVisible()) { - onAppear(view, activity); - } - } - - /** - * Called when fragment is moved in started state and it's {@link #isMenuVisible()} sets to true. - * Usually it is indicating that user can't see fragment on screen and useful to track analytics events. - * - * @param view Instantiated view. - * @param activity Activity which fragment attached to. - */ - protected void onAppear(@NonNull final View view, @NonNull final TActivity activity) { - appeared = true; - } - - @Deprecated - @Override - public void onResume() { - super.onResume(); - callMethodAfterInstantiation(this::onResume); - } - - /** - * Replacement of {@link #onResume} with non null activity as first parameter. - * - * @param view Instantiated view. - * @param activity Activity which fragment attached to. - */ - @CallSuper - protected void onResume(@NonNull final View view, @NonNull final TActivity activity) { - //do nothing - } - - @Override - public void setMenuVisibility(final boolean menuVisible) { - super.setMenuVisibility(menuVisible); - if (getBaseActivity() != null && getView() != null) { - final boolean started = getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED); - if (!appeared && menuVisible && started) { - onAppear(getView(), getBaseActivity()); - } - if (appeared && (!menuVisible || !started)) { - onDisappear(getView(), getBaseActivity()); - } - } - } - - @Deprecated - @Override - public void onPause() { - callMethodAfterInstantiation(this::onPause); - super.onPause(); - } - - /** - * Replacement of {@link #onPause} with non null activity as first parameter. - * - * @param view Instantiated view. - * @param activity Activity which fragment attached to. - */ - @CallSuper - protected void onPause(@NonNull final View view, @NonNull final TActivity activity) { - // do nothing - } - - /** - * Called when fragment is moved in stopped state or it's {@link #isMenuVisible()} sets to false. - * Usually it is indicating that user can't see fragment on screen and useful to track analytics events. - * - * @param view Instantiated view. - * @param activity Activity which fragment attached to. - */ - protected void onDisappear(@NonNull final View view, @NonNull final TActivity activity) { - appeared = false; - } - - @Deprecated - @Override - public void onStop() { - callMethodAfterInstantiation(this::onStop); - super.onStop(); - } - - /** - * Replacement of {@link #onStop} with non null activity as first parameter. - * - * @param view Instantiated view. - * @param activity Activity which fragment attached to. - */ - @CallSuper - protected void onStop(@NonNull final View view, @NonNull final TActivity activity) { - if (appeared) { - onDisappear(view, activity); - } - } - -} diff --git a/sample/build.gradle b/sample/build.gradle index 65044a9..24ac6ff 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -3,14 +3,16 @@ apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' android { - compileSdkVersion 27 + compileSdkVersion versions.compileSdk + defaultConfig { applicationId "ru.touchin.roboswag.components" - minSdkVersion 16 - targetSdkVersion 27 + minSdkVersion versions.minSdk + targetSdkVersion versions.compileSdk versionCode 1 versionName "1.0" } + buildTypes { release { minifyEnabled false @@ -21,6 +23,7 @@ android { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation 'com.android.support:appcompat-v7:27.1.1' + + implementation "com.android.support:appcompat-v7:$versions.supportLibrary" implementation 'com.android.support.constraint:constraint-layout:1.1.2' } diff --git a/settings.gradle b/settings.gradle index 9e1f446..c6b0741 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -include ':sample', ':utils', ':logging', ':navigation', ':storable' +include ':sample', ':utils', ':logging', ':navigation', ':storable', ':api-logansquare', ':lifecycle-common', ':lifecycle-rx' diff --git a/storable/build.gradle b/storable/build.gradle index 2a6be21..18cb730 100644 --- a/storable/build.gradle +++ b/storable/build.gradle @@ -4,7 +4,7 @@ android { compileSdkVersion versions.compileSdk defaultConfig { - minSdkVersion 16 + minSdkVersion versions.minSdk } compileOptions { diff --git a/utils/build.gradle b/utils/build.gradle index 217c17b..69333c1 100644 --- a/utils/build.gradle +++ b/utils/build.gradle @@ -4,7 +4,7 @@ android { compileSdkVersion versions.compileSdk defaultConfig { - minSdkVersion 16 + minSdkVersion versions.minSdk } compileOptions { diff --git a/utils/src/main/java/ru/touchin/roboswag/core/utils/BiConsumer.java b/utils/src/main/java/ru/touchin/roboswag/core/utils/BiConsumer.java deleted file mode 100644 index c13ac22..0000000 --- a/utils/src/main/java/ru/touchin/roboswag/core/utils/BiConsumer.java +++ /dev/null @@ -1,19 +0,0 @@ -package ru.touchin.roboswag.core.utils; - -import android.support.annotation.Nullable; - -/** - * A functional interface (callback) that accepts two values (of possibly different types). - * @param the first value type - * @param the second value type - */ -public interface BiConsumer { - - /** - * Performs an operation on the given values. - * @param t1 the first value - * @param t2 the second value - * @throws Exception on error - */ - void accept(@Nullable T1 t1, @Nullable T2 t2) throws Exception; -} diff --git a/utils/src/main/java/ru/touchin/templates/DeviceUtils.java b/utils/src/main/java/ru/touchin/templates/DeviceUtils.java new file mode 100644 index 0000000..8d8284d --- /dev/null +++ b/utils/src/main/java/ru/touchin/templates/DeviceUtils.java @@ -0,0 +1,146 @@ +/* + * 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.Manifest; +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.pm.PackageManager; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.os.Process; +import android.support.annotation.NonNull; +import android.telephony.TelephonyManager; + +/** + * Utility class that is providing common methods related to android device. + */ +public final class DeviceUtils { + + /** + * Detects active network type. + * + * @param context Application context + * @return Active network type {@link NetworkType} + */ + @NonNull + public static NetworkType getNetworkType(@NonNull final Context context) { + if (context.checkPermission(Manifest.permission.ACCESS_NETWORK_STATE, Process.myPid(), Process.myUid()) + != PackageManager.PERMISSION_GRANTED) { + return NetworkType.UNKNOWN; + } + final ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + @SuppressLint("MissingPermission") final NetworkInfo info = cm.getActiveNetworkInfo(); + if (info == null || !info.isConnected()) { + return NetworkType.NONE; + } + if (info.getType() == ConnectivityManager.TYPE_WIFI) { + return NetworkType.WI_FI; + } + if (info.getType() == ConnectivityManager.TYPE_MOBILE) { + final int networkType = info.getSubtype(); + switch (networkType) { + case TelephonyManager.NETWORK_TYPE_GPRS: + case TelephonyManager.NETWORK_TYPE_EDGE: + case TelephonyManager.NETWORK_TYPE_CDMA: + case TelephonyManager.NETWORK_TYPE_1xRTT: + case TelephonyManager.NETWORK_TYPE_IDEN: + return NetworkType.MOBILE_2G; + case TelephonyManager.NETWORK_TYPE_UMTS: + case TelephonyManager.NETWORK_TYPE_EVDO_0: + case TelephonyManager.NETWORK_TYPE_EVDO_A: + case TelephonyManager.NETWORK_TYPE_HSDPA: + case TelephonyManager.NETWORK_TYPE_HSUPA: + case TelephonyManager.NETWORK_TYPE_HSPA: + case TelephonyManager.NETWORK_TYPE_EVDO_B: + case TelephonyManager.NETWORK_TYPE_EHRPD: + case TelephonyManager.NETWORK_TYPE_HSPAP: + return NetworkType.MOBILE_3G; + case TelephonyManager.NETWORK_TYPE_LTE: + case 19: // NETWORK_TYPE_LTE_CA is hide + return NetworkType.MOBILE_LTE; + case TelephonyManager.NETWORK_TYPE_UNKNOWN: + default: + return NetworkType.UNKNOWN; + } + } + return NetworkType.UNKNOWN; + } + + /** + * Detects if some network connected. + * + * @param context Application context + * @return true if network connected, false otherwise. + */ + public static boolean isNetworkConnected(@NonNull final Context context) { + return getNetworkType(context) != NetworkType.NONE; + } + + private DeviceUtils() { + } + + /** + * Available network types. + */ + public enum NetworkType { + /** + * Mobile 2G network. + */ + MOBILE_2G("2g"), + /** + * Mobile 3G network. + */ + MOBILE_3G("3g"), + /** + * Mobile LTE network. + */ + MOBILE_LTE("lte"), + /** + * Wi-Fi network. + */ + WI_FI("Wi-Fi"), + /** + * Unknown network type. + */ + UNKNOWN("unknown"), + /** + * No network. + */ + NONE("none"); + + @NonNull + private final String name; + + NetworkType(@NonNull final String name) { + this.name = name; + } + + /** + * @return Network type readable name. + */ + @NonNull + public String getName() { + return name; + } + + } + +} From 4e81ba9c9aab12d781135759565d7b27db3c1d8a Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Tue, 21 Aug 2018 12:40:23 +0300 Subject: [PATCH 85/95] Added views module --- settings.gradle | 2 +- .../touchin/defaults/DefaultTextWatcher.java | 24 + views/.gitignore | 1 + views/build.gradle | 21 + views/src/main/AndroidManifest.xml | 2 + .../components/views/MaterialLoadingBar.java | 119 +++++ .../views/MaterialProgressDrawable.java | 292 +++++++++++ .../components/views/TypefacedEditText.java | 332 +++++++++++++ .../components/views/TypefacedTextView.java | 467 ++++++++++++++++++ .../views/internal/AttributesUtils.java | 170 +++++++ views/src/main/res/values/attrs.xml | 27 + 11 files changed, 1456 insertions(+), 1 deletion(-) create mode 100644 utils/src/main/java/ru/touchin/defaults/DefaultTextWatcher.java create mode 100644 views/.gitignore create mode 100644 views/build.gradle create mode 100644 views/src/main/AndroidManifest.xml create mode 100644 views/src/main/java/ru/touchin/roboswag/components/views/MaterialLoadingBar.java create mode 100644 views/src/main/java/ru/touchin/roboswag/components/views/MaterialProgressDrawable.java create mode 100644 views/src/main/java/ru/touchin/roboswag/components/views/TypefacedEditText.java create mode 100644 views/src/main/java/ru/touchin/roboswag/components/views/TypefacedTextView.java create mode 100644 views/src/main/java/ru/touchin/roboswag/components/views/internal/AttributesUtils.java create mode 100644 views/src/main/res/values/attrs.xml diff --git a/settings.gradle b/settings.gradle index c6b0741..3e31f83 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -include ':sample', ':utils', ':logging', ':navigation', ':storable', ':api-logansquare', ':lifecycle-common', ':lifecycle-rx' +include ':sample', ':utils', ':logging', ':navigation', ':storable', ':api-logansquare', ':lifecycle-common', ':lifecycle-rx', ':views' diff --git a/utils/src/main/java/ru/touchin/defaults/DefaultTextWatcher.java b/utils/src/main/java/ru/touchin/defaults/DefaultTextWatcher.java new file mode 100644 index 0000000..563c518 --- /dev/null +++ b/utils/src/main/java/ru/touchin/defaults/DefaultTextWatcher.java @@ -0,0 +1,24 @@ +package ru.touchin.defaults; + +import android.support.annotation.NonNull; +import android.text.Editable; +import android.text.TextWatcher; + +public class DefaultTextWatcher implements TextWatcher { + + @Override + public void beforeTextChanged(@NonNull final CharSequence oldText, final int start, final int count, final int after) { + // Do nothing + } + + @Override + public void onTextChanged(@NonNull final CharSequence inputText, final int start, final int before, final int count) { + // Do nothing + } + + @Override + public void afterTextChanged(@NonNull final Editable editable) { + // Do nothing + } + +} diff --git a/views/.gitignore b/views/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/views/.gitignore @@ -0,0 +1 @@ +/build diff --git a/views/build.gradle b/views/build.gradle new file mode 100644 index 0000000..9ea4f9e --- /dev/null +++ b/views/build.gradle @@ -0,0 +1,21 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion versions.compileSdk + + defaultConfig { + minSdkVersion versions.minSdk + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + api project(":utils") + api project(":logging") + + implementation "com.android.support:design:$versions.supportLibrary" +} diff --git a/views/src/main/AndroidManifest.xml b/views/src/main/AndroidManifest.xml new file mode 100644 index 0000000..354617c --- /dev/null +++ b/views/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + diff --git a/views/src/main/java/ru/touchin/roboswag/components/views/MaterialLoadingBar.java b/views/src/main/java/ru/touchin/roboswag/components/views/MaterialLoadingBar.java new file mode 100644 index 0000000..c3b88ef --- /dev/null +++ b/views/src/main/java/ru/touchin/roboswag/components/views/MaterialLoadingBar.java @@ -0,0 +1,119 @@ +/* + * 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.roboswag.components.views; + +import android.content.Context; +import android.content.res.TypedArray; +import android.os.Build; +import android.support.annotation.ColorInt; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.widget.AppCompatImageView; +import android.util.AttributeSet; +import android.util.TypedValue; + +import ru.touchin.roboswag.components.utils.UiUtils; + +/** + * Created by Ilia Kurtov on 07/12/2016. + * Simple endless progress bar view in material (round circle) style. + * It is able to setup size, stroke width and color. + * See MaterialLoadingBar Attributes: + * R.styleable#MaterialLoadingBar_size + * R.styleable#MaterialLoadingBar_strokeWidth + * R.styleable#MaterialLoadingBar_color + * Use + * R.styleable#MaterialLoadingBar_materialLoadingBarStyle + * to set default style of MaterialLoadingBar in your Theme. + * Sample: + * + * + */ +public class MaterialLoadingBar extends AppCompatImageView { + + private static int getPrimaryColor(@NonNull final Context context) { + final int colorAttr; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + colorAttr = android.R.attr.colorPrimary; + } else { + colorAttr = context.getResources().getIdentifier("colorPrimary", "attr", context.getPackageName()); + } + final TypedValue outValue = new TypedValue(); + context.getTheme().resolveAttribute(colorAttr, outValue, true); + return outValue.data; + } + + private MaterialProgressDrawable progressDrawable; + + public MaterialLoadingBar(@NonNull final Context context) { + this(context, null); + } + + public MaterialLoadingBar(@NonNull final Context context, @Nullable final AttributeSet attrs) { + this(context, attrs, R.attr.materialLoadingBarStyle); + } + + public MaterialLoadingBar(@NonNull final Context context, @Nullable final AttributeSet attrs, final int defStyleAttr) { + super(context, attrs, defStyleAttr); + + final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MaterialLoadingBar, + defStyleAttr, + 0); + final int size = (int) typedArray.getDimension(R.styleable.MaterialLoadingBar_size, UiUtils.OfMetrics.dpToPixels(context, 48)); + final int color = typedArray.getColor(R.styleable.MaterialLoadingBar_color, getPrimaryColor(context)); + final float strokeWidth = typedArray.getDimension(R.styleable.MaterialLoadingBar_strokeWidth, + UiUtils.OfMetrics.dpToPixels(context, 4)); + typedArray.recycle(); + + progressDrawable = new MaterialProgressDrawable(context, size); + setColor(color); + progressDrawable.setStrokeWidth(strokeWidth); + setScaleType(ScaleType.CENTER); + setImageDrawable(progressDrawable); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + progressDrawable.start(); + } + + @Override + protected void onDetachedFromWindow() { + progressDrawable.stop(); + super.onDetachedFromWindow(); + } + + /** + * Set color of loader. + * + * @param colorInt Color of loader to be set. + */ + public void setColor(@ColorInt final int colorInt) { + progressDrawable.setColor(colorInt); + } + +} diff --git a/views/src/main/java/ru/touchin/roboswag/components/views/MaterialProgressDrawable.java b/views/src/main/java/ru/touchin/roboswag/components/views/MaterialProgressDrawable.java new file mode 100644 index 0000000..a152cb4 --- /dev/null +++ b/views/src/main/java/ru/touchin/roboswag/components/views/MaterialProgressDrawable.java @@ -0,0 +1,292 @@ +/* + * 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.roboswag.components.views; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.drawable.Animatable; +import android.graphics.drawable.Drawable; +import android.os.SystemClock; +import android.support.annotation.ColorInt; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import ru.touchin.roboswag.components.utils.UiUtils; + +/** + * Created by Gavriil Sitnikov on 01/03/16. + * Simple realization of endless progress bar which is looking material-like. + */ +public class MaterialProgressDrawable extends Drawable implements Runnable, Animatable { + + private static final int UPDATE_INTERVAL = 1000 / 60; + + private static final float DEFAULT_STROKE_WIDTH_DP = 4.5f; + private static final Parameters DEFAULT_PARAMETERS = new Parameters(20, 270, 4, 12, 4, 8); + + private final int size; + @NonNull + private final Paint paint; + @NonNull + private Parameters parameters = DEFAULT_PARAMETERS; + @NonNull + private final RectF arcBounds = new RectF(); + + private float rotationAngle; + private float arcSize; + private boolean running; + + public MaterialProgressDrawable(@NonNull final Context context) { + this(context, -1); + } + + public MaterialProgressDrawable(@NonNull final Context context, final int size) { + super(); + + this.size = size; + paint = new Paint(Paint.ANTI_ALIAS_FLAG); + paint.setStyle(Paint.Style.STROKE); + paint.setStrokeWidth(UiUtils.OfMetrics.dpToPixels(context, DEFAULT_STROKE_WIDTH_DP)); + paint.setColor(Color.BLACK); + } + + @Override + public int getIntrinsicWidth() { + return size; + } + + @Override + public int getIntrinsicHeight() { + return size; + } + + /** + * Returns width of arc. + * + * @return Width. + */ + public float getStrokeWidth() { + return paint.getStrokeWidth(); + } + + /** + * Sets width of arc. + * + * @param strokeWidth Width. + */ + public void setStrokeWidth(final float strokeWidth) { + paint.setStrokeWidth(strokeWidth); + updateArcBounds(); + invalidateSelf(); + } + + /** + * Sets color of arc. + * + * @param color Color. + */ + public void setColor(@ColorInt final int color) { + paint.setColor(color); + invalidateSelf(); + } + + /** + * Returns magic parameters of spinning. + * + * @return Parameters of spinning. + */ + @NonNull + public Parameters getParameters() { + return parameters; + } + + /** + * Sets magic parameters of spinning. + * + * @param parameters Parameters of spinning. + */ + public void setParameters(@NonNull final Parameters parameters) { + this.parameters = parameters; + invalidateSelf(); + } + + @Override + protected void onBoundsChange(@NonNull final Rect bounds) { + super.onBoundsChange(bounds); + updateArcBounds(); + } + + private void updateArcBounds() { + arcBounds.set(getBounds()); + //HACK: + 1 as anti-aliasing drawing bug workaround + final int inset = (int) (paint.getStrokeWidth() / 2) + 1; + arcBounds.inset(inset, inset); + } + + @SuppressWarnings("PMD.NPathComplexity") + @Override + public void draw(@NonNull final Canvas canvas) { + final boolean isGrowingCycle = (((int) (arcSize / parameters.maxAngle)) % 2) == 0; + final float angle = arcSize % parameters.maxAngle; + final float shift = (angle / parameters.maxAngle) * parameters.gapAngle; + canvas.drawArc(arcBounds, isGrowingCycle ? rotationAngle + shift : rotationAngle + parameters.gapAngle - shift, + isGrowingCycle ? angle + parameters.gapAngle : parameters.maxAngle - angle + parameters.gapAngle, false, paint); + //TODO: compute based on animation start time + rotationAngle += isGrowingCycle ? parameters.rotationMagicNumber1 : parameters.rotationMagicNumber2; + arcSize += isGrowingCycle ? parameters.arcMagicNumber1 : parameters.arcMagicNumber2; + if (arcSize < 0) { + arcSize = 0; + } + if (isRunning()) { + scheduleSelf(this, SystemClock.uptimeMillis() + UPDATE_INTERVAL); + } + } + + @Override + public void setAlpha(final int alpha) { + paint.setAlpha(alpha); + invalidateSelf(); + } + + @Override + public void setColorFilter(@Nullable final ColorFilter colorFilter) { + paint.setColorFilter(colorFilter); + invalidateSelf(); + } + + @Override + public int getOpacity() { + return PixelFormat.TRANSLUCENT; + } + + @Override + public void start() { + if (!running) { + running = true; + run(); + } + } + + @Override + public void stop() { + if (running) { + unscheduleSelf(this); + running = false; + } + } + + @Override + public boolean isRunning() { + return running; + } + + @Override + public void run() { + if (running) { + invalidateSelf(); + } + } + + /** + * Some parameters which are using to spin progress bar. + */ + public static class Parameters { + + private final float gapAngle; + private final float maxAngle; + private final float rotationMagicNumber1; + private final float rotationMagicNumber2; + private final float arcMagicNumber1; + private final float arcMagicNumber2; + + public Parameters(final float gapAngle, final float maxAngle, + final float rotationMagicNumber1, final float rotationMagicNumber2, + final float arcMagicNumber1, final float arcMagicNumber2) { + this.gapAngle = gapAngle; + this.maxAngle = maxAngle; + this.rotationMagicNumber1 = rotationMagicNumber1; + this.rotationMagicNumber2 = rotationMagicNumber2; + this.arcMagicNumber1 = arcMagicNumber1; + this.arcMagicNumber2 = arcMagicNumber2; + } + + /** + * Returns angle of gap of arc. + * + * @return Angle of gap. + */ + public float getGapAngle() { + return gapAngle; + } + + /** + * Returns maximum angle of arc. + * + * @return Maximum angle of arc. + */ + public float getMaxAngle() { + return maxAngle; + } + + /** + * Magic parameter 1. + * + * @return Magic. + */ + public float getRotationMagicNumber1() { + return rotationMagicNumber1; + } + + /** + * Magic parameter 2. + * + * @return Magic. + */ + public float getRotationMagicNumber2() { + return rotationMagicNumber2; + } + + /** + * Magic parameter 3. + * + * @return Magic. + */ + public float getArcMagicNumber1() { + return arcMagicNumber1; + } + + /** + * Magic parameter 4. + * + * @return Magic. + */ + public float getArcMagicNumber2() { + return arcMagicNumber2; + } + + } + +} \ No newline at end of file diff --git a/views/src/main/java/ru/touchin/roboswag/components/views/TypefacedEditText.java b/views/src/main/java/ru/touchin/roboswag/components/views/TypefacedEditText.java new file mode 100644 index 0000000..d556a81 --- /dev/null +++ b/views/src/main/java/ru/touchin/roboswag/components/views/TypefacedEditText.java @@ -0,0 +1,332 @@ +/* + * 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.roboswag.components.views; + +import android.content.Context; +import android.content.res.TypedArray; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.TextInputLayout; +import android.support.v7.widget.AppCompatEditText; +import android.text.InputType; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.text.method.SingleLineTransformationMethod; +import android.text.method.TransformationMethod; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewParent; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; + +import java.util.ArrayList; +import java.util.List; + +import ru.touchin.defaults.DefaultTextWatcher; +import ru.touchin.roboswag.components.views.internal.AttributesUtils; +import ru.touchin.roboswag.core.log.Lc; + +/** + * Created by Gavriil Sitnikov on 18/07/2014. + * TextView that supports fonts from Typefaces class + */ + +/** + * Created by Gavriil Sitnikov on 18/07/2014. + * EditText that supports custom typeface and forces developer to specify if this view multiline or not. + * Also in debug mode it has common checks for popular bugs. + */ +@SuppressWarnings("PMD.ConstructorCallsOverridableMethod") +//ConstructorCallsOverridableMethod: it's ok as we need to setTypeface +public class TypefacedEditText extends AppCompatEditText { + + private static boolean inDebugMode; + + /** + * Enables debugging features like checking attributes on inflation. + */ + public static void setInDebugMode() { + inDebugMode = true; + } + + private boolean multiline; + private boolean constructed; + + @Nullable + private OnTextChangedListener onTextChangedListener; + + public TypefacedEditText(@NonNull final Context context) { + super(context); + initialize(context, null); + } + + public TypefacedEditText(@NonNull final Context context, @Nullable final AttributeSet attrs) { + super(context, attrs); + initialize(context, attrs); + } + + public TypefacedEditText(@NonNull final Context context, @Nullable final AttributeSet attrs, final int defStyle) { + super(context, attrs, defStyle); + initialize(context, attrs); + } + + private void initialize(@NonNull final Context context, @Nullable final AttributeSet attrs) { + constructed = true; + super.setIncludeFontPadding(false); + initializeTextChangedListener(); + if (attrs != null) { + final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.TypefacedEditText); + final boolean multiline = typedArray.getBoolean(R.styleable.TypefacedEditText_isMultiline, false); + if (multiline) { + setMultiline(AttributesUtils.getMaxLinesFromAttrs(context, attrs)); + } else { + setSingleLine(); + } + typedArray.recycle(); + if (inDebugMode) { + checkAttributes(context, attrs); + } + } + } + + @Nullable + public InputConnection onCreateInputConnection(@NonNull final EditorInfo attrs) { + final InputConnection inputConnection = super.onCreateInputConnection(attrs); + if (inputConnection != null && attrs.hintText == null) { + for (ViewParent parent = getParent(); parent instanceof View; parent = parent.getParent()) { + if (parent instanceof TextInputLayout) { + attrs.hintText = ((TextInputLayout) parent).getHint(); + break; + } + } + } + + return inputConnection; + } + + private void checkAttributes(@NonNull final Context context, @NonNull final AttributeSet attrs) { + final List errors = new ArrayList<>(); + Boolean multiline = null; + TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.TypefacedEditText); + AttributesUtils.checkAttribute(typedArray, errors, R.styleable.TypefacedEditText_isMultiline, true, + "isMultiline required parameter"); + if (typedArray.hasValue(R.styleable.TypefacedEditText_isMultiline)) { + multiline = typedArray.getBoolean(R.styleable.TypefacedEditText_isMultiline, false); + } + typedArray.recycle(); + + try { + final Class androidRes = Class.forName("com.android.internal.R$styleable"); + + typedArray = context.obtainStyledAttributes(attrs, AttributesUtils.getField(androidRes, "TextView")); + AttributesUtils.checkRegularTextViewAttributes(typedArray, androidRes, errors, "isMultiline"); + checkEditTextSpecificAttributes(typedArray, androidRes, errors); + if (multiline != null) { + checkMultilineAttributes(typedArray, androidRes, errors, multiline); + } + } catch (final Exception exception) { + Lc.e(exception, "Error during checking attributes"); + } + AttributesUtils.handleErrors(this, errors); + typedArray.recycle(); + } + + private void checkEditTextSpecificAttributes(@NonNull final TypedArray typedArray, @NonNull final Class androidRes, + @NonNull final List errors) + throws NoSuchFieldException, IllegalAccessException { + AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_singleLine"), false, + "remove singleLine and use isMultiline"); + AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_includeFontPadding"), false, + "includeFontPadding forbid parameter"); + AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_ellipsize"), false, + "ellipsize forbid parameter"); + + if (typedArray.hasValue(AttributesUtils.getField(androidRes, "TextView_hint"))) { + AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_textColorHint"), true, + "textColorHint required parameter if hint is not null"); + } + + AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_textSize"), true, + "textSize required parameter. If it's dynamic then use '0sp'"); + AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_inputType"), true, + "inputType required parameter"); + + final int inputType = typedArray.getInt(AttributesUtils.getField(androidRes, "TextView_inputType"), -1); + if (AttributesUtils.isNumberInputType(inputType)) { + errors.add("use inputType phone instead of number"); + } + + AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_imeOptions"), true, + "imeOptions required parameter"); + } + + private void checkMultilineAttributes(@NonNull final TypedArray typedArray, @NonNull final Class androidRes, + @NonNull final List errors, final boolean multiline) + throws NoSuchFieldException, IllegalAccessException { + if (multiline) { + if (typedArray.getInt(AttributesUtils.getField(androidRes, "TextView_lines"), -1) == 1) { + errors.add("lines should be more than 1 if isMultiline is true"); + } + if (typedArray.getInt(AttributesUtils.getField(androidRes, "TextView_maxLines"), -1) == 1) { + errors.add("maxLines should be more than 1 if isMultiline is true"); + } + if (!typedArray.hasValue(AttributesUtils.getField(androidRes, "TextView_maxLines")) + && !typedArray.hasValue(AttributesUtils.getField(androidRes, "TextView_maxLength"))) { + errors.add("specify maxLines or maxLength if isMultiline is true"); + } + } else { + AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_lines"), false, + "remove lines and use isMultiline"); + AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_maxLines"), false, + "maxLines remove and use isMultiline"); + AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_minLines"), false, + "minLines remove and use isMultiline"); + AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_maxLength"), true, + "maxLength required parameter if isMultiline is false"); + } + } + + private void initializeTextChangedListener() { + addTextChangedListener(new DefaultTextWatcher() { + @Override + public void onTextChanged(@NonNull final CharSequence inputText, final int start, final int before, final int count) { + if (onTextChangedListener != null) { + onTextChangedListener.onTextChanged(inputText); + } + } + }); + } + + /** + * Sets if view supports multiline text alignment. + * + * @param maxLines Maximum lines to be set. + */ + public void setMultiline(final int maxLines) { + if (maxLines <= 1) { + Lc.assertion("Wrong maxLines: " + maxLines); + return; + } + multiline = true; + final TransformationMethod transformationMethod = getTransformationMethod(); + super.setSingleLine(false); + super.setMaxLines(maxLines); + if (!(transformationMethod instanceof SingleLineTransformationMethod)) { + setTransformationMethod(transformationMethod); + } + } + + @Override + public void setSingleLine(final boolean singleLine) { + if (singleLine) { + setSingleLine(); + } else { + setMultiline(Integer.MAX_VALUE); + } + } + + @Override + public void setSingleLine() { + final TransformationMethod transformationMethod = getTransformationMethod(); + super.setSingleLine(true); + if (transformationMethod != null) { + /*DEBUG if (!(transformationMethod instanceof SingleLineTransformationMethod)) { + Lc.w("SingleLineTransformationMethod method ignored because of previous transformation method: " + transformationMethod); + }*/ + setTransformationMethod(transformationMethod); + } + setLines(1); + multiline = false; + } + + @Override + public void setLines(final int lines) { + if (constructed && multiline && lines == 1) { + Lc.assertion(new IllegalStateException(AttributesUtils.viewError(this, "lines = 1 is illegal if multiline is set to true"))); + return; + } + super.setLines(lines); + } + + @Override + public void setMaxLines(final int maxLines) { + if (constructed && !multiline && maxLines > 1) { + Lc.assertion(new IllegalStateException(AttributesUtils.viewError(this, "maxLines > 1 is illegal if multiline is set to false"))); + return; + } + super.setMaxLines(maxLines); + } + + @Override + public void setMinLines(final int minLines) { + if (constructed && !multiline && minLines > 1) { + Lc.assertion(new IllegalStateException(AttributesUtils.viewError(this, "minLines > 1 is illegal if multiline is set to false"))); + return; + } + super.setMinLines(minLines); + } + + @Override + public final void setIncludeFontPadding(final boolean includeFontPadding) { + if (!constructed) { + return; + } + Lc.assertion(new IllegalStateException( + AttributesUtils.viewError(this, "Do not specify font padding as it is hard to make pixel-perfect design with such option"))); + } + + @Override + public void setEllipsize(@NonNull final TextUtils.TruncateAt ellipsis) { + if (!constructed) { + return; + } + Lc.assertion(new IllegalStateException(AttributesUtils.viewError(this, "Do not specify ellipsize for EditText"))); + } + + @Override + public void setInputType(final int type) { + if (AttributesUtils.isNumberInputType(type)) { + Lc.assertion(new IllegalStateException(AttributesUtils.viewError(this, + "Do not specify number InputType for EditText, use phone instead"))); + super.setInputType(InputType.TYPE_CLASS_PHONE); + return; + } + super.setInputType(type); + } + + public void setOnTextChangedListener(@Nullable final OnTextChangedListener onTextChangedListener) { + this.onTextChangedListener = onTextChangedListener; + } + + /** + * Simplified variant of {@link TextWatcher}. + */ + public interface OnTextChangedListener { + + /** + * Calls when text have changed. + * + * @param text New text. + */ + void onTextChanged(@NonNull CharSequence text); + + } + +} diff --git a/views/src/main/java/ru/touchin/roboswag/components/views/TypefacedTextView.java b/views/src/main/java/ru/touchin/roboswag/components/views/TypefacedTextView.java new file mode 100644 index 0000000..188686c --- /dev/null +++ b/views/src/main/java/ru/touchin/roboswag/components/views/TypefacedTextView.java @@ -0,0 +1,467 @@ +/* + * 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.roboswag.components.views; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.TypedArray; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.widget.AppCompatTextView; +import android.text.TextUtils; +import android.text.method.TransformationMethod; +import android.util.AttributeSet; +import android.util.TypedValue; + +import java.util.ArrayList; +import java.util.List; + +import ru.touchin.roboswag.components.utils.UiUtils; +import ru.touchin.roboswag.components.views.internal.AttributesUtils; +import ru.touchin.roboswag.core.log.Lc; + +/** + * Created by Gavriil Sitnikov on 18/07/2014. + * TextView that supports custom typeface and forces developer to specify {@link LineStrategy}. + * Also in debug mode it has common checks for popular bugs. + */ +@SuppressWarnings("PMD.ConstructorCallsOverridableMethod") +//ConstructorCallsOverridableMethod: it's ok as we need to setTypeface +public class TypefacedTextView extends AppCompatTextView { + + private static final int SIZE_THRESHOLD = 10000; + + private static final int UNSPECIFIED_MEASURE_SPEC = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + private static final int START_SCALABLE_DIFFERENCE = 4; + + private static boolean inDebugMode; + + /** + * Enables debugging features like checking attributes on inflation. + */ + public static void setInDebugMode() { + inDebugMode = true; + } + + private boolean constructed; + @NonNull + private LineStrategy lineStrategy = LineStrategy.SINGLE_LINE_ELLIPSIZE; + + public TypefacedTextView(@NonNull final Context context) { + super(context); + initialize(context, null); + } + + public TypefacedTextView(@NonNull final Context context, @Nullable final AttributeSet attrs) { + super(context, attrs); + initialize(context, attrs); + } + + public TypefacedTextView(@NonNull final Context context, @Nullable final AttributeSet attrs, final int defStyle) { + super(context, attrs, defStyle); + initialize(context, attrs); + } + + private void initialize(@NonNull final Context context, @Nullable final AttributeSet attrs) { + constructed = true; + super.setIncludeFontPadding(false); + if (attrs != null) { + final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.TypefacedTextView); + final LineStrategy lineStrategy = LineStrategy + .byResIndex(typedArray.getInt(R.styleable.TypefacedTextView_lineStrategy, LineStrategy.MULTILINE_ELLIPSIZE.ordinal())); + if (lineStrategy.multiline) { + setLineStrategy(lineStrategy, AttributesUtils.getMaxLinesFromAttrs(context, attrs)); + } else { + setLineStrategy(lineStrategy); + } + typedArray.recycle(); + if (inDebugMode) { + checkAttributes(context, attrs); + } + } + } + + private void checkAttributes(@NonNull final Context context, @NonNull final AttributeSet attrs) { + final List errors = new ArrayList<>(); + LineStrategy lineStrategy = null; + TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.TypefacedTextView); + AttributesUtils.checkAttribute(typedArray, errors, R.styleable.TypefacedTextView_lineStrategy, true, + "lineStrategy required parameter"); + if (typedArray.hasValue(R.styleable.TypefacedTextView_lineStrategy)) { + lineStrategy = LineStrategy.byResIndex(typedArray.getInt(R.styleable.TypefacedTextView_lineStrategy, -1)); + } + typedArray.recycle(); + + try { + final Class androidRes = Class.forName("com.android.internal.R$styleable"); + + typedArray = context.obtainStyledAttributes(attrs, AttributesUtils.getField(androidRes, "TextView")); + AttributesUtils.checkRegularTextViewAttributes(typedArray, androidRes, errors, "lineStrategy"); + checkTextViewSpecificAttributes(typedArray, androidRes, errors); + + if (lineStrategy != null) { + checkLineStrategyAttributes(typedArray, androidRes, errors, lineStrategy); + } + } catch (final Exception exception) { + Lc.e(exception, "Error during checking attributes"); + } + AttributesUtils.handleErrors(this, errors); + typedArray.recycle(); + } + + private void checkTextViewSpecificAttributes(@NonNull final TypedArray typedArray, @NonNull final Class androidRes, + @NonNull final List errors) + throws NoSuchFieldException, IllegalAccessException { + + AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_phoneNumber"), false, + "phoneNumber forbid parameter"); + AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_password"), false, + "password forbid parameter"); + AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_numeric"), false, + "numeric forbid parameter"); + AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_inputType"), false, + "inputType forbid parameter"); + AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_imeOptions"), false, + "imeOptions forbid parameter"); + AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_imeActionId"), false, + "imeActionId forbid parameter"); + AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_imeActionLabel"), false, + "imeActionLabel forbid parameter"); + AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_hint"), false, + "hint forbid parameter"); + AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_editable"), false, + "editable forbid parameter"); + AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_digits"), false, + "digits forbid parameter"); + AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_cursorVisible"), false, + "cursorVisible forbid parameter"); + } + + private void checkLineStrategyAttributes(@NonNull final TypedArray typedArray, @NonNull final Class androidRes, + @NonNull final List errors, @NonNull final LineStrategy lineStrategy) + throws NoSuchFieldException, IllegalAccessException { + if (!lineStrategy.scalable) { + AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_textSize"), true, + "textSize required parameter. If it's dynamic then use '0sp'"); + } + if (lineStrategy.multiline) { + if (typedArray.getInt(AttributesUtils.getField(androidRes, "TextView_lines"), -1) == 1) { + errors.add("lines should be more than 1 if lineStrategy is true"); + } + if (typedArray.getInt(AttributesUtils.getField(androidRes, "TextView_maxLines"), -1) == 1) { + errors.add("maxLines should be more than 1 if lineStrategy is true"); + } + } else { + AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_lines"), false, + "remove lines and use lineStrategy"); + AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_maxLines"), false, + "remove maxLines and use lineStrategy"); + AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_minLines"), false, + "remove minLines and use lineStrategy"); + AttributesUtils.checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_textAllCaps"), false, + "remove textAllCaps and use app:textAllCaps"); + } + } + + /** + * Sets behavior of text if there is no space for it in one line. + * + * @param lineStrategy Specific {@link LineStrategy}. + */ + public void setLineStrategy(@NonNull final LineStrategy lineStrategy) { + setLineStrategy(lineStrategy, Integer.MAX_VALUE); + } + + /** + * Sets behavior of text if there is no space for it in one line. + * + * @param lineStrategy Specific {@link LineStrategy}; + * @param maxLines Max lines if line strategy is multiline. + */ + public void setLineStrategy(@NonNull final LineStrategy lineStrategy, final int maxLines) { + this.lineStrategy = lineStrategy; + final TransformationMethod transformationMethod = getTransformationMethod(); + super.setSingleLine(!lineStrategy.multiline); + if (transformationMethod != null) { + /*DEBUG if (!(transformationMethod instanceof SingleLineTransformationMethod)) { + Lc.w("SingleLineTransformationMethod method ignored because of previous transformation method: " + transformationMethod); + }*/ + setTransformationMethod(transformationMethod); + } + if (lineStrategy.multiline) { + super.setMaxLines(maxLines); + } + switch (lineStrategy) { + case SINGLE_LINE_ELLIPSIZE: + case MULTILINE_ELLIPSIZE: + super.setEllipsize(TextUtils.TruncateAt.END); + break; + case SINGLE_LINE_MARQUEE: + case MULTILINE_MARQUEE: + super.setEllipsize(TextUtils.TruncateAt.MARQUEE); + break; + case SINGLE_LINE_AUTO_SCALE: + super.setEllipsize(null); + break; + case SINGLE_LINE_ELLIPSIZE_MIDDLE: + case MULTILINE_ELLIPSIZE_MIDDLE: + super.setEllipsize(TextUtils.TruncateAt.MIDDLE); + break; + default: + Lc.assertion("Unknown line strategy: " + lineStrategy); + break; + } + if (lineStrategy.scalable) { + requestLayout(); + } + } + + /** + * Returns behavior of text if there is no space for it in one line. + * + * @return Specific {@link LineStrategy}. + */ + @NonNull + public LineStrategy getLineStrategy() { + return lineStrategy; + } + + @Override + public void setSingleLine() { + if (!constructed) { + return; + } + Lc.assertion(new IllegalStateException(AttributesUtils.viewError(this, "Do not specify setSingleLine use setLineStrategy instead"))); + } + + @Override + public void setSingleLine(final boolean singleLine) { + if (!constructed) { + return; + } + Lc.assertion(new IllegalStateException(AttributesUtils.viewError(this, "Do not specify setSingleLine use setLineStrategy instead"))); + } + + @Override + public void setLines(final int lines) { + if (constructed && lineStrategy.multiline && lines == 1) { + Lc.assertion(new IllegalStateException(AttributesUtils.viewError(this, "lines = 1 is illegal if lineStrategy is multiline"))); + return; + } + super.setLines(lines); + } + + @Override + public void setMaxLines(final int maxLines) { + if (constructed && !lineStrategy.multiline && maxLines > 1) { + Lc.assertion(new IllegalStateException( + AttributesUtils.viewError(this, "maxLines > 1 is illegal if lineStrategy is single line"))); + return; + } + super.setMaxLines(maxLines); + } + + @Override + public final void setIncludeFontPadding(final boolean includeFontPadding) { + if (!constructed) { + return; + } + Lc.assertion(new IllegalStateException( + AttributesUtils.viewError(this, "Do not specify font padding as it is hard to make pixel-perfect design with such option"))); + } + + @Override + public void setMinLines(final int minLines) { + if (constructed && !lineStrategy.multiline && minLines > 1) { + Lc.assertion(new IllegalStateException( + AttributesUtils.viewError(this, "minLines > 1 is illegal if lineStrategy is single line"))); + return; + } + super.setMinLines(minLines); + } + + @Override + public void setEllipsize(@NonNull final TextUtils.TruncateAt ellipsize) { + if (!constructed) { + return; + } + Lc.assertion(new IllegalStateException(AttributesUtils.viewError(this, "Do not specify ellipsize use setLineStrategy instead"))); + } + + @Override + public void setText(@Nullable final CharSequence text, @Nullable final BufferType type) { + super.setText(text, type); + if (constructed && lineStrategy.scalable) { + requestLayout(); + } + } + + @Override + public void setTextSize(final float size) { + if (constructed && lineStrategy.scalable) { + Lc.cutAssertion(new IllegalStateException(AttributesUtils.viewError(this, "textSize call is illegal if lineStrategy is scalable"))); + return; + } + super.setTextSize(size); + } + + @Override + public void setTextSize(final int unit, final float size) { + if (constructed && lineStrategy.scalable) { + Lc.assertion(new IllegalStateException(AttributesUtils.viewError(this, "textSize call is illegal if lineStrategy is scalable"))); + return; + } + super.setTextSize(unit, size); + } + + @SuppressLint("WrongCall") + //WrongCall: actually this method is always calling from onMeasure + private void computeScalableTextSize(final int maxWidth, final int maxHeight) { + final int minDifference = (int) UiUtils.OfMetrics.dpToPixels(getContext(), 1); + int difference = (int) UiUtils.OfMetrics.dpToPixels(getContext(), START_SCALABLE_DIFFERENCE); + ScaleAction scaleAction = ScaleAction.DO_NOTHING; + ScaleAction previousScaleAction = ScaleAction.DO_NOTHING; + do { + switch (scaleAction) { + case SCALE_DOWN: + if (difference > minDifference) { + difference -= minDifference; + super.setTextSize(TypedValue.COMPLEX_UNIT_PX, Math.max(0, getTextSize() - difference)); + } else { + super.setTextSize(TypedValue.COMPLEX_UNIT_PX, Math.max(0, getTextSize() - minDifference)); + if (previousScaleAction == ScaleAction.SCALE_UP) { + return; + } + } + break; + case SCALE_UP: + if (difference > minDifference) { + difference -= minDifference; + super.setTextSize(TypedValue.COMPLEX_UNIT_PX, Math.max(0, getTextSize() + difference)); + } else { + if (previousScaleAction == ScaleAction.SCALE_DOWN) { + return; + } + super.setTextSize(TypedValue.COMPLEX_UNIT_PX, Math.max(0, getTextSize() + minDifference)); + } + break; + case DO_NOTHING: + default: + break; + } + super.onMeasure(UNSPECIFIED_MEASURE_SPEC, UNSPECIFIED_MEASURE_SPEC); + previousScaleAction = scaleAction; + scaleAction = computeScaleAction(maxWidth, maxHeight); + } + while (scaleAction != ScaleAction.DO_NOTHING); + } + + @NonNull + private ScaleAction computeScaleAction(final int maxWidth, final int maxHeight) { + ScaleAction result = ScaleAction.DO_NOTHING; + if (maxWidth < getMeasuredWidth()) { + result = ScaleAction.SCALE_DOWN; + } else if (maxWidth > getMeasuredWidth()) { + result = ScaleAction.SCALE_UP; + } + + if (maxHeight < getMeasuredHeight()) { + result = ScaleAction.SCALE_DOWN; + } else if (maxHeight > getMeasuredHeight() && result != ScaleAction.SCALE_DOWN) { + result = ScaleAction.SCALE_UP; + } + return result; + } + + @Override + protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { + final int maxWidth = MeasureSpec.getSize(widthMeasureSpec); + final int maxHeight = MeasureSpec.getSize(heightMeasureSpec); + if (!constructed || !lineStrategy.scalable || (maxWidth <= 0 && maxHeight <= 0) || TextUtils.isEmpty(getText())) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + return; + } + + computeScalableTextSize(MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED ? maxWidth : SIZE_THRESHOLD, + MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.UNSPECIFIED ? maxHeight : SIZE_THRESHOLD); + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + private enum ScaleAction { + SCALE_DOWN, + SCALE_UP, + DO_NOTHING + } + + /** + * Specific behavior, mostly based on combination of {@link #getEllipsize()} and {@link #getMaxLines()} to specify how view should show text + * if there is no space for it on one line. + */ + public enum LineStrategy { + + /** + * Not more than one line and ellipsize text with dots at the end. + */ + SINGLE_LINE_ELLIPSIZE(false, false), + /** + * Not more than one line and ellipsize text with marquee at the end. + */ + SINGLE_LINE_MARQUEE(false, false), + /** + * Not more than one line and scale text to maximum possible size. + */ + SINGLE_LINE_AUTO_SCALE(false, true), + /** + * More than one line and ellipsize text with dots at the end. + */ + MULTILINE_ELLIPSIZE(true, false), + /** + * More than one line and ellipsize text with marquee at the end. + */ + MULTILINE_MARQUEE(true, false), + /** + * Not more than one line and ellipsize text with dots in the middle. + */ + SINGLE_LINE_ELLIPSIZE_MIDDLE(false, false), + /** + * More than one line and ellipsize text with dots in the middle. + */ + MULTILINE_ELLIPSIZE_MIDDLE(true, false); + + @NonNull + public static LineStrategy byResIndex(final int resIndex) { + if (resIndex < 0 || resIndex >= values().length) { + Lc.assertion("Unexpected resIndex " + resIndex); + return MULTILINE_ELLIPSIZE; + } + return values()[resIndex]; + } + + private final boolean multiline; + private final boolean scalable; + + LineStrategy(final boolean multiline, final boolean scalable) { + this.multiline = multiline; + this.scalable = scalable; + } + + } + +} diff --git a/views/src/main/java/ru/touchin/roboswag/components/views/internal/AttributesUtils.java b/views/src/main/java/ru/touchin/roboswag/components/views/internal/AttributesUtils.java new file mode 100644 index 0000000..a8c6474 --- /dev/null +++ b/views/src/main/java/ru/touchin/roboswag/components/views/internal/AttributesUtils.java @@ -0,0 +1,170 @@ +/* + * 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.roboswag.components.views.internal; + +import android.content.Context; +import android.content.res.TypedArray; +import android.support.annotation.NonNull; +import android.support.annotation.StyleableRes; +import android.text.InputType; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.view.View; + +import java.lang.reflect.Field; +import java.util.Collection; + +import ru.touchin.roboswag.components.utils.UiUtils; +import ru.touchin.roboswag.core.log.Lc; +import ru.touchin.roboswag.core.utils.ShouldNotHappenException; + +/** + * Created by Gavriil Sitnikov on 13/06/2016. + * Bunch of inner helper library methods to validate attributes of custom views. + */ +public final class AttributesUtils { + + /** + * Gets static field of class. + * + * @param resourcesClass Class to get field from; + * @param fieldName name of field; + * @param Type of object that is stored in field; + * @return Field value; + * @throws NoSuchFieldException Throws on reflection call; + * @throws IllegalAccessException Throws on reflection call. + */ + @NonNull + @SuppressWarnings("unchecked") + public static T getField(@NonNull final Class resourcesClass, @NonNull final String fieldName) + throws NoSuchFieldException, IllegalAccessException { + final Field field = resourcesClass.getDeclaredField(fieldName); + field.setAccessible(true); + return (T) field.get(null); + } + + /** + * Checks if attribute is in array or not and collecterror if attribute missed. + * + * @param typedArray Array of attributes; + * @param errors Errors to collect into; + * @param resourceId Id of attribute; + * @param required Is parameter have to be in array OR it have not to be in; + * @param description Description of error. + */ + public static void checkAttribute( + @NonNull final TypedArray typedArray, + @NonNull final Collection errors, + @StyleableRes final int resourceId, + final boolean required, + @NonNull final String description + ) { + if ((required && typedArray.hasValue(resourceId)) + || (!required && !typedArray.hasValue(resourceId))) { + return; + } + errors.add(description); + } + + /** + * Collects regular {@link android.widget.TextView} errors. + * + * @param typedArray Array of attributes; + * @param androidRes Class of styleable attributes; + * @param errors Errors to collect into; + * @param lineStrategyParameterName name of line strategy parameter; + * @throws NoSuchFieldException Throws during getting attribute values through reflection; + * @throws IllegalAccessException Throws during getting attribute values through reflection. + */ + public static void checkRegularTextViewAttributes( + @NonNull final TypedArray typedArray, + @NonNull final Class androidRes, + @NonNull final Collection errors, + @NonNull final String lineStrategyParameterName + ) + throws NoSuchFieldException, IllegalAccessException { + checkAttribute(typedArray, errors, getField(androidRes, "TextView_fontFamily"), true, "fontFamily required parameter"); + checkAttribute(typedArray, errors, getField(androidRes, "TextView_includeFontPadding"), false, "includeFontPadding forbid parameter"); + checkAttribute(typedArray, errors, getField(androidRes, "TextView_singleLine"), false, + "remove singleLine and use " + lineStrategyParameterName); + checkAttribute(typedArray, errors, getField(androidRes, "TextView_ellipsize"), false, + "remove ellipsize and use " + lineStrategyParameterName); + checkAttribute(typedArray, errors, AttributesUtils.getField(androidRes, "TextView_textColor"), true, + "textColor required parameter. If it's dynamic then use 'android:color/transparent'"); + } + + /** + * Inner helper library method to merge errors in string and assert it. + * + * @param view View with errors; + * @param errors Errors of view. + */ + public static void handleErrors(@NonNull final View view, @NonNull final Collection errors) { + if (!errors.isEmpty()) { + final String exceptionText = viewError(view, TextUtils.join("\n", errors)); + Lc.cutAssertion(new ShouldNotHappenException(exceptionText)); + } + } + + /** + * Returns max lines attribute value for views extended from {@link android.widget.TextView}. + * + * @param context Context of attributes; + * @param attrs TextView based attributes; + * @return Max lines value. + */ + public static int getMaxLinesFromAttrs(@NonNull final Context context, @NonNull final AttributeSet attrs) { + try { + final Class androidRes = Class.forName("com.android.internal.R$styleable"); + final TypedArray typedArray = context.obtainStyledAttributes(attrs, AttributesUtils.getField(androidRes, "TextView")); + final int result = typedArray.getInt(AttributesUtils.getField(androidRes, "TextView_maxLines"), Integer.MAX_VALUE); + typedArray.recycle(); + return result; + } catch (final Exception exception) { + return Integer.MAX_VALUE; + } + } + + /** + * Creates readable view error. + * + * @param view View of error; + * @param errorText Text of error; + * @return Readable error string. + */ + @NonNull + public static String viewError(@NonNull final View view, @NonNull final String errorText) { + return "Errors for view id=" + UiUtils.OfViews.getViewIdString(view) + ":\n" + errorText; + } + + /** + * Returns true if input type equals number input type. + * + * @param inputType Input type to check; + * @return true if input type equals number input type. + */ + public static boolean isNumberInputType(final int inputType) { + return inputType == InputType.TYPE_CLASS_NUMBER || inputType == InputType.TYPE_DATETIME_VARIATION_NORMAL; + } + + private AttributesUtils() { + } + +} diff --git a/views/src/main/res/values/attrs.xml b/views/src/main/res/values/attrs.xml new file mode 100644 index 0000000..43424f2 --- /dev/null +++ b/views/src/main/res/values/attrs.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + From 1d6d6d06be09aeb8c1733b0d67f4f8b0e03cef36 Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Tue, 21 Aug 2018 15:16:24 +0300 Subject: [PATCH 86/95] Gradle for submodules --- api-logansquare/build.gradle | 2 +- lifecycle-common/build.gradle | 2 +- lifecycle-rx/build.gradle | 4 ++-- .../dispatcher/BaseLiveDataDispatcher.kt | 8 +++---- .../livedata/dispatcher/LiveDataDispatcher.kt | 8 +++---- .../livedata/event/CompletableEvent.kt | 2 +- .../livedata/event/MaybeEvent.kt | 2 +- .../livedata/event/ObservableEvent.kt | 2 +- .../livedata/event/SingleEvent.kt | 2 +- modules.gradle | 24 +++++++++++++++++++ navigation/build.gradle | 4 ++-- settings.gradle | 4 +++- storable/build.gradle | 4 ++-- views/build.gradle | 4 ++-- 14 files changed, 49 insertions(+), 23 deletions(-) rename lifecycle-rx/src/main/java/ru/touchin/{ => templates}/livedata/event/CompletableEvent.kt (85%) rename lifecycle-rx/src/main/java/ru/touchin/{ => templates}/livedata/event/MaybeEvent.kt (89%) rename lifecycle-rx/src/main/java/ru/touchin/{ => templates}/livedata/event/ObservableEvent.kt (90%) rename lifecycle-rx/src/main/java/ru/touchin/{ => templates}/livedata/event/SingleEvent.kt (88%) create mode 100644 modules.gradle diff --git a/api-logansquare/build.gradle b/api-logansquare/build.gradle index 9daa9da..aa39db6 100644 --- a/api-logansquare/build.gradle +++ b/api-logansquare/build.gradle @@ -18,7 +18,7 @@ repositories { } dependencies { - api project(":storable") + api project(":components-storable") api 'net.danlew:android.joda:2.9.9.4' implementation "com.android.support:support-annotations:$versions.supportLibrary" diff --git a/lifecycle-common/build.gradle b/lifecycle-common/build.gradle index e7ab339..76f9f0c 100644 --- a/lifecycle-common/build.gradle +++ b/lifecycle-common/build.gradle @@ -15,7 +15,7 @@ android { } dependencies { - api project(":navigation") + api project(":components-navigation") compileOnly "javax.inject:javax.inject:1" diff --git a/lifecycle-rx/build.gradle b/lifecycle-rx/build.gradle index 0956030..67f0308 100644 --- a/lifecycle-rx/build.gradle +++ b/lifecycle-rx/build.gradle @@ -10,8 +10,8 @@ android { } dependencies { - api project(":utils") - api project(":logging") + api project(":components-utils") + api project(":components-logging") implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" diff --git a/lifecycle-rx/src/main/java/ru/touchin/livedata/dispatcher/BaseLiveDataDispatcher.kt b/lifecycle-rx/src/main/java/ru/touchin/livedata/dispatcher/BaseLiveDataDispatcher.kt index afcc376..dc6f5f4 100644 --- a/lifecycle-rx/src/main/java/ru/touchin/livedata/dispatcher/BaseLiveDataDispatcher.kt +++ b/lifecycle-rx/src/main/java/ru/touchin/livedata/dispatcher/BaseLiveDataDispatcher.kt @@ -9,10 +9,10 @@ import io.reactivex.Single import io.reactivex.disposables.Disposable import ru.touchin.livedata.destroyable.BaseDestroyable import ru.touchin.livedata.destroyable.Destroyable -import ru.touchin.livedata.event.CompletableEvent -import ru.touchin.livedata.event.MaybeEvent -import ru.touchin.livedata.event.ObservableEvent -import ru.touchin.livedata.event.SingleEvent +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 { diff --git a/lifecycle-rx/src/main/java/ru/touchin/livedata/dispatcher/LiveDataDispatcher.kt b/lifecycle-rx/src/main/java/ru/touchin/livedata/dispatcher/LiveDataDispatcher.kt index e1d7d8f..8ef3510 100644 --- a/lifecycle-rx/src/main/java/ru/touchin/livedata/dispatcher/LiveDataDispatcher.kt +++ b/lifecycle-rx/src/main/java/ru/touchin/livedata/dispatcher/LiveDataDispatcher.kt @@ -7,10 +7,10 @@ import io.reactivex.Maybe import io.reactivex.Observable import io.reactivex.Single import io.reactivex.disposables.Disposable -import ru.touchin.livedata.event.CompletableEvent -import ru.touchin.livedata.event.MaybeEvent -import ru.touchin.livedata.event.ObservableEvent -import ru.touchin.livedata.event.SingleEvent +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 { diff --git a/lifecycle-rx/src/main/java/ru/touchin/livedata/event/CompletableEvent.kt b/lifecycle-rx/src/main/java/ru/touchin/templates/livedata/event/CompletableEvent.kt similarity index 85% rename from lifecycle-rx/src/main/java/ru/touchin/livedata/event/CompletableEvent.kt rename to lifecycle-rx/src/main/java/ru/touchin/templates/livedata/event/CompletableEvent.kt index d0832ef..05fa013 100644 --- a/lifecycle-rx/src/main/java/ru/touchin/livedata/event/CompletableEvent.kt +++ b/lifecycle-rx/src/main/java/ru/touchin/templates/livedata/event/CompletableEvent.kt @@ -1,4 +1,4 @@ -package ru.touchin.livedata.event +package ru.touchin.templates.livedata.event /** * Event class that emits from [io.reactivex.Completable]. diff --git a/lifecycle-rx/src/main/java/ru/touchin/livedata/event/MaybeEvent.kt b/lifecycle-rx/src/main/java/ru/touchin/templates/livedata/event/MaybeEvent.kt similarity index 89% rename from lifecycle-rx/src/main/java/ru/touchin/livedata/event/MaybeEvent.kt rename to lifecycle-rx/src/main/java/ru/touchin/templates/livedata/event/MaybeEvent.kt index 0f88077..779fa5e 100644 --- a/lifecycle-rx/src/main/java/ru/touchin/livedata/event/MaybeEvent.kt +++ b/lifecycle-rx/src/main/java/ru/touchin/templates/livedata/event/MaybeEvent.kt @@ -1,4 +1,4 @@ -package ru.touchin.livedata.event +package ru.touchin.templates.livedata.event /** * Event class that emits from [io.reactivex.Maybe]. diff --git a/lifecycle-rx/src/main/java/ru/touchin/livedata/event/ObservableEvent.kt b/lifecycle-rx/src/main/java/ru/touchin/templates/livedata/event/ObservableEvent.kt similarity index 90% rename from lifecycle-rx/src/main/java/ru/touchin/livedata/event/ObservableEvent.kt rename to lifecycle-rx/src/main/java/ru/touchin/templates/livedata/event/ObservableEvent.kt index 5a6b8e2..02373d5 100644 --- a/lifecycle-rx/src/main/java/ru/touchin/livedata/event/ObservableEvent.kt +++ b/lifecycle-rx/src/main/java/ru/touchin/templates/livedata/event/ObservableEvent.kt @@ -1,4 +1,4 @@ -package ru.touchin.livedata.event +package ru.touchin.templates.livedata.event /** * Event class that emits from [io.reactivex.Observable]. diff --git a/lifecycle-rx/src/main/java/ru/touchin/livedata/event/SingleEvent.kt b/lifecycle-rx/src/main/java/ru/touchin/templates/livedata/event/SingleEvent.kt similarity index 88% rename from lifecycle-rx/src/main/java/ru/touchin/livedata/event/SingleEvent.kt rename to lifecycle-rx/src/main/java/ru/touchin/templates/livedata/event/SingleEvent.kt index 8b53f63..f023393 100644 --- a/lifecycle-rx/src/main/java/ru/touchin/livedata/event/SingleEvent.kt +++ b/lifecycle-rx/src/main/java/ru/touchin/templates/livedata/event/SingleEvent.kt @@ -1,4 +1,4 @@ -package ru.touchin.livedata.event +package ru.touchin.templates.livedata.event /** * Event class that emits from [io.reactivex.Single]. diff --git a/modules.gradle b/modules.gradle new file mode 100644 index 0000000..fa997e9 --- /dev/null +++ b/modules.gradle @@ -0,0 +1,24 @@ +final String rootDir +if (gradle.ext.has('componentsRoot')) { + rootDir = gradle.ext['componentsRoot'] +} else { + rootDir = settingsDir +} + +include ':components-logging' +include ':components-utils' +include ':components-navigation' +include ':components-storable' +include ':components-api-logansquare' +include ':components-lifecycle-common' +include ':components-lifecycle-rx' +include ':components-views' + +project(':components-utils').projectDir = new File(rootDir, 'utils') +project(':components-logging').projectDir = new File(rootDir, 'logging') +project(':components-navigation').projectDir = new File(rootDir, 'navigation') +project(':components-storable').projectDir = new File(rootDir, 'storable') +project(':components-api-logansquare').projectDir = new File(rootDir, 'api-logansquare') +project(':components-lifecycle-common').projectDir = new File(rootDir, 'lifecycle-common') +project(':components-lifecycle-rx').projectDir = new File(rootDir, 'lifecycle-rx') +project(':components-views').projectDir = new File(rootDir, 'views') diff --git a/navigation/build.gradle b/navigation/build.gradle index 8a95e1c..43b0e28 100644 --- a/navigation/build.gradle +++ b/navigation/build.gradle @@ -15,8 +15,8 @@ android { } dependencies { - api project(":utils") - api project(":logging") + api project(":components-utils") + api project(":components-logging") implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" diff --git a/settings.gradle b/settings.gradle index 3e31f83..c8a70f0 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,3 @@ -include ':sample', ':utils', ':logging', ':navigation', ':storable', ':api-logansquare', ':lifecycle-common', ':lifecycle-rx', ':views' +apply from: 'modules.gradle' + +include 'sample' diff --git a/storable/build.gradle b/storable/build.gradle index 18cb730..3afe4ee 100644 --- a/storable/build.gradle +++ b/storable/build.gradle @@ -14,8 +14,8 @@ android { } dependencies { - api project(":utils") - api project(":logging") + api project(":components-utils") + api project(":components-logging") implementation "com.android.support:support-annotations:$versions.supportLibrary" diff --git a/views/build.gradle b/views/build.gradle index 9ea4f9e..992dbea 100644 --- a/views/build.gradle +++ b/views/build.gradle @@ -14,8 +14,8 @@ android { } dependencies { - api project(":utils") - api project(":logging") + api project(":components-utils") + api project(":components-logging") implementation "com.android.support:design:$versions.supportLibrary" } From 7be175f7501be3eadcb052952691d7974ad74151 Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Tue, 21 Aug 2018 16:45:56 +0300 Subject: [PATCH 87/95] Added modules: recyclerview-adapters, kotlin-extensions --- api-logansquare/build.gradle | 2 +- kotlin-extensions/.gitignore | 1 + kotlin-extensions/build.gradle | 16 ++++ .../src/main/AndroidManifest.xml | 2 + .../components/extensions/Delegates.kt | 24 +++++ .../roboswag/components/extensions/View.kt | 20 ++++ .../components/extensions/ViewHolder.kt | 32 +++++++ lifecycle-common/build.gradle | 2 +- lifecycle-rx/build.gradle | 4 +- modules.gradle | 36 +++---- navigation/build.gradle | 4 +- recyclerview-adapters/.gitignore | 1 + recyclerview-adapters/build.gradle | 18 ++++ .../src/main/AndroidManifest.xml | 2 + .../components/adapters/AdapterDelegate.java | 96 +++++++++++++++++++ .../components/adapters/DelegatesManager.kt | 52 ++++++++++ .../adapters/DelegationListAdapter.kt | 89 +++++++++++++++++ .../adapters/ItemAdapterDelegate.java | 85 ++++++++++++++++ .../adapters/OffsetAdapterUpdateCallback.kt | 24 +++++ .../adapters/PositionAdapterDelegate.java | 68 +++++++++++++ storable/build.gradle | 4 +- views/build.gradle | 4 +- 22 files changed, 560 insertions(+), 26 deletions(-) create mode 100644 kotlin-extensions/.gitignore create mode 100644 kotlin-extensions/build.gradle create mode 100644 kotlin-extensions/src/main/AndroidManifest.xml create mode 100644 kotlin-extensions/src/main/java/ru/touchin/roboswag/components/extensions/Delegates.kt create mode 100644 kotlin-extensions/src/main/java/ru/touchin/roboswag/components/extensions/View.kt create mode 100644 kotlin-extensions/src/main/java/ru/touchin/roboswag/components/extensions/ViewHolder.kt create mode 100644 recyclerview-adapters/.gitignore create mode 100644 recyclerview-adapters/build.gradle create mode 100644 recyclerview-adapters/src/main/AndroidManifest.xml create mode 100644 recyclerview-adapters/src/main/java/ru/touchin/roboswag/components/adapters/AdapterDelegate.java create mode 100644 recyclerview-adapters/src/main/java/ru/touchin/roboswag/components/adapters/DelegatesManager.kt create mode 100644 recyclerview-adapters/src/main/java/ru/touchin/roboswag/components/adapters/DelegationListAdapter.kt create mode 100644 recyclerview-adapters/src/main/java/ru/touchin/roboswag/components/adapters/ItemAdapterDelegate.java create mode 100644 recyclerview-adapters/src/main/java/ru/touchin/roboswag/components/adapters/OffsetAdapterUpdateCallback.kt create mode 100644 recyclerview-adapters/src/main/java/ru/touchin/roboswag/components/adapters/PositionAdapterDelegate.java diff --git a/api-logansquare/build.gradle b/api-logansquare/build.gradle index aa39db6..9daa9da 100644 --- a/api-logansquare/build.gradle +++ b/api-logansquare/build.gradle @@ -18,7 +18,7 @@ repositories { } dependencies { - api project(":components-storable") + api project(":storable") api 'net.danlew:android.joda:2.9.9.4' implementation "com.android.support:support-annotations:$versions.supportLibrary" diff --git a/kotlin-extensions/.gitignore b/kotlin-extensions/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/kotlin-extensions/.gitignore @@ -0,0 +1 @@ +/build diff --git a/kotlin-extensions/build.gradle b/kotlin-extensions/build.gradle new file mode 100644 index 0000000..bcc6129 --- /dev/null +++ b/kotlin-extensions/build.gradle @@ -0,0 +1,16 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + compileSdkVersion versions.compileSdk + + defaultConfig { + minSdkVersion versions.minSdk + } +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + + implementation "com.android.support:recyclerview-v7:$versions.supportLibrary" +} diff --git a/kotlin-extensions/src/main/AndroidManifest.xml b/kotlin-extensions/src/main/AndroidManifest.xml new file mode 100644 index 0000000..25060ad --- /dev/null +++ b/kotlin-extensions/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + diff --git a/kotlin-extensions/src/main/java/ru/touchin/roboswag/components/extensions/Delegates.kt b/kotlin-extensions/src/main/java/ru/touchin/roboswag/components/extensions/Delegates.kt new file mode 100644 index 0000000..050bd6b --- /dev/null +++ b/kotlin-extensions/src/main/java/ru/touchin/roboswag/components/extensions/Delegates.kt @@ -0,0 +1,24 @@ +package ru.touchin.roboswag.components.extensions + +import kotlin.properties.Delegates +import kotlin.properties.ObservableProperty +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty + +/** + * Simple observable delegate only for notification of new value. + */ +inline fun Delegates.observable( + initialValue: T, + crossinline onChange: (newValue: T) -> Unit +): ReadWriteProperty = object : ObservableProperty(initialValue) { + override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = onChange(newValue) +} + +inline fun Delegates.distinctUntilChanged( + initialValue: T, + crossinline onChange: (newValue: T) -> Unit +): ReadWriteProperty = object : ObservableProperty(initialValue) { + override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = + if (newValue != null && oldValue != newValue) onChange(newValue) else Unit +} diff --git a/kotlin-extensions/src/main/java/ru/touchin/roboswag/components/extensions/View.kt b/kotlin-extensions/src/main/java/ru/touchin/roboswag/components/extensions/View.kt new file mode 100644 index 0000000..9f4ddca --- /dev/null +++ b/kotlin-extensions/src/main/java/ru/touchin/roboswag/components/extensions/View.kt @@ -0,0 +1,20 @@ +package ru.touchin.roboswag.components.extensions + +import android.os.Build +import android.view.View + +private const val RIPPLE_EFFECT_DELAY = 150L + +/** + * Sets click listener to view. On click it will call something after delay. + * + * @param delay Delay after which click listener will be called; + * @param listener Click listener. + */ +fun View.setOnRippleClickListener(delay: Long = RIPPLE_EFFECT_DELAY, listener: (View) -> Unit) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + setOnClickListener { view -> postDelayed({ if (hasWindowFocus()) listener(view) }, delay) } + } else { + setOnClickListener(listener) + } +} diff --git a/kotlin-extensions/src/main/java/ru/touchin/roboswag/components/extensions/ViewHolder.kt b/kotlin-extensions/src/main/java/ru/touchin/roboswag/components/extensions/ViewHolder.kt new file mode 100644 index 0000000..db7165f --- /dev/null +++ b/kotlin-extensions/src/main/java/ru/touchin/roboswag/components/extensions/ViewHolder.kt @@ -0,0 +1,32 @@ +package ru.touchin.roboswag.components.extensions + +import android.content.Context +import android.content.res.ColorStateList +import android.graphics.drawable.Drawable +import android.support.annotation.ColorInt +import android.support.annotation.ColorRes +import android.support.annotation.DrawableRes +import android.support.annotation.IdRes +import android.support.annotation.StringRes +import android.support.v4.content.ContextCompat +import android.support.v7.widget.RecyclerView +import android.view.View + +fun RecyclerView.ViewHolder.findViewById(@IdRes resId: Int): T = itemView.findViewById(resId) + +val RecyclerView.ViewHolder.context: Context + get() = itemView.context + +fun RecyclerView.ViewHolder.getText(@StringRes resId: Int): CharSequence = context.getText(resId) + +fun RecyclerView.ViewHolder.getString(@StringRes resId: Int): String = context.getString(resId) + +@SuppressWarnings("SpreadOperator") // it's OK for small arrays +fun RecyclerView.ViewHolder.getString(@StringRes resId: Int, vararg args: Any): String = context.getString(resId, *args) + +@ColorInt +fun RecyclerView.ViewHolder.getColor(@ColorRes resId: Int): Int = ContextCompat.getColor(context, resId) + +fun RecyclerView.ViewHolder.getColorStateList(@ColorRes resId: Int): ColorStateList? = ContextCompat.getColorStateList(context, resId) + +fun RecyclerView.ViewHolder.getDrawable(@DrawableRes resId: Int): Drawable? = ContextCompat.getDrawable(context, resId) diff --git a/lifecycle-common/build.gradle b/lifecycle-common/build.gradle index 76f9f0c..e7ab339 100644 --- a/lifecycle-common/build.gradle +++ b/lifecycle-common/build.gradle @@ -15,7 +15,7 @@ android { } dependencies { - api project(":components-navigation") + api project(":navigation") compileOnly "javax.inject:javax.inject:1" diff --git a/lifecycle-rx/build.gradle b/lifecycle-rx/build.gradle index 67f0308..0956030 100644 --- a/lifecycle-rx/build.gradle +++ b/lifecycle-rx/build.gradle @@ -10,8 +10,8 @@ android { } dependencies { - api project(":components-utils") - api project(":components-logging") + api project(":utils") + api project(":logging") implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" diff --git a/modules.gradle b/modules.gradle index fa997e9..9b11a59 100644 --- a/modules.gradle +++ b/modules.gradle @@ -5,20 +5,24 @@ if (gradle.ext.has('componentsRoot')) { rootDir = settingsDir } -include ':components-logging' -include ':components-utils' -include ':components-navigation' -include ':components-storable' -include ':components-api-logansquare' -include ':components-lifecycle-common' -include ':components-lifecycle-rx' -include ':components-views' +include ':logging' +include ':utils' +include ':navigation' +include ':storable' +include ':api-logansquare' +include ':lifecycle-common' +include ':lifecycle-rx' +include ':views' +include ':recyclerview-adapters' +include ':kotlin-extensions' -project(':components-utils').projectDir = new File(rootDir, 'utils') -project(':components-logging').projectDir = new File(rootDir, 'logging') -project(':components-navigation').projectDir = new File(rootDir, 'navigation') -project(':components-storable').projectDir = new File(rootDir, 'storable') -project(':components-api-logansquare').projectDir = new File(rootDir, 'api-logansquare') -project(':components-lifecycle-common').projectDir = new File(rootDir, 'lifecycle-common') -project(':components-lifecycle-rx').projectDir = new File(rootDir, 'lifecycle-rx') -project(':components-views').projectDir = new File(rootDir, 'views') +project(':utils').projectDir = new File(rootDir, 'utils') +project(':logging').projectDir = new File(rootDir, 'logging') +project(':navigation').projectDir = new File(rootDir, 'navigation') +project(':storable').projectDir = new File(rootDir, 'storable') +project(':api-logansquare').projectDir = new File(rootDir, 'api-logansquare') +project(':lifecycle-common').projectDir = new File(rootDir, 'lifecycle-common') +project(':lifecycle-rx').projectDir = new File(rootDir, 'lifecycle-rx') +project(':views').projectDir = new File(rootDir, 'views') +project(':recyclerview-adapters').projectDir = new File(rootDir, 'recyclerview-adapters') +project(':kotlin-extensions').projectDir = new File(rootDir, 'kotlin-extensions') diff --git a/navigation/build.gradle b/navigation/build.gradle index 43b0e28..8a95e1c 100644 --- a/navigation/build.gradle +++ b/navigation/build.gradle @@ -15,8 +15,8 @@ android { } dependencies { - api project(":components-utils") - api project(":components-logging") + api project(":utils") + api project(":logging") implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" diff --git a/recyclerview-adapters/.gitignore b/recyclerview-adapters/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/recyclerview-adapters/.gitignore @@ -0,0 +1 @@ +/build diff --git a/recyclerview-adapters/build.gradle b/recyclerview-adapters/build.gradle new file mode 100644 index 0000000..12fc082 --- /dev/null +++ b/recyclerview-adapters/build.gradle @@ -0,0 +1,18 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + compileSdkVersion versions.compileSdk + + defaultConfig { + minSdkVersion versions.minSdk + } +} + +dependencies { + api project(':kotlin-extensions') + + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + + implementation "com.android.support:recyclerview-v7:$versions.supportLibrary" +} diff --git a/recyclerview-adapters/src/main/AndroidManifest.xml b/recyclerview-adapters/src/main/AndroidManifest.xml new file mode 100644 index 0000000..2228245 --- /dev/null +++ b/recyclerview-adapters/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + diff --git a/recyclerview-adapters/src/main/java/ru/touchin/roboswag/components/adapters/AdapterDelegate.java b/recyclerview-adapters/src/main/java/ru/touchin/roboswag/components/adapters/AdapterDelegate.java new file mode 100644 index 0000000..25be0c9 --- /dev/null +++ b/recyclerview-adapters/src/main/java/ru/touchin/roboswag/components/adapters/AdapterDelegate.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2017 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.roboswag.components.adapters; + +import android.support.annotation.NonNull; +import android.support.v4.view.ViewCompat; +import android.support.v7.widget.RecyclerView; +import android.view.ViewGroup; + +import java.util.List; + +/** + * Objects of such class controls creation and binding of specific type of RecyclerView's ViewHolders. + * Default {@link #getItemViewType} is generating on construction of object. + * + * @param Type of {@link RecyclerView.ViewHolder} of delegate. + */ +public abstract class AdapterDelegate { + + private final int defaultItemViewType = ViewCompat.generateViewId(); + + /** + * Unique ID of AdapterDelegate. + * + * @return Unique ID. + */ + public int getItemViewType() { + return defaultItemViewType; + } + + /** + * Returns if object is processable by this delegate. + * + * @param items Items to check; + * @param adapterPosition Position of item in adapter; + * @param collectionPosition Position of item in collection; + * @return True if item is processable by this delegate. + */ + public abstract boolean isForViewType(@NonNull final List items, final int adapterPosition, final int collectionPosition); + + /** + * Returns unique ID of item to support stable ID's logic of RecyclerView's adapter. + * + * @param items Items in adapter; + * @param adapterPosition Position of item in adapter; + * @param collectionPosition Position of item in collection; + * @return Unique item ID. + */ + public long getItemId(@NonNull final List items, final int adapterPosition, final int collectionPosition) { + return 0; + } + + /** + * Creates ViewHolder to bind item to it later. + * + * @param parent Container of ViewHolder's view. + * @return New ViewHolder. + */ + @NonNull + public abstract TViewHolder onCreateViewHolder(@NonNull final ViewGroup parent); + + /** + * Binds item to created by this object ViewHolder. + * + * @param holder ViewHolder to bind item to; + * @param items Items in adapter; + * @param adapterPosition Position of item in adapter; + * @param collectionPosition Position of item in collection that contains item; + * @param payloads Payloads; + */ + public abstract void onBindViewHolder( + @NonNull final RecyclerView.ViewHolder holder, + @NonNull final List items, + final int adapterPosition, + final int collectionPosition, + @NonNull final List payloads + ); + +} diff --git a/recyclerview-adapters/src/main/java/ru/touchin/roboswag/components/adapters/DelegatesManager.kt b/recyclerview-adapters/src/main/java/ru/touchin/roboswag/components/adapters/DelegatesManager.kt new file mode 100644 index 0000000..e3ba14f --- /dev/null +++ b/recyclerview-adapters/src/main/java/ru/touchin/roboswag/components/adapters/DelegatesManager.kt @@ -0,0 +1,52 @@ +package ru.touchin.roboswag.components.adapters + +import android.support.v7.widget.RecyclerView +import android.util.SparseArray +import android.view.ViewGroup + +/** + * Manager for delegation callbacks from [RecyclerView.Adapter] to delegates. + */ +class DelegatesManager { + + private val delegates = SparseArray>() + + fun getItemViewType(items: List<*>, adapterPosition: Int, collectionPosition: Int): Int { + for (index in 0 until delegates.size()) { + val delegate = delegates.valueAt(index) + if (delegate.isForViewType(items, adapterPosition, collectionPosition)) { + return delegate.itemViewType + } + } + throw IllegalStateException("Delegate not found for adapterPosition: $adapterPosition") + } + + fun getItemId(items: List<*>, adapterPosition: Int, collectionPosition: Int): Long { + val delegate = getDelegate(getItemViewType(items, adapterPosition, collectionPosition)) + return delegate.getItemId(items, adapterPosition, collectionPosition) + } + + fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder = getDelegate(viewType).onCreateViewHolder(parent) + + fun onBindViewHolder(holder: RecyclerView.ViewHolder, items: List<*>, adapterPosition: Int, collectionPosition: Int, payloads: List) { + val delegate = getDelegate(getItemViewType(items, adapterPosition, collectionPosition)) + delegate.onBindViewHolder(holder, items, adapterPosition, collectionPosition, payloads) + } + + /** + * Adds [PositionAdapterDelegate] to adapter. + * + * @param delegate Delegate to add. + */ + fun addDelegate(delegate: AdapterDelegate<*>) = delegates.put(delegate.itemViewType, delegate) + + /** + * Removes [AdapterDelegate] from adapter. + * + * @param delegate Delegate to remove. + */ + fun removeDelegate(delegate: AdapterDelegate<*>) = delegates.remove(delegate.itemViewType) + + private fun getDelegate(viewType: Int) = delegates[viewType] ?: throw IllegalStateException("No AdapterDelegate added for view type: $viewType") + +} diff --git a/recyclerview-adapters/src/main/java/ru/touchin/roboswag/components/adapters/DelegationListAdapter.kt b/recyclerview-adapters/src/main/java/ru/touchin/roboswag/components/adapters/DelegationListAdapter.kt new file mode 100644 index 0000000..094b298 --- /dev/null +++ b/recyclerview-adapters/src/main/java/ru/touchin/roboswag/components/adapters/DelegationListAdapter.kt @@ -0,0 +1,89 @@ +package ru.touchin.roboswag.components.adapters + +import android.support.v7.recyclerview.extensions.AsyncDifferConfig +import android.support.v7.recyclerview.extensions.AsyncListDiffer +import android.support.v7.util.DiffUtil +import android.support.v7.widget.RecyclerView +import android.view.ViewGroup +import ru.touchin.roboswag.components.extensions.setOnRippleClickListener + +/** + * Base adapter with delegation and diff computing on background thread. + */ +open class DelegationListAdapter(config: AsyncDifferConfig) : RecyclerView.Adapter() { + + constructor(diffCallback: DiffUtil.ItemCallback) : this(AsyncDifferConfig.Builder(diffCallback).build()) + + var itemClickListener: ((TItem, RecyclerView.ViewHolder) -> Unit)? = null + + private val delegatesManager = DelegatesManager() + private var differ = AsyncListDiffer(OffsetAdapterUpdateCallback(this, ::getHeadersCount), config) + + open fun getHeadersCount() = 0 + + open fun getFootersCount() = 0 + + override fun getItemCount() = getHeadersCount() + getList().size + getFootersCount() + + override fun getItemViewType(position: Int) = delegatesManager.getItemViewType(getList(), position, getCollectionPosition(position)) + + override fun getItemId(position: Int) = delegatesManager.getItemId(getList(), position, getCollectionPosition(position)) + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = delegatesManager.onCreateViewHolder(parent, viewType) + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int, payloads: List) { + val collectionPosition = getCollectionPosition(position) + if (collectionPosition in 0 until getList().size) { + if (itemClickListener != null) { + holder.itemView.setOnRippleClickListener { + itemClickListener?.invoke(getList()[getCollectionPosition(holder.adapterPosition)], holder) + } + } else { + holder.itemView.setOnClickListener(null) + } + } + delegatesManager.onBindViewHolder(holder, getList(), position, collectionPosition, payloads) + } + + final override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) = Unit + + /** + * Adds [AdapterDelegate] to adapter. + * + * @param delegate Delegate to add. + */ + fun addDelegate(delegate: AdapterDelegate<*>) = delegatesManager.addDelegate(delegate) + + /** + * Removes [AdapterDelegate] from adapter. + * + * @param delegate Delegate to remove. + */ + fun removeDelegate(delegate: AdapterDelegate<*>) = delegatesManager.removeDelegate(delegate) + + /** + * Submits a new list to be diffed, and displayed. + * + * If a list is already being displayed, a diff will be computed on a background thread, which + * will dispatch Adapter.notifyItem events on the main thread. + * + * @param list The new list to be displayed. + */ + fun submitList(list: List) = differ.submitList(list) + + /** + * Get the current List - any diffing to present this list has already been computed and + * dispatched via the ListUpdateCallback. + *

+ * If a null List, or no List has been submitted, an empty list will be returned. + *

+ * The returned list may not be mutated - mutations to content must be done through + * {@link #submitList(List)}. + * + * @return current List. + */ + fun getList(): List = differ.currentList + + fun getCollectionPosition(adapterPosition: Int) = adapterPosition - getHeadersCount() + +} diff --git a/recyclerview-adapters/src/main/java/ru/touchin/roboswag/components/adapters/ItemAdapterDelegate.java b/recyclerview-adapters/src/main/java/ru/touchin/roboswag/components/adapters/ItemAdapterDelegate.java new file mode 100644 index 0000000..324bcf4 --- /dev/null +++ b/recyclerview-adapters/src/main/java/ru/touchin/roboswag/components/adapters/ItemAdapterDelegate.java @@ -0,0 +1,85 @@ +package ru.touchin.roboswag.components.adapters; + +import android.support.annotation.NonNull; +import android.support.v7.widget.RecyclerView; + +import java.util.List; + +/** + * Objects of such class controls creation and binding of specific type of RecyclerView's ViewHolders. + * Such delegates are creating and binding ViewHolders for specific items. + * Default {@link #getItemViewType} is generating on construction of object. + * + * @param Type of {@link RecyclerView.ViewHolder} of delegate; + * @param Type of items to bind to {@link RecyclerView.ViewHolder}s. + */ +public abstract class ItemAdapterDelegate extends AdapterDelegate { + + @Override + public boolean isForViewType(@NonNull final List items, final int adapterPosition, final int collectionPosition) { + return collectionPosition >= 0 + && collectionPosition < items.size() + && isForViewType(items.get(collectionPosition), adapterPosition, collectionPosition); + } + + /** + * Returns if object is processable by this delegate. + * This item will be casted to {@link TItem} and passes to {@link #onBindViewHolder(TViewHolder, TItem, int, int, List)}. + * + * @param item Item to check; + * @param adapterPosition Position of item in adapter; + * @param collectionPosition Position of item in collection that contains item; + * @return True if item is processable by this delegate. + */ + public boolean isForViewType(@NonNull final Object item, final int adapterPosition, final int collectionPosition) { + return true; + } + + @Override + public long getItemId(@NonNull final List items, final int adapterPosition, final int collectionPosition) { + //noinspection unchecked + return getItemId((TItem) items.get(collectionPosition), adapterPosition, collectionPosition); + } + + /** + * Returns unique ID of item to support stable ID's logic of RecyclerView's adapter. + * + * @param item Item in adapter; + * @param adapterPosition Position of item in adapter; + * @param collectionPosition Position of item in collection that contains item; + * @return Unique item ID. + */ + public long getItemId(@NonNull final TItem item, final int adapterPosition, final int collectionPosition) { + return 0; + } + + @Override + public void onBindViewHolder( + @NonNull final RecyclerView.ViewHolder holder, + @NonNull final List items, + final int adapterPosition, + final int collectionPosition, + @NonNull final List payloads + ) { + //noinspection unchecked + onBindViewHolder((TViewHolder) holder, (TItem) items.get(collectionPosition), adapterPosition, collectionPosition, payloads); + } + + /** + * Binds item with payloads to created by this object ViewHolder. + * + * @param holder ViewHolder to bind item to; + * @param item Item in adapter; + * @param adapterPosition Position of item in adapter; + * @param collectionPosition Position of item in collection that contains item; + * @param payloads Payloads; + */ + public abstract void onBindViewHolder( + @NonNull final TViewHolder holder, + @NonNull final TItem item, + final int adapterPosition, + final int collectionPosition, + @NonNull final List payloads + ); + +} diff --git a/recyclerview-adapters/src/main/java/ru/touchin/roboswag/components/adapters/OffsetAdapterUpdateCallback.kt b/recyclerview-adapters/src/main/java/ru/touchin/roboswag/components/adapters/OffsetAdapterUpdateCallback.kt new file mode 100644 index 0000000..9715eb2 --- /dev/null +++ b/recyclerview-adapters/src/main/java/ru/touchin/roboswag/components/adapters/OffsetAdapterUpdateCallback.kt @@ -0,0 +1,24 @@ +package ru.touchin.roboswag.components.adapters + +import android.support.v7.util.ListUpdateCallback +import android.support.v7.widget.RecyclerView + +class OffsetAdapterUpdateCallback(private val adapter: RecyclerView.Adapter<*>, private val offsetProvider: () -> Int) : ListUpdateCallback { + + override fun onInserted(position: Int, count: Int) { + adapter.notifyItemRangeInserted(position + offsetProvider(), count) + } + + override fun onRemoved(position: Int, count: Int) { + adapter.notifyItemRangeRemoved(position + offsetProvider(), count) + } + + override fun onMoved(fromPosition: Int, toPosition: Int) { + adapter.notifyItemMoved(fromPosition + offsetProvider(), toPosition + offsetProvider()) + } + + override fun onChanged(position: Int, count: Int, payload: Any?) { + adapter.notifyItemRangeChanged(position + offsetProvider(), count, payload) + } + +} diff --git a/recyclerview-adapters/src/main/java/ru/touchin/roboswag/components/adapters/PositionAdapterDelegate.java b/recyclerview-adapters/src/main/java/ru/touchin/roboswag/components/adapters/PositionAdapterDelegate.java new file mode 100644 index 0000000..ada8888 --- /dev/null +++ b/recyclerview-adapters/src/main/java/ru/touchin/roboswag/components/adapters/PositionAdapterDelegate.java @@ -0,0 +1,68 @@ +package ru.touchin.roboswag.components.adapters; + +import android.support.annotation.NonNull; +import android.support.v7.widget.RecyclerView; + +import java.util.List; + +/** + * Objects of such class controls creation and binding of specific type of RecyclerView's ViewHolders. + * Such delegates are creating and binding ViewHolders by position in adapter. + * Default {@link #getItemViewType} is generating on construction of object. + * + * @param Type of {@link RecyclerView.ViewHolder} of delegate. + */ +public abstract class PositionAdapterDelegate extends AdapterDelegate { + + @Override + public boolean isForViewType(@NonNull final List items, final int adapterPosition, final int collectionPosition) { + return isForViewType(adapterPosition); + } + + /** + * Returns if object is processable by this delegate. + * + * @param adapterPosition Position of item in adapter; + * @return True if item is processable by this delegate. + */ + public abstract boolean isForViewType(final int adapterPosition); + + @Override + public long getItemId(@NonNull final List objects, final int adapterPosition, final int itemsOffset) { + return getItemId(adapterPosition); + } + + /** + * Returns unique ID of item to support stable ID's logic of RecyclerView's adapter. + * + * @param adapterPosition Position of item in adapter; + * @return Unique item ID. + */ + public long getItemId(final int adapterPosition) { + return 0; + } + + @Override + public void onBindViewHolder( + @NonNull final RecyclerView.ViewHolder holder, + @NonNull final List items, + final int adapterPosition, + final int collectionPosition, + @NonNull final List payloads + ) { + //noinspection unchecked + onBindViewHolder((TViewHolder) holder, adapterPosition, payloads); + } + + /** + * Binds position with payloads to ViewHolder. + * + * @param holder ViewHolder to bind position to; + * @param adapterPosition Position of item in adapter; + * @param payloads Payloads. + */ + public void onBindViewHolder(@NonNull final TViewHolder holder, final int adapterPosition, @NonNull final List payloads) { + //do nothing by default + } + +} diff --git a/storable/build.gradle b/storable/build.gradle index 3afe4ee..18cb730 100644 --- a/storable/build.gradle +++ b/storable/build.gradle @@ -14,8 +14,8 @@ android { } dependencies { - api project(":components-utils") - api project(":components-logging") + api project(":utils") + api project(":logging") implementation "com.android.support:support-annotations:$versions.supportLibrary" diff --git a/views/build.gradle b/views/build.gradle index 992dbea..9ea4f9e 100644 --- a/views/build.gradle +++ b/views/build.gradle @@ -14,8 +14,8 @@ android { } dependencies { - api project(":components-utils") - api project(":components-logging") + api project(":utils") + api project(":logging") implementation "com.android.support:design:$versions.supportLibrary" } From b68c651e6ad99666639a61926a63ea25f2a2eb75 Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Tue, 21 Aug 2018 19:08:21 +0300 Subject: [PATCH 88/95] Added templates module --- api-logansquare/build.gradle | 4 - build.gradle | 4 +- modules.gradle | 2 + templates/.gitignore | 1 + templates/build.gradle | 31 ++++ templates/src/main/AndroidManifest.xml | 3 + .../ru/touchin/templates/TouchinActivity.java | 70 +++++++++ .../java/ru/touchin/templates/TouchinApp.java | 146 ++++++++++++++++++ .../components/views/TypefacedEditText.java | 16 +- .../components/views/TypefacedTextView.java | 11 +- 10 files changed, 258 insertions(+), 30 deletions(-) create mode 100644 templates/.gitignore create mode 100644 templates/build.gradle create mode 100644 templates/src/main/AndroidManifest.xml create mode 100644 templates/src/main/java/ru/touchin/templates/TouchinActivity.java create mode 100644 templates/src/main/java/ru/touchin/templates/TouchinApp.java diff --git a/api-logansquare/build.gradle b/api-logansquare/build.gradle index 9daa9da..3e774b1 100644 --- a/api-logansquare/build.gradle +++ b/api-logansquare/build.gradle @@ -13,10 +13,6 @@ android { } } -repositories { - maven { url "http://dl.bintray.com/touchin/touchin-tools" } -} - dependencies { api project(":storable") api 'net.danlew:android.joda:2.9.9.4' diff --git a/build.gradle b/build.gradle index d489a4e..ae4e81d 100644 --- a/build.gradle +++ b/build.gradle @@ -14,6 +14,7 @@ allprojects { repositories { google() jcenter() + maven { url "http://dl.bintray.com/touchin/touchin-tools" } } } @@ -31,6 +32,7 @@ ext { dagger : '2.16', retrofit : '2.4.0', rxJava : '2.1.17', - rxAndroid : '2.0.2' + rxAndroid : '2.0.2', + crashlytics : '2.9.4' ] } diff --git a/modules.gradle b/modules.gradle index 9b11a59..750126e 100644 --- a/modules.gradle +++ b/modules.gradle @@ -15,6 +15,7 @@ include ':lifecycle-rx' include ':views' include ':recyclerview-adapters' include ':kotlin-extensions' +include ':templates' project(':utils').projectDir = new File(rootDir, 'utils') project(':logging').projectDir = new File(rootDir, 'logging') @@ -26,3 +27,4 @@ project(':lifecycle-rx').projectDir = new File(rootDir, 'lifecycle-rx') project(':views').projectDir = new File(rootDir, 'views') project(':recyclerview-adapters').projectDir = new File(rootDir, 'recyclerview-adapters') project(':kotlin-extensions').projectDir = new File(rootDir, 'kotlin-extensions') +project(':templates').projectDir = new File(rootDir, 'templates') diff --git a/templates/.gitignore b/templates/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/templates/.gitignore @@ -0,0 +1 @@ +/build diff --git a/templates/build.gradle b/templates/build.gradle new file mode 100644 index 0000000..1ee1e3b --- /dev/null +++ b/templates/build.gradle @@ -0,0 +1,31 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion versions.compileSdk + + defaultConfig { + minSdkVersion versions.minSdk + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + api project(":utils") + api project(":logging") + api project(":api-logansquare") + api project(":navigation") + + api 'com.android.support:multidex:1.0.3' + + api 'net.danlew:android.joda:2.9.9.4' + + implementation "com.android.support:appcompat-v7:$versions.supportLibrary" + + implementation("com.crashlytics.sdk.android:crashlytics:$versions.crashlytics@aar") { + transitive = true + } +} diff --git a/templates/src/main/AndroidManifest.xml b/templates/src/main/AndroidManifest.xml new file mode 100644 index 0000000..ad64e1e --- /dev/null +++ b/templates/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + diff --git a/templates/src/main/java/ru/touchin/templates/TouchinActivity.java b/templates/src/main/java/ru/touchin/templates/TouchinActivity.java new file mode 100644 index 0000000..41b2e28 --- /dev/null +++ b/templates/src/main/java/ru/touchin/templates/TouchinActivity.java @@ -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; + +import android.app.ActivityManager; +import android.content.Intent; +import android.graphics.drawable.BitmapDrawable; +import android.os.Build; +import android.os.Bundle; +import android.support.annotation.ColorRes; +import android.support.annotation.DrawableRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.content.ContextCompat; + +import ru.touchin.roboswag.components.navigation.activities.BaseActivity; +import ru.touchin.roboswag.core.log.Lc; + +/** + * Created by Gavriil Sitnikov on 11/03/16. + * Base class of activity to extends for Touch Instinct related projects. + */ +public abstract class TouchinActivity extends BaseActivity { + + @Override + protected void onCreate(@Nullable final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // Possible work around for market launches. See http://code.google.com/p/android/issues/detail?id=2373 + // for more details. Essentially, the market launches the main activity on top of other activities. + // we never want this to happen. Instead, we check if we are the root and if not, we finish. + if (!isTaskRoot() && getIntent().hasCategory(Intent.CATEGORY_LAUNCHER) && Intent.ACTION_MAIN.equals(getIntent().getAction())) { + Lc.e("Finishing activity as it is launcher but not root"); + finish(); + } + } + + /** + * Setup task description of application for Android 5.0 and later. It is showing when user opens task bar. + * + * @param label Name of application to show in task bar; + * @param iconRes Icon 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) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + final ActivityManager.TaskDescription taskDescription = new ActivityManager.TaskDescription(label, + ((BitmapDrawable) ContextCompat.getDrawable(this, iconRes)).getBitmap(), + ContextCompat.getColor(this, primaryColorRes)); + setTaskDescription(taskDescription); + } + } + +} diff --git a/templates/src/main/java/ru/touchin/templates/TouchinApp.java b/templates/src/main/java/ru/touchin/templates/TouchinApp.java new file mode 100644 index 0000000..8d33cb0 --- /dev/null +++ b/templates/src/main/java/ru/touchin/templates/TouchinApp.java @@ -0,0 +1,146 @@ +/* + * 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.app.Application; +import android.content.Context; +import android.os.StrictMode; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.multidex.MultiDex; +import android.util.Log; + +import com.crashlytics.android.Crashlytics; + +import net.danlew.android.joda.JodaTimeAndroid; + +import java.util.ArrayList; +import java.util.List; + +import io.fabric.sdk.android.Fabric; +import ru.touchin.roboswag.core.log.ConsoleLogProcessor; +import ru.touchin.roboswag.core.log.Lc; +import ru.touchin.roboswag.core.log.LcGroup; +import ru.touchin.roboswag.core.log.LcLevel; +import ru.touchin.roboswag.core.log.LogProcessor; +import ru.touchin.roboswag.core.utils.ShouldNotHappenException; + +/** + * Created by Gavriil Sitnikov on 10/03/16. + * Base class of application to extends for Touch Instinct related projects. + */ +public abstract class TouchinApp extends Application { + + @Override + protected void attachBaseContext(@NonNull final Context base) { + super.attachBaseContext(base); + MultiDex.install(base); + } + + @Override + public void onCreate() { + super.onCreate(); + JodaTimeAndroid.init(this); + if (BuildConfig.DEBUG) { + enableStrictMode(); + Lc.initialize(new ConsoleLogProcessor(LcLevel.VERBOSE), true); + LcGroup.UI_LIFECYCLE.disable(); + } else { + try { + final Crashlytics crashlytics = new Crashlytics(); + Fabric.with(this, crashlytics); + Fabric.getLogger().setLogLevel(Log.ERROR); + Lc.initialize(new CrashlyticsLogProcessor(crashlytics), false); + } catch (final NoClassDefFoundError error) { + Lc.initialize(new ConsoleLogProcessor(LcLevel.INFO), false); + Lc.e("Crashlytics initialization error! Did you forget to add\n" + + "compile('com.crashlytics.sdk.android:crashlytics:+@aar') {\n" + + " transitive = true;\n" + + "}\n" + + "to your build.gradle?", error); + } + } + } + + private void enableStrictMode() { + StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() + .detectAll() + .permitDiskReads() + .permitDiskWrites() + .penaltyLog() + .build()); + StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() + .detectAll() + .penaltyLog() + .build()); + } + + private static class CrashlyticsLogProcessor extends LogProcessor { + + @NonNull + private final Crashlytics crashlytics; + + public CrashlyticsLogProcessor(@NonNull final Crashlytics crashlytics) { + super(LcLevel.INFO); + this.crashlytics = crashlytics; + } + + @Override + public void processLogMessage(@NonNull final LcGroup group, + @NonNull final LcLevel level, + @NonNull final String tag, + @NonNull final String message, + @Nullable final Throwable throwable) { + if (group == LcGroup.UI_LIFECYCLE) { + crashlytics.core.log(level.getPriority(), tag, message); + } else if (!level.lessThan(LcLevel.ASSERT) + || (group == ApiModel.API_VALIDATION_LC_GROUP && level == LcLevel.ERROR)) { + Log.e(tag, message); + if (throwable != null) { + crashlytics.core.log(level.getPriority(), tag, message); + crashlytics.core.logException(throwable); + } else { + final ShouldNotHappenException exceptionToLog = new ShouldNotHappenException(tag + ':' + message); + reduceStackTrace(exceptionToLog); + crashlytics.core.logException(exceptionToLog); + } + } + } + + private void reduceStackTrace(@NonNull final Throwable throwable) { + final StackTraceElement[] stackTrace = throwable.getStackTrace(); + final List reducedStackTraceList = new ArrayList<>(); + for (int i = stackTrace.length - 1; i >= 0; i--) { + final StackTraceElement stackTraceElement = stackTrace[i]; + if (stackTraceElement.getClassName().contains(getClass().getSimpleName()) + || stackTraceElement.getClassName().contains(LcGroup.class.getName()) + || stackTraceElement.getClassName().contains(Lc.class.getName())) { + break; + } + reducedStackTraceList.add(0, stackTraceElement); + } + StackTraceElement[] reducedStackTrace = new StackTraceElement[reducedStackTraceList.size()]; + reducedStackTrace = reducedStackTraceList.toArray(reducedStackTrace); + throwable.setStackTrace(reducedStackTrace); + } + + } + +} diff --git a/views/src/main/java/ru/touchin/roboswag/components/views/TypefacedEditText.java b/views/src/main/java/ru/touchin/roboswag/components/views/TypefacedEditText.java index d556a81..ceac558 100644 --- a/views/src/main/java/ru/touchin/roboswag/components/views/TypefacedEditText.java +++ b/views/src/main/java/ru/touchin/roboswag/components/views/TypefacedEditText.java @@ -43,11 +43,6 @@ import ru.touchin.defaults.DefaultTextWatcher; import ru.touchin.roboswag.components.views.internal.AttributesUtils; import ru.touchin.roboswag.core.log.Lc; -/** - * Created by Gavriil Sitnikov on 18/07/2014. - * TextView that supports fonts from Typefaces class - */ - /** * Created by Gavriil Sitnikov on 18/07/2014. * EditText that supports custom typeface and forces developer to specify if this view multiline or not. @@ -57,15 +52,6 @@ import ru.touchin.roboswag.core.log.Lc; //ConstructorCallsOverridableMethod: it's ok as we need to setTypeface public class TypefacedEditText extends AppCompatEditText { - private static boolean inDebugMode; - - /** - * Enables debugging features like checking attributes on inflation. - */ - public static void setInDebugMode() { - inDebugMode = true; - } - private boolean multiline; private boolean constructed; @@ -100,7 +86,7 @@ public class TypefacedEditText extends AppCompatEditText { setSingleLine(); } typedArray.recycle(); - if (inDebugMode) { + if (BuildConfig.DEBUG) { checkAttributes(context, attrs); } } diff --git a/views/src/main/java/ru/touchin/roboswag/components/views/TypefacedTextView.java b/views/src/main/java/ru/touchin/roboswag/components/views/TypefacedTextView.java index 188686c..702342d 100644 --- a/views/src/main/java/ru/touchin/roboswag/components/views/TypefacedTextView.java +++ b/views/src/main/java/ru/touchin/roboswag/components/views/TypefacedTextView.java @@ -51,15 +51,6 @@ public class TypefacedTextView extends AppCompatTextView { private static final int UNSPECIFIED_MEASURE_SPEC = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); private static final int START_SCALABLE_DIFFERENCE = 4; - private static boolean inDebugMode; - - /** - * Enables debugging features like checking attributes on inflation. - */ - public static void setInDebugMode() { - inDebugMode = true; - } - private boolean constructed; @NonNull private LineStrategy lineStrategy = LineStrategy.SINGLE_LINE_ELLIPSIZE; @@ -92,7 +83,7 @@ public class TypefacedTextView extends AppCompatTextView { setLineStrategy(lineStrategy); } typedArray.recycle(); - if (inDebugMode) { + if (BuildConfig.DEBUG) { checkAttributes(context, attrs); } } From f15d2f73afc43bf1cca77f62cd8fa64fbf618133 Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Tue, 21 Aug 2018 23:17:01 +0300 Subject: [PATCH 89/95] Build fix --- lifecycle-common/src/main/AndroidManifest.xml | 3 +- lifecycle-rx/src/main/AndroidManifest.xml | 4 +- .../viewcontrollers/ViewController.java | 2 +- .../ru/touchin/templates/DeviceUtils.java | 68 +++++++++++-------- 4 files changed, 41 insertions(+), 36 deletions(-) diff --git a/lifecycle-common/src/main/AndroidManifest.xml b/lifecycle-common/src/main/AndroidManifest.xml index f617b1f..26f2069 100644 --- a/lifecycle-common/src/main/AndroidManifest.xml +++ b/lifecycle-common/src/main/AndroidManifest.xml @@ -1,2 +1 @@ - + diff --git a/lifecycle-rx/src/main/AndroidManifest.xml b/lifecycle-rx/src/main/AndroidManifest.xml index 68016e6..9145647 100644 --- a/lifecycle-rx/src/main/AndroidManifest.xml +++ b/lifecycle-rx/src/main/AndroidManifest.xml @@ -1,3 +1 @@ - + diff --git a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java index 8e4c7d0..0aa771b 100644 --- a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java +++ b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/ViewController.java @@ -222,7 +222,7 @@ public class ViewController + * *

Starting in {@link android.os.Build.VERSION_CODES#M}, the returned * color state list will be styled for the specified Context's theme. * diff --git a/utils/src/main/java/ru/touchin/templates/DeviceUtils.java b/utils/src/main/java/ru/touchin/templates/DeviceUtils.java index 8d8284d..8a7e1fb 100644 --- a/utils/src/main/java/ru/touchin/templates/DeviceUtils.java +++ b/utils/src/main/java/ru/touchin/templates/DeviceUtils.java @@ -47,41 +47,21 @@ public final class DeviceUtils { return NetworkType.UNKNOWN; } final ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + if (cm == null) { + return NetworkType.UNKNOWN; + } @SuppressLint("MissingPermission") final NetworkInfo info = cm.getActiveNetworkInfo(); if (info == null || !info.isConnected()) { return NetworkType.NONE; } - if (info.getType() == ConnectivityManager.TYPE_WIFI) { - return NetworkType.WI_FI; + switch (info.getType()) { + case ConnectivityManager.TYPE_WIFI: + return NetworkType.WI_FI; + case ConnectivityManager.TYPE_MOBILE: + return getMobileNetworkType(info); + default: + return NetworkType.UNKNOWN; } - if (info.getType() == ConnectivityManager.TYPE_MOBILE) { - final int networkType = info.getSubtype(); - switch (networkType) { - case TelephonyManager.NETWORK_TYPE_GPRS: - case TelephonyManager.NETWORK_TYPE_EDGE: - case TelephonyManager.NETWORK_TYPE_CDMA: - case TelephonyManager.NETWORK_TYPE_1xRTT: - case TelephonyManager.NETWORK_TYPE_IDEN: - return NetworkType.MOBILE_2G; - case TelephonyManager.NETWORK_TYPE_UMTS: - case TelephonyManager.NETWORK_TYPE_EVDO_0: - case TelephonyManager.NETWORK_TYPE_EVDO_A: - case TelephonyManager.NETWORK_TYPE_HSDPA: - case TelephonyManager.NETWORK_TYPE_HSUPA: - case TelephonyManager.NETWORK_TYPE_HSPA: - case TelephonyManager.NETWORK_TYPE_EVDO_B: - case TelephonyManager.NETWORK_TYPE_EHRPD: - case TelephonyManager.NETWORK_TYPE_HSPAP: - return NetworkType.MOBILE_3G; - case TelephonyManager.NETWORK_TYPE_LTE: - case 19: // NETWORK_TYPE_LTE_CA is hide - return NetworkType.MOBILE_LTE; - case TelephonyManager.NETWORK_TYPE_UNKNOWN: - default: - return NetworkType.UNKNOWN; - } - } - return NetworkType.UNKNOWN; } /** @@ -94,6 +74,34 @@ public final class DeviceUtils { return getNetworkType(context) != NetworkType.NONE; } + @NonNull + private static NetworkType getMobileNetworkType(@NonNull final NetworkInfo info) { + switch (info.getSubtype()) { + case TelephonyManager.NETWORK_TYPE_GPRS: + case TelephonyManager.NETWORK_TYPE_EDGE: + case TelephonyManager.NETWORK_TYPE_CDMA: + case TelephonyManager.NETWORK_TYPE_1xRTT: + case TelephonyManager.NETWORK_TYPE_IDEN: + return NetworkType.MOBILE_2G; + case TelephonyManager.NETWORK_TYPE_UMTS: + case TelephonyManager.NETWORK_TYPE_EVDO_0: + case TelephonyManager.NETWORK_TYPE_EVDO_A: + case TelephonyManager.NETWORK_TYPE_HSDPA: + case TelephonyManager.NETWORK_TYPE_HSUPA: + case TelephonyManager.NETWORK_TYPE_HSPA: + case TelephonyManager.NETWORK_TYPE_EVDO_B: + case TelephonyManager.NETWORK_TYPE_EHRPD: + case TelephonyManager.NETWORK_TYPE_HSPAP: + return NetworkType.MOBILE_3G; + case TelephonyManager.NETWORK_TYPE_LTE: + case 19: // NETWORK_TYPE_LTE_CA is hide + return NetworkType.MOBILE_LTE; + case TelephonyManager.NETWORK_TYPE_UNKNOWN: + default: + return NetworkType.UNKNOWN; + } + } + private DeviceUtils() { } From 52ae28096a9c01d9929df6ff66127126b321e506 Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Tue, 21 Aug 2018 23:43:37 +0300 Subject: [PATCH 90/95] Build scripts and static --- .gitmodules | 3 +++ BuildScripts | 1 + build.gradle | 1 + sample/build.gradle | 2 ++ sample/src/main/AndroidManifest.xml | 9 +++++---- 5 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 .gitmodules create mode 160000 BuildScripts diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..4aebbba --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "BuildScripts"] + path = BuildScripts + url = git@github.com:TouchInstinct/BuildScripts.git diff --git a/BuildScripts b/BuildScripts new file mode 160000 index 0000000..64424e9 --- /dev/null +++ b/BuildScripts @@ -0,0 +1 @@ +Subproject commit 64424e91972838b84217da0878a2ecac074c76a7 diff --git a/build.gradle b/build.gradle index ae4e81d..a720f4f 100644 --- a/build.gradle +++ b/build.gradle @@ -7,6 +7,7 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:3.1.4' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath 'de.aaschmid:gradle-cpd-plugin:1.0' } } diff --git a/sample/build.gradle b/sample/build.gradle index 24ac6ff..fa25d2c 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -27,3 +27,5 @@ dependencies { implementation "com.android.support:appcompat-v7:$versions.supportLibrary" implementation 'com.android.support.constraint:constraint-layout:1.1.2' } + +apply from: "$rootDir/BuildScripts/gradle/staticAnalysis.gradle" diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index 515c1fc..a99bb50 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -1,22 +1,23 @@ + android:theme="@style/AppTheme" + tools:ignore="GoogleAppIndexingWarning"> - - \ No newline at end of file + From 95aa507c39af8f5be394fec2639a8843bbb76f9d Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Wed, 22 Aug 2018 00:48:07 +0300 Subject: [PATCH 91/95] Configuring path for static --- BuildScripts | 2 +- sample/build.gradle | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/BuildScripts b/BuildScripts index 64424e9..3fe4b2a 160000 --- a/BuildScripts +++ b/BuildScripts @@ -1 +1 @@ -Subproject commit 64424e91972838b84217da0878a2ecac074c76a7 +Subproject commit 3fe4b2a1aea149050b34c60ec7e2a876dfd8c0b2 diff --git a/sample/build.gradle b/sample/build.gradle index fa25d2c..7a28aea 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -28,4 +28,5 @@ dependencies { implementation 'com.android.support.constraint:constraint-layout:1.1.2' } -apply from: "$rootDir/BuildScripts/gradle/staticAnalysis.gradle" +ext.buildScriptsDir = "$rootDir/BuildScripts" +apply from: "$buildScriptsDir/gradle/staticAnalysis.gradle" From 20705feac657652ad5a84eef76dfc10f0a409fcd Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Wed, 22 Aug 2018 16:08:45 +0300 Subject: [PATCH 92/95] Typefaced* views now deprecated due to using reflection --- .../ru/touchin/roboswag/components/views/TypefacedEditText.java | 1 + .../ru/touchin/roboswag/components/views/TypefacedTextView.java | 1 + .../roboswag/components/views/internal/AttributesUtils.java | 1 + 3 files changed, 3 insertions(+) diff --git a/views/src/main/java/ru/touchin/roboswag/components/views/TypefacedEditText.java b/views/src/main/java/ru/touchin/roboswag/components/views/TypefacedEditText.java index ceac558..40415e5 100644 --- a/views/src/main/java/ru/touchin/roboswag/components/views/TypefacedEditText.java +++ b/views/src/main/java/ru/touchin/roboswag/components/views/TypefacedEditText.java @@ -49,6 +49,7 @@ import ru.touchin.roboswag.core.log.Lc; * Also in debug mode it has common checks for popular bugs. */ @SuppressWarnings("PMD.ConstructorCallsOverridableMethod") +@Deprecated //ConstructorCallsOverridableMethod: it's ok as we need to setTypeface public class TypefacedEditText extends AppCompatEditText { diff --git a/views/src/main/java/ru/touchin/roboswag/components/views/TypefacedTextView.java b/views/src/main/java/ru/touchin/roboswag/components/views/TypefacedTextView.java index 702342d..0c19b4d 100644 --- a/views/src/main/java/ru/touchin/roboswag/components/views/TypefacedTextView.java +++ b/views/src/main/java/ru/touchin/roboswag/components/views/TypefacedTextView.java @@ -43,6 +43,7 @@ import ru.touchin.roboswag.core.log.Lc; * Also in debug mode it has common checks for popular bugs. */ @SuppressWarnings("PMD.ConstructorCallsOverridableMethod") +@Deprecated //ConstructorCallsOverridableMethod: it's ok as we need to setTypeface public class TypefacedTextView extends AppCompatTextView { diff --git a/views/src/main/java/ru/touchin/roboswag/components/views/internal/AttributesUtils.java b/views/src/main/java/ru/touchin/roboswag/components/views/internal/AttributesUtils.java index a8c6474..0c85eac 100644 --- a/views/src/main/java/ru/touchin/roboswag/components/views/internal/AttributesUtils.java +++ b/views/src/main/java/ru/touchin/roboswag/components/views/internal/AttributesUtils.java @@ -39,6 +39,7 @@ import ru.touchin.roboswag.core.utils.ShouldNotHappenException; * Created by Gavriil Sitnikov on 13/06/2016. * Bunch of inner helper library methods to validate attributes of custom views. */ +@Deprecated public final class AttributesUtils { /** From 2e511b7d5533437cfa718836bd7cacdcaf50bded Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Wed, 22 Aug 2018 16:35:58 +0300 Subject: [PATCH 93/95] Storable update --- .../utils/storables/PreferenceStore.java | 84 ++++++++++--------- .../utils/storables/PreferenceUtils.java | 24 +++--- .../observables/storable/BaseStorable.java | 27 ++++-- .../core/observables/storable/Migration.java | 2 +- .../core/observables/storable/Store.java | 19 +++++ 5 files changed, 95 insertions(+), 61 deletions(-) diff --git a/storable/src/main/java/ru/touchin/roboswag/components/utils/storables/PreferenceStore.java b/storable/src/main/java/ru/touchin/roboswag/components/utils/storables/PreferenceStore.java index 73d90bf..3349dc9 100644 --- a/storable/src/main/java/ru/touchin/roboswag/components/utils/storables/PreferenceStore.java +++ b/storable/src/main/java/ru/touchin/roboswag/components/utils/storables/PreferenceStore.java @@ -25,11 +25,11 @@ import android.support.annotation.Nullable; import java.lang.reflect.Type; +import io.reactivex.Completable; +import io.reactivex.Single; import ru.touchin.roboswag.core.log.Lc; import ru.touchin.roboswag.core.observables.storable.Store; import ru.touchin.roboswag.core.utils.Optional; -import io.reactivex.Completable; -import io.reactivex.Single; /** * Created by Gavriil Sitnikov on 18/03/16. @@ -71,52 +71,54 @@ public class PreferenceStore implements Store { @NonNull @Override public Completable storeObject(@NonNull final Type storeObjectType, @NonNull final String key, @Nullable final T storeObject) { - return Completable.fromAction(() -> { - if (storeObject == null) { - preferences.edit().remove(key).apply(); - return; - } - - if (isTypeBoolean(storeObjectType)) { - preferences.edit().putBoolean(key, (Boolean) storeObject).apply(); - } else if (storeObjectType.equals(String.class)) { - preferences.edit().putString(key, (String) storeObject).apply(); - } else if (isTypeInteger(storeObjectType)) { - preferences.edit().putInt(key, (Integer) storeObject).apply(); - } else if (isTypeLong(storeObjectType)) { - preferences.edit().putLong(key, (Long) storeObject).apply(); - } else if (isTypeFloat(storeObjectType)) { - preferences.edit().putFloat(key, (Float) storeObject).apply(); - } else { - Lc.assertion("Unsupported type of object " + storeObjectType); - } - }); + return Completable.fromAction(() -> setObject(storeObjectType, key, storeObject)); } @NonNull @Override - @SuppressWarnings("unchecked") - //unchecked: it is checking class in if-else statements public Single> loadObject(@NonNull final Type storeObjectType, @NonNull final String key) { - return Single.fromCallable(() -> { - if (!preferences.contains(key)) { - return new Optional<>(null); - } + return Single.fromCallable(() -> new Optional<>(!preferences.contains(key) ? null : getObject(storeObjectType, key))); + } - if (isTypeBoolean(storeObjectType)) { - return new Optional<>((T) ((Boolean) preferences.getBoolean(key, false))); - } else if (storeObjectType.equals(String.class)) { - return new Optional<>((T) (preferences.getString(key, null))); - } else if (isTypeInteger(storeObjectType)) { - return new Optional<>((T) ((Integer) preferences.getInt(key, 0))); - } else if (isTypeLong(storeObjectType)) { - return new Optional<>((T) ((Long) preferences.getLong(key, 0L))); - } else if (isTypeFloat(storeObjectType)) { - return new Optional<>((T) ((Float) preferences.getFloat(key, 0f))); - } + @Override + public void setObject(@NonNull final Type storeObjectType, @NonNull final String key, @Nullable final T storeObject) { + if (storeObject == null) { + preferences.edit().remove(key).apply(); + return; + } + if (isTypeBoolean(storeObjectType)) { + preferences.edit().putBoolean(key, (Boolean) storeObject).apply(); + } else if (storeObjectType.equals(String.class)) { + preferences.edit().putString(key, (String) storeObject).apply(); + } else if (isTypeInteger(storeObjectType)) { + preferences.edit().putInt(key, (Integer) storeObject).apply(); + } else if (isTypeLong(storeObjectType)) { + preferences.edit().putLong(key, (Long) storeObject).apply(); + } else if (isTypeFloat(storeObjectType)) { + preferences.edit().putFloat(key, (Float) storeObject).apply(); + } else { Lc.assertion("Unsupported type of object " + storeObjectType); - return new Optional<>(null); - }); + } + } + + @Nullable + @SuppressWarnings("unchecked") //unchecked: it is checking class in if-else statements + @Override + public T getObject(@NonNull final Type storeObjectType, @NonNull final String key) { + if (isTypeBoolean(storeObjectType)) { + return (T) ((Boolean) preferences.getBoolean(key, false)); + } else if (storeObjectType.equals(String.class)) { + return (T) (preferences.getString(key, null)); + } else if (isTypeInteger(storeObjectType)) { + return (T) ((Integer) preferences.getInt(key, 0)); + } else if (isTypeLong(storeObjectType)) { + return (T) ((Long) preferences.getLong(key, 0L)); + } else if (isTypeFloat(storeObjectType)) { + return (T) ((Float) preferences.getFloat(key, 0f)); + } else { + Lc.assertion("Unsupported type of object " + storeObjectType); + return null; + } } } diff --git a/storable/src/main/java/ru/touchin/roboswag/components/utils/storables/PreferenceUtils.java b/storable/src/main/java/ru/touchin/roboswag/components/utils/storables/PreferenceUtils.java index 5ae4521..b9e9883 100644 --- a/storable/src/main/java/ru/touchin/roboswag/components/utils/storables/PreferenceUtils.java +++ b/storable/src/main/java/ru/touchin/roboswag/components/utils/storables/PreferenceUtils.java @@ -49,7 +49,7 @@ public final class PreferenceUtils { name, String.class, String.class, - new PreferenceStore(preferences), + new PreferenceStore<>(preferences), new SameTypesConverter<>() ).build(); } @@ -72,7 +72,7 @@ public final class PreferenceUtils { name, String.class, String.class, - new PreferenceStore(preferences), + new PreferenceStore<>(preferences), new SameTypesConverter<>() ).setDefaultValue(defaultValue).build(); } @@ -90,7 +90,7 @@ public final class PreferenceUtils { name, Long.class, Long.class, - new PreferenceStore(preferences), + new PreferenceStore<>(preferences), new SameTypesConverter<>() ).build(); } @@ -113,7 +113,7 @@ public final class PreferenceUtils { name, Long.class, Long.class, - new PreferenceStore(preferences), + new PreferenceStore<>(preferences), new SameTypesConverter<>() ).setDefaultValue(defaultValue).build(); } @@ -131,7 +131,7 @@ public final class PreferenceUtils { name, Boolean.class, Boolean.class, - new PreferenceStore(preferences), + new PreferenceStore<>(preferences), new SameTypesConverter<>() ).build(); } @@ -154,7 +154,7 @@ public final class PreferenceUtils { name, Boolean.class, Boolean.class, - new PreferenceStore(preferences), + new PreferenceStore<>(preferences), new SameTypesConverter<>() ).setDefaultValue(defaultValue).build(); } @@ -172,7 +172,7 @@ public final class PreferenceUtils { name, Integer.class, Integer.class, - new PreferenceStore(preferences), + new PreferenceStore<>(preferences), new SameTypesConverter<>() ).build(); } @@ -195,7 +195,7 @@ public final class PreferenceUtils { name, Integer.class, Integer.class, - new PreferenceStore(preferences), + new PreferenceStore<>(preferences), new SameTypesConverter<>() ).setDefaultValue(defaultValue).build(); } @@ -213,7 +213,7 @@ public final class PreferenceUtils { name, Float.class, Float.class, - new PreferenceStore(preferences), + new PreferenceStore<>(preferences), new SameTypesConverter<>() ).build(); } @@ -236,7 +236,7 @@ public final class PreferenceUtils { name, Float.class, Float.class, - new PreferenceStore(preferences), + new PreferenceStore<>(preferences), new SameTypesConverter<>() ).setDefaultValue(defaultValue).build(); } @@ -258,7 +258,7 @@ public final class PreferenceUtils { name, enumClass, String.class, - new PreferenceStore(preferences), + new PreferenceStore<>(preferences), new EnumToStringConverter<>() ).build(); } @@ -282,7 +282,7 @@ public final class PreferenceUtils { name, enumClass, String.class, - new PreferenceStore(preferences), + new PreferenceStore<>(preferences), new EnumToStringConverter<>() ).setDefaultValue(defaultValue).build(); } diff --git a/storable/src/main/java/ru/touchin/roboswag/core/observables/storable/BaseStorable.java b/storable/src/main/java/ru/touchin/roboswag/core/observables/storable/BaseStorable.java index 390a024..7b721b4 100644 --- a/storable/src/main/java/ru/touchin/roboswag/core/observables/storable/BaseStorable.java +++ b/storable/src/main/java/ru/touchin/roboswag/core/observables/storable/BaseStorable.java @@ -298,10 +298,18 @@ public abstract class BaseStorable { * * @param newValue Value to set; */ - @Deprecated - //deprecation: it should be used for debug only and in very rare cases. public void setSync(@Nullable final TObject newValue) { - set(newValue).blockingAwait(); + final TStoreObject newStoreValue; + try { + newStoreValue = converter.toStoreObject(objectType, storeObjectType, newValue); + } catch (final Converter.ConversionException exception) { + STORABLE_LC_GROUP.w(exception, "Exception while trying to store value of '%s' from store %s by %s", + key, newValue, store, converter); + return; + } + store.setObject(storeObjectType, key, newStoreValue); + newStoreValueEvent.onNext(new Optional<>(newStoreValue)); + STORABLE_LC_GROUP.i("Value of '%s' force changed to '%s'", key, newStoreValue); } @NonNull @@ -334,11 +342,16 @@ public abstract class BaseStorable { * * @return Returns value; */ - @Deprecated - //deprecation: it should be used for debug only and in very rare cases. @Nullable - public TReturnObject getSync() { - return get().blockingGet(); + public TObject getSync() { + final TStoreObject storeObject = store.getObject(storeObjectType, key); + try { + return converter.toObject(objectType, storeObjectType, storeObject); + } catch (final Converter.ConversionException exception) { + STORABLE_LC_GROUP.w(exception, "Exception while trying to converting value of '%s' from store %s by %s", + key, storeObject, store, converter); + return null; + } } /** diff --git a/storable/src/main/java/ru/touchin/roboswag/core/observables/storable/Migration.java b/storable/src/main/java/ru/touchin/roboswag/core/observables/storable/Migration.java index 3fa2475..62397f2 100644 --- a/storable/src/main/java/ru/touchin/roboswag/core/observables/storable/Migration.java +++ b/storable/src/main/java/ru/touchin/roboswag/core/observables/storable/Migration.java @@ -101,7 +101,7 @@ public class Migration { .switchMap(throwable -> throwable instanceof NextLoopMigrationException ? Flowable.just(new Object()) : Flowable.error(throwable))); }) - .toCompletable() + .ignoreElement() .andThen(versionsStore.storeObject(Long.class, key, latestVersion)) .onErrorResumeNext(throwable -> { if (throwable instanceof MigrationException) { diff --git a/storable/src/main/java/ru/touchin/roboswag/core/observables/storable/Store.java b/storable/src/main/java/ru/touchin/roboswag/core/observables/storable/Store.java index c6c3a3b..ad31c4d 100644 --- a/storable/src/main/java/ru/touchin/roboswag/core/observables/storable/Store.java +++ b/storable/src/main/java/ru/touchin/roboswag/core/observables/storable/Store.java @@ -67,4 +67,23 @@ public interface Store { @NonNull Single> loadObject(@NonNull Type storeObjectType, @NonNull TKey key); + /** + * Stores object to store with related key. + * + * @param storeObjectType Type of object to store; + * @param key Key related to object; + * @param storeObject Object to store; + */ + void setObject(@NonNull Type storeObjectType, @NonNull TKey key, @Nullable TStoreObject storeObject); + + /** + * Gets object from store by key. + * + * @param storeObjectType Type of object to store; + * @param key Key related to object; + * @return Object from store found by key; + */ + @Nullable + TStoreObject getObject(@NonNull Type storeObjectType, @NonNull TKey key); + } From 771565e812ca55d660660f3967af7a60f0e55c19 Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Wed, 22 Aug 2018 17:48:49 +0300 Subject: [PATCH 94/95] toString for ViewControllerFragment --- .../navigation/fragments/ViewControllerFragment.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java index 34e4b32..cb38f7a 100644 --- a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java +++ b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java @@ -375,6 +375,12 @@ public class ViewControllerFragment Date: Wed, 22 Aug 2018 18:17:14 +0300 Subject: [PATCH 95/95] Merge fix --- .../roboswag/components/views/MaterialLoadingBar.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/views/src/main/java/ru/touchin/roboswag/components/views/MaterialLoadingBar.java b/views/src/main/java/ru/touchin/roboswag/components/views/MaterialLoadingBar.java index a7d83bd..c3b88ef 100644 --- a/views/src/main/java/ru/touchin/roboswag/components/views/MaterialLoadingBar.java +++ b/views/src/main/java/ru/touchin/roboswag/components/views/MaterialLoadingBar.java @@ -116,13 +116,4 @@ public class MaterialLoadingBar extends AppCompatImageView { progressDrawable.setColor(colorInt); } - /** - * Set color of loader. - * - * @param colorInt Color of loader to be set. - */ - public void setColor(@ColorInt final int colorInt) { - progressDrawable.setColor(colorInt); - } - }