Compare commits

..

128 Commits
master ... mvvm

Author SHA1 Message Date
Anton Domnikov bcb22fa542
Merge pull request #136 from TouchInstinct/bugs/view_controller
Bugs/view controller
2018-10-29 16:29:50 +03:00
Anton Domnikov 1770e03302 added field to Javadoc 2018-10-29 16:02:25 +03:00
Anton Domnikov 3581f0bb5e added ability to set tag when pushing VC, added getter for VC container 2018-10-29 16:00:00 +03:00
Denis Karmyshakov 06a5157010 Navigation up fix 2018-10-23 00:56:54 +03:00
Alexander Sirota 52c618a1bf
Merge pull request #135 from TouchInstinct/almond_rush
Fix null check for support v28
2018-10-16 15:53:45 +03:00
Alexander Sirota 45cf82bf10 Fix null check for support v28 2018-10-16 15:51:13 +03:00
Denis Karmyshakov ca46bc290a Static 2018-10-10 14:14:32 +03:00
Denis Karmyshakov 5e2d3431dc Switcher moved to animations 2018-10-10 13:48:59 +03:00
maxbach ea3013baec
Add remove all back listeners method (#133) 2018-10-05 19:58:17 +03:00
Denis Karmyshakov a9987b89ff Another possible switcher fix 2018-10-01 21:12:57 +03:00
Denis Karmyshakov 6677b0d0b7 Possible Switcher fix 2018-09-24 22:29:30 +03:00
Oleg Kuznetcov e68809010e safeStartActivity fix 2018-09-21 13:01:41 +03:00
Denis Karmyshakov 72a2412f41 Switcher widget for loading/content/error implementation 2018-09-20 01:30:28 +03:00
Denis Karmyshakov 726f466c02 Removed PlaceholderView nesting 2018-09-07 13:42:08 +03:00
Denis Karmyshakov b2e3d6b0f0 safeStartActivity extension 2018-08-31 14:15:05 +03:00
Denis Karmyshakov 1ffe50bdae
Merge pull request #130 from TouchInstinct/hideSoftInput
fix hideSoftInput onStart
2018-07-31 15:59:30 +03:00
Arseniy Borisov 6ff1409d02 fix hideSoftInput onstart 2018-07-31 15:57:24 +03:00
PilotOfSparrow f9cac685ac
Merge pull request #129 from TouchInstinct/feature/onStateRestored
fix non final Bundle
2018-07-13 13:14:03 +03:00
Unknown 598dad199d fix non final Bundle 2018-07-13 13:13:04 +03:00
PilotOfSparrow e5a0942ea5
Merge pull request #128 from TouchInstinct/feature/onStateRestored
Added onViewStateRestored callback to viewcontroller
2018-07-13 12:52:17 +03:00
Unknown 7d377614cb Added onViewStateRestored callback to viewcontroller 2018-07-13 12:39:02 +03:00
Denis Karmyshakov 276e697d83 untilDestroy parameters naming 2018-07-03 15:14:21 +03:00
Denis Karmyshakov 448501aa06 untilDestroy moved to extension 2018-07-03 15:00:36 +03:00
Arseniy Borisov 42750812dd hide keyboard onStart ViewController 2018-07-02 12:23:53 +03:00
Denis Karmyshakov 9453856713 Up navigation fix 2018-06-07 18:05:02 +03:00
Denis Karmyshakov f3d076aa6c Diff computing fix 2018-05-30 16:59:01 +03:00
Denis Karmyshakov 5b3ccdbf91 List adapter click listener fix 2018-05-29 13:52:17 +03:00
Denis Karmyshakov a55510f8f1 Transition fix 2018-05-29 12:58:41 +03:00
Denis Karmyshakov a789ae9efe Static Analysis 2018-05-29 12:54:12 +03:00
Denis Karmyshakov d9981592af
Merge pull request #126 from TouchInstinct/add_on_request_permission
Add on request permissons result callback
2018-05-29 12:23:25 +03:00
Unknown 2cfa072319 Delete method 2018-05-29 09:40:44 +03:00
Unknown dc773302f0 add comment 2018-05-28 20:46:21 +03:00
Unknown 78d213abc5 Add on request permissons result callback 2018-05-28 20:06:00 +03:00
Denis Karmyshakov b528ec185e Static analysis 2018-05-27 00:03:01 +03:00
Denis Karmyshakov 818d8aedaf ViewController: added onCreateAnimation callbacks
Navigation: some fixes
Resources: added slide fragment transition animations
2018-05-26 23:18:17 +03:00
Denis Karmyshakov a3c113125d Destroyable: added clear subscriptions method 2018-05-25 21:08:25 +03:00
Denis Karmyshakov 77a0160fce Fragment custom animations fix 2018-05-16 02:44:38 +03:00
Denis Karmyshakov d71938c87f ViewControllerNavigation: added wildcard for activity 2018-05-10 21:24:06 +03:00
Denis Karmyshakov 89377c3fea Added getter for viewControllerClass 2018-05-04 20:45:38 +03:00
Denis Karmyshakov 0815cdd421 Invalidation menu fix 2018-04-28 18:07:57 +03:00
Denis Karmyshakov e2107e9990 Simple delegate for properties 2018-04-27 14:02:29 +03:00
Denis Karmyshakov 90c7c25f6c
Merge pull request #125 from TouchInstinct/get_string_fix
fix getString in ViewHolder extension
2018-04-24 17:38:24 +03:00
Arseniy Borisov 400ae4f00d fix getString 2018-04-24 17:34:29 +03:00
Denis Karmyshakov 996fefe395 setOnRippleClickListener fix 2018-04-22 23:53:22 +03:00
Denis Karmyshakov fbdaad47ca
Merge pull request #124 from TouchInstinct/static
Delete space betwen semicolon
2018-04-22 21:33:11 +03:00
Unknown ebdba5a90b Delete space betwen semicolon 2018-04-22 21:28:44 +03:00
Denis Karmyshakov 7b15f41968
Merge pull request #123 from TouchInstinct/fix_back_stack
Fix back stack
2018-04-22 21:22:40 +03:00
Unknown 7edc1fabe1 Fix back stack 2018-04-22 21:11:37 +03:00
Denis Karmyshakov 496e0da5da Removed TFragment generic in ViewController 2018-04-21 02:37:35 +03:00
Denis Karmyshakov 9508946b5a ViewHolder.context extension, ViewController startActivity methods 2018-04-11 13:40:57 +03:00
Denis Karmyshakov b64452b086 Initial fragment fix in navigation 2018-04-04 18:57:56 +03:00
nbnbbs ed7cd8bd42
small sA fixes (#122)
* small sA fixes

* small fixes

* remove suppress
2018-04-03 13:09:52 +03:00
Denis Karmyshakov b06a1232b7 getText in ViewController 2018-03-30 18:35:42 +03:00
Denis Karmyshakov 0cbf55d81b ViewHolder extensions 2018-03-30 18:19:11 +03:00
Denis Karmyshakov 399ec7d7f5 Getters for compat resources 2018-03-30 16:24:27 +03:00
Denis Karmyshakov 496abde610 setOnRippleClickListener moved to extension 2018-03-30 15:25:22 +03:00
Denis Karmyshakov cd9db1203a Flowable handling 2018-03-27 17:40:34 +03:00
Denis Karmyshakov bbc73d3d12 SoftInput method moved to UiUtils 2018-03-21 16:51:31 +03:00
Denis Karmyshakov 7e35087c0a Diff config for DelegationListAdapter, added DefaultViewController 2018-03-20 18:05:28 +03:00
Denis Karmyshakov 5f6152050b Navigation refactor, DelegationListAdapter fix 2018-03-19 12:29:25 +03:00
Denis Karmyshakov 69f1c9af4a Added DelegationListAdapter, small refactor of delegates 2018-03-16 18:29:17 +03:00
Denis Karmyshakov e3e978b7f7 LifecycleViewHolder removed 2018-03-15 11:26:38 +03:00
Denis Karmyshakov b64e1c9e35 Added empty Parcelable state and state getter in ViewController class 2018-03-14 19:58:18 +03:00
Denis Karmyshakov dc87783252 Renaming BindableViewHolder to LifecycleViewHolder 2018-03-14 16:25:06 +03:00
Denis Karmyshakov 9b1275ceec Migration to AAC lifecycle, simplification of some logic 2018-03-14 15:44:17 +03:00
maxbach 44af99f9cf Fix import (#112) 2017-12-28 16:32:09 +03:00
maxbach 8eaa50d891 Add to MaterialLoadingBar method setColor (#109) 2017-12-28 15:17:42 +03:00
Denis Karmyshakov 5805c63288
Merge pull request #108 from TouchInstinct/gradle_update
Gradle update
2017-12-01 16:11:40 +03:00
Denis Karmyshakov a3dca51488 Gradle update 2017-11-30 18:02:23 +03:00
Arseniy Borisov 426a213a44 Updating by payload fixed (#105) 2017-11-13 11:58:17 +03:00
Denis Karmyshakov 352ff5a8b4 Versions in constants (#102) 2017-10-04 12:29:08 +03:00
Ilia Kurtov 03689b5382 Merge pull request #98 from TouchInstinct/update/rxjava
Update/rxjava
2017-09-22 16:42:37 +03:00
Ilia Kurtov e3c3c95767 update 2017-09-22 16:41:17 +03:00
Denis Karmyshakov 6a6ea0ec08 onActivityResult in viewControllers (#95) 2017-09-07 13:07:05 +03:00
Denis Karmyshakov 43ad3d62d9 Added generic in findViewById (#93) 2017-08-28 13:20:55 +03:00
Denis Karmyshakov 3a3f416b8c Fonts in xml (#92) 2017-08-23 17:05:46 +03:00
Ilia Kurtov bab8ceb74f rxjava update (#91) 2017-08-15 21:02:55 +03:00
Ilia Kurtov d3191cd472 libs update (#90) 2017-08-14 17:41:32 +03:00
Elena Bobkova 6d0f74a972 fixed on activity result bindings for large files (#89) 2017-08-14 15:34:00 +03:00
Ilia Kurtov 37f5664187 static 2017-08-09 18:13:53 +03:00
Ilia Kurtov 46d00ffc5f restored setOnRippleClickListener for Action and delete for Consumer 2017-08-09 18:13:39 +03:00
Ilia Kurtov dc94030420 Merge branch 'master-rx-java-2' into kotlin/rx-java2-merge 2017-08-09 18:00:48 +03:00
Denis Karmyshakov 527de35579 Support lib update (#84) 2017-08-03 18:21:53 +03:00
Denis Karmyshakov dd2732c129 Function setContentView made final (#82)
setContentView теперь final, чтобы студия не ругалась при вызове этих методов в конструкторе
2017-08-01 13:04:10 +03:00
Gavriil Sitnikov 6faabed582 Merge branch 'master' into master-rx-java-2
# Conflicts:
#	src/main/java/ru/touchin/roboswag/components/utils/BaseLifecycleBindable.java
2017-07-31 16:53:18 +03:00
Arseniy Borisov c9448639d5 Merge pull request #80 from TouchInstinct/feature/improvement_for_kotlin
В котлине единственный параметр в лямбде и так опционален
2017-07-31 16:23:36 +03:00
Denis Karmyshakov fda49db855 Removed unnecessary setOnRippleClickListener overloads 2017-07-31 16:17:33 +03:00
Gavriil Sitnikov 99e1046907 Merge branch 'bugs/on_resume_lifecycle_fix' into master-rx-java-2 2017-07-31 16:07:16 +03:00
Gavriil Sitnikov de52c32e98 Merge branch 'blur_upd' into master-rx-java-2 2017-07-28 11:25:27 +03:00
gorodeckii 0a82b7a57c rxjava2 version 2017-07-26 17:53:54 +03:00
Arseniy Borisov ea35a1dc5d Merge pull request #76 from TouchInstinct/build_tools_update
Build tools update
2017-07-25 19:58:16 +03:00
Denis Karmyshakov 3f8955f31b Build tools update 2017-07-25 19:54:06 +03:00
Arseniy Borisov 64ae48ae39 Merge pull request #75 from TouchInstinct/typefacedEditText
Добавил support design библиотеку, добавил в typefactedEditText перео…
2017-07-25 19:09:34 +03:00
Alexander Bubnov 8405c76bf0 удалил ненужный this 2017-07-25 19:08:59 +03:00
Alexander Bubnov 97c9a7571b Добавил support design библиотеку, добавил в typefactedEditText переопределения метода, для работы с TextInputLayout 2017-07-25 18:56:13 +03:00
Denis Karmyshakov ef7cf6d50d Merge pull request #74 from TouchInstinct/idea_formatting
idea formatting
2017-07-24 12:46:19 +03:00
Arseniy Borisov 70e6b99e79 idea formatting 2017-07-24 12:33:29 +03:00
Anton Domnikov 00d3433b87 Merge branch 'master-rx-java-2' into kotlin_migration
# Conflicts:
#	build.gradle
2017-07-11 14:30:55 +03:00
gorodeckii ffe587a8f5 Merge branch 'master' into master-rx-java-2 2017-07-05 14:34:05 +03:00
Anton Domnikov a517b16334 added ability to push viewcontrollers without adding to back stack 2017-06-30 20:39:49 +03:00
Denis Karmyshakov c32eb4b835 Merge fix 2017-06-30 19:32:45 +03:00
Denis Karmyshakov 155ad932f6 Merge remote-tracking branch 'origin/master' into master-rx-java-2
# Conflicts:
#	src/main/java/ru/touchin/roboswag/components/adapters/ObservableCollectionAdapter.java
2017-06-30 19:24:57 +03:00
Gavriil dfc7615187 Merge pull request #71 from TouchInstinct/master-rx-java-2-maybe
add maybe support to bindable lifecycle
2017-06-29 16:15:48 +03:00
Alexander Bubnov bb7473e839 fix sA 2017-06-29 13:22:29 +03:00
Alexander Bubnov 2398b4fc10 add maybe support to bindable lifecycle 2017-06-29 13:17:58 +03:00
Anton Domnikov 6faf786ba4 Merge branch 'master-rx-java-2' into kotlin_migration 2017-06-23 13:33:04 +03:00
Alexander Bubnov d3bcd89743 update rxJava2 to 2.1.1 2017-06-21 17:05:02 +03:00
Anton Domnikov 540e573888 revert retrolambda 2017-06-19 18:23:27 +03:00
Anton Domnikov 06f9b1d2cc remove retrolambda 2017-06-14 17:51:06 +03:00
gorodeckii 9da0e059b5 Merge branch 'master' into master-rx-java-2 2017-06-09 15:53:48 +03:00
Gavriil Sitnikov 8953170dca Merge branch 'feature/general_improvements' into master-rx-java-2 2017-06-06 15:08:48 +03:00
gorodeckii e9f7815ca3 fixed imports to rxjava-2 style 2017-05-29 12:34:02 +03:00
gorodeckii e23d9d55b0 Merge branch 'feature/general_improvements' into master-rx-java-2 2017-05-29 12:21:07 +03:00
Gavriil Sitnikov 16624b3657 Merge branch 'feature/general_improvements' into master-rx-java-2 2017-05-18 16:31:02 +03:00
Gavriil Sitnikov 660065490d Merge branch 'feature/general_improvements' into master-rx-java-2 2017-05-18 15:49:15 +03:00
Alexander Bubnov 88113ef0ee add supress 2017-05-17 19:06:19 +03:00
Alexander Bubnov c57f30aca1 fix wrong import 2017-05-17 19:00:46 +03:00
gorodeckii e847fcedc5 unused import 2017-05-12 20:30:35 +03:00
Gavriil Sitnikov a2b69d64a1 Merge branch 'feature/general_improvements' into master-rx-java-2
# Conflicts:
#	build.gradle
#	src/main/java/ru/touchin/roboswag/components/adapters/AdapterDelegate.java
#	src/main/java/ru/touchin/roboswag/components/adapters/ObservableCollectionAdapter.java
2017-05-12 19:45:37 +03:00
Arhipov 38f8a8018e Merge branch 'master' into master-rx-java-2 2017-05-04 14:05:06 +03:00
gorodeckii 5d0847370d rxjava version 2017-05-03 09:26:52 +03:00
Arhipov d8ca0e610b merge continued 2017-04-26 16:27:30 +03:00
Arhipov e2d584b5f7 Merge branch 'master' into master-rx-java-2
# Conflicts:
#	src/main/java/ru/touchin/roboswag/components/adapters/ObservableCollectionAdapter.java
2017-04-26 16:26:41 +03:00
Gavriil Sitnikov 5f13aefa84 viewcontroller null fixed 2017-04-21 00:31:26 +03:00
Gavriil Sitnikov 949025a106 static fixes 2017-04-21 00:10:41 +03:00
Gavriil Sitnikov a4a0e0d4b8 Merge branch 'feature/general_improvements' into master-rx-java-2
# Conflicts:
#	src/main/java/ru/touchin/roboswag/components/adapters/BindableViewHolder.java
#	src/main/java/ru/touchin/roboswag/components/adapters/ObservableCollectionAdapter.java
#	src/main/java/ru/touchin/roboswag/components/navigation/ViewController.java
#	src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java
#	src/main/java/ru/touchin/roboswag/components/utils/BaseLifecycleBindable.java
2017-04-20 19:54:12 +03:00
Gavriil Sitnikov 68f7c73ff9 started empty value crash fix 2017-04-19 19:09:34 +03:00
Gavriil Sitnikov fd68f1ffd4 RxJava2 migration 2017-04-17 03:11:46 +03:00
63 changed files with 1765 additions and 5421 deletions

View File

@ -1,4 +1,5 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
compileSdkVersion compileSdk
@ -16,9 +17,12 @@ 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"
compileOnly "io.reactivex:rxandroid:$rxAndroidVersion"
compileOnly "io.reactivex:rxjava:$rxJavaVersion"
compileOnly "io.reactivex.rxjava2:rxandroid:$rxAndroidVersion"
compileOnly "io.reactivex.rxjava2:rxjava:$rxJavaVersion"
}

View File

@ -20,45 +20,21 @@
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 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;
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 <TViewHolder> Type of {@link BindableViewHolder} of delegate.
* @param <TViewHolder> Type of {@link RecyclerView.ViewHolder} of delegate.
*/
@SuppressWarnings("PMD.TooManyMethods")
//TooManyMethods: it's ok
public abstract class AdapterDelegate<TViewHolder extends BindableViewHolder> implements LifecycleBindable {
public abstract class AdapterDelegate<TViewHolder extends RecyclerView.ViewHolder> {
@NonNull
private final LifecycleBindable parentLifecycleBindable;
private final int defaultItemViewType;
public AdapterDelegate(@NonNull final LifecycleBindable parentLifecycleBindable) {
this.parentLifecycleBindable = parentLifecycleBindable;
this.defaultItemViewType = UiUtils.OfViews.generateViewId();
}
/**
* Returns parent {@link LifecycleBindable} that this delegate created from (e.g. Activity or ViewController).
*
* @return Parent {@link LifecycleBindable}.
*/
@NonNull
public LifecycleBindable getParentLifecycleBindable() {
return parentLifecycleBindable;
}
private final int defaultItemViewType = ViewCompat.generateViewId();
/**
* Unique ID of AdapterDelegate.
@ -69,6 +45,28 @@ public abstract class AdapterDelegate<TViewHolder extends BindableViewHolder> im
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<Object> 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<Object> items, final int adapterPosition, final int collectionPosition) {
return 0;
}
/**
* Creates ViewHolder to bind item to it later.
*
@ -78,144 +76,21 @@ public abstract class AdapterDelegate<TViewHolder extends BindableViewHolder> 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 <T> Subscription untilStop(@NonNull final Observable<T> observable) {
return parentLifecycleBindable.untilStop(observable);
}
@NonNull
@Override
public <T> Subscription untilStop(@NonNull final Observable<T> observable, @NonNull final Action1<T> onNextAction) {
return parentLifecycleBindable.untilStop(observable, onNextAction);
}
@NonNull
@Override
public <T> Subscription untilStop(@NonNull final Observable<T> observable,
@NonNull final Action1<T> onNextAction,
@NonNull final Action1<Throwable> onErrorAction) {
return parentLifecycleBindable.untilStop(observable, onNextAction, onErrorAction);
}
@NonNull
@Override
public <T> Subscription untilStop(@NonNull final Observable<T> observable,
@NonNull final Action1<T> onNextAction,
@NonNull final Action1<Throwable> onErrorAction,
@NonNull final Action0 onCompletedAction) {
return parentLifecycleBindable.untilStop(observable, onNextAction, onErrorAction, onCompletedAction);
}
@NonNull
@Override
public <T> Subscription untilStop(@NonNull final Single<T> single) {
return parentLifecycleBindable.untilStop(single);
}
@NonNull
@Override
public <T> Subscription untilStop(@NonNull final Single<T> single, @NonNull final Action1<T> onSuccessAction) {
return parentLifecycleBindable.untilStop(single, onSuccessAction);
}
@NonNull
@Override
public <T> Subscription untilStop(@NonNull final Single<T> single,
@NonNull final Action1<T> onSuccessAction,
@NonNull final Action1<Throwable> onErrorAction) {
return parentLifecycleBindable.untilStop(single, onSuccessAction, onErrorAction);
}
@NonNull
@Override
public Subscription untilStop(@NonNull final Completable completable) {
return parentLifecycleBindable.untilStop(completable);
}
@NonNull
@Override
public Subscription untilStop(@NonNull final Completable completable, @NonNull final Action0 onCompletedAction) {
return parentLifecycleBindable.untilStop(completable, onCompletedAction);
}
@NonNull
@Override
public Subscription untilStop(@NonNull final Completable completable,
@NonNull final Action0 onCompletedAction,
@NonNull final Action1<Throwable> onErrorAction) {
return parentLifecycleBindable.untilStop(completable, onCompletedAction, onErrorAction);
}
@NonNull
@Override
public <T> Subscription untilDestroy(@NonNull final Observable<T> observable) {
return parentLifecycleBindable.untilDestroy(observable);
}
@NonNull
@Override
public <T> Subscription untilDestroy(@NonNull final Observable<T> observable, @NonNull final Action1<T> onNextAction) {
return parentLifecycleBindable.untilDestroy(observable, onNextAction);
}
@NonNull
@Override
public <T> Subscription untilDestroy(@NonNull final Observable<T> observable,
@NonNull final Action1<T> onNextAction,
@NonNull final Action1<Throwable> onErrorAction) {
return parentLifecycleBindable.untilDestroy(observable, onNextAction, onErrorAction);
}
@NonNull
@Override
public <T> Subscription untilDestroy(@NonNull final Observable<T> observable,
@NonNull final Action1<T> onNextAction,
@NonNull final Action1<Throwable> onErrorAction,
@NonNull final Action0 onCompletedAction) {
return parentLifecycleBindable.untilDestroy(observable, onNextAction, onErrorAction, onCompletedAction);
}
@NonNull
@Override
public <T> Subscription untilDestroy(@NonNull final Single<T> single) {
return parentLifecycleBindable.untilDestroy(single);
}
@NonNull
@Override
public <T> Subscription untilDestroy(@NonNull final Single<T> single, @NonNull final Action1<T> onSuccessAction) {
return parentLifecycleBindable.untilDestroy(single, onSuccessAction);
}
@NonNull
@Override
public <T> Subscription untilDestroy(@NonNull final Single<T> single,
@NonNull final Action1<T> onSuccessAction,
@NonNull final Action1<Throwable> onErrorAction) {
return parentLifecycleBindable.untilDestroy(single, onSuccessAction, onErrorAction);
}
@NonNull
@Override
public Subscription untilDestroy(@NonNull final Completable completable) {
return parentLifecycleBindable.untilDestroy(completable);
}
@NonNull
@Override
public Subscription untilDestroy(@NonNull final Completable completable, @NonNull final Action0 onCompletedAction) {
return parentLifecycleBindable.untilDestroy(completable, onCompletedAction);
}
@NonNull
@Override
public Subscription untilDestroy(@NonNull final Completable completable,
@NonNull final Action0 onCompletedAction,
@NonNull final Action1<Throwable> onErrorAction) {
return parentLifecycleBindable.untilDestroy(completable, onCompletedAction, onErrorAction);
}
/**
* 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<Object> items,
final int adapterPosition,
final int collectionPosition,
@NonNull final List<Object> payloads
);
}

View File

@ -1,263 +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 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.
* 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 extends View> 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);
}
@NonNull
@Override
public <T> Subscription untilStop(@NonNull final Observable<T> observable) {
return baseLifecycleBindable.untilStop(observable);
}
@NonNull
@Override
public <T> Subscription untilStop(@NonNull final Observable<T> observable, @NonNull final Action1<T> onNextAction) {
return baseLifecycleBindable.untilStop(observable, onNextAction);
}
@NonNull
@Override
public <T> Subscription untilStop(@NonNull final Observable<T> observable,
@NonNull final Action1<T> onNextAction,
@NonNull final Action1<Throwable> onErrorAction) {
return baseLifecycleBindable.untilStop(observable, onNextAction, onErrorAction);
}
@NonNull
@Override
public <T> Subscription untilStop(@NonNull final Observable<T> observable,
@NonNull final Action1<T> onNextAction,
@NonNull final Action1<Throwable> onErrorAction,
@NonNull final Action0 onCompletedAction) {
return baseLifecycleBindable.untilStop(observable, onNextAction, onErrorAction, onCompletedAction);
}
@NonNull
@Override
public <T> Subscription untilStop(@NonNull final Single<T> single) {
return baseLifecycleBindable.untilStop(single);
}
@NonNull
@Override
public <T> Subscription untilStop(@NonNull final Single<T> single, @NonNull final Action1<T> onSuccessAction) {
return baseLifecycleBindable.untilStop(single, onSuccessAction);
}
@NonNull
@Override
public <T> Subscription untilStop(@NonNull final Single<T> single,
@NonNull final Action1<T> onSuccessAction,
@NonNull final Action1<Throwable> onErrorAction) {
return baseLifecycleBindable.untilStop(single, onSuccessAction, onErrorAction);
}
@NonNull
@Override
public Subscription untilStop(@NonNull final Completable completable) {
return baseLifecycleBindable.untilStop(completable);
}
@NonNull
@Override
public Subscription untilStop(@NonNull final Completable completable, @NonNull final Action0 onCompletedAction) {
return baseLifecycleBindable.untilStop(completable, onCompletedAction);
}
@NonNull
@Override
public Subscription untilStop(@NonNull final Completable completable,
@NonNull final Action0 onCompletedAction,
@NonNull final Action1<Throwable> onErrorAction) {
return baseLifecycleBindable.untilStop(completable, onCompletedAction, onErrorAction);
}
@NonNull
@Override
public <T> Subscription untilDestroy(@NonNull final Observable<T> observable) {
return baseLifecycleBindable.untilDestroy(observable);
}
@NonNull
@Override
public <T> Subscription untilDestroy(@NonNull final Observable<T> observable, @NonNull final Action1<T> onNextAction) {
return baseLifecycleBindable.untilDestroy(observable, onNextAction);
}
@NonNull
@Override
public <T> Subscription untilDestroy(@NonNull final Observable<T> observable,
@NonNull final Action1<T> onNextAction,
@NonNull final Action1<Throwable> onErrorAction) {
return baseLifecycleBindable.untilDestroy(observable, onNextAction, onErrorAction);
}
@NonNull
@Override
public <T> Subscription untilDestroy(@NonNull final Observable<T> observable,
@NonNull final Action1<T> onNextAction,
@NonNull final Action1<Throwable> onErrorAction,
@NonNull final Action0 onCompletedAction) {
return baseLifecycleBindable.untilDestroy(observable, onNextAction, onErrorAction, onCompletedAction);
}
@NonNull
@Override
public <T> Subscription untilDestroy(@NonNull final Single<T> single) {
return baseLifecycleBindable.untilDestroy(single);
}
@NonNull
@Override
public <T> Subscription untilDestroy(@NonNull final Single<T> single, @NonNull final Action1<T> onSuccessAction) {
return baseLifecycleBindable.untilDestroy(single, onSuccessAction);
}
@NonNull
@Override
public <T> Subscription untilDestroy(@NonNull final Single<T> single,
@NonNull final Action1<T> onSuccessAction,
@NonNull final Action1<Throwable> onErrorAction) {
return baseLifecycleBindable.untilDestroy(single, onSuccessAction, onErrorAction);
}
@NonNull
@Override
public Subscription untilDestroy(@NonNull final Completable completable) {
return baseLifecycleBindable.untilDestroy(completable);
}
@NonNull
@Override
public Subscription untilDestroy(@NonNull final Completable completable, @NonNull final Action0 onCompletedAction) {
return baseLifecycleBindable.untilDestroy(completable, onCompletedAction);
}
@NonNull
@Override
public Subscription untilDestroy(@NonNull final Completable completable,
@NonNull final Action0 onCompletedAction,
@NonNull final Action1<Throwable> onErrorAction) {
return baseLifecycleBindable.untilDestroy(completable, onCompletedAction, onErrorAction);
}
}

View File

@ -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<AdapterDelegate<*>>()
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<Any>) {
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")
}

View File

@ -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<TItem>(config: AsyncDifferConfig<TItem>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
constructor(diffCallback: DiffUtil.ItemCallback<TItem>) : this(AsyncDifferConfig.Builder<TItem>(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<Any>) {
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<TItem>) = differ.submitList(list)
/**
* Get the current List - any diffing to present this list has already been computed and
* dispatched via the ListUpdateCallback.
* <p>
* If a <code>null</code> List, or no List has been submitted, an empty list will be returned.
* <p>
* The returned list may not be mutated - mutations to content must be done through
* {@link #submitList(List)}.
*
* @return current List.
*/
fun getList(): List<TItem> = differ.currentList
fun getCollectionPosition(adapterPosition: Int) = adapterPosition - getHeadersCount()
}

View File

@ -1,81 +1,85 @@
package ru.touchin.roboswag.components.adapters;
import android.support.annotation.NonNull;
import android.view.ViewGroup;
import android.support.v7.widget.RecyclerView;
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 <TViewHolder> Type of {@link BindableViewHolder} of delegate;
* @param <TItem> Type of items to bind to {@link BindableViewHolder}s.
* @param <TViewHolder> Type of {@link RecyclerView.ViewHolder} of delegate;
* @param <TItem> Type of items to bind to {@link RecyclerView.ViewHolder}s.
*/
public abstract class ItemAdapterDelegate<TViewHolder extends BindableViewHolder, TItem> extends AdapterDelegate<TViewHolder> {
public abstract class ItemAdapterDelegate<TViewHolder extends RecyclerView.ViewHolder, TItem> extends AdapterDelegate<TViewHolder> {
public ItemAdapterDelegate(@NonNull final LifecycleBindable parentLifecycleBindable) {
super(parentLifecycleBindable);
@Override
public boolean isForViewType(@NonNull final List<Object> 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<Object> 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 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<Object> items,
final int adapterPosition,
final int collectionPosition,
@NonNull final List<Object> 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(@NonNull final TViewHolder holder, @NonNull final TItem item, @NonNull final List<Object> payloads,
final int positionInAdapter, final int positionInCollection) {
//do nothing by default
}
public abstract void onBindViewHolder(
@NonNull final TViewHolder holder,
@NonNull final TItem item,
final int adapterPosition,
final int collectionPosition,
@NonNull final List<Object> payloads
);
}

View File

@ -1,681 +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 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;
import rx.Observable;
import rx.functions.Action1;
import rx.functions.Action2;
import rx.functions.Action3;
import rx.subjects.BehaviorSubject;
/**
* 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 <TItem> Type of items to bind to ViewHolders;
* @param <TItemViewHolder> Type of ViewHolders to show items.
*/
@SuppressWarnings({"unchecked", "PMD.TooManyMethods"})
//TooManyMethods: it's ok
public abstract class ObservableCollectionAdapter<TItem, TItemViewHolder extends BindableViewHolder>
extends RecyclerView.Adapter<BindableViewHolder> {
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<Optional<ObservableCollection<TItem>>> observableCollectionSubject
= BehaviorSubject.create(new Optional<>(null));
@NonNull
private final BehaviorSubject<Boolean> moreAutoLoadingRequested = BehaviorSubject.create();
@NonNull
private final LifecycleBindable lifecycleBindable;
@Nullable
private Object onItemClickListener;
private int lastUpdatedChangeNumber = -1;
@NonNull
private final ObservableList<TItem> innerCollection = new ObservableList<>();
private boolean anyChangeApplied;
private long itemClickDelayMillis;
@NonNull
private final List<RecyclerView> attachedRecyclerViews = new LinkedList<>();
@NonNull
private final List<AdapterDelegate<? extends BindableViewHolder>> 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<TItem> collection = optional.get();
if (collection instanceof ObservableList) {
innerCollection.setDiffUtilsSource((ObservableList<TItem>) 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<TItem> 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)
.onErrorResumeNext(Observable.empty())
.doOnCompleted(() -> 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<TItem> 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<Optional<ObservableCollection<TItem>>> 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<TItem> observableCollection) {
this.observableCollectionSubject.onNext(new Optional<>(observableCollection));
}
/**
* Simply sets items.
*
* @param items Items to set.
*/
public void setItems(@NonNull final Collection<TItem> items) {
setObservableCollection(new ObservableList<>(items));
}
/**
* Calls when collection changes.
*
* @param collectionChanges Changes of collection.
*/
protected void onItemsChanged(@NonNull final CollectionChanges<TItem> 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<Change> 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<AdapterDelegate<? extends BindableViewHolder>> getDelegates() {
return Collections.unmodifiableList(delegates);
}
/**
* Adds {@link ItemAdapterDelegate} to adapter.
*
* @param delegate Delegate to add.
*/
public void addDelegate(@NonNull final ItemAdapterDelegate<? extends TItemViewHolder, ? extends TItem> delegate) {
addDelegateInternal(delegate);
}
/**
* Adds {@link PositionAdapterDelegate} to adapter.
*
* @param delegate Delegate to add.
*/
public void addDelegate(@NonNull final PositionAdapterDelegate<? extends BindableViewHolder> delegate) {
addDelegateInternal(delegate);
}
private void addDelegateInternal(@NonNull final AdapterDelegate<? extends BindableViewHolder> 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<? extends BindableViewHolder> 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, item, positionInCollection) ->
result.value = itemAdapterDelegate.getItemId(item, positionInAdapter, positionInCollection),
positionAdapterDelegate -> result.value = positionAdapterDelegate.getItemId(positionInAdapter),
(item, positionInCollection) -> result.value = super.getItemId(positionInAdapter));
return result.value;
}
private void tryDelegateAction(final int positionInAdapter,
@NonNull final Action3<ItemAdapterDelegate, TItem, Integer> itemAdapterDelegateAction,
@NonNull final Action1<PositionAdapterDelegate> positionAdapterDelegateAction,
@NonNull final Action2<TItem, Integer> defaultAction) {
final int viewType = getItemViewType(positionInAdapter);
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 && viewType == delegate.getItemViewType()) {
itemAdapterDelegateAction.call((ItemAdapterDelegate) delegate, item, positionInCollection);
return;
}
} else if (delegate instanceof PositionAdapterDelegate) {
if (viewType == delegate.getItemViewType()) {
positionAdapterDelegateAction.call((PositionAdapterDelegate) delegate);
return;
}
} else {
Lc.assertion("Delegate of type " + delegate.getClass());
}
}
defaultAction.call(item, positionInCollection);
}
@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, item, positionInCollection) -> {
bindItemViewHolder(itemAdapterDelegate, holder, item, null, positionInAdapter, positionInCollection);
updateMoreAutoLoadingRequest(positionInCollection);
},
positionAdapterDelegate -> positionAdapterDelegate.onBindViewHolder(holder, positionInAdapter),
(item, positionInCollection) -> {
if (item != null) {
bindItemViewHolder(null, holder, item, null, positionInAdapter, positionInCollection);
}
});
}
@Override
public void onBindViewHolder(@NonNull final BindableViewHolder holder, final int positionInAdapter, @NonNull final List<Object> payloads) {
super.onBindViewHolder(holder, positionInAdapter, payloads);
tryDelegateAction(positionInAdapter,
(itemAdapterDelegate, item, positionInCollection) -> {
bindItemViewHolder(itemAdapterDelegate, holder, item, payloads, positionInAdapter, positionInCollection);
updateMoreAutoLoadingRequest(positionInCollection);
},
positionAdapterDelegate -> positionAdapterDelegate.onBindViewHolder(holder, positionInAdapter),
(item, positionInCollection) -> {
if (item != null) {
bindItemViewHolder(null, holder, item, payloads, positionInAdapter, positionInCollection);
}
});
}
private void bindItemViewHolder(@Nullable final ItemAdapterDelegate<TItemViewHolder, TItem> itemAdapterDelegate,
@NonNull final BindableViewHolder holder, @NonNull final TItem item, @Nullable final List<Object> 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<Object> 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<TItem> 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<TItem> 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<TItem> 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<TItem> 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<TItem> sameItemsPredicate,
@Nullable final ChangePayloadProducer<TItem> 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 <TItem> Type of item
*/
public interface OnItemClickListener<TItem> {
/**
* 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 <TItem> Type of item
*/
public interface OnItemWithPositionClickListener<TItem> {
/**
* 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;
}
}

View File

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

View File

@ -1,68 +1,67 @@
package ru.touchin.roboswag.components.adapters;
import android.support.annotation.NonNull;
import android.view.ViewGroup;
import android.support.v7.widget.RecyclerView;
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.
* Default {@link #getItemViewType} is generating on construction of object.
*
* @param <TViewHolder> Type of {@link BindableViewHolder} of delegate.
* @param <TViewHolder> Type of {@link RecyclerView.ViewHolder} of delegate.
*/
public abstract class PositionAdapterDelegate<TViewHolder extends BindableViewHolder> extends AdapterDelegate<TViewHolder> {
public abstract class PositionAdapterDelegate<TViewHolder extends RecyclerView.ViewHolder> extends AdapterDelegate<TViewHolder> {
public PositionAdapterDelegate(@NonNull final LifecycleBindable parentLifecycleBindable) {
super(parentLifecycleBindable);
@Override
public boolean isForViewType(@NonNull final List<Object> 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<Object> 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<Object> items,
final int adapterPosition,
final int collectionPosition,
@NonNull final List<Object> 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<Object> payloads, final int positionInAdapter) {
public void onBindViewHolder(@NonNull final TViewHolder holder, final int adapterPosition, @NonNull final List<Object> payloads) {
//do nothing by default
}

View File

@ -1,75 +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.deeplinks;
import android.app.Activity;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
import ru.touchin.roboswag.components.navigation.activities.BaseActivity;
/**
* Controller that helps to manage deep links in activity.
* It helps to save and restore deep link and deletes deep link info from intent.
* As tin he base class - call methods that starts with 'on' prefix from activity.
*
* @see #onActivityRestoreInstanceState(Bundle)
* @see #onActivitySavedInstanceState(Bundle)
*/
public abstract class ActivityDeepLinkController<TActivity extends BaseActivity> extends DeepLinkController<TActivity> {
private static final String DEEP_LINK_EXTRA = "DEEP_LINK_EXTRA";
/**
* Call this method on restore instance state -
* in {@link Activity#onCreate(Bundle)} or in {@link Activity#onRestoreInstanceState(Bundle)}.
*
* @param savedInstanceState - activity's savedInstanceState.
*/
public void onActivityRestoreInstanceState(@NonNull final Bundle savedInstanceState) {
final String deepLinkUrl = savedInstanceState.getString(DEEP_LINK_EXTRA, null);
onNewDeepLink(deepLinkUrl == null ? null : Uri.parse(deepLinkUrl));
}
/**
* Call this method while saving stat of activity - in {@link Activity#onSaveInstanceState(Bundle)}.
*
* @param stateToSave - activity's stateToSave.
*/
public void onActivitySavedInstanceState(@NonNull final Bundle stateToSave) {
if (getDeepLinkUri() != null) {
stateToSave.putString(DEEP_LINK_EXTRA, getDeepLinkUri().toString());
}
}
/**
* Helps to delete info about deep link from activity's intent and from this controller.
* Call this after successful deep link processing.
*
* @param activity - that should delete info about processed deep link.
*/
protected void deleteDeepLink(@NonNull final TActivity activity) {
onNewDeepLink(null);
activity.getIntent().setData(null);
}
}

View File

@ -1,51 +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.deeplinks;
import android.net.Uri;
import android.support.annotation.NonNull;
import ru.touchin.roboswag.components.navigation.activities.BaseActivity;
/**
* Created by Ilia Kurtov on 04.08.2015.
* Class that helps to operate with deep links.
*
* @param <TActivity> Type of Activity to process deep links.
*/
public interface DeepLink<TActivity extends BaseActivity> {
/**
* Called by deep link to provide unique name.
*/
@NonNull
String getName();
/**
* Called by deep link to decide - whenever deep link should process uri or if we are already on that screen that deep link links to.
*/
boolean isOnSuchScreen(@NonNull TActivity activity, @NonNull Uri deepLinkUri);
/**
* Called by deep link to navigate to the specific screen.
*/
void navigateTo(@NonNull TActivity activity, @NonNull Uri deepLinkUri);
}

View File

@ -1,123 +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.deeplinks;
import android.app.Activity;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import ru.touchin.roboswag.components.navigation.activities.BaseActivity;
/**
* Created by Ilia Kurtov on 04.08.2015.
* Controller for deep links. Its main goal to decide when deep link should be processed.
* Call methods that starts with 'on' prefix from {@link TActivity} that should process deep links.
*
* @param <TActivity> Type of Activity to process deep links.
* @see #onNewDeepLink(Uri)
* @see #onActivityReadyToProcessDeepLink(BaseActivity)
* @see #onActivityStopBeingReady()
*/
public abstract class DeepLinkController<TActivity extends BaseActivity> {
@Nullable
private Uri deepLinkUri;
@Nullable
private TActivity activity;
private boolean allowDeepLinkToProcess = true;
/**
* Get current deep link.
*
* @return - current deep link
*/
@Nullable
protected Uri getDeepLinkUri() {
return deepLinkUri;
}
/**
* Call this method after receiving new deep link {@link Uri} from your activity.
* It saves new deepLinkUri and tries to process deep link if possible.
* In most common cases call this method in {@link Activity#onCreate(Bundle)}
* if bundle == null or if you want to restore deep link
* in {@link Activity#onCreate(Bundle)} or in {@link Activity#onRestoreInstanceState(Bundle)}
* methods.
*
* @param deepLinkUri - received deep link.
*/
public void onNewDeepLink(@Nullable final Uri deepLinkUri) {
this.deepLinkUri = deepLinkUri;
startToProcessDeepLinkIfPossible();
}
/**
* Call this method when your activity should be ready to process deep link.
* In most common cases call this method on {@link Activity#onStart()}
*
* @param activity - that should be able to process deep link.
*/
public void onActivityReadyToProcessDeepLink(@NonNull final TActivity activity) {
this.activity = activity;
startToProcessDeepLinkIfPossible();
}
/**
* Call this method when your activity stopped being ready to process deep link.
* In most common cases call this method on {@link Activity#onStop()}
*/
public void onActivityStopBeingReady() {
activity = null;
}
/**
* This method should be called when you need to add additional condition
* for processing deep links. By default {@link #allowDeepLinkToProcess}
* equals true.
*
* @param allowDeepLinkToProcess - pass true here if you want to allow deep
* link to process, otherwise - pass false.
*/
public void setAllowDeepLinkToProcess(final boolean allowDeepLinkToProcess) {
this.allowDeepLinkToProcess = allowDeepLinkToProcess;
startToProcessDeepLinkIfPossible();
}
private void startToProcessDeepLinkIfPossible() {
if (activity != null && deepLinkUri != null && allowDeepLinkToProcess) {
processDeepLink(activity, deepLinkUri);
}
}
/**
* This method would be called if there are non null {@link TActivity},
* non null {@link #deepLinkUri} and {@link #allowDeepLinkToProcess} equals true.
* Don't forget to call activity.getIntent().setData(null) after deep link processing
*
* @param activity - that should be able to process deep link.
* @param deepLinkUri - received deep link.
*/
protected abstract void processDeepLink(@NonNull final TActivity activity,
@NonNull final Uri deepLinkUri);
}

View File

@ -1,51 +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.deeplinks;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import ru.touchin.roboswag.components.navigation.activities.BaseActivity;
/**
* Created by Ilia Kurtov on 04.08.2015.
* Simple DeepLinkController that process deep links as it is. When deep links received it would have been processing and navigating id should.
*/
public abstract class SimpleActivityDeepLinkController<TActivity extends BaseActivity, TDeepLink extends DeepLink<TActivity>>
extends ActivityDeepLinkController<TActivity> {
@Override
protected void processDeepLink(@NonNull final TActivity activity, @NonNull final Uri deepLinkUri) {
deleteDeepLink(activity);
final TDeepLink deepLink = getDeepLinkByUri(deepLinkUri);
if (deepLink != null && !deepLink.isOnSuchScreen(activity, deepLinkUri)) {
deleteDeepLink(activity);
deepLink.navigateTo(activity, deepLinkUri);
}
}
/**
* Returns deep link that extending {@link DeepLink}.
*/
@Nullable
protected abstract TDeepLink getDeepLinkByUri(@NonNull final Uri deepLinkUri);
}

View File

@ -0,0 +1,12 @@
package ru.touchin.roboswag.components.extensions
import android.content.Context
import android.content.Intent
fun Context.safeStartActivity(intent: Intent, flags: Int = 0): Boolean =
if (packageManager.resolveActivity(intent, flags) != null) {
startActivity(intent)
true
} else {
false
}

View File

@ -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 <T> Delegates.observable(
initialValue: T,
crossinline onChange: (newValue: T) -> Unit
): ReadWriteProperty<Any?, T> = object : ObservableProperty<T>(initialValue) {
override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = onChange(newValue)
}
inline fun <T> Delegates.distinctUntilChanged(
initialValue: T,
crossinline onChange: (newValue: T) -> Unit
): ReadWriteProperty<Any?, T> = object : ObservableProperty<T>(initialValue) {
override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) =
if (newValue != null && oldValue != newValue) onChange(newValue) else Unit
}

View File

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

View File

@ -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 <T : View> 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)

View File

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

View File

@ -1,419 +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 ru.touchin.roboswag.core.log.Lc;
import rx.functions.Func1;
/**
* 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(Func1)} 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 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<? extends Fragment> fragmentClass,
@Nullable final Fragment targetFragment,
@Nullable final Bundle args,
@Nullable final String backStackTag,
@Nullable final Func1<FragmentTransaction, FragmentTransaction> 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)
.addToBackStack(backStackTag);
if (fragmentManager.getBackStackEntryCount() != 0) {
fragmentTransaction.setTransition(getDefaultTransition());
}
if (transactionSetup != null) {
transactionSetup.call(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 Func1<FragmentManager.BackStackEntry, Boolean> 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;
}
}
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<? extends Fragment> fragmentClass) {
addToStack(fragmentClass, null, 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<? extends Fragment> fragmentClass,
@NonNull final Bundle args) {
addToStack(fragmentClass, null, 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<? extends Fragment> fragmentClass,
@NonNull final Func1<FragmentTransaction, FragmentTransaction> transactionSetup) {
addToStack(fragmentClass, null, 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<? extends Fragment> fragmentClass,
@Nullable final Bundle args,
@Nullable final Func1<FragmentTransaction, FragmentTransaction> transactionSetup) {
addToStack(fragmentClass, null, 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<? extends Fragment> fragmentClass,
@NonNull final Fragment targetFragment) {
addToStack(fragmentClass, targetFragment, 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<? extends Fragment> fragmentClass,
@NonNull final Fragment targetFragment,
@NonNull final Bundle args) {
addToStack(fragmentClass, targetFragment, 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<? extends Fragment> fragmentClass,
@NonNull final Fragment targetFragment,
@NonNull final Func1<FragmentTransaction, FragmentTransaction> transactionSetup) {
addToStack(fragmentClass, targetFragment, 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<? extends Fragment> fragmentClass,
@NonNull final Fragment targetFragment,
@Nullable final Bundle args,
@Nullable final Func1<FragmentTransaction, FragmentTransaction> transactionSetup) {
addToStack(fragmentClass, targetFragment, 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<? extends Fragment> fragmentClass) {
addToStack(fragmentClass, null, 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<? extends Fragment> fragmentClass,
@NonNull final Bundle args) {
addToStack(fragmentClass, null, 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<? extends Fragment> fragmentClass,
@NonNull final Func1<FragmentTransaction, FragmentTransaction> transactionSetup) {
addToStack(fragmentClass, null, 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<? extends Fragment> fragmentClass,
@Nullable final Bundle args,
@Nullable final Func1<FragmentTransaction, FragmentTransaction> transactionSetup) {
addToStack(fragmentClass, null, 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<? extends Fragment> 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<? extends Fragment> 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<? extends Fragment> fragmentClass,
@NonNull final Func1<FragmentTransaction, FragmentTransaction> 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<? extends Fragment> fragmentClass,
@Nullable final Bundle args,
@Nullable final Func1<FragmentTransaction, FragmentTransaction> 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);
}
}
}

View File

@ -0,0 +1,257 @@
/*
* 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"
}
/**
* 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 backStackName Name 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<out Fragment>,
targetFragment: Fragment?,
targetRequestCode: Int,
addToStack: Boolean,
args: Bundle?,
backStackName: String?,
transactionSetup: ((FragmentTransaction) -> Unit)?
) {
if (fragmentManager.isDestroyed) {
Lc.assertion("FragmentManager is destroyed")
return
}
val fragment = Fragment.instantiate(context, fragmentClass.name, args)
fragment.setTargetFragment(targetFragment, targetRequestCode)
val fragmentTransaction = fragmentManager.beginTransaction()
transactionSetup?.invoke(fragmentTransaction)
fragmentTransaction.replace(containerViewId, fragment, null)
if (addToStack) {
fragmentTransaction
.addToBackStack(backStackName)
.setTransition(transition)
}
fragmentTransaction
.setPrimaryNavigationFragment(fragment)
.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.
*/
fun up(name: String? = null, inclusive: Boolean = false) {
fragmentManager.popBackStack(name, if (inclusive) FragmentManager.POP_BACK_STACK_INCLUSIVE else 0)
}
/**
* 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<out Fragment>,
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<out Fragment>,
targetFragment: Fragment,
targetRequestCode: Int,
args: Bundle? = null,
transactionSetup: ((FragmentTransaction) -> Unit)? = null
) {
addToStack(
fragmentClass,
targetFragment,
targetRequestCode,
true,
args,
null,
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<out Fragment>,
args: Bundle? = null,
addToStack: Boolean = true,
transactionSetup: ((FragmentTransaction) -> Unit)? = null
) {
addToStack(fragmentClass, null, 0, addToStack, args, 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<out Fragment>,
args: Bundle? = null,
transactionSetup: ((FragmentTransaction) -> Unit)? = null
) {
beforeSetInitialActions()
setAsTop(fragmentClass, args, false, 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)
}
}
}

View File

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

View File

@ -1,506 +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.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.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
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;
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.
* Class to control view of specific fragment, activity and application by logic bridge.
*
* @param <TActivity> Type of activity where such {@link ViewController} could be;
* @param <TFragment> Type of fragment where such {@link ViewController} could be;
*/
@SuppressWarnings({"PMD.TooManyMethods", "PMD.ExcessivePublicCount"})
public class ViewController<TActivity extends ViewControllerActivity<?>,
TFragment extends ViewControllerFragment<?, TActivity>>
implements LifecycleBindable {
@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
public ViewController(@NonNull final CreationContext creationContext, @Nullable final Bundle savedInstanceState) {
this.activity = (TActivity) creationContext.activity;
this.fragment = (TFragment) creationContext.fragment;
this.container = creationContext.container;
}
/**
* Returns activity where {@link ViewController} could be.
*
* @return Returns activity.
*/
@NonNull
public final TActivity getActivity() {
return activity;
}
/**
* Returns fragment where {@link ViewController} could be.
*
* @return Returns fragment.
*/
@NonNull
public final TFragment getFragment() {
return fragment;
}
/**
* 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}.
*
* @return Returns view.
*/
@NonNull
public final ViewGroup getContainer() {
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.
*
* @param layoutResId Resource ID to be inflated.
*/
public void setContentView(@LayoutRes final int layoutResId) {
if (getContainer().getChildCount() > 0) {
getContainer().removeAllViews();
}
UiUtils.inflateAndAdd(layoutResId, getContainer());
}
/**
* Set the view controller content to an explicit view.
* This view is placed directly into the container's ({@link #getContainer()}) view hierarchy.
*
* @param view The desired content to display.
*/
public void setContentView(@NonNull final View view) {
setContentView(view, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
}
/**
* Set the view controller content to an explicit view with specific layout parameters.
* This view is placed directly into the container's ({@link #getContainer()}) view hierarchy.
*
* @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) {
if (getContainer().getChildCount() > 0) {
getContainer().removeAllViews();
}
getContainer().addView(view, layoutParams);
}
/**
* 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 extends View> 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);
}
/**
* Calls when activity configuring ActionBar, Toolbar, Sidebar etc.
* If it will be called or not depends on {@link Fragment#hasOptionsMenu()} and {@link Fragment#isMenuVisible()}.
*
* @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) {
// do nothing
}
/**
* Calls right after construction of {@link ViewController}.
* Happens at {@link ViewControllerFragment#onActivityCreated(View, ViewControllerActivity, Bundle)}.
*/
@CallSuper
public void onCreate() {
UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this));
baseLifecycleBindable.onCreate();
}
/**
* Calls when {@link ViewController} have started.
* Happens at {@link ViewControllerFragment#onStart(View, ViewControllerActivity)}.
*/
@CallSuper
public void onStart() {
UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this));
baseLifecycleBindable.onStart();
}
/**
* Called when fragment is moved in started state and it's {@link #getFragment().isMenuVisible()} sets to true.
* Usually it is indicating that user can't see fragment on screen and useful to track analytics events.
*/
public void onAppear() {
UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this));
}
/**
* Calls when {@link ViewController} have resumed.
* Happens at {@link ViewControllerFragment#onResume(View, ViewControllerActivity)}.
*/
@CallSuper
public void onResume() {
UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this));
baseLifecycleBindable.onResume();
}
/**
* Calls when {@link ViewController} have goes near out of memory state.
* Happens at {@link ViewControllerFragment#onLowMemory()}.
*/
@CallSuper
public void onLowMemory() {
//do nothing
}
/**
* Calls when {@link ViewController} have paused.
* Happens at {@link ViewControllerFragment#onPause(View, ViewControllerActivity)}.
*/
@CallSuper
public void onPause() {
UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this));
}
/**
* Calls when {@link ViewController} should save it's state.
* Happens at {@link ViewControllerFragment#onSaveInstanceState(Bundle)}.
* Try not to use such method for saving state but use {@link ViewControllerFragment#getState()} from {@link #getFragment()}.
*/
@CallSuper
public void onSaveInstanceState(@NonNull final Bundle savedInstanceState) {
baseLifecycleBindable.onSaveInstanceState();
UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this));
}
/**
* Called when fragment is moved in stopped state or it's {@link #getFragment().isMenuVisible()} sets to false.
* Usually it is indicating that user can't see fragment on screen and useful to track analytics events.
*/
public void onDisappear() {
UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this));
}
/**
* Calls when {@link ViewController} have stopped.
* Happens at {@link ViewControllerFragment#onStop(View, ViewControllerActivity)}.
*/
@CallSuper
public void onStop() {
UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this));
baseLifecycleBindable.onStop();
}
/**
* Calls when {@link ViewController} have destroyed.
* Happens usually at {@link ViewControllerFragment#onDestroyView(View)}. In some cases at {@link ViewControllerFragment#onDestroy()}.
*/
@CallSuper
public void onDestroy() {
UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this));
baseLifecycleBindable.onDestroy();
destroyed = true;
}
/**
* Similar to {@link ViewControllerFragment#onOptionsItemSelected(MenuItem)}.
*
* @param item Selected menu item;
* @return True if selection processed.
*/
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
return false;
}
@SuppressWarnings("CPD-START")
//CPD: it is same as in other implementation based on BaseLifecycleBindable
@NonNull
@Override
public <T> Subscription untilStop(@NonNull final Observable<T> observable) {
return baseLifecycleBindable.untilStop(observable);
}
@NonNull
@Override
public <T> Subscription untilStop(@NonNull final Observable<T> observable, @NonNull final Action1<T> onNextAction) {
return baseLifecycleBindable.untilStop(observable, onNextAction);
}
@NonNull
@Override
public <T> Subscription untilStop(@NonNull final Observable<T> observable,
@NonNull final Action1<T> onNextAction,
@NonNull final Action1<Throwable> onErrorAction) {
return baseLifecycleBindable.untilStop(observable, onNextAction, onErrorAction);
}
@NonNull
@Override
public <T> Subscription untilStop(@NonNull final Observable<T> observable,
@NonNull final Action1<T> onNextAction,
@NonNull final Action1<Throwable> onErrorAction,
@NonNull final Action0 onCompletedAction) {
return baseLifecycleBindable.untilStop(observable, onNextAction, onErrorAction, onCompletedAction);
}
@NonNull
@Override
public <T> Subscription untilStop(@NonNull final Single<T> single) {
return baseLifecycleBindable.untilStop(single);
}
@NonNull
@Override
public <T> Subscription untilStop(@NonNull final Single<T> single, @NonNull final Action1<T> onSuccessAction) {
return baseLifecycleBindable.untilStop(single, onSuccessAction);
}
@NonNull
@Override
public <T> Subscription untilStop(@NonNull final Single<T> single,
@NonNull final Action1<T> onSuccessAction,
@NonNull final Action1<Throwable> onErrorAction) {
return baseLifecycleBindable.untilStop(single, onSuccessAction, onErrorAction);
}
@NonNull
@Override
public Subscription untilStop(@NonNull final Completable completable) {
return baseLifecycleBindable.untilStop(completable);
}
@NonNull
@Override
public Subscription untilStop(@NonNull final Completable completable, @NonNull final Action0 onCompletedAction) {
return baseLifecycleBindable.untilStop(completable, onCompletedAction);
}
@NonNull
@Override
public Subscription untilStop(@NonNull final Completable completable,
@NonNull final Action0 onCompletedAction,
@NonNull final Action1<Throwable> onErrorAction) {
return baseLifecycleBindable.untilStop(completable, onCompletedAction, onErrorAction);
}
@NonNull
@Override
public <T> Subscription untilDestroy(@NonNull final Observable<T> observable) {
return baseLifecycleBindable.untilDestroy(observable);
}
@NonNull
@Override
public <T> Subscription untilDestroy(@NonNull final Observable<T> observable, @NonNull final Action1<T> onNextAction) {
return baseLifecycleBindable.untilDestroy(observable, onNextAction);
}
@NonNull
@Override
public <T> Subscription untilDestroy(@NonNull final Observable<T> observable,
@NonNull final Action1<T> onNextAction,
@NonNull final Action1<Throwable> onErrorAction) {
return baseLifecycleBindable.untilDestroy(observable, onNextAction, onErrorAction);
}
@NonNull
@Override
public <T> Subscription untilDestroy(@NonNull final Observable<T> observable,
@NonNull final Action1<T> onNextAction,
@NonNull final Action1<Throwable> onErrorAction,
@NonNull final Action0 onCompletedAction) {
return baseLifecycleBindable.untilDestroy(observable, onNextAction, onErrorAction, onCompletedAction);
}
@NonNull
@Override
public <T> Subscription untilDestroy(@NonNull final Single<T> single) {
return baseLifecycleBindable.untilDestroy(single);
}
@NonNull
@Override
public <T> Subscription untilDestroy(@NonNull final Single<T> single, @NonNull final Action1<T> onSuccessAction) {
return baseLifecycleBindable.untilDestroy(single, onSuccessAction);
}
@NonNull
@Override
public <T> Subscription untilDestroy(@NonNull final Single<T> single,
@NonNull final Action1<T> onSuccessAction,
@NonNull final Action1<Throwable> onErrorAction) {
return baseLifecycleBindable.untilDestroy(single, onSuccessAction, onErrorAction);
}
@NonNull
@Override
public Subscription untilDestroy(@NonNull final Completable completable) {
return baseLifecycleBindable.untilDestroy(completable);
}
@NonNull
@Override
public Subscription untilDestroy(@NonNull final Completable completable, @NonNull final Action0 onCompletedAction) {
return baseLifecycleBindable.untilDestroy(completable, onCompletedAction);
}
@NonNull
@Override
public Subscription untilDestroy(@NonNull final Completable completable,
@NonNull final Action0 onCompletedAction,
@NonNull final Action1<Throwable> 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 {
@NonNull
private final ViewControllerActivity 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) {
this.activity = activity;
this.fragment = fragment;
this.container = container;
}
}
}

View File

@ -1,503 +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 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.
* 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 <TActivity> Type of activity where {@link ViewController}s should be showed.
*/
public class ViewControllerNavigation<TActivity extends ViewControllerActivity<?>> 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 <TState> Type of state of fragment.
*/
public <TState extends AbstractState> void push(@NonNull final Class<? extends ViewControllerFragment<TState, TActivity>> fragmentClass,
@NonNull final TState state) {
addToStack(fragmentClass, null, 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 <TState> Type of state of fragment.
*/
public <TState extends AbstractState> void push(@NonNull final Class<? extends ViewControllerFragment<TState, TActivity>> fragmentClass,
@Nullable final TState state,
@Nullable final Func1<FragmentTransaction, FragmentTransaction> transactionSetup) {
addToStack(fragmentClass, null, 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 <TState> Type of state of fragment.
*/
public <TState extends AbstractState> void pushForResult(@NonNull final Class<? extends ViewControllerFragment<TState, TActivity>> fragmentClass,
@NonNull final Fragment targetFragment,
@NonNull final TState state) {
addToStack(fragmentClass, targetFragment, 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 <TState> Type of state of fragment.
*/
public <TState extends AbstractState> void pushForResult(@NonNull final Class<? extends ViewControllerFragment<TState, TActivity>> fragmentClass,
@NonNull final Fragment targetFragment,
@Nullable final TState state,
@Nullable final Func1<FragmentTransaction, FragmentTransaction> transactionSetup) {
addToStack(fragmentClass, targetFragment, 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 <TState> Type of state of fragment.
*/
public <TState extends AbstractState> void setAsTop(@NonNull final Class<? extends ViewControllerFragment<TState, TActivity>> 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 <TState> Type of state of fragment.
*/
public <TState extends AbstractState> void setAsTop(@NonNull final Class<? extends ViewControllerFragment<TState, TActivity>> fragmentClass,
@Nullable final TState state,
@Nullable final Func1<FragmentTransaction, FragmentTransaction> 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 <TState> Type of state of fragment.
*/
public <TState extends AbstractState> void setInitial(@NonNull final Class<? extends ViewControllerFragment<TState, TActivity>> 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 <TState> Type of state of fragment.
*/
public <TState extends AbstractState> void setInitial(@NonNull final Class<? extends ViewControllerFragment<TState, TActivity>> fragmentClass,
@Nullable final TState state,
@Nullable final Func1<FragmentTransaction, FragmentTransaction> 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<? extends ViewController<TActivity,
StatelessViewControllerFragment<TActivity>>> 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 <TState> Type of state of fragment.
*/
public <TState extends AbstractState> void pushViewController(@NonNull final Class<? extends ViewController<TActivity,
SimpleViewControllerFragment<TState, TActivity>>> 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<? extends ViewController<TActivity, StatelessViewControllerFragment<TActivity>>> viewControllerClass,
@Nullable final Func1<FragmentTransaction, FragmentTransaction> 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 <TState> Type of state of fragment.
*/
public <TState extends AbstractState> void pushViewController(
@NonNull final Class<? extends ViewController<TActivity, SimpleViewControllerFragment<TState, TActivity>>> viewControllerClass,
@NonNull final TState state,
@Nullable final Func1<FragmentTransaction, FragmentTransaction> transactionSetup) {
addViewControllerToStack(viewControllerClass, null, state, null, transactionSetup);
}
/**
* Pushes {@link ViewController} on top of stack with specific {@link StatelessTargetedViewControllerFragment#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 <TTargetState> Type of state of target fragment. State is using to affect on that fragment;
* @param <TTargetFragment> Type of target fragment.
*/
@SuppressWarnings("CPD-START")
public <TTargetState extends AbstractState,
TTargetFragment extends ViewControllerFragment<? extends TTargetState, TActivity>> void pushStatelessTargetedViewControllerForResult(
@NonNull final Class<? extends ViewController<TActivity,
TargetedViewControllerFragment<AbstractState, TTargetState, TActivity>>> viewControllerClass,
@NonNull final TTargetFragment targetFragment) {
addToStack(StatelessTargetedViewControllerFragment.class, targetFragment,
StatelessTargetedViewControllerFragment.createState(viewControllerClass),
viewControllerClass.getName() + ';' + WITH_TARGET_FRAGMENT_TAG_MARK, 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 <TTargetState> Type of state of target fragment. State is using to affect on that fragment;
* @param <TTargetFragment> Type of target fragment.
*/
@SuppressWarnings("CPD-END")
public <TTargetState extends AbstractState,
TTargetFragment extends ViewControllerFragment<? extends TTargetState, TActivity>> void pushViewControllerForResult(
@NonNull final Class<? extends ViewController<TActivity,
StatelessTargetedViewControllerFragment<TTargetState, TActivity>>> 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 <TTargetState> Type of state of target fragment. State is using to affect on that fragment;
* @param <TTargetFragment> Type of target fragment.
*/
public <TTargetState extends AbstractState,
TTargetFragment extends ViewControllerFragment<? extends TTargetState, TActivity>> void pushViewControllerForResult(
@NonNull final Class<? extends ViewController<TActivity,
StatelessTargetedViewControllerFragment<TTargetState, TActivity>>> viewControllerClass,
@NonNull final TTargetFragment targetFragment,
@Nullable final Func1<FragmentTransaction, FragmentTransaction> 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 <TState> Type of state of fragment;
* @param <TTargetState> Type of state of target fragment. State is using to affect on that fragment;
* @param <TTargetFragment> Type of target fragment.
*/
@SuppressWarnings("CPD-START")
public <TState extends AbstractState, TTargetState extends AbstractState,
TTargetFragment extends ViewControllerFragment<? extends TTargetState, TActivity>> void pushViewControllerForResult(
@NonNull final Class<? extends ViewController<TActivity,
TargetedViewControllerFragment<TState, TTargetState, TActivity>>> 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 <TState> Type of state of fragment;
* @param <TTargetState> Type of state of target fragment. State is using to affect on that fragment;
* @param <TTargetFragment> Type of target fragment.
*/
@SuppressWarnings("CPD-END")
public <TState extends AbstractState, TTargetState extends AbstractState,
TTargetFragment extends ViewControllerFragment<? extends TTargetState, TActivity>> void pushViewControllerForResult(
@NonNull final Class<? extends ViewController<TActivity,
TargetedViewControllerFragment<TState, TTargetState, TActivity>>> viewControllerClass,
@NonNull final TTargetFragment targetFragment,
@NonNull final TState state,
@Nullable final Func1<FragmentTransaction, FragmentTransaction> 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<? extends ViewController<TActivity, StatelessViewControllerFragment<TActivity>>> 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 <TState> Type of state of fragment.
*/
public <TState extends AbstractState> void setViewControllerAsTop(
@NonNull final Class<? extends ViewController<TActivity, SimpleViewControllerFragment<TState, TActivity>>> 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<? extends ViewController<TActivity, StatelessViewControllerFragment<TActivity>>> viewControllerClass,
@Nullable final Func1<FragmentTransaction, FragmentTransaction> 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 <TState> Type of state of fragment.
*/
public <TState extends AbstractState> void setViewControllerAsTop(
@NonNull final Class<? extends ViewController<TActivity, SimpleViewControllerFragment<TState, TActivity>>> viewControllerClass,
@NonNull final TState state,
@Nullable final Func1<FragmentTransaction, FragmentTransaction> 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<? extends ViewController<TActivity, StatelessViewControllerFragment<TActivity>>> 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 <TState> Type of state of fragment.
*/
public <TState extends AbstractState> void setInitialViewController(
@NonNull final Class<? extends ViewController<TActivity, SimpleViewControllerFragment<TState, TActivity>>> 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<? extends ViewController<TActivity, StatelessViewControllerFragment<TActivity>>> viewControllerClass,
@Nullable final Func1<FragmentTransaction, FragmentTransaction> 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 <TState> Type of state of fragment.
*/
public <TState extends AbstractState> void setInitialViewController(
@NonNull final Class<? extends ViewController<TActivity, SimpleViewControllerFragment<TState, TActivity>>> viewControllerClass,
@NonNull final TState state,
@Nullable final Func1<FragmentTransaction, FragmentTransaction> 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<? extends ViewController<TActivity, ? extends StatelessViewControllerFragment<TActivity>>> viewControllerClass,
@Nullable final Fragment targetFragment,
@Nullable final String backStackTag,
@Nullable final Func1<FragmentTransaction, FragmentTransaction> transactionSetup) {
addToStack(StatelessViewControllerFragment.class, targetFragment,
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 <TState> Type of state of fragment.
* @param <TTargetState> Type of state of target fragment. State is using to affect on that fragment;
*/
protected <TState extends AbstractState, TTargetState extends AbstractState> void addTargetedViewControllerToStack(
@NonNull final Class<? extends ViewController<TActivity,
? extends TargetedViewControllerFragment<TState, TTargetState, TActivity>>> viewControllerClass,
@NonNull final Fragment targetFragment,
@NonNull final TState state,
@Nullable final String backStackTag,
@Nullable final Func1<FragmentTransaction, FragmentTransaction> transactionSetup) {
addToStack(TargetedViewControllerFragment.class, targetFragment,
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 <TState> Type of state of fragment.
*/
protected <TState extends AbstractState> void addTargetedStatelessViewControllerToStack(
@NonNull final Class<? extends ViewController<TActivity,
? extends StatelessTargetedViewControllerFragment<TState, TActivity>>> viewControllerClass,
@NonNull final Fragment targetFragment,
@Nullable final String backStackTag,
@Nullable final Func1<FragmentTransaction, FragmentTransaction> transactionSetup) {
addToStack(StatelessTargetedViewControllerFragment.class, targetFragment,
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 <TState> Type of state of fragment.
*/
protected <TState extends AbstractState> void addViewControllerToStack(
@NonNull final Class<? extends ViewController<TActivity, ? extends SimpleViewControllerFragment<TState, TActivity>>> viewControllerClass,
@Nullable final Fragment targetFragment,
@NonNull final TState state,
@Nullable final String backStackTag,
@Nullable final Func1<FragmentTransaction, FragmentTransaction> transactionSetup) {
addToStack(SimpleViewControllerFragment.class, targetFragment,
SimpleViewControllerFragment.createState(viewControllerClass, state), backStackTag, transactionSetup);
}
}

View File

@ -19,145 +19,62 @@
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 android.view.MenuItem;
import java.util.ArrayList;
import java.util.Set;
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.
* 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<OnBackPressedListener> onBackPressedListeners = new ArrayList<>();
@NonNull
private final BaseLifecycleBindable baseLifecycleBindable = new BaseLifecycleBindable();
private boolean resumed;
@NonNull
private final BehaviorSubject<Optional<HalfNullablePair<Integer, Intent>>> lastActivityResult
= BehaviorSubject.create(new Optional<HalfNullablePair<Integer, Intent>>(null));
/**
* Returns if activity resumed.
*
* @return True if resumed.
*/
public boolean isActuallyResumed() {
return resumed;
}
private final Set<OnBackPressedListener> onBackPressedListeners = new ArraySet<>();
@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<Intent> observeActivityResult(final int requestCode) {
return lastActivityResult
.concatMap(optional -> {
final HalfNullablePair<Integer, Intent> 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<Integer, Intent> 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
@ -169,68 +86,23 @@ 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();
}
/**
* 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;
@Override
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
if (item.getItemId() == android.R.id.home) {
onBackPressed();
return true;
} else {
return super.onOptionsItemSelected(item);
}
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.
*/
@NonNull
public Drawable getDrawableCompat(@DrawableRes final int resId) {
return ContextCompat.getDrawable(this, resId);
}
public void addOnBackPressedListener(@NonNull final OnBackPressedListener onBackPressedListener) {
@ -241,6 +113,10 @@ public abstract class BaseActivity extends AppCompatActivity
onBackPressedListeners.remove(onBackPressedListener);
}
public void removeAllOnBackPressedListeners() {
onBackPressedListeners.clear();
}
@Override
public void onBackPressed() {
for (final OnBackPressedListener onBackPressedListener : onBackPressedListeners) {
@ -248,155 +124,9 @@ public abstract class BaseActivity extends AppCompatActivity
return;
}
}
if (getSupportFragmentManager().getBackStackEntryCount() <= 1) {
supportFinishAfterTransition();
} else {
getSupportFragmentManager().popBackStack();
}
super.onBackPressed();
}
@SuppressWarnings("CPD-START")
//CPD: it is same as in other implementation based on BaseLifecycleBindable
@NonNull
@Override
public <T> Subscription untilStop(@NonNull final Observable<T> observable,
@NonNull final Action1<T> onNextAction,
@NonNull final Action1<Throwable> onErrorAction) {
return baseLifecycleBindable.untilStop(observable, onNextAction, onErrorAction);
}
@NonNull
@Override
public <T> Subscription untilStop(@NonNull final Observable<T> observable,
@NonNull final Action1<T> onNextAction,
@NonNull final Action1<Throwable> onErrorAction,
@NonNull final Action0 onCompletedAction) {
return baseLifecycleBindable.untilStop(observable, onNextAction, onErrorAction, onCompletedAction);
}
@NonNull
@Override
public <T> Subscription untilStop(@NonNull final Single<T> single) {
return baseLifecycleBindable.untilStop(single);
}
@NonNull
@Override
public <T> Subscription untilStop(@NonNull final Single<T> single, @NonNull final Action1<T> onSuccessAction) {
return baseLifecycleBindable.untilStop(single, onSuccessAction);
}
@NonNull
@Override
public <T> Subscription untilStop(@NonNull final Single<T> single,
@NonNull final Action1<T> onSuccessAction,
@NonNull final Action1<Throwable> onErrorAction) {
return baseLifecycleBindable.untilStop(single, onSuccessAction, onErrorAction);
}
@NonNull
@Override
public Subscription untilStop(@NonNull final Completable completable) {
return baseLifecycleBindable.untilStop(completable);
}
@NonNull
@Override
public Subscription untilStop(@NonNull final Completable completable, @NonNull final Action0 onCompletedAction) {
return baseLifecycleBindable.untilStop(completable, onCompletedAction);
}
@NonNull
@Override
public Subscription untilStop(@NonNull final Completable completable,
@NonNull final Action0 onCompletedAction,
@NonNull final Action1<Throwable> onErrorAction) {
return baseLifecycleBindable.untilStop(completable, onCompletedAction, onErrorAction);
}
@NonNull
@Override
public <T> Subscription untilStop(@NonNull final Observable<T> observable) {
return baseLifecycleBindable.untilStop(observable);
}
@NonNull
@Override
public <T> Subscription untilStop(@NonNull final Observable<T> observable, @NonNull final Action1<T> onNextAction) {
return baseLifecycleBindable.untilStop(observable, onNextAction);
}
@NonNull
@Override
public <T> Subscription untilDestroy(@NonNull final Observable<T> observable) {
return baseLifecycleBindable.untilDestroy(observable);
}
@NonNull
@Override
public <T> Subscription untilDestroy(@NonNull final Observable<T> observable, @NonNull final Action1<T> onNextAction) {
return baseLifecycleBindable.untilDestroy(observable, onNextAction);
}
@NonNull
@Override
public <T> Subscription untilDestroy(@NonNull final Observable<T> observable,
@NonNull final Action1<T> onNextAction,
@NonNull final Action1<Throwable> onErrorAction) {
return baseLifecycleBindable.untilDestroy(observable, onNextAction, onErrorAction);
}
@NonNull
@Override
public <T> Subscription untilDestroy(@NonNull final Observable<T> observable,
@NonNull final Action1<T> onNextAction,
@NonNull final Action1<Throwable> onErrorAction,
@NonNull final Action0 onCompletedAction) {
return baseLifecycleBindable.untilDestroy(observable, onNextAction, onErrorAction, onCompletedAction);
}
@NonNull
@Override
public <T> Subscription untilDestroy(@NonNull final Single<T> single) {
return baseLifecycleBindable.untilDestroy(single);
}
@NonNull
@Override
public <T> Subscription untilDestroy(@NonNull final Single<T> single, @NonNull final Action1<T> onSuccessAction) {
return baseLifecycleBindable.untilDestroy(single, onSuccessAction);
}
@NonNull
@Override
public <T> Subscription untilDestroy(@NonNull final Single<T> single,
@NonNull final Action1<T> onSuccessAction,
@NonNull final Action1<Throwable> onErrorAction) {
return baseLifecycleBindable.untilDestroy(single, onSuccessAction, onErrorAction);
}
@NonNull
@Override
public Subscription untilDestroy(@NonNull final Completable completable) {
return baseLifecycleBindable.untilDestroy(completable);
}
@NonNull
@Override
public Subscription untilDestroy(@NonNull final Completable completable, @NonNull final Action0 onCompletedAction) {
return baseLifecycleBindable.untilDestroy(completable, onCompletedAction);
}
@NonNull
@Override
public Subscription untilDestroy(@NonNull final Completable completable,
@NonNull final Action0 onCompletedAction,
@NonNull final Action1<Throwable> onErrorAction) {
return baseLifecycleBindable.untilDestroy(completable, onCompletedAction, onErrorAction);
}
@SuppressWarnings("CPD-END")
/*
* Interface to be implemented for someone who want to intercept device back button pressing event.
*/

View File

@ -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 <TLogic> Type of application's {@link Logic}.
*/
public abstract class ViewControllerActivity<TLogic extends Logic> 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<TLogic> 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 View findViewById(@IdRes final int id) {
final View viewById = super.findViewById(id);
if (viewById == null) {
throw new ShouldNotHappenException("No view for id=" + getResources().getResourceName(id));
}
return viewById;
}
}

View File

@ -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 <TState> Type of object which is representing it's fragment state;
* @param <TActivity> Type of {@link ViewControllerActivity} where fragment could be attached to.
*/
public class SimpleViewControllerFragment<TState extends AbstractState, TActivity extends ViewControllerActivity<?>>
extends ViewControllerFragment<TState, TActivity> {
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<? extends ViewController> viewControllerClass,
@NonNull final AbstractState state) {
final Bundle result = createState(state);
result.putSerializable(VIEW_CONTROLLER_CLASS_EXTRA, viewControllerClass);
return result;
}
private Class<? extends ViewController<TActivity,
? extends ViewControllerFragment<TState, TActivity>>> viewControllerClass;
@NonNull
@Override
public Class<? extends ViewController<TActivity,
? extends ViewControllerFragment<TState, TActivity>>> getViewControllerClass() {
return viewControllerClass;
}
@Override
protected boolean isStateRequired() {
return true;
}
@SuppressWarnings("unchecked")
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
viewControllerClass = (Class<? extends ViewController<TActivity,
? extends ViewControllerFragment<TState, TActivity>>>) getArguments().getSerializable(VIEW_CONTROLLER_CLASS_EXTRA);
}
protected static class DefaultState extends AbstractState {
// just default implementation
}
}

View File

@ -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 <TActivity> Type of {@link ViewControllerActivity} where fragment could be attached to.
*/
@SuppressWarnings("PMD.UseUtilityClass")
//UseUtilityClass: PMD bug
public class StatelessTargetedViewControllerFragment<TTargetState extends AbstractState,
TActivity extends ViewControllerActivity<?>>
extends TargetedViewControllerFragment<AbstractState, TTargetState, TActivity> {
/**
* 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<? extends ViewController> 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();
}
}

View File

@ -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 <TActivity> Type of {@link ViewControllerActivity} where fragment could be attached to.
*/
@SuppressWarnings("PMD.UseUtilityClass")
//UseUtilityClass: PMD bug
public class StatelessViewControllerFragment<TActivity extends ViewControllerActivity<?>>
extends SimpleViewControllerFragment<AbstractState, TActivity> {
/**
* 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<? extends ViewController> 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;
}
}

View File

@ -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 <TState> Type of object which is representing it's fragment state;
* @param <TActivity> Type of {@link ViewControllerActivity} where fragment could be attached to.
*/
public class TargetedViewControllerFragment<TState extends AbstractState,
TTargetState extends AbstractState,
TActivity extends ViewControllerActivity<?>>
extends SimpleViewControllerFragment<TState, TActivity> {
/**
* Returns specific {@link ViewControllerFragment} which is attached to this fragment as {@link #getTargetFragment()}.
*
* @return Target fragment.
*/
@SuppressWarnings("unchecked")
@NonNull
public ViewControllerFragment<TTargetState, TActivity> getTarget() {
if (!(getTargetFragment() instanceof ViewControllerFragment)) {
throw new ShouldNotHappenException();
}
return (ViewControllerFragment<TTargetState, TActivity>) getTargetFragment();
}
}

View File

@ -19,50 +19,41 @@
package ru.touchin.roboswag.components.navigation.fragments;
import android.content.Context;
import android.graphics.Canvas;
import android.animation.Animator;
import android.content.Intent;
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;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.view.animation.Animation;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
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.navigation.viewcontrollers.ViewController;
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;
import rx.Observable;
import rx.Subscription;
import rx.exceptions.OnErrorThrowable;
import rx.subjects.BehaviorSubject;
/**
* Created by Gavriil Sitnikov on 21/10/2015.
* Fragment instantiated in specific activity of {@link TActivity} type that is holding {@link ViewController} inside.
*
* @param <TState> Type of object which is representing it's fragment state;
* @param <TActivity> Type of {@link ViewControllerActivity} where fragment could be attached to.
* @param <TActivity> Type of {@link FragmentActivity} where fragment could be attached to.
*/
@SuppressWarnings("PMD.TooManyMethods")
public abstract class ViewControllerFragment<TState extends AbstractState, TActivity extends ViewControllerActivity<?>>
extends ViewFragment<TActivity> {
public class ViewControllerFragment<TActivity extends FragmentActivity, TState extends Parcelable> extends ViewFragment<TActivity> {
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<TState extends AbstractState, TActi
ViewControllerFragment.acceptableUiCalculationTime = acceptableUiCalculationTime;
}
@SuppressWarnings("unchecked")
@NonNull
private static <T extends Serializable> T reserialize(@NonNull final T serializable) {
private static <T extends Parcelable> 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(Thread.currentThread().getContextClassLoader());
parcel.recycle();
return result;
}
@ -106,32 +96,22 @@ public abstract class ViewControllerFragment<TState extends AbstractState, TActi
* @return Returns bundle with state inside.
*/
@NonNull
public static Bundle createState(@Nullable final AbstractState state) {
public static Bundle args(@NonNull final Class<? extends ViewController> 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<Optional<TActivity>> activitySubject = BehaviorSubject.create();
@NonNull
private final BehaviorSubject<NullablePair<PlaceholderView, Bundle>> viewSubject = BehaviorSubject.create();
@Nullable
private ViewController viewController;
private Subscription viewControllerSubscription;
private Class<ViewController<TActivity, TState>> 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;
}
}
@Nullable
private ActivityResult pendingActivityResult;
/**
* 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,66 +120,40 @@ public abstract class ViewControllerFragment<TState extends AbstractState, TActi
return state;
}
/**
* It should return specific {@link ViewController} class to control instantiated view by logic after activity creation.
*
* @return Returns class of specific {@link ViewController}.
*/
@NonNull
public abstract Class<? extends ViewController<TActivity,
? extends ViewControllerFragment<TState, TActivity>>> getViewControllerClass();
public Class<ViewController<TActivity, TState>> getViewControllerClass() {
return viewControllerClass;
}
/**
* 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<ViewController<TActivity, TState>>) 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 null;
}
final ViewController newViewController = createViewController(activity, container, viewInfo.getSecond());
newViewController.onCreate();
return newViewController;
})
.subscribe(this::onViewControllerChanged,
throwable -> Lc.cutAssertion(throwable,
OnErrorThrowable.class, 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 OnErrorThrowable.from(new ShouldNotHappenException("There should be single constructor for " + getViewControllerClass()));
private ViewController createViewController(
@NonNull final ViewController.CreationContext creationContext,
@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 ViewController.CreationContext creationContext = new ViewController.CreationContext(activity, this, view);
final Constructor<?> constructor = viewControllerClass.getConstructors()[0];
final long creationTime = inDebugMode ? SystemClock.elapsedRealtime() : 0;
try {
switch (constructor.getParameterTypes().length) {
@ -208,11 +162,10 @@ public abstract class ViewControllerFragment<TState extends AbstractState, TActi
case 3:
return (ViewController) constructor.newInstance(this, creationContext, savedInstanceState);
default:
throw OnErrorThrowable
.from(new ShouldNotHappenException("Wrong constructor parameters count: " + constructor.getParameterTypes().length));
throw new ShouldNotHappenException("Wrong constructor parameters count: " + constructor.getParameterTypes().length);
}
} catch (final Exception exception) {
throw OnErrorThrowable.from(exception);
} catch (@NonNull final Exception exception) {
throw new ShouldNotHappenException(exception);
} finally {
checkCreationTime(creationTime);
}
@ -222,46 +175,64 @@ public abstract class ViewControllerFragment<TState extends AbstractState, TActi
if (inDebugMode) {
final long creationPeriod = SystemClock.elapsedRealtime() - creationTime;
if (creationPeriod > 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
) {
viewController = createViewController(
new ViewController.CreationContext(requireActivity(), this, inflater, container), savedInstanceState);
viewController.onCreate();
return viewController.getView();
}
@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));
public void onActivityCreated(@Nullable final Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (viewController != null && pendingActivityResult != null) {
viewController.onActivityResult(pendingActivityResult.requestCode, pendingActivityResult.resultCode, pendingActivityResult.data);
pendingActivityResult = null;
}
}
@Nullable
@Override
public Animation onCreateAnimation(final int transit, final boolean enter, final int nextAnim) {
if (viewController != null) {
return viewController.onCreateAnimation(transit, enter, nextAnim);
} else {
Lc.assertion("View should be instanceof PlaceholderView");
return null;
}
}
@Nullable
@Override
public Animator onCreateAnimator(final int transit, final boolean enter, final int nextAnim) {
if (viewController != null) {
return viewController.onCreateAnimator(transit, enter, nextAnim);
} else {
return null;
}
}
@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 onViewStateRestored(@Nullable final Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState);
if (viewController != null) {
viewController.onViewStateRestored(savedInstanceState);
}
}
@Override
protected void onStart(@NonNull final View view, @NonNull final TActivity activity) {
super.onStart(view, activity);
started = true;
if (viewController != null) {
viewController.onStart();
}
@ -291,23 +262,12 @@ public abstract class ViewControllerFragment<TState extends AbstractState, TActi
}
}
/**
* Calls when activity configuring ActionBar, Toolbar, Sidebar etc.
* If it will be called or not depends on {@link #hasOptionsMenu()} and {@link #isMenuVisible()}.
*
* @param menu The options menu in which you place your items;
* @param inflater Helper to inflate menu items.
*/
protected void onConfigureNavigation(@NonNull final Menu menu, @NonNull final MenuInflater inflater) {
if (viewController != null) {
viewController.onConfigureNavigation(menu, inflater);
}
}
@Override
public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
onConfigureNavigation(menu, inflater);
if (viewController != null) {
viewController.onCreateOptionsMenu(menu, inflater);
}
}
@Override
@ -315,19 +275,6 @@ public abstract class ViewControllerFragment<TState extends AbstractState, TActi
return (viewController != null && viewController.onOptionsItemSelected(item)) || super.onOptionsItemSelected(item);
}
private void onViewControllerChanged(@Nullable final ViewController viewController) {
if (this.viewController != null) {
this.viewController.onDestroy();
}
this.viewController = viewController;
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);
@ -342,7 +289,7 @@ public abstract class ViewControllerFragment<TState extends AbstractState, TActi
if (viewController != null) {
viewController.onSaveInstanceState(savedInstanceState);
}
savedInstanceState.putSerializable(VIEW_CONTROLLER_STATE_EXTRA, state);
savedInstanceState.putParcelable(VIEW_CONTROLLER_STATE_EXTRA, state);
}
@Override
@ -355,7 +302,6 @@ public abstract class ViewControllerFragment<TState extends AbstractState, TActi
@Override
protected void onStop(@NonNull final View view, @NonNull final TActivity activity) {
started = false;
if (viewController != null) {
viewController.onStop();
}
@ -363,64 +309,42 @@ public abstract class ViewControllerFragment<TState extends AbstractState, TActi
}
@Override
protected void onDestroyView(@NonNull final View view) {
viewSubject.onNext(new NullablePair<>(null, null));
super.onDestroyView(view);
}
@Override
public void onDetach() {
activitySubject.onNext(new Optional<>(null));
super.onDetach();
}
@Override
public void onDestroy() {
viewControllerSubscription.unsubscribe();
if (viewController != null && !viewController.isDestroyed()) {
public void onDestroyView() {
if (viewController != null) {
viewController.onDestroy();
viewController = null;
}
super.onDestroy();
super.onDestroyView();
}
@NonNull
@Override
public String toString() {
return super.toString() + "ViewController: " + getViewControllerClass();
public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) {
if (viewController != null) {
viewController.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
private static class PlaceholderView extends FrameLayout {
@NonNull
private final String tagName;
private long lastMeasureTime;
public PlaceholderView(@NonNull final Context context, @NonNull final String tagName) {
super(context);
this.tagName = tagName;
@Override
public void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) {
if (viewController != null) {
viewController.onActivityResult(requestCode, resultCode, data);
} else {
pendingActivityResult = new ActivityResult(requestCode, resultCode, data);
}
}
@Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (inDebugMode && lastMeasureTime == 0) {
lastMeasureTime = SystemClock.uptimeMillis();
}
private static class ActivityResult {
public final int requestCode;
public final int resultCode;
@Nullable
public final Intent data;
ActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) {
this.requestCode = requestCode;
this.resultCode = resultCode;
this.data = data;
}
@Override
protected void onDraw(@NonNull final Canvas canvas) {
super.onDraw(canvas);
if (inDebugMode && lastMeasureTime > 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);
}
lastMeasureTime = 0;
}
}
}
}

View File

@ -19,19 +19,20 @@
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.v7.app.AppCompatActivity;
import android.support.v4.app.FragmentActivity;
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 rx.functions.Action2;
/**
* Created by Gavriil Sitnikov on 21/10/2015.
@ -39,11 +40,9 @@ import rx.functions.Action2;
*
* @param <TActivity> Type of activity which to such fragment could be attached.
*/
public abstract class ViewFragment<TActivity extends AppCompatActivity> extends Fragment
implements OnFragmentStartedListener {
public abstract class ViewFragment<TActivity extends FragmentActivity> extends Fragment implements OnFragmentStartedListener {
private boolean appeared;
private boolean started;
/**
* Returns if fragment have parent fragment.
@ -88,42 +87,30 @@ public abstract class ViewFragment<TActivity extends AppCompatActivity> 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 Action2<View, TActivity> action) {
private void callMethodAfterInstantiation(@NonNull final BiConsumer<View, TActivity> 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
@Override
public void onStart() {
super.onStart();
started = true;
callMethodAfterInstantiation(this::onStart);
}
@ -180,6 +167,7 @@ public abstract class ViewFragment<TActivity extends AppCompatActivity> extends
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());
}
@ -221,7 +209,6 @@ public abstract class ViewFragment<TActivity extends AppCompatActivity> extends
@Deprecated
@Override
public void onStop() {
started = false;
callMethodAfterInstantiation(this::onStop);
super.onStop();
}
@ -239,25 +226,4 @@ public abstract class ViewFragment<TActivity extends AppCompatActivity> 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
}
}

View File

@ -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<EmptyState> {
override fun createFromParcel(parcel: Parcel) = EmptyState
override fun newArray(size: Int): Array<EmptyState?> = arrayOfNulls(size)
}
}

View File

@ -0,0 +1,442 @@
/*
* 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.viewcontrollers;
import android.animation.Animator;
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.app.FragmentTransaction;
import android.support.v4.content.ContextCompat;
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 ru.touchin.roboswag.components.navigation.fragments.ViewControllerFragment;
import ru.touchin.roboswag.components.utils.UiUtils;
import ru.touchin.roboswag.core.log.Lc;
/**
* Created by Gavriil Sitnikov on 21/10/2015.
* Class to control view of specific fragment, activity and application by logic bridge.
*
* @param <TActivity> Type of activity where such {@link ViewController} could be;
* @param <TState> Type of state;
*/
public abstract class ViewController<TActivity extends FragmentActivity, TState extends Parcelable> implements LifecycleOwner {
@NonNull
private final LifecycleRegistry lifecycleRegistry = new LifecycleRegistry(this);
@NonNull
private final TActivity activity;
@NonNull
private final ViewControllerFragment<TActivity, TState> fragment;
@NonNull
private final View view;
@SuppressWarnings({"unchecked", "PMD.UnusedFormalParameter"})
//UnusedFormalParameter: savedInstanceState could be used by children
public ViewController(@NonNull final CreationContext creationContext, @Nullable final Bundle savedInstanceState, @LayoutRes final int layoutRes) {
this.activity = (TActivity) creationContext.activity;
this.fragment = creationContext.fragment;
view = creationContext.inflater.inflate(layoutRes, creationContext.container, false);
}
@NonNull
@Override
public Lifecycle getLifecycle() {
return lifecycleRegistry;
}
/**
* Returns activity where {@link ViewController} could be.
*
* @return Returns activity.
*/
@NonNull
public final TActivity getActivity() {
return activity;
}
/**
* Returns fragment where {@link ViewController} could be.
*
* @return Returns fragment.
*/
@NonNull
public final ViewControllerFragment<TActivity, TState> getFragment() {
return fragment;
}
/**
* Returns state from fragment.
*
* @return Returns state.
*/
@NonNull
public 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}.
*
* @return Returns view.
*/
@NonNull
public final View getView() {
return view;
}
/**
* 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
public final <T extends View> T findViewById(@IdRes final int id) {
return getView().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.
*
* @param resId Resource id for the string
*/
@NonNull
public final String getString(@StringRes final int resId) {
return activity.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 activity.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.
*
* <p>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(activity, 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(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()}.
*
* @param menu The options menu in which you place your items;
* @param inflater Helper to inflate menu items.
*/
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(Bundle)}.
*/
@CallSuper
public void onCreate() {
UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this));
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
}
/**
* Called when a fragment loads an animation. Note that if
* {@link FragmentTransaction#setCustomAnimations(int, int)} was called with
* {@link Animator} resources instead of {@link Animation} resources, {@code nextAnim}
* will be an animator resource.
*
* @param transit The value set in {@link FragmentTransaction#setTransition(int)} or 0 if not
* set.
* @param enter {@code true} when the fragment is added/attached/shown or {@code false} when
* the fragment is removed/detached/hidden.
* @param nextAnim The resource set in
* {@link FragmentTransaction#setCustomAnimations(int, int)},
* {@link FragmentTransaction#setCustomAnimations(int, int, int, int)}, or
* 0 if neither was called. The value will depend on the current operation.
*/
@Nullable
public Animation onCreateAnimation(final int transit, final boolean enter, final int nextAnim) {
return null;
}
/**
* Called when a fragment loads an animator. This will be called when
* {@link #onCreateAnimation(int, boolean, int)} returns null. Note that if
* {@link FragmentTransaction#setCustomAnimations(int, int)} was called with
* {@link Animation} resources instead of {@link Animator} resources, {@code nextAnim}
* will be an animation resource.
*
* @param transit The value set in {@link FragmentTransaction#setTransition(int)} or 0 if not
* set.
* @param enter {@code true} when the fragment is added/attached/shown or {@code false} when
* the fragment is removed/detached/hidden.
* @param nextAnim The resource set in
* {@link FragmentTransaction#setCustomAnimations(int, int)},
* {@link FragmentTransaction#setCustomAnimations(int, int, int, int)}, or
* 0 if neither was called. The value will depend on the current operation.
*/
@Nullable
public Animator onCreateAnimator(final int transit, final boolean enter, final int nextAnim) {
return null;
}
/**
* Calls when {@link ViewController} saved state has been restored into the view hierarchy.
* Happens at {@link ViewControllerFragment#onViewStateRestored}.
*/
@CallSuper
public void onViewStateRestored(@Nullable final Bundle savedInstanceState) {
// do nothing
}
/**
* Calls when {@link ViewController} have started.
* Happens at {@link ViewControllerFragment#onStart()}.
*/
@CallSuper
public void onStart() {
UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this));
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
UiUtils.OfViews.hideSoftInput(getView());
}
/**
* Called when fragment is moved in started state and it's {@link #getFragment().isMenuVisible()} sets to true.
* Usually it is indicating that user can't see fragment on screen and useful to track analytics events.
*/
public void onAppear() {
UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this));
}
/**
* Calls when {@link ViewController} have resumed.
* Happens at {@link ViewControllerFragment#onResume()}.
*/
@CallSuper
public void onResume() {
UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this));
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
}
/**
* Calls when {@link ViewController} have goes near out of memory state.
* Happens at {@link ViewControllerFragment#onLowMemory()}.
*/
@CallSuper
public void onLowMemory() {
//do nothing
}
/**
* Calls when {@link ViewController} have paused.
* Happens at {@link ViewControllerFragment#onPause()}.
*/
@CallSuper
public void onPause() {
UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this));
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE);
}
/**
* Calls when {@link ViewController} should save it's state.
* Happens at {@link ViewControllerFragment#onSaveInstanceState(Bundle)}.
* Try not to use such method for saving state but use {@link ViewControllerFragment#getState()} from {@link #getFragment()}.
*/
@CallSuper
public void onSaveInstanceState(@NonNull final Bundle savedInstanceState) {
UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this));
}
/**
* Called when fragment is moved in stopped state or it's {@link #getFragment().isMenuVisible()} sets to false.
* Usually it is indicating that user can't see fragment on screen and useful to track analytics events.
*/
public void onDisappear() {
UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this));
}
/**
* Calls when {@link ViewController} have stopped.
* Happens at {@link ViewControllerFragment#onStop()}.
*/
@CallSuper
public void onStop() {
UiUtils.UI_LIFECYCLE_LC_GROUP.i(Lc.getCodePoint(this));
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
}
/**
* Calls when {@link ViewController} have destroyed.
* 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));
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);
}
/**
* Calls when {@link ViewController} have requested permissions results.
* Happens at {@link ViewControllerFragment#onRequestPermissionsResult(int, String[], int[])} ()}.
*/
@CallSuper
@SuppressWarnings("PMD.UseVarargs")
public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) {
//do nothing
}
/**
* 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)}.
*
* @param item Selected menu item;
* @return True if selection processed.
*/
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
return false;
}
/*
* Helper class to simplify constructor override.
*/
public static class CreationContext {
@NonNull
private final FragmentActivity activity;
@NonNull
private final ViewControllerFragment fragment;
@NonNull
private final LayoutInflater inflater;
@Nullable
private final ViewGroup container;
public CreationContext(
@NonNull final FragmentActivity activity,
@NonNull final ViewControllerFragment fragment,
@NonNull final LayoutInflater inflater,
@Nullable final ViewGroup container
) {
this.activity = activity;
this.fragment = fragment;
this.inflater = inflater;
this.container = container;
}
@Nullable
public ViewGroup getContainer() {
return container;
}
}
}

View File

@ -0,0 +1,148 @@
/*
* 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.viewcontrollers
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 ru.touchin.roboswag.components.navigation.FragmentNavigation
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<TActivity : FragmentActivity>(
context: Context,
fragmentManager: FragmentManager,
@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 backStackName Name of [Fragment] 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.
*/
fun <TState : Parcelable> pushViewController(
viewControllerClass: Class<out ViewController<out TActivity, TState>>,
state: TState,
addToStack: Boolean = true,
backStackName: String? = null,
transactionSetup: ((FragmentTransaction) -> Unit)? = null
) {
addToStack(
ViewControllerFragment::class.java,
null,
0,
addToStack,
ViewControllerFragment.args(viewControllerClass, state),
backStackName,
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 <TState : Parcelable, TTargetFragment : Fragment> pushViewControllerForResult(
viewControllerClass: Class<out ViewController<out TActivity, TState>>,
state: TState,
targetFragment: TTargetFragment,
targetRequestCode: Int,
transactionSetup: ((FragmentTransaction) -> Unit)? = null
) {
addToStack(
ViewControllerFragment::class.java,
targetFragment,
targetRequestCode,
true,
ViewControllerFragment.args(viewControllerClass, state),
null,
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 <TState : Parcelable> setViewControllerAsTop(
viewControllerClass: Class<out ViewController<out TActivity, TState>>,
state: TState,
addToStack: Boolean = true,
transactionSetup: ((FragmentTransaction) -> Unit)? = null
) {
addToStack(
ViewControllerFragment::class.java,
null,
0,
addToStack,
ViewControllerFragment.args(viewControllerClass, state),
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 <TState : Parcelable> setInitialViewController(
viewControllerClass: Class<out ViewController<out TActivity, TState>>,
state: TState,
transactionSetup: ((FragmentTransaction) -> Unit)? = null
) {
beforeSetInitialActions()
setViewControllerAsTop(viewControllerClass, state, false, transactionSetup)
}
}

View File

@ -1,287 +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 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.
* 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<Boolean> isCreatedSubject = BehaviorSubject.create();
@NonNull
private final BehaviorSubject<Boolean> isStartedSubject = BehaviorSubject.create();
@NonNull
private final BehaviorSubject<Boolean> isInAfterSaving = BehaviorSubject.create();
/**
* 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 <T> Subscription untilStop(@NonNull final Observable<T> observable) {
final String codePoint = Lc.getCodePoint(this, 2);
return untilStop(observable, Actions.empty(), getActionThrowableForAssertion(codePoint, UNTIL_STOP_METHOD), Actions.empty());
}
@NonNull
@Override
public <T> Subscription untilStop(@NonNull final Observable<T> observable, @NonNull final Action1<T> onNextAction) {
final String codePoint = Lc.getCodePoint(this, 2);
return untilStop(observable, onNextAction, getActionThrowableForAssertion(codePoint, UNTIL_STOP_METHOD), Actions.empty());
}
@NonNull
@Override
public <T> Subscription untilStop(@NonNull final Observable<T> observable,
@NonNull final Action1<T> onNextAction,
@NonNull final Action1<Throwable> onErrorAction) {
return untilStop(observable, onNextAction, onErrorAction, Actions.empty());
}
@NonNull
@Override
public <T> Subscription untilStop(@NonNull final Observable<T> observable,
@NonNull final Action1<T> onNextAction,
@NonNull final Action1<Throwable> onErrorAction,
@NonNull final Action0 onCompletedAction) {
return until(observable.delay(item -> isInAfterSaving.first(inAfterSaving -> !inAfterSaving)),
isStartedSubject.map(started -> !started),
onNextAction, onErrorAction, onCompletedAction);
}
@NonNull
@Override
public <T> Subscription untilStop(@NonNull final Single<T> single) {
final String codePoint = Lc.getCodePoint(this, 2);
return untilStop(single, Actions.empty(), getActionThrowableForAssertion(codePoint, UNTIL_STOP_METHOD));
}
@NonNull
@Override
public <T> Subscription untilStop(@NonNull final Single<T> single, @NonNull final Action1<T> onSuccessAction) {
final String codePoint = Lc.getCodePoint(this, 2);
return untilStop(single, onSuccessAction, getActionThrowableForAssertion(codePoint, UNTIL_STOP_METHOD));
}
@NonNull
@Override
public <T> Subscription untilStop(@NonNull final Single<T> single,
@NonNull final Action1<T> onSuccessAction,
@NonNull final Action1<Throwable> onErrorAction) {
return untilStop(single.toObservable(), onSuccessAction, onErrorAction, Actions.empty());
}
@NonNull
@Override
public Subscription untilStop(@NonNull final Completable completable) {
final String codePoint = Lc.getCodePoint(this, 2);
return untilStop(completable, Actions.empty(), getActionThrowableForAssertion(codePoint, UNTIL_STOP_METHOD));
}
@NonNull
@Override
public Subscription untilStop(@NonNull final Completable completable,
@NonNull final Action0 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<Throwable> onErrorAction) {
return untilStop(completable.toObservable(), Actions.empty(), onErrorAction, onCompletedAction);
}
@NonNull
@Override
public <T> Subscription untilDestroy(@NonNull final Observable<T> observable) {
final String codePoint = Lc.getCodePoint(this, 2);
return untilDestroy(observable, Actions.empty(), getActionThrowableForAssertion(codePoint, UNTIL_DESTROY_METHOD), Actions.empty());
}
@NonNull
@Override
public <T> Subscription untilDestroy(@NonNull final Observable<T> observable,
@NonNull final Action1<T> onNextAction) {
final String codePoint = Lc.getCodePoint(this, 2);
return untilDestroy(observable, onNextAction, getActionThrowableForAssertion(codePoint, UNTIL_DESTROY_METHOD), Actions.empty());
}
@NonNull
@Override
public <T> Subscription untilDestroy(@NonNull final Observable<T> observable,
@NonNull final Action1<T> onNextAction,
@NonNull final Action1<Throwable> onErrorAction) {
return untilDestroy(observable, onNextAction, onErrorAction, Actions.empty());
}
@NonNull
@Override
public <T> Subscription untilDestroy(@NonNull final Observable<T> observable,
@NonNull final Action1<T> onNextAction,
@NonNull final Action1<Throwable> onErrorAction,
@NonNull final Action0 onCompletedAction) {
return until(observable, isCreatedSubject.map(created -> !created), onNextAction, onErrorAction, onCompletedAction);
}
@NonNull
@Override
public <T> Subscription untilDestroy(@NonNull final Single<T> single) {
final String codePoint = Lc.getCodePoint(this, 2);
return untilDestroy(single, Actions.empty(), getActionThrowableForAssertion(codePoint, UNTIL_DESTROY_METHOD));
}
@NonNull
@Override
public <T> Subscription untilDestroy(@NonNull final Single<T> single, @NonNull final Action1<T> onSuccessAction) {
final String codePoint = Lc.getCodePoint(this, 2);
return untilDestroy(single, onSuccessAction, getActionThrowableForAssertion(codePoint, UNTIL_DESTROY_METHOD));
}
@NonNull
@Override
public <T> Subscription untilDestroy(@NonNull final Single<T> single,
@NonNull final Action1<T> onSuccessAction,
@NonNull final Action1<Throwable> onErrorAction) {
return until(single.toObservable(), isCreatedSubject.map(created -> !created), onSuccessAction, onErrorAction, Actions.empty());
}
@NonNull
@Override
public Subscription untilDestroy(@NonNull final Completable completable) {
final String codePoint = Lc.getCodePoint(this, 2);
return untilDestroy(completable, Actions.empty(), getActionThrowableForAssertion(codePoint, UNTIL_DESTROY_METHOD));
}
@NonNull
@Override
public Subscription untilDestroy(@NonNull final Completable completable, @NonNull final Action0 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<Throwable> onErrorAction) {
return until(completable.toObservable(), isCreatedSubject.map(created -> !created), Actions.empty(), onErrorAction, onCompletedAction);
}
@NonNull
private <T> Subscription until(@NonNull final Observable<T> observable,
@NonNull final Observable<Boolean> conditionSubject,
@NonNull final Action1<T> onNextAction,
@NonNull final Action1<Throwable> onErrorAction,
@NonNull final Action0 onCompletedAction) {
final Observable<T> actualObservable;
if (onNextAction == Actions.empty() && onErrorAction == (Action1) Actions.empty() && onCompletedAction == Actions.empty()) {
actualObservable = observable;
} else {
actualObservable = observable.observeOn(AndroidSchedulers.mainThread())
.doOnCompleted(onCompletedAction)
.doOnNext(onNextAction)
.doOnError(onErrorAction);
}
return isCreatedSubject.first()
.switchMap(created -> created ? actualObservable : Observable.empty())
.takeUntil(conditionSubject.filter(condition -> condition))
.onErrorResumeNext(throwable -> {
final boolean isRxError = throwable instanceof OnErrorThrowable;
if ((!isRxError && throwable instanceof RuntimeException)
|| (isRxError && throwable.getCause() instanceof RuntimeException)) {
Lc.assertion(throwable);
}
return Observable.empty();
})
.subscribe();
}
@NonNull
private Action1<Throwable> getActionThrowableForAssertion(@NonNull final String codePoint, @NonNull final String method) {
return throwable -> Lc.assertion(new ShouldNotHappenException("Unexpected error on " + method + " at " + codePoint, throwable));
}
}

View File

@ -1,314 +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 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;
/**
* 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 <T> Type of emitted by observable items;
* @return {@link Subscription} which will unsubscribes from observable onStop.
*/
@NonNull
<T> Subscription untilStop(@NonNull Observable<T> 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 Subscriber#onNext(Object)} item;
* @param <T> Type of emitted by observable items;
* @return {@link Subscription} which will unsubscribes from observable onStop.
*/
@NonNull
<T> Subscription untilStop(@NonNull Observable<T> observable, @NonNull Action1<T> 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 Subscriber#onNext(Object)} item;
* @param onErrorAction Action which will raise on every {@link Subscriber#onError(Throwable)} throwable;
* @param <T> Type of emitted by observable items;
* @return {@link Subscription} which will unsubscribes from observable onStop.
*/
@NonNull
<T> Subscription untilStop(@NonNull Observable<T> observable, @NonNull Action1<T> onNextAction, @NonNull Action1<Throwable> 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 Subscriber#onNext(Object)} item;
* @param onErrorAction Action which will raise on every {@link Subscriber#onError(Throwable)} throwable;
* @param onCompletedAction Action which will raise at {@link Subscriber#onCompleted()} on completion of observable;
* @param <T> Type of emitted by observable items;
* @return {@link Subscription} which is wrapping source observable to unsubscribe from it onStop.
*/
@NonNull
<T> Subscription untilStop(@NonNull Observable<T> observable,
@NonNull Action1<T> onNextAction, @NonNull Action1<Throwable> onErrorAction, @NonNull Action0 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 <T> Type of emitted by single item;
* @return {@link Subscription} which will unsubscribes from single onStop.
*/
@NonNull
<T> Subscription untilStop(@NonNull Single<T> 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 SingleSubscriber#onSuccess(Object)} item;
* @param <T> Type of emitted by single item;
* @return {@link Subscription} which will unsubscribes from single onStop.
*/
@NonNull
<T> Subscription untilStop(@NonNull Single<T> single, @NonNull Action1<T> 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 SingleSubscriber#onSuccess(Object)} item;
* @param onErrorAction Action which will raise on every {@link SingleSubscriber#onError(Throwable)} throwable;
* @param <T> Type of emitted by observable items;
* @return {@link Subscription} which is wrapping source single to unsubscribe from it onStop.
*/
@NonNull
<T> Subscription untilStop(@NonNull Single<T> single, @NonNull Action1<T> onSuccessAction, @NonNull Action1<Throwable> 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 Subscription} which will unsubscribes from completable onStop.
*/
@NonNull
Subscription 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 CompletableSubscriber#onCompleted()} on completion of observable;
* @return {@link Subscription} which is wrapping source completable to unsubscribe from it onStop.
*/
@NonNull
Subscription untilStop(@NonNull Completable completable, @NonNull Action0 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 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.
*/
@NonNull
Subscription untilStop(@NonNull Completable completable, @NonNull Action0 onCompletedAction, @NonNull Action1<Throwable> 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 <T> Type of emitted by observable items;
* @return {@link Subscription} which is wrapping source observable to unsubscribe from it onDestroy.
*/
@NonNull
<T> Subscription untilDestroy(@NonNull Observable<T> 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 Subscriber#onNext(Object)} item;
* @param <T> Type of emitted by observable items;
* @return {@link Subscription} which is wrapping source observable to unsubscribe from it onDestroy.
*/
@NonNull
<T> Subscription untilDestroy(@NonNull Observable<T> observable, @NonNull Action1<T> 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 Subscriber#onNext(Object)} item;
* @param onErrorAction Action which will raise on every {@link Subscriber#onError(Throwable)} throwable;
* @param <T> Type of emitted by observable items;
* @return {@link Subscription} which is wrapping source observable to unsubscribe from it onDestroy.
*/
@NonNull
<T> Subscription untilDestroy(@NonNull Observable<T> observable, @NonNull Action1<T> onNextAction, @NonNull Action1<Throwable> 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 Subscriber#onNext(Object)} item;
* @param onErrorAction Action which will raise on every {@link Subscriber#onError(Throwable)} throwable;
* @param onCompletedAction Action which will raise at {@link Subscriber#onCompleted()} on completion of observable;
* @param <T> Type of emitted by observable items;
* @return {@link Subscription} which is wrapping source observable to unsubscribe from it onDestroy.
*/
@NonNull
<T> Subscription untilDestroy(@NonNull Observable<T> observable,
@NonNull Action1<T> onNextAction, @NonNull Action1<Throwable> onErrorAction, @NonNull Action0 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 <T> Type of emitted by single items;
* @return {@link Subscription} which is wrapping source single to unsubscribe from it onDestroy.
*/
@NonNull
<T> Subscription untilDestroy(@NonNull Single<T> 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 SingleSubscriber#onSuccess(Object)} item;
* @param <T> Type of emitted by single items;
* @return {@link Subscription} which is wrapping source single to unsubscribe from it onDestroy.
*/
@NonNull
<T> Subscription untilDestroy(@NonNull Single<T> single, @NonNull Action1<T> 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 SingleSubscriber#onSuccess(Object)} item;
* @param onErrorAction Action which will raise on every {@link SingleSubscriber#onError(Throwable)} throwable;
* @param <T> Type of emitted by single items;
* @return {@link Subscription} which is wrapping source single to unsubscribe from it onDestroy.
*/
@NonNull
<T> Subscription untilDestroy(@NonNull Single<T> single, @NonNull Action1<T> onSuccessAction, @NonNull Action1<Throwable> 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 Subscription} which is wrapping source completable to unsubscribe from it onDestroy.
*/
@NonNull
Subscription 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 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.
*/
@NonNull
Subscription untilDestroy(@NonNull Completable completable, @NonNull Action0 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 CompletableSubscriber#onCompleted()} item;
* @param onErrorAction Action which will raise on every {@link CompletableSubscriber#onError(Throwable)} throwable;
* @return {@link Subscription} which is wrapping source completable to unsubscribe from it onDestroy.
*/
@NonNull
Subscription untilDestroy(@NonNull Completable completable, @NonNull Action0 onCompletedAction, @NonNull Action1<Throwable> onErrorAction);
}

View File

@ -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 ru.touchin.roboswag.core.utils.ShouldNotHappenException;
import rx.Observable;
/**
* 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<Class<? extends Logic>, WeakReference<Logic>> 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 <T> 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 extends Logic> T getInstance(@NonNull final Context context, @NonNull final Class<T> logicClass) {
T result;
synchronized (LOGIC_INSTANCES) {
final WeakReference<Logic> 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 extends Logic> T constructLogic(@NonNull final Context context, @NonNull final Class<T> 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;
}
}

View File

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

View File

@ -25,12 +25,8 @@ 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;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.Display;
@ -40,12 +36,9 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import java.util.concurrent.atomic.AtomicInteger;
import ru.touchin.roboswag.components.navigation.activities.BaseActivity;
import ru.touchin.roboswag.core.log.LcGroup;
import rx.functions.Action0;
/**
* Created by Gavriil Sitnikov on 13/11/2015.
@ -61,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.
@ -93,67 +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 Action0 onClickListener, final long delay) {
setOnRippleClickListener(targetView, onClickListener != null ? v -> onClickListener.call() : 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 Action0 onClickListener) {
setOnRippleClickListener(targetView, onClickListener != null ? v -> onClickListener.call() : 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 View.OnClickListener onClickListener) {
setOnRippleClickListener(targetView, onClickListener, 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 View.OnClickListener onClickListener,
final long delay) {
if (onClickListener == null) {
targetView.setOnClickListener(null);
return;
}
final Runnable runnable = () -> {
if (targetView.getWindowVisibility() != View.VISIBLE
|| !targetView.hasWindowFocus()
|| (targetView.getContext() instanceof BaseActivity && !((BaseActivity) targetView.getContext()).isActuallyResumed())) {
return;
}
onClickListener.onClick(targetView);
};
targetView.setOnClickListener(v -> {
RIPPLE_HANDLER.removeCallbacksAndMessages(null);
RIPPLE_HANDLER.postDelayed(runnable, delay);
});
}
private UiUtils() {
}
@ -298,35 +224,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.
*
@ -342,6 +239,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() {
}

View File

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

View File

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

View File

@ -0,0 +1,65 @@
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
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()
override fun clearSubscriptions() = subscriptions.clear()
/**
* Call it on parent's onDestroy method.
*/
fun onDestroy() = subscriptions.dispose()
override fun <T> Flowable<T>.untilDestroy(
onNext: (T) -> Unit,
onError: (Throwable) -> Unit,
onComplete: () -> Unit
): Disposable = observeOn(AndroidSchedulers.mainThread())
.subscribe(onNext, onError, onComplete)
.also { subscriptions.add(it) }
override fun <T> Observable<T>.untilDestroy(
onNext: (T) -> Unit,
onError: (Throwable) -> Unit,
onComplete: () -> Unit
): Disposable = observeOn(AndroidSchedulers.mainThread())
.subscribe(onNext, onError, onComplete)
.also { subscriptions.add(it) }
override fun <T> Single<T>.untilDestroy(
onSuccess: (T) -> Unit,
onError: (Throwable) -> Unit
): Disposable = observeOn(AndroidSchedulers.mainThread())
.subscribe(onSuccess, onError)
.also { subscriptions.add(it) }
override fun Completable.untilDestroy(
onComplete: () -> Unit,
onError: (Throwable) -> Unit
): Disposable = observeOn(AndroidSchedulers.mainThread())
.subscribe(onComplete, onError)
.also { subscriptions.add(it) }
override fun <T> Maybe<T>.untilDestroy(
onSuccess: (T) -> Unit,
onError: (Throwable) -> Unit,
onComplete: () -> Unit
): Disposable = observeOn(AndroidSchedulers.mainThread())
.subscribe(onSuccess, onError, onComplete)
.also { subscriptions.add(it) }
}

View File

@ -0,0 +1,108 @@
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
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))
}
}
/**
* 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.
* Don't forget to process errors if observable can emit them.
*
* @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 <T> Flowable<T>.untilDestroy(
onNext: (T) -> Unit = Functions.emptyConsumer<T>()::accept,
onError: (Throwable) -> Unit = getActionThrowableForAssertion(Lc.getCodePoint(this, 2)),
onComplete: () -> 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.
* Don't forget to process errors if observable can emit them.
*
* @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 <T> Observable<T>.untilDestroy(
onNext: (T) -> Unit = Functions.emptyConsumer<T>()::accept,
onError: (Throwable) -> Unit = getActionThrowableForAssertion(Lc.getCodePoint(this, 2)),
onComplete: () -> 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 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 <T> Single<T>.untilDestroy(
onSuccess: (T) -> Unit = Functions.emptyConsumer<T>()::accept,
onError: (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 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(
onComplete: () -> Unit = Functions.EMPTY_ACTION::run,
onError: (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 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 <T> Maybe<T>.untilDestroy(
onSuccess: (T) -> Unit = Functions.emptyConsumer<T>()::accept,
onError: (Throwable) -> Unit = getActionThrowableForAssertion(Lc.getCodePoint(this, 2)),
onComplete: () -> Unit = Functions.EMPTY_ACTION::run
): Disposable
}

View File

@ -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;
/**

View File

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

View File

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

View File

@ -1,284 +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 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;
/**
* 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 <T> Subscription untilStop(@NonNull final Observable<T> observable) {
return baseLifecycleBindable.untilStop(observable);
}
@NonNull
@Override
public <T> Subscription untilStop(@NonNull final Observable<T> observable, @NonNull final Action1<T> onNextAction) {
return baseLifecycleBindable.untilStop(observable, onNextAction);
}
@NonNull
@Override
public <T> Subscription untilStop(@NonNull final Observable<T> observable,
@NonNull final Action1<T> onNextAction,
@NonNull final Action1<Throwable> onErrorAction) {
return baseLifecycleBindable.untilStop(observable, onNextAction, onErrorAction);
}
@NonNull
@Override
public <T> Subscription untilStop(@NonNull final Observable<T> observable,
@NonNull final Action1<T> onNextAction,
@NonNull final Action1<Throwable> onErrorAction,
@NonNull final Action0 onCompletedAction) {
return baseLifecycleBindable.untilStop(observable, onNextAction, onErrorAction, onCompletedAction);
}
@NonNull
@Override
public <T> Subscription untilStop(@NonNull final Single<T> single) {
return baseLifecycleBindable.untilStop(single);
}
@NonNull
@Override
public <T> Subscription untilStop(@NonNull final Single<T> single, @NonNull final Action1<T> onSuccessAction) {
return baseLifecycleBindable.untilStop(single, onSuccessAction);
}
@NonNull
@Override
public <T> Subscription untilStop(@NonNull final Single<T> single,
@NonNull final Action1<T> onSuccessAction,
@NonNull final Action1<Throwable> onErrorAction) {
return baseLifecycleBindable.untilStop(single, onSuccessAction, onErrorAction);
}
@NonNull
@Override
public Subscription untilStop(@NonNull final Completable completable) {
return baseLifecycleBindable.untilStop(completable);
}
@NonNull
@Override
public Subscription untilStop(@NonNull final Completable completable, @NonNull final Action0 onCompletedAction) {
return baseLifecycleBindable.untilStop(completable, onCompletedAction);
}
@NonNull
@Override
public Subscription untilStop(@NonNull final Completable completable,
@NonNull final Action0 onCompletedAction,
@NonNull final Action1<Throwable> onErrorAction) {
return baseLifecycleBindable.untilStop(completable, onCompletedAction, onErrorAction);
}
@NonNull
@Override
public <T> Subscription untilDestroy(@NonNull final Observable<T> observable) {
return baseLifecycleBindable.untilDestroy(observable);
}
@NonNull
@Override
public <T> Subscription untilDestroy(@NonNull final Observable<T> observable, @NonNull final Action1<T> onNextAction) {
return baseLifecycleBindable.untilDestroy(observable, onNextAction);
}
@NonNull
@Override
public <T> Subscription untilDestroy(@NonNull final Observable<T> observable,
@NonNull final Action1<T> onNextAction,
@NonNull final Action1<Throwable> onErrorAction) {
return baseLifecycleBindable.untilDestroy(observable, onNextAction, onErrorAction);
}
@NonNull
@Override
public <T> Subscription untilDestroy(@NonNull final Observable<T> observable,
@NonNull final Action1<T> onNextAction,
@NonNull final Action1<Throwable> onErrorAction,
@NonNull final Action0 onCompletedAction) {
return baseLifecycleBindable.untilDestroy(observable, onNextAction, onErrorAction, onCompletedAction);
}
@NonNull
@Override
public <T> Subscription untilDestroy(@NonNull final Single<T> single) {
return baseLifecycleBindable.untilDestroy(single);
}
@NonNull
@Override
public <T> Subscription untilDestroy(@NonNull final Single<T> single, @NonNull final Action1<T> onSuccessAction) {
return baseLifecycleBindable.untilDestroy(single, onSuccessAction);
}
@NonNull
@Override
public <T> Subscription untilDestroy(@NonNull final Single<T> single,
@NonNull final Action1<T> onSuccessAction,
@NonNull final Action1<Throwable> onErrorAction) {
return baseLifecycleBindable.untilDestroy(single, onSuccessAction, onErrorAction);
}
@NonNull
@Override
public Subscription untilDestroy(@NonNull final Completable completable) {
return baseLifecycleBindable.untilDestroy(completable);
}
@NonNull
@Override
public Subscription untilDestroy(@NonNull final Completable completable, @NonNull final Action0 onCompletedAction) {
return baseLifecycleBindable.untilDestroy(completable, onCompletedAction);
}
@NonNull
@Override
public Subscription untilDestroy(@NonNull final Completable completable,
@NonNull final Action0 onCompletedAction,
@NonNull final Action1<Throwable> onErrorAction) {
return baseLifecycleBindable.untilDestroy(completable, onCompletedAction, onErrorAction);
}
}

View File

@ -90,7 +90,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,7 +107,7 @@ public class MaterialLoadingBar extends AppCompatImageView {
progressDrawable.stop();
super.onDetachedFromWindow();
}
/**
* Set color of loader.
*

View File

@ -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,12 +32,15 @@ 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.utils.Typefaces;
import ru.touchin.roboswag.components.views.internal.AttributesUtils;
import ru.touchin.roboswag.core.log.Lc;
@ -96,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);
@ -107,12 +107,25 @@ 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 = 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<String> 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)) {
@ -139,12 +152,6 @@ public class TypefacedEditText extends AppCompatEditText {
private void checkEditTextSpecificAttributes(@NonNull final TypedArray typedArray, @NonNull final Class androidRes,
@NonNull final List<String> 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,
@ -317,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;
}

View File

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

View File

@ -94,9 +94,7 @@ public final class AttributesUtils {
public static void checkRegularTextViewAttributes(@NonNull final TypedArray typedArray, @NonNull final Class androidRes,
@NonNull final Collection<String> 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);

View File

@ -0,0 +1,80 @@
package ru.touchin.roboswag.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.support.annotation.IdRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.FrameLayout;
import java.util.NoSuchElementException;
import ru.touchin.roboswag.components.R;
public class Switcher extends FrameLayout {
@IdRes
private final int defaultChild;
@Nullable
private Animation inAnimation;
@Nullable
private Animation outAnimation;
public Switcher(@NonNull final Context context) {
this(context, null);
}
public Switcher(@NonNull final Context context, @Nullable final AttributeSet attrs) {
super(context, attrs);
final TypedArray array = getContext().obtainStyledAttributes(attrs, R.styleable.Switcher, 0, R.style.Switcher);
inAnimation = AnimationUtils.loadAnimation(context, array.getResourceId(R.styleable.Switcher_android_inAnimation, View.NO_ID));
outAnimation = AnimationUtils.loadAnimation(context, array.getResourceId(R.styleable.Switcher_android_outAnimation, View.NO_ID));
defaultChild = array.getResourceId(R.styleable.Switcher_defaultChild, View.NO_ID);
array.recycle();
}
@Override
public void addView(@NonNull final View child, final int index, @Nullable final ViewGroup.LayoutParams params) {
if (child.getId() == defaultChild || defaultChild == View.NO_ID && getChildCount() == 0) {
child.setVisibility(View.VISIBLE);
} else {
child.setVisibility(View.GONE);
}
super.addView(child, index, params);
}
public void showChild(@IdRes final int id) {
boolean found = false;
for (int index = 0; index < getChildCount(); index++) {
final View child = getChildAt(index);
if (child.getId() == id) {
found = true;
setVisibilityWithAnimation(child, View.VISIBLE);
} else {
setVisibilityWithAnimation(child, View.GONE);
}
}
if (!found) {
throw new NoSuchElementException();
}
}
private void setVisibilityWithAnimation(@NonNull final View view, final int targetVisibility) {
final Animation animation = targetVisibility == View.VISIBLE ? inAnimation : outAnimation;
if (view.getVisibility() != targetVisibility) {
if (ViewCompat.isLaidOut(this) && animation != null) {
view.startAnimation(animation);
}
view.setVisibility(targetVisibility);
}
}
}

View File

@ -0,0 +1,6 @@
<translate
xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@integer/fragmentTransitionTime"
android:fromXDelta="-50%p"
android:interpolator="@android:anim/accelerate_interpolator"
android:toXDelta="0"/>

View File

@ -0,0 +1,6 @@
<translate
xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@integer/fragmentTransitionTime"
android:fromXDelta="100%p"
android:interpolator="@android:anim/decelerate_interpolator"
android:toXDelta="0"/>

View File

@ -0,0 +1,6 @@
<translate
xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@integer/fragmentTransitionTime"
android:fromXDelta="0"
android:interpolator="@android:anim/decelerate_interpolator"
android:toXDelta="-50%p"/>

View File

@ -0,0 +1,6 @@
<translate
xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@integer/fragmentTransitionTime"
android:fromXDelta="0"
android:interpolator="@android:anim/accelerate_interpolator"
android:toXDelta="100%p"/>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<alpha
xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_mediumAnimTime"
android:fromAlpha="0.0"
android:interpolator="@android:anim/decelerate_interpolator"
android:toAlpha="1.0"/>

View File

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2013 The Android Open Source Project
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.
-->
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/decelerate_interpolator"
android:fromAlpha="0.0" android:toAlpha="1.0"
android:duration="@android:integer/config_mediumAnimTime" />

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<alpha
xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_mediumAnimTime"
android:fromAlpha="1.0"
android:interpolator="@android:anim/decelerate_interpolator"
android:toAlpha="0.0"/>

View File

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2013 The Android Open Source Project
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.
-->
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/decelerate_interpolator"
android:fromAlpha="1.0" android:toAlpha="0.0"
android:duration="@android:integer/config_mediumAnimTime" />

View File

@ -4,7 +4,6 @@
<attr name="customTypeface" format="string"/>
<declare-styleable name="TypefacedTextView">
<attr name="customTypeface"/>
<attr name="lineStrategy" format="enum">
<enum name="singleLineEllipsize" value="0"/>
<enum name="singleLineMarquee" value="1"/>
@ -17,15 +16,9 @@
</declare-styleable>
<declare-styleable name="TypefacedEditText">
<attr name="customTypeface"/>
<attr name="isMultiline" format="boolean"/>
</declare-styleable>
<declare-styleable name="AspectRatioFrameLayout">
<attr name="aspectRatio" format="float" />
<attr name="wrapToContent" format="boolean" />
</declare-styleable>
<declare-styleable name="MaterialLoadingBar">
<attr name="strokeWidth" format="dimension"/>
<attr name="color" format="color"/>
@ -33,4 +26,10 @@
<attr name="materialLoadingBarStyle" format="reference"/>
</declare-styleable>
</resources>
<declare-styleable name="Switcher">
<attr name="android:inAnimation" format="reference"/>
<attr name="android:outAnimation" format="reference"/>
<attr name="defaultChild" format="reference"/>
</declare-styleable>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="fragmentTransitionTime">250</integer>
</resources>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Switcher">
<item name="android:inAnimation">@anim/global_fade_in</item>
<item name="android:outAnimation">@anim/global_fade_out</item>
</style>
</resources>