From 6d583faec9cac7d79559b362aa11b878e7497cca Mon Sep 17 00:00:00 2001 From: Kartashov Andrey Date: Tue, 6 Apr 2021 12:03:29 +0300 Subject: [PATCH 01/17] ANDDEP-1207 extracted ActivityNavigator and GlobalNavigator interfaces --- .../dagger/app/dagger/DefaultAppModule.kt | 3 +- .../activity/navigator/ActivityNavigator.kt | 137 ++++++++++++++ .../ActivityNavigatorForActivity.java | 2 +- .../ActivityNavigatorForFragment.java | 2 +- ...igator.java => BaseActivityNavigator.java} | 167 ++++++------------ .../activity/navigator/GlobalNavigator.kt | 19 ++ ...avigator.java => GlobalNavigatorImpl.java} | 16 +- .../app/dagger/AppModule.kt | 3 +- .../sample/app/dagger/CustomAppModule.kt | 3 +- .../core/ui/permission/PermissionManager.kt | 2 +- .../standard/application/app/di/AppModule.kt | 3 +- .../f_debug/injector/DebugAppModule.kt | 3 +- .../di/modules/TestAppModule.kt | 3 +- 13 files changed, 227 insertions(+), 136 deletions(-) create mode 100644 core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/activity/navigator/ActivityNavigator.kt rename core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/activity/navigator/{ActivityNavigator.java => BaseActivityNavigator.java} (69%) create mode 100644 core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/activity/navigator/GlobalNavigator.kt rename core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/activity/navigator/{GlobalNavigator.java => GlobalNavigatorImpl.java} (74%) diff --git a/common/lib-sample-dagger/src/main/java/ru/surfstudio/android/sample/dagger/app/dagger/DefaultAppModule.kt b/common/lib-sample-dagger/src/main/java/ru/surfstudio/android/sample/dagger/app/dagger/DefaultAppModule.kt index 070fc7986d..fa918bc025 100755 --- a/common/lib-sample-dagger/src/main/java/ru/surfstudio/android/sample/dagger/app/dagger/DefaultAppModule.kt +++ b/common/lib-sample-dagger/src/main/java/ru/surfstudio/android/sample/dagger/app/dagger/DefaultAppModule.kt @@ -8,6 +8,7 @@ import dagger.Provides import ru.surfstudio.android.activity.holder.ActiveActivityHolder import ru.surfstudio.android.connection.ConnectionProvider import ru.surfstudio.android.core.ui.navigation.activity.navigator.GlobalNavigator +import ru.surfstudio.android.core.ui.navigation.activity.navigator.GlobalNavigatorImpl import ru.surfstudio.android.dagger.scope.PerApplication import ru.surfstudio.android.rx.extension.scheduler.SchedulersProvider import ru.surfstudio.android.rx.extension.scheduler.SchedulersProviderImpl @@ -40,7 +41,7 @@ class DefaultAppModule( context: Context, activityHolder: ActiveActivityHolder ): GlobalNavigator { - return GlobalNavigator(context, activityHolder) + return GlobalNavigatorImpl(context, activityHolder) } @Provides diff --git a/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/activity/navigator/ActivityNavigator.kt b/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/activity/navigator/ActivityNavigator.kt new file mode 100644 index 0000000000..06efeaff7e --- /dev/null +++ b/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/activity/navigator/ActivityNavigator.kt @@ -0,0 +1,137 @@ +package ru.surfstudio.android.core.ui.navigation.activity.navigator + +import io.reactivex.Observable +import ru.surfstudio.android.core.ui.navigation.Navigator +import ru.surfstudio.android.core.ui.navigation.ScreenResult +import ru.surfstudio.android.core.ui.navigation.activity.route.ActivityRoute +import ru.surfstudio.android.core.ui.navigation.activity.route.ActivityWithResultRoute +import ru.surfstudio.android.core.ui.navigation.activity.route.NewIntentRoute +import ru.surfstudio.android.core.ui.navigation.event.result.CrossFeatureSupportOnActivityResultRoute +import ru.surfstudio.android.core.ui.navigation.event.result.SupportOnActivityResultRoute +import ru.surfstudio.android.core.ui.navigation.feature.installer.SplitFeatureInstallState +import ru.surfstudio.android.core.ui.navigation.feature.route.feature.ActivityCrossFeatureRoute +import java.io.Serializable + + +/** + * позволяет осуществлять навигацияю между активити + *

+ * !!!В случае конфликтов возвращения результата между несколькими инстансами навигаторами + * можно рассмотреть добавление к RequestCode хеша от имени экрана контейнера + * Конфликт может возникнуть при открытии одинаковых экранов из, например, кастомной вью с + * презентером и родительской активити вью + */ +interface ActivityNavigator : Navigator { + + /** + * позволяет подписываться на событие OnActivityResult + * + * @param routeClass класс маршрута экрана, который должен вернуть результат + * @param тип возвращаемых данных + */ + fun observeResult(routeClass: Class>): Observable> + + /** + * позволяет подписываться на событие OnActivityResult + * + * @param route маршрут экрана, который должен вернуть результат + * @param тип возвращаемых данных + */ + fun observeResult(route: SupportOnActivityResultRoute): Observable> + + /** + * Закрываает текущую активити + */ + fun finishCurrent() + + /** + * Закрываает текущую активити Affinity + */ + fun finishAffinity() + + /** + * Закрываает текущую активити c результатом + * + * @param route маршрут текущего экрана + * @param success показывает успешное ли завершение + * @param тип возвращаемого значения + */ + fun finishWithResult(route: ActivityWithResultRoute, success: Boolean) + + /** + * Закрываает текущую активити c результатом + * + * @param activeScreenRoute маршрут текущего экрана + * @param result возвращаемый результат + * @param тип возвращаемого значения + */ + fun finishWithResult(activeScreenRoute: SupportOnActivityResultRoute, result: T) + + /** + * Закрываает текущую активити c результатом + * + * @param currentScreenRoute маршрут текущего экрана + * @param result возвращаемый результат + * @param success показывает успешное ли завершение + * @param тип возвращаемого значения + */ + fun finishWithResult(currentScreenRoute: SupportOnActivityResultRoute, result: T, success: Boolean) + + /** + * Launch a new activity. + *

+ * Works synchronously. + * + * @param route navigation route + * @return true if activity started successfully, false otherwise + */ + fun start(route: ActivityRoute): Boolean + + /** + * Launch a new activity from another Feature Module. + *

+ * Performs asynchronously due to type of the target Feature Module. + * This method returns stream of install state change events. You can make a subscription in + * your Presenter and handle errors or any other type of events during Dynamic Feature + * installation. + * + * @param route navigation route + * @return stream of install state change events + */ + fun start(route: ActivityCrossFeatureRoute): Observable + + /** + * Launch a new Activity for result from another Feature Module. + *

+ * Performs asynchronously due to type of the target Feature Module. + * This method returns stream of install state change events. You can make a subscription in + * your Presenter and handle errors or any other type of events during Dynamic Feature + * installation. + * + * @param route navigation route + * @return stream of install state change events + */ + fun startForResult(route: CrossFeatureSupportOnActivityResultRoute<*>): Observable + + /** + * Launch a new activity for result. + * + * @param route navigation route + * @return true if activity started successfully, false otherwise + */ + fun startForResult(route: SupportOnActivityResultRoute<*>): Boolean + + /** + * позволяет подписываться на событие OnNewIntent + * + * @param newIntentRouteClass класс, отвечающий за парсинг intent + */ + fun observeNewIntent(newIntentRouteClass: Class): Observable + + /** + * позволяет подписываться на событие OnNewIntent + * + * @param newIntentRoute отвечает за парсинг intent + */ + fun observeNewIntent(newIntentRoute: T): Observable +} \ No newline at end of file diff --git a/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/activity/navigator/ActivityNavigatorForActivity.java b/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/activity/navigator/ActivityNavigatorForActivity.java index 750c3f0978..a1fe84509c 100755 --- a/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/activity/navigator/ActivityNavigatorForActivity.java +++ b/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/activity/navigator/ActivityNavigatorForActivity.java @@ -28,7 +28,7 @@ /** * ActivityNavigator working in Activity. */ -public class ActivityNavigatorForActivity extends ActivityNavigator { +public class ActivityNavigatorForActivity extends BaseActivityNavigator { private ActivityProvider activityProvider; diff --git a/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/activity/navigator/ActivityNavigatorForFragment.java b/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/activity/navigator/ActivityNavigatorForFragment.java index daf6e9651c..c139deaca1 100755 --- a/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/activity/navigator/ActivityNavigatorForFragment.java +++ b/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/activity/navigator/ActivityNavigatorForFragment.java @@ -29,7 +29,7 @@ /** * ActivityNavigator working in Fragment. */ -public class ActivityNavigatorForFragment extends ActivityNavigator { +public class ActivityNavigatorForFragment extends BaseActivityNavigator { private FragmentProvider fragmentProvider; diff --git a/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/activity/navigator/ActivityNavigator.java b/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/activity/navigator/BaseActivityNavigator.java similarity index 69% rename from core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/activity/navigator/ActivityNavigator.java rename to core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/activity/navigator/BaseActivityNavigator.java index 3a682d44c7..a24c11de2a 100755 --- a/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/activity/navigator/ActivityNavigator.java +++ b/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/activity/navigator/BaseActivityNavigator.java @@ -27,6 +27,8 @@ import com.agna.ferro.rx.ObservableOperatorFreeze; +import org.jetbrains.annotations.NotNull; + import java.io.Serializable; import java.util.HashMap; import java.util.Map; @@ -42,15 +44,14 @@ import ru.surfstudio.android.core.ui.event.lifecycle.pause.OnPauseDelegate; import ru.surfstudio.android.core.ui.event.lifecycle.resume.OnResumeDelegate; import ru.surfstudio.android.core.ui.event.newintent.NewIntentDelegate; -import ru.surfstudio.android.core.ui.navigation.event.result.BaseActivityResultDelegate; -import ru.surfstudio.android.core.ui.navigation.event.result.CrossFeatureSupportOnActivityResultRoute; -import ru.surfstudio.android.core.ui.navigation.event.result.SupportOnActivityResultRoute; import ru.surfstudio.android.core.ui.navigation.ActivityRouteInterface; -import ru.surfstudio.android.core.ui.navigation.Navigator; import ru.surfstudio.android.core.ui.navigation.ScreenResult; import ru.surfstudio.android.core.ui.navigation.activity.route.ActivityRoute; import ru.surfstudio.android.core.ui.navigation.activity.route.ActivityWithResultRoute; import ru.surfstudio.android.core.ui.navigation.activity.route.NewIntentRoute; +import ru.surfstudio.android.core.ui.navigation.event.result.BaseActivityResultDelegate; +import ru.surfstudio.android.core.ui.navigation.event.result.CrossFeatureSupportOnActivityResultRoute; +import ru.surfstudio.android.core.ui.navigation.event.result.SupportOnActivityResultRoute; import ru.surfstudio.android.core.ui.navigation.feature.installer.SplitFeatureEvent; import ru.surfstudio.android.core.ui.navigation.feature.installer.SplitFeatureInstallState; import ru.surfstudio.android.core.ui.navigation.feature.installer.SplitFeatureInstallStatus; @@ -59,16 +60,9 @@ import ru.surfstudio.android.core.ui.navigation.feature.route.feature.ActivityCrossFeatureRoute; import ru.surfstudio.android.core.ui.provider.ActivityProvider; -/** - * позволяет осуществлять навигацияю между активити - *

- * !!!В случае конфликтов возвращения результата между несколькими инстансами навигаторами - * можно рассмотреть добавление к RequestCode хеша от имени экрана контейнера - * Конфликт может возникнуть при открытии одинаковых экранов из, например, кастомной вью с - * презентером и родительской активити вью - */ -public abstract class ActivityNavigator extends BaseActivityResultDelegate - implements Navigator, NewIntentDelegate, OnCompletelyDestroyDelegate, OnResumeDelegate, OnPauseDelegate { + +public abstract class BaseActivityNavigator extends BaseActivityResultDelegate + implements ActivityNavigator, NewIntentDelegate, OnCompletelyDestroyDelegate, OnResumeDelegate, OnPauseDelegate { private Map newIntentSubjects = new HashMap<>(); private final ActivityProvider activityProvider; @@ -87,8 +81,8 @@ private interface OnFeatureInstallListener { * @param activityProvider actual Activity provider instance * @param eventDelegateManager screen event delegate manager instance */ - public ActivityNavigator(ActivityProvider activityProvider, - ScreenEventDelegateManager eventDelegateManager) { + public BaseActivityNavigator(ActivityProvider activityProvider, + ScreenEventDelegateManager eventDelegateManager) { this(activityProvider, eventDelegateManager, null, false); } @@ -103,10 +97,10 @@ public ActivityNavigator(ActivityProvider activityProvider, * has already been deployed to Google Play. Otherwise, cross-feature * navigation won't work at all. */ - public ActivityNavigator(ActivityProvider activityProvider, - ScreenEventDelegateManager eventDelegateManager, - SplitFeatureInstaller splitFeatureInstaller, - Boolean isSplitFeatureModeOn) { + public BaseActivityNavigator(ActivityProvider activityProvider, + ScreenEventDelegateManager eventDelegateManager, + SplitFeatureInstaller splitFeatureInstaller, + Boolean isSplitFeatureModeOn) { eventDelegateManager.registerDelegate(this); this.activityProvider = activityProvider; this.splitFeatureInstaller = splitFeatureInstaller; @@ -130,12 +124,9 @@ public void onPause() { freezeSelector.onNext(true); } - /** - * позволяет подписываться на событие OnActivityResult - * - * @param routeClass класс маршрута экрана, который должен вернуть результат - * @param тип возвращаемых данных - */ + + @NotNull + @Override public Observable> observeResult( Class> routeClass) { try { @@ -146,63 +137,41 @@ public Observable> observeResult( } } - /** - * позволяет подписываться на событие OnActivityResult - * - * @param route маршрут экрана, который должен вернуть результат - * @param тип возвращаемых данных - */ + + @NotNull + @Override public Observable> observeResult( - SupportOnActivityResultRoute route) { + @NotNull SupportOnActivityResultRoute route) { return super.observeOnActivityResult(route); } - /** - * Закрываает текущую активити - */ + + @Override public void finishCurrent() { activityProvider.get().finish(); } - /** - * Закрываает текущую активити Affinity - */ + public void finishAffinity() { ActivityCompat.finishAffinity(activityProvider.get()); } - /** - * Закрываает текущую активити c результатом - * - * @param activeScreenRoute маршрут текущего экрана - * @param success показывает успешное ли завершение - * @param тип возвращаемого значения - */ - public void finishWithResult(ActivityWithResultRoute activeScreenRoute, + + @Override + public void finishWithResult(@NotNull ActivityWithResultRoute activeScreenRoute, boolean success) { finishWithResult(activeScreenRoute, null, success); } - /** - * Закрываает текущую активити c результатом - * - * @param activeScreenRoute маршрут текущего экрана - * @param result возвращаемый результат - * @param тип возвращаемого значения - */ - public void finishWithResult(SupportOnActivityResultRoute activeScreenRoute, - T result) { + + @Override + public void finishWithResult(@NotNull SupportOnActivityResultRoute activeScreenRoute, + @NotNull T result) { finishWithResult(activeScreenRoute, result, true); } - /** - * Закрываает текущую активити c результатом - * - * @param currentScreenRoute маршрут текущего экрана - * @param result возвращаемый результат - * @param success показывает успешное ли завершение - * @param тип возвращаемого значения - */ + + @Override public void finishWithResult(SupportOnActivityResultRoute currentScreenRoute, T result, boolean success) { Intent resultIntent = currentScreenRoute.prepareResultIntent(result); @@ -212,14 +181,8 @@ public void finishWithResult(SupportOnActivityResultRou finishCurrent(); } - /** - * Launch a new activity. - *

- * Works synchronically. - * - * @param route navigation route - * @return {@code true} if activity started successfully, {@code false} otherwise - */ + + @Override public boolean start(ActivityRoute route) { Context context = activityProvider.get(); Intent intent = route.prepareIntent(context); @@ -230,33 +193,17 @@ public boolean start(ActivityRoute route) { return false; } - /** - * Launch a new activity from another Feature Module. - *

- * Performs asynchronically due to type of the target Feature Module. - * This method returns stream of install state change events. You can make a subscription in - * your Presenter and handle errors or any other type of events during Dynamic Feature - * installation. - * - * @param route navigation route - * @return stream of install state change events - */ - public Observable start(ActivityCrossFeatureRoute route) { + + @NotNull + @Override + public Observable start(@NotNull ActivityCrossFeatureRoute route) { return startCrossFeature(route, startStatusSubject -> performStart(route, startStatusSubject)); } - /** - * Launch a new Activity for result from another Feature Module. - *

- * Performs asynchronously due to type of the target Feature Module. - * This method returns stream of install state change events. You can make a subscription in - * your Presenter and handle errors or any other type of events during Dynamic Feature - * installation. - * - * @param route navigation route - * @return stream of install state change events - */ - public Observable startForResult(CrossFeatureSupportOnActivityResultRoute route) { + + @NotNull + @Override + public Observable startForResult(@NotNull CrossFeatureSupportOnActivityResultRoute route) { return startCrossFeature(route, startStatusSubject -> performStartForResult(route, startStatusSubject)); } @@ -298,13 +245,9 @@ private void emitFeatureInstallState(BehaviorSubject s splitFeatureInstallStateSubject.onNext(new SplitFeatureInstallState(status)); } - /** - * Launch a new activity for result. - * - * @param route navigation route - * @return {@code true} if activity started successfully, {@code false} otherwise - */ - public boolean startForResult(SupportOnActivityResultRoute route) { + + @Override + public boolean startForResult(@NotNull SupportOnActivityResultRoute route) { if (!super.isObserved(route)) { throw new IllegalStateException("route class " + route.getClass().getSimpleName() + " must be registered by method ActivityNavigator#observeResult"); @@ -354,11 +297,9 @@ public boolean onNewIntent(Intent intent) { return false; } - /** - * позволяет подписываться на событие OnNewIntent - * - * @param newIntentRouteClass класс, отвечающий за парсинг intent - */ + + @NotNull + @Override public Observable observeNewIntent(Class newIntentRouteClass) { try { return this.observeNewIntent(newIntentRouteClass.newInstance()); @@ -368,12 +309,10 @@ public Observable observeNewIntent(Class newInt } } - /** - * позволяет подписываться на событие OnNewIntent - * - * @param newIntentRoute отвечает за парсинг intent - */ - public Observable observeNewIntent(T newIntentRoute) { + + @NotNull + @Override + public Observable observeNewIntent(@NotNull T newIntentRoute) { tryRemoveDuplicateNewIntentEventSubjects(newIntentRoute); PublishSubject eventSubject = PublishSubject.create(); newIntentSubjects.put(newIntentRoute, eventSubject); diff --git a/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/activity/navigator/GlobalNavigator.kt b/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/activity/navigator/GlobalNavigator.kt new file mode 100644 index 0000000000..41671e1346 --- /dev/null +++ b/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/activity/navigator/GlobalNavigator.kt @@ -0,0 +1,19 @@ +package ru.surfstudio.android.core.ui.navigation.activity.navigator + +import ru.surfstudio.android.core.ui.navigation.Navigator +import ru.surfstudio.android.core.ui.navigation.activity.route.ActivityRoute + +/** + * глобальный навигатор для перехода по экранам не имея доступ + * к контексту активити (из слоя Interactor) + */ +interface GlobalNavigator : Navigator { + + /** + * Запуск активити. + * + * @param route роутер + * @return true если активити успешно запущен, иначе false + */ + fun start(route: ActivityRoute): Boolean +} \ No newline at end of file diff --git a/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/activity/navigator/GlobalNavigator.java b/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/activity/navigator/GlobalNavigatorImpl.java similarity index 74% rename from core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/activity/navigator/GlobalNavigator.java rename to core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/activity/navigator/GlobalNavigatorImpl.java index 2ea8a564bb..777bcbd9e0 100755 --- a/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/activity/navigator/GlobalNavigator.java +++ b/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/activity/navigator/GlobalNavigatorImpl.java @@ -21,28 +21,18 @@ import android.os.Bundle; import ru.surfstudio.android.activity.holder.ActiveActivityHolder; -import ru.surfstudio.android.core.ui.navigation.Navigator; import ru.surfstudio.android.core.ui.navigation.activity.route.ActivityRoute; -/** - * глобальный навигатор для перехода по экранам не имея доступ - * к контексту активити (из слоя Interactor) - */ -public class GlobalNavigator implements Navigator { +public class GlobalNavigatorImpl implements GlobalNavigator { private final Context context; private final ActiveActivityHolder activityHolder; - public GlobalNavigator(Context context, ActiveActivityHolder activityHolder) { + public GlobalNavigatorImpl(Context context, ActiveActivityHolder activityHolder) { this.context = context; this.activityHolder = activityHolder; } - /** - * Запуск активити. - * - * @param route роутер - * @return {@code true} если активити успешно запущен, иначе {@code false} - */ + @Override public boolean start(ActivityRoute route) { Activity activity = activityHolder.getActivity(); Context localContext = activity != null ? activity : context; diff --git a/dagger-scope/sample/src/main/java/ru/surfstudio/android/custom_scope_sample/app/dagger/AppModule.kt b/dagger-scope/sample/src/main/java/ru/surfstudio/android/custom_scope_sample/app/dagger/AppModule.kt index 317862fab0..ed6374c7be 100755 --- a/dagger-scope/sample/src/main/java/ru/surfstudio/android/custom_scope_sample/app/dagger/AppModule.kt +++ b/dagger-scope/sample/src/main/java/ru/surfstudio/android/custom_scope_sample/app/dagger/AppModule.kt @@ -7,6 +7,7 @@ import dagger.Provides import ru.surfstudio.android.activity.holder.ActiveActivityHolder import ru.surfstudio.android.connection.ConnectionProvider import ru.surfstudio.android.core.ui.navigation.activity.navigator.GlobalNavigator +import ru.surfstudio.android.core.ui.navigation.activity.navigator.GlobalNavigatorImpl import ru.surfstudio.android.custom_scope_sample.app.App import ru.surfstudio.android.dagger.scope.PerApplication import ru.surfstudio.android.rx.extension.scheduler.SchedulersProvider @@ -39,7 +40,7 @@ class AppModule(private val coreApp: App) { @Provides internal fun provideGlobalNavigator(context: Context, activityHolder: ActiveActivityHolder): GlobalNavigator { - return GlobalNavigator(context, activityHolder) + return GlobalNavigatorImpl(context, activityHolder) } @Provides diff --git a/filestorage/sample/src/main/java/ru/surfstudio/android/filestorage/sample/app/dagger/CustomAppModule.kt b/filestorage/sample/src/main/java/ru/surfstudio/android/filestorage/sample/app/dagger/CustomAppModule.kt index 7102811d2b..2a34bda436 100644 --- a/filestorage/sample/src/main/java/ru/surfstudio/android/filestorage/sample/app/dagger/CustomAppModule.kt +++ b/filestorage/sample/src/main/java/ru/surfstudio/android/filestorage/sample/app/dagger/CustomAppModule.kt @@ -6,6 +6,7 @@ import dagger.Provides import ru.surfstudio.android.activity.holder.ActiveActivityHolder import ru.surfstudio.android.connection.ConnectionProvider import ru.surfstudio.android.core.ui.navigation.activity.navigator.GlobalNavigator +import ru.surfstudio.android.core.ui.navigation.activity.navigator.GlobalNavigatorImpl import ru.surfstudio.android.dagger.scope.PerApplication import ru.surfstudio.android.filestorage.sample.app.CustomApp import ru.surfstudio.android.rx.extension.scheduler.SchedulersProvider @@ -38,7 +39,7 @@ class CustomAppModule(private val coreApp: CustomApp) { @Provides internal fun provideGlobalNavigator(context: Context, activityHolder: ActiveActivityHolder): GlobalNavigator { - return GlobalNavigator(context, activityHolder) + return GlobalNavigatorImpl(context, activityHolder) } @Provides diff --git a/permission/lib-permission/src/main/java/ru/surfstudio/android/core/ui/permission/PermissionManager.kt b/permission/lib-permission/src/main/java/ru/surfstudio/android/core/ui/permission/PermissionManager.kt index 6729c324eb..005b9a7a00 100755 --- a/permission/lib-permission/src/main/java/ru/surfstudio/android/core/ui/permission/PermissionManager.kt +++ b/permission/lib-permission/src/main/java/ru/surfstudio/android/core/ui/permission/PermissionManager.kt @@ -211,7 +211,7 @@ abstract class PermissionManager( private fun startAndObserveReturnFromScreen(route: ActivityWithResultRoute<*>): Completable = activityNavigator - .observeResult(route) + .observeResult(route) .firstElement() .flatMapCompletable { Completable.complete() } .doOnSubscribe { activityNavigator.startForResult(route) } diff --git a/template/base_feature/src/main/java/ru/surfstudio/standard/application/app/di/AppModule.kt b/template/base_feature/src/main/java/ru/surfstudio/standard/application/app/di/AppModule.kt index 1469ab71bb..ff0fd144d8 100644 --- a/template/base_feature/src/main/java/ru/surfstudio/standard/application/app/di/AppModule.kt +++ b/template/base_feature/src/main/java/ru/surfstudio/standard/application/app/di/AppModule.kt @@ -7,6 +7,7 @@ import dagger.Provides import ru.surfstudio.android.activity.holder.ActiveActivityHolder import ru.surfstudio.android.connection.ConnectionProvider import ru.surfstudio.android.core.ui.navigation.activity.navigator.GlobalNavigator +import ru.surfstudio.android.core.ui.navigation.activity.navigator.GlobalNavigatorImpl import ru.surfstudio.android.dagger.scope.PerApplication import ru.surfstudio.android.rx.extension.scheduler.SchedulersProvider import ru.surfstudio.android.rx.extension.scheduler.SchedulersProviderImpl @@ -43,7 +44,7 @@ class AppModule( context: Context, activityHolder: ActiveActivityHolder ): GlobalNavigator { - return GlobalNavigator(context, activityHolder) + return GlobalNavigatorImpl(context, activityHolder) } @Provides diff --git a/template/f-debug/src/main/java/ru/surfstudio/standard/f_debug/injector/DebugAppModule.kt b/template/f-debug/src/main/java/ru/surfstudio/standard/f_debug/injector/DebugAppModule.kt index 35f21a1543..c0175e2923 100644 --- a/template/f-debug/src/main/java/ru/surfstudio/standard/f_debug/injector/DebugAppModule.kt +++ b/template/f-debug/src/main/java/ru/surfstudio/standard/f_debug/injector/DebugAppModule.kt @@ -7,6 +7,7 @@ import dagger.Provides import ru.surfstudio.android.activity.holder.ActiveActivityHolder import ru.surfstudio.android.connection.ConnectionProvider import ru.surfstudio.android.core.ui.navigation.activity.navigator.GlobalNavigator +import ru.surfstudio.android.core.ui.navigation.activity.navigator.GlobalNavigatorImpl import ru.surfstudio.android.dagger.scope.PerApplication import ru.surfstudio.android.rx.extension.scheduler.SchedulersProvider import ru.surfstudio.android.rx.extension.scheduler.SchedulersProviderImpl @@ -47,7 +48,7 @@ class DebugAppModule( @Provides internal fun provideGlobalNavigator(context: Context, activityHolder: ActiveActivityHolder): GlobalNavigator { - return GlobalNavigator(context, activityHolder) + return GlobalNavigatorImpl(context, activityHolder) } @Provides diff --git a/template/small-test-utils/src/main/java/ru/surfstudio/standard/small_test_utils/di/modules/TestAppModule.kt b/template/small-test-utils/src/main/java/ru/surfstudio/standard/small_test_utils/di/modules/TestAppModule.kt index c366498800..06c5193829 100644 --- a/template/small-test-utils/src/main/java/ru/surfstudio/standard/small_test_utils/di/modules/TestAppModule.kt +++ b/template/small-test-utils/src/main/java/ru/surfstudio/standard/small_test_utils/di/modules/TestAppModule.kt @@ -7,6 +7,7 @@ import dagger.Provides import ru.surfstudio.android.activity.holder.ActiveActivityHolder import ru.surfstudio.android.connection.ConnectionProvider import ru.surfstudio.android.core.ui.navigation.activity.navigator.GlobalNavigator +import ru.surfstudio.android.core.ui.navigation.activity.navigator.GlobalNavigatorImpl import ru.surfstudio.android.dagger.scope.PerApplication import ru.surfstudio.android.rx.extension.scheduler.SchedulersProvider import ru.surfstudio.android.rx.extension.scheduler.SchedulersProviderImpl @@ -43,7 +44,7 @@ class TestAppModule( context: Context, activityHolder: ActiveActivityHolder ): GlobalNavigator { - return GlobalNavigator(context, activityHolder) + return GlobalNavigatorImpl(context, activityHolder) } @Provides From 7b96907749f661d41dc526172165e39af2a8eb07 Mon Sep 17 00:00:00 2001 From: Kartashov Andrey Date: Tue, 6 Apr 2021 13:50:50 +0300 Subject: [PATCH 02/17] ANDDEP-1207 extracted FragmentNavigator, TabFragmentNavigator interfaces --- .../dagger/activity/DefaultActivityModule.kt | 3 +- .../fragment/ChildFragmentNavigator.java | 2 +- .../navigation/fragment/FragmentNavigator.kt | 63 +++ ...igator.java => FragmentNavigatorImpl.java} | 60 +-- .../tabfragment/TabFragmentNavigator.kt | 357 +---------------- .../tabfragment/TabFragmentNavigatorImpl.kt | 359 ++++++++++++++++++ .../ui/base/dagger/activity/ActivityModule.kt | 3 +- .../ui/base/di/NavigationScreenModule.kt | 3 +- .../sample/ui/screen/main/MainPresenter.kt | 4 +- .../standard/ui/activity/di/ActivityModule.kt | 11 +- .../injector/ui/DebugActivityModule.kt | 11 +- 11 files changed, 466 insertions(+), 410 deletions(-) create mode 100644 core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/fragment/FragmentNavigator.kt rename core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/fragment/{FragmentNavigator.java => FragmentNavigatorImpl.java} (75%) mode change 100755 => 100644 core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/fragment/tabfragment/TabFragmentNavigator.kt create mode 100755 core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/fragment/tabfragment/TabFragmentNavigatorImpl.kt diff --git a/common/lib-sample-dagger/src/main/java/ru/surfstudio/android/sample/dagger/ui/base/dagger/activity/DefaultActivityModule.kt b/common/lib-sample-dagger/src/main/java/ru/surfstudio/android/sample/dagger/ui/base/dagger/activity/DefaultActivityModule.kt index f0a630dbd1..e38b8927cf 100755 --- a/common/lib-sample-dagger/src/main/java/ru/surfstudio/android/sample/dagger/ui/base/dagger/activity/DefaultActivityModule.kt +++ b/common/lib-sample-dagger/src/main/java/ru/surfstudio/android/sample/dagger/ui/base/dagger/activity/DefaultActivityModule.kt @@ -8,6 +8,7 @@ import ru.surfstudio.android.core.ui.event.ScreenEventDelegateManager import ru.surfstudio.android.core.ui.navigation.activity.navigator.ActivityNavigator import ru.surfstudio.android.core.ui.navigation.activity.navigator.ActivityNavigatorForActivity import ru.surfstudio.android.core.ui.navigation.fragment.FragmentNavigator +import ru.surfstudio.android.core.ui.navigation.fragment.FragmentNavigatorImpl import ru.surfstudio.android.core.ui.permission.PermissionManager import ru.surfstudio.android.core.ui.permission.PermissionManagerForActivity import ru.surfstudio.android.core.ui.provider.ActivityProvider @@ -97,7 +98,7 @@ class DefaultActivityModule(private val persistentScope: ActivityPersistentScope @Provides @PerActivity internal fun provideFragmentNavigator(activityProvider: ActivityProvider): FragmentNavigator { - return FragmentNavigator(activityProvider) + return FragmentNavigatorImpl(activityProvider) } @Provides diff --git a/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/fragment/ChildFragmentNavigator.java b/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/fragment/ChildFragmentNavigator.java index 0fb06fa1a6..b64f85d1f6 100755 --- a/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/fragment/ChildFragmentNavigator.java +++ b/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/fragment/ChildFragmentNavigator.java @@ -31,7 +31,7 @@ * Изначально ядром не поставляется, поскольку не должно быть кейсов его использования, * но класс оставлен на всякий случай =) */ -public class ChildFragmentNavigator extends FragmentNavigator { +public class ChildFragmentNavigator extends FragmentNavigatorImpl { private final FragmentProvider fragmentProvider; public ChildFragmentNavigator(ActivityProvider activityProvider, diff --git a/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/fragment/FragmentNavigator.kt b/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/fragment/FragmentNavigator.kt new file mode 100644 index 0000000000..658a525d4b --- /dev/null +++ b/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/fragment/FragmentNavigator.kt @@ -0,0 +1,63 @@ +package ru.surfstudio.android.core.ui.navigation.fragment + +import android.app.FragmentTransaction.* +import androidx.annotation.IntDef +import ru.surfstudio.android.core.ui.navigation.Navigator +import ru.surfstudio.android.core.ui.navigation.fragment.route.FragmentRoute + +/** + * позволяет осуществлять навигацияю между фрагментами + */ +interface FragmentNavigator : Navigator { + + fun add(route: FragmentRoute?, stackable: Boolean, @Transit transition: Int) + + fun replace(route: FragmentRoute?, stackable: Boolean, @Transit transition: Int) + + /** + * @return возвращает true если фрагмент был удален успешно + */ + fun remove(route: FragmentRoute?, @Transit transition: Int): Boolean + + /** + * @return возвращает true если фрагмент успешно отобразился + */ + fun show(route: FragmentRoute?, @Transit transition: Int): Boolean + + /** + * @return возвращает true если фрагмент был скрыт успешно + */ + fun hide(route: FragmentRoute?, @Transit transition: Int): Boolean + + /** + * @return возвращает true если какой-либо фрагмент верхнего уровня был удален из стека + */ + fun popBackStack(): Boolean + + /** + * Очищает стек фрагментов до роута. + * Пример: + * Фрагменты А, Б, С, Д добавлены в стек + * popBackStack(Б, true) очистит стек до Б включительно + * то есть, останется в стеке только А + * или + * popBackStack(Б, false) в стеке останется А и Б, + * Б не удаляется из стека + * + * @param route очистка до уровня роута + * @param inclusive удалить стек включая и роут + * @return возвращает true если фрагмент(ы) был(и) удален(ы) из стека + */ + fun popBackStack(route: FragmentRoute, inclusive: Boolean): Boolean + + /** + * Очистка бэкстека + * + * @return true если успешно + */ + fun clearBackStack(): Boolean + + @IntDef(TRANSIT_NONE, TRANSIT_FRAGMENT_OPEN, TRANSIT_FRAGMENT_CLOSE, TRANSIT_FRAGMENT_FADE) + @Retention(AnnotationRetention.SOURCE) + annotation class Transit +} \ No newline at end of file diff --git a/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/fragment/FragmentNavigator.java b/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/fragment/FragmentNavigatorImpl.java similarity index 75% rename from core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/fragment/FragmentNavigator.java rename to core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/fragment/FragmentNavigatorImpl.java index 5af6c84cf9..1db1f0ff39 100755 --- a/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/fragment/FragmentNavigator.java +++ b/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/fragment/FragmentNavigatorImpl.java @@ -17,40 +17,24 @@ import androidx.annotation.IdRes; -import androidx.annotation.IntDef; import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - import ru.surfstudio.android.core.ui.FragmentContainer; -import ru.surfstudio.android.core.ui.navigation.Navigator; import ru.surfstudio.android.core.ui.navigation.fragment.route.FragmentRoute; import ru.surfstudio.android.core.ui.provider.ActivityProvider; -import static android.app.FragmentTransaction.TRANSIT_FRAGMENT_CLOSE; -import static android.app.FragmentTransaction.TRANSIT_FRAGMENT_FADE; -import static android.app.FragmentTransaction.TRANSIT_FRAGMENT_OPEN; -import static android.app.FragmentTransaction.TRANSIT_NONE; -/** - * позволяет осуществлять навигацияю между фрагментами - */ -public class FragmentNavigator implements Navigator { +public class FragmentNavigatorImpl implements FragmentNavigator { protected final ActivityProvider activityProvider; - @IntDef({TRANSIT_NONE, TRANSIT_FRAGMENT_OPEN, TRANSIT_FRAGMENT_CLOSE, TRANSIT_FRAGMENT_FADE}) - @Retention(RetentionPolicy.SOURCE) - private @interface Transit { - } - - public FragmentNavigator(ActivityProvider activityProvider) { + public FragmentNavigatorImpl(ActivityProvider activityProvider) { this.activityProvider = activityProvider; } + @Override public void add(FragmentRoute route, boolean stackable, @Transit int transition) { int viewContainerId = getViewContainerIdOrThrow(); FragmentManager fragmentManager = getFragmentManager(); @@ -66,6 +50,7 @@ public void add(FragmentRoute route, boolean stackable, @Transit int transition) fragmentTransaction.commit(); } + @Override public void replace(FragmentRoute route, boolean stackable, @Transit int transition) { int viewContainerId = getViewContainerIdOrThrow(); FragmentManager fragmentManager = getFragmentManager(); @@ -81,9 +66,7 @@ public void replace(FragmentRoute route, boolean stackable, @Transit int transit fragmentTransaction.commit(); } - /** - * @return возвращает {@code true} если фрагмент был удален успешно - */ + @Override public boolean remove(FragmentRoute route, @Transit int transition) { FragmentManager fragmentManager = getFragmentManager(); fragmentManager.executePendingTransactions(); @@ -101,23 +84,17 @@ public boolean remove(FragmentRoute route, @Transit int transition) { return true; } - /** - * @return возвращает {@code true} если фрагмент успешно отобразился - */ + @Override public boolean show(FragmentRoute route, @Transit int transition) { return toggleVisibility(route, true, transition); } - /** - * @return возвращает {@code true} если фрагмент был скрыт успешно - */ + @Override public boolean hide(FragmentRoute route, @Transit int transition) { return toggleVisibility(route, false, transition); } - /** - * @return возвращает {@code true} если какой-либо фрагмент верхнего уровня был удален из стека - */ + @Override public boolean popBackStack() { FragmentManager fragmentManager = getFragmentManager(); fragmentManager.executePendingTransactions(); @@ -125,20 +102,7 @@ public boolean popBackStack() { return fragmentManager.popBackStackImmediate(); } - /** - * Очищает стек фрагментов до роута. - * Пример: - * Фрагменты А, Б, С, Д добавлены в стек - * popBackStack(Б, true) очистит стек до Б включительно - * то есть, останется в стеке только А - * или - * popBackStack(Б, false) в стеке останется А и Б, - * Б не удаляется из стека - * - * @param route очистка до уровня роута - * @param inclusive удалить стек включая и роут - * @return возвращает {@code true} если фрагмент(ы) был(и) удален(ы) из стека - */ + @Override public boolean popBackStack(@NonNull FragmentRoute route, boolean inclusive) { FragmentManager fragmentManager = getFragmentManager(); fragmentManager.executePendingTransactions(); @@ -173,11 +137,7 @@ public boolean popBackStack(@NonNull FragmentRoute route, boolean inclusive) { inclusive ? FragmentManager.POP_BACK_STACK_INCLUSIVE : 0); } - /** - * Очистка бэкстека - * - * @return true если успешно - */ + @Override public boolean clearBackStack() { FragmentManager fragmentManager = getFragmentManager(); fragmentManager.executePendingTransactions(); diff --git a/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/fragment/tabfragment/TabFragmentNavigator.kt b/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/fragment/tabfragment/TabFragmentNavigator.kt old mode 100755 new mode 100644 index ada0e439a4..520e180dba --- a/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/fragment/tabfragment/TabFragmentNavigator.kt +++ b/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/fragment/tabfragment/TabFragmentNavigator.kt @@ -1,385 +1,50 @@ -/* - Copyright (c) 2018-present, SurfStudio LLC, Maxim Tuev. - - 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.surfstudio.android.core.ui.navigation.fragment.tabfragment -import android.annotation.SuppressLint -import android.os.Bundle -import androidx.annotation.IdRes -import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentTransaction import io.reactivex.Observable -import io.reactivex.subjects.PublishSubject -import org.json.JSONArray -import ru.surfstudio.android.core.ui.FragmentContainer -import ru.surfstudio.android.core.ui.event.ScreenEventDelegateManager -import ru.surfstudio.android.core.ui.event.back.OnBackPressedDelegate import ru.surfstudio.android.core.ui.event.back.OnBackPressedEvent -import ru.surfstudio.android.core.ui.event.lifecycle.state.OnRestoreStateDelegate -import ru.surfstudio.android.core.ui.event.lifecycle.state.OnSaveStateDelegate import ru.surfstudio.android.core.ui.navigation.Navigator -import ru.surfstudio.android.core.ui.navigation.fragment.FragmentNavigator import ru.surfstudio.android.core.ui.navigation.fragment.route.FragmentRoute import ru.surfstudio.android.core.ui.navigation.fragment.route.RootFragmentRoute -import ru.surfstudio.android.core.ui.provider.ActivityProvider -import ru.surfstudio.android.logger.Logger -import java.util.* /** * Навигатор для фрагментов в табах */ -open class TabFragmentNavigator( - val activityProvider: ActivityProvider, - eventDelegateManager: ScreenEventDelegateManager -) - : Navigator, - OnBackPressedDelegate, - OnRestoreStateDelegate, - OnSaveStateDelegate { - - private val EXTRA_CURRENT_TAB_TAG: String = TabFragmentNavigator::class.toString() + "CURRENT_TAB_TAG" - private val EXTRA_CURRENT_FRAGMENT: String = TabFragmentNavigator::class.toString() + "CURRENT_FRAGMENT_TAG" - private val EXTRA_FRAGMENT_STACK: String = TabFragmentNavigator::class.toString() + "FRAGMENT_STACK" - - private val fragmentNavigator: FragmentNavigator = FragmentNavigator(activityProvider) - - private var activeTabTag: String? = null - private val fragmentMap: HashMap> = hashMapOf() - - private val fragmentManager get() = activityProvider.get().supportFragmentManager - private val activeStack get() = fragmentMap[activeTabTag] ?: Stack() - private val activeTagsStack get() = activeStack.map { it.tag } - private val currentFragment get() = if (!activeStack.empty()) activeStack.peek() else null +interface TabFragmentNavigator : Navigator { - private val onBackPressEventSubject = PublishSubject.create() - val backPressedEventObservable: Observable get() = onBackPressEventSubject + val backPressedEventObservable: Observable - private val activeTabReOpenSubject = PublishSubject.create() - val activeTabReOpenObservable: Observable get() = activeTabReOpenSubject - - init { - eventDelegateManager.registerDelegate(this) - } + val activeTabReOpenObservable: Observable /** * Показ фрагмента */ - fun open(route: FragmentRoute) { - if (route is RootFragmentRoute) { - showRoot(route) - } else { - showChild(route) - } - } + fun open(route: FragmentRoute) /** * Переход к фрагменту на конкретном табе * @param clearStack флаг очистки стека под открывающимся фрагментом */ fun showAtTab(tabRoute: T, fragmentRoute: FragmentRoute, clearStack: Boolean = false) - where T : FragmentRoute, - T : RootFragmentRoute { - showRoot(tabRoute) - //чистим стек под тем фрагментом, который открываем на табе - if (clearStack) { - clearStack() - } - - showChild(fragmentRoute) - } + where T : FragmentRoute, + T : RootFragmentRoute /** * Замена текущего фрагмента новым c заменой в стеке */ - fun replace(fragmentRoute: FragmentRoute) { - if (activeStack.isNotEmpty()) { - activeStack.pop() - } - val fragment = fragmentRoute.createFragment() - replace(fragment, fragmentRoute.tag) - activeStack.push(fragment) - } + fun replace(fragmentRoute: FragmentRoute) /** * Чистит стек выбранных табов, и показывает активный */ - fun clearTabs(vararg routes: T) where T : FragmentRoute, T : RootFragmentRoute { - for (r in routes) { - fragmentMap[r.tag] - ?.drop(1) //пропускаем корневой таб - ?.forEach { - fragmentMap[r.tag]?.pop() - replace(activeStack.firstElement(), activeTabTag) - } - } - } + fun clearTabs(vararg routes: T) where T : FragmentRoute, T : RootFragmentRoute /** * Чистит стек активного таба */ - fun clearStack() { - popStack(activeStack.size - 1) - } + fun clearStack() /** * Очистка активного стека до определенного фрагмента по его роуту */ - fun clearStackTo(route: FragmentRoute) { - if (!activeTagsStack.contains(route.tag)) { - throw IllegalStateException("активный таб $activeTabTag не содержит такого фрагмента ${route.tag}") - } - - val popDepth = activeTagsStack.lastIndex - activeTagsStack.indexOf(route.tag) - - popStack(popDepth) - } - - /** - * Очистка стека на определенную глубину - * @param popDepth глубина, на которую надо чистить стек (по умолчанию на один фрагмент) - */ - private fun popStack(popDepth: Int = 1) { - if (popDepth < 0) return - activeStack.takeLast(popDepth) - .forEach { - remove(activeStack.pop().tag) - } - } - - /** - * Добавляет фрагмент в стек активного таба - */ - private fun addToStack(route: FragmentRoute) { - val fragment = route.createFragment() - - replace(fragment, route.tag) - activeStack.push(fragment) - } - - /** - * Показывает корневой фрагмент - */ - private fun showRoot(route: FragmentRoute) { - if (fragmentMap.keys.contains(route.tag)) { - //открывает существующий таб (+ проверка на активность таба -> ex. сброс стека при повторном выборе) - if (activeTabTag != route.tag) { - showExistent(route.tag) - } else { - //Оповещаем об повторном тапе на открытый таб - activeTabReOpenSubject.onNext(Unit) - } - } else { - addRoot(route) //добавляем фрагмент в мапу - } - } - - /** - * Показывает фрагмент на табе - */ - @SuppressLint("WrongConstant") - private fun showChild(route: FragmentRoute) { - if (fragmentMap.isEmpty()) { - fragmentNavigator.add(route, false, FragmentTransaction.TRANSIT_FRAGMENT_OPEN) - } else { - if (activeTagsStack.contains(route.tag)) { - clearStackTo(route) - } else { - addToStack(route) - } - } - } - - /** - * Добавление рутового фрагмента - */ - private fun addRoot( - fragmentRoute: FragmentRoute, - transition: Int = FragmentTransaction.TRANSIT_FRAGMENT_OPEN - ) { - val fragment = fragmentRoute.createFragment() - activeTabTag = fragmentRoute.tag - replace(fragment, fragmentRoute.tag, transition) - - val stack = Stack() - stack.push(fragment) - fragmentMap[fragmentRoute.tag] = stack - } - - /** - * Показывает существующий фрагмент - */ - private fun showExistent(routeTag: String) { - activeTabTag = routeTag - replace(activeStack.peek(), activeStack.peek().tag) - } - - private fun replace( - fragment: Fragment, - routeTag: String?, - transition: Int = FragmentTransaction.TRANSIT_FRAGMENT_OPEN - ) { - fragmentManager.executePendingTransactions() - - val fragmentTransaction = fragmentManager.beginTransaction() - detachAll(fragmentTransaction) - if (isFragmentExistInMap(fragment)) { - fragmentTransaction.attach(fragment) - } else { - add(fragment, routeTag, fragmentTransaction, transition) - } - - fragmentTransaction.commitAllowingStateLoss() - //commitAllowingStateLoss жёстко решает проблему редкого непонятного крэша - //https://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html - } - - private fun add( - fragment: Fragment, - routeTag: String?, - fragmentTransaction: FragmentTransaction, - transition: Int = FragmentTransaction.TRANSIT_FRAGMENT_FADE - ) { - val viewContainerId = getViewContainerIdOrThrow() - fragmentTransaction.add(viewContainerId, fragment, routeTag) - fragmentTransaction.setTransition(transition) - } - - private fun detachAll(fragmentTransaction: FragmentTransaction) { - fragmentMap.forEach { (_, stack) -> - stack.forEach { - Logger.i("Detach : ${it.tag}") - fragmentTransaction.detach(it) - } - } - } - - private fun isFragmentExistInMap(fragment: Fragment): Boolean = fragmentMap.values.any { it.any { it.tag == fragment.tag } } - - private fun remove(routeTag: String?, transition: Int = FragmentTransaction.TRANSIT_FRAGMENT_CLOSE): Boolean { - fragmentManager.executePendingTransactions() - - val fragment = fragmentManager.findFragmentByTag(routeTag) ?: return false - - fragmentManager.beginTransaction() - .setTransition(transition) - .remove(fragment) - .commitAllowingStateLoss() - //commitAllowingStateLoss жёстко решает проблему редкого непонятного крэша - //https://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html - - val fragmentToShow = activeStack.peek() - replace(fragmentToShow, activeStack.peek().tag) - - return true - } - - @IdRes - protected fun getViewContainerIdOrThrow(): Int { - val contentContainerView = activityProvider.get() - if (contentContainerView is FragmentContainer) { - val viewContainerId = (contentContainerView as FragmentContainer).contentContainerViewId - if (viewContainerId > 0) { - return viewContainerId - } - } - - throw IllegalStateException("Container has to have a ContentViewContainer " + "implementation in order to make fragment navigation") - } - - override fun onBackPressed(): Boolean { - if (activeStack.size <= 1) { - onBackPressEventSubject.onNext(OnBackPressedEvent()) - } else { - popStack() - } - - return true - } - - override fun onSaveState(outState: Bundle?) { - Logger.i("onSaveState TabFragmentNavigator bundle = $outState") - if (outState != null) onSaveInstanceState(outState) - } - - override fun onRestoreState(savedInstanceState: Bundle?) { - Logger.i("onRestoreState TabFragmentNavigator bundle = $savedInstanceState") - restoreFromBundle(savedInstanceState) - } - - private fun onSaveInstanceState(outState: Bundle) { - outState.putString(EXTRA_CURRENT_TAB_TAG, activeTabTag) - - currentFragment?.let { - outState.putString(EXTRA_CURRENT_FRAGMENT, it.tag) - } - - try { - val stackArrays = JSONArray() - - fragmentMap.forEach { (_, stack) -> - val stackArray = JSONArray() - val stackIterator = stack.iterator() - - while (stackIterator.hasNext()) { - val fragment = stackIterator.next() as Fragment - stackArray.put(fragment.tag) - } - stackArrays.put(stackArray) - } - - outState.putString(EXTRA_FRAGMENT_STACK, stackArrays.toString()) - } catch (t: Throwable) { - Logger.e(t) - } - } - - private fun restoreFromBundle(savedInstanceState: Bundle?): Boolean { - if (savedInstanceState == null) { - return false - } else { - return try { - val stackArrays = JSONArray(savedInstanceState.getString(EXTRA_FRAGMENT_STACK)) - - var x = 0 - while (x < stackArrays.length()) { - val stackArray = stackArrays.getJSONArray(x) - val stack = Stack() - val tagList = mutableListOf() - - (0 until stackArray.length()) - .map { stackArray.getString(it) } - .filter { it != null && !"null".equals(it, ignoreCase = true) } - .onEach { tagList.add(it) } - .mapNotNull { fragmentManager.findFragmentByTag(it) } - .forEach { stack.push(it) } - - fragmentMap[tagList.first()] = stack - ++x - } - - Logger.i("TabFragmentNavigator restoreFromBundle after restore map = $fragmentMap") - - val savedCurrentTabTag = savedInstanceState.getString(EXTRA_CURRENT_TAB_TAG) ?: "" - showExistent(savedCurrentTabTag) - - true - } catch (t: Throwable) { - Logger.e(t) - false - } - - } - } -} + fun clearStackTo(route: FragmentRoute) +} \ No newline at end of file diff --git a/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/fragment/tabfragment/TabFragmentNavigatorImpl.kt b/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/fragment/tabfragment/TabFragmentNavigatorImpl.kt new file mode 100755 index 0000000000..ca71c0c139 --- /dev/null +++ b/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/fragment/tabfragment/TabFragmentNavigatorImpl.kt @@ -0,0 +1,359 @@ +/* + Copyright (c) 2018-present, SurfStudio LLC, Maxim Tuev. + + 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.surfstudio.android.core.ui.navigation.fragment.tabfragment + +import android.annotation.SuppressLint +import android.os.Bundle +import androidx.annotation.IdRes +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentTransaction +import io.reactivex.Observable +import io.reactivex.subjects.PublishSubject +import org.json.JSONArray +import ru.surfstudio.android.core.ui.FragmentContainer +import ru.surfstudio.android.core.ui.event.ScreenEventDelegateManager +import ru.surfstudio.android.core.ui.event.back.OnBackPressedDelegate +import ru.surfstudio.android.core.ui.event.back.OnBackPressedEvent +import ru.surfstudio.android.core.ui.event.lifecycle.state.OnRestoreStateDelegate +import ru.surfstudio.android.core.ui.event.lifecycle.state.OnSaveStateDelegate +import ru.surfstudio.android.core.ui.navigation.fragment.FragmentNavigator +import ru.surfstudio.android.core.ui.navigation.fragment.route.FragmentRoute +import ru.surfstudio.android.core.ui.navigation.fragment.route.RootFragmentRoute +import ru.surfstudio.android.core.ui.provider.ActivityProvider +import ru.surfstudio.android.logger.Logger +import java.util.* + +open class TabFragmentNavigatorImpl( + private val activityProvider: ActivityProvider, + private val fragmentNavigator: FragmentNavigator, + eventDelegateManager: ScreenEventDelegateManager +) : TabFragmentNavigator, + OnBackPressedDelegate, + OnRestoreStateDelegate, + OnSaveStateDelegate { + + private val EXTRA_CURRENT_TAB_TAG: String = TabFragmentNavigatorImpl::class.toString() + "CURRENT_TAB_TAG" + private val EXTRA_CURRENT_FRAGMENT: String = TabFragmentNavigatorImpl::class.toString() + "CURRENT_FRAGMENT_TAG" + private val EXTRA_FRAGMENT_STACK: String = TabFragmentNavigatorImpl::class.toString() + "FRAGMENT_STACK" + + private var activeTabTag: String? = null + private val fragmentMap: HashMap> = hashMapOf() + + private val fragmentManager get() = activityProvider.get().supportFragmentManager + private val activeStack get() = fragmentMap[activeTabTag] ?: Stack() + private val activeTagsStack get() = activeStack.map { it.tag } + private val currentFragment get() = if (!activeStack.empty()) activeStack.peek() else null + + private val onBackPressEventSubject = PublishSubject.create() + override val backPressedEventObservable: Observable get() = onBackPressEventSubject + + private val activeTabReOpenSubject = PublishSubject.create() + override val activeTabReOpenObservable: Observable get() = activeTabReOpenSubject + + init { + eventDelegateManager.registerDelegate(this) + } + + override fun open(route: FragmentRoute) { + if (route is RootFragmentRoute) { + showRoot(route) + } else { + showChild(route) + } + } + + override fun showAtTab(tabRoute: T, fragmentRoute: FragmentRoute, clearStack: Boolean) + where T : FragmentRoute, + T : RootFragmentRoute { + showRoot(tabRoute) + //чистим стек под тем фрагментом, который открываем на табе + if (clearStack) { + clearStack() + } + + showChild(fragmentRoute) + } + + override fun replace(fragmentRoute: FragmentRoute) { + if (activeStack.isNotEmpty()) { + activeStack.pop() + } + val fragment = fragmentRoute.createFragment() + replace(fragment, fragmentRoute.tag) + activeStack.push(fragment) + } + + override fun clearTabs(vararg routes: T) where T : FragmentRoute, T : RootFragmentRoute { + for (r in routes) { + fragmentMap[r.tag] + ?.drop(1) //пропускаем корневой таб + ?.forEach { + fragmentMap[r.tag]?.pop() + replace(activeStack.firstElement(), activeTabTag) + } + } + } + + override fun clearStack() { + popStack(activeStack.size - 1) + } + + override fun clearStackTo(route: FragmentRoute) { + if (!activeTagsStack.contains(route.tag)) { + throw IllegalStateException("активный таб $activeTabTag не содержит такого фрагмента ${route.tag}") + } + + val popDepth = activeTagsStack.lastIndex - activeTagsStack.indexOf(route.tag) + + popStack(popDepth) + } + + /** + * Очистка стека на определенную глубину + * @param popDepth глубина, на которую надо чистить стек (по умолчанию на один фрагмент) + */ + private fun popStack(popDepth: Int = 1) { + if (popDepth < 0) return + activeStack.takeLast(popDepth) + .forEach { + remove(activeStack.pop().tag) + } + } + + /** + * Добавляет фрагмент в стек активного таба + */ + private fun addToStack(route: FragmentRoute) { + val fragment = route.createFragment() + + replace(fragment, route.tag) + activeStack.push(fragment) + } + + /** + * Показывает корневой фрагмент + */ + private fun showRoot(route: FragmentRoute) { + if (fragmentMap.keys.contains(route.tag)) { + //открывает существующий таб (+ проверка на активность таба -> ex. сброс стека при повторном выборе) + if (activeTabTag != route.tag) { + showExistent(route.tag) + } else { + //Оповещаем об повторном тапе на открытый таб + activeTabReOpenSubject.onNext(Unit) + } + } else { + addRoot(route) //добавляем фрагмент в мапу + } + } + + /** + * Показывает фрагмент на табе + */ + @SuppressLint("WrongConstant") + private fun showChild(route: FragmentRoute) { + if (fragmentMap.isEmpty()) { + fragmentNavigator.add(route, false, FragmentTransaction.TRANSIT_FRAGMENT_OPEN) + } else { + if (activeTagsStack.contains(route.tag)) { + clearStackTo(route) + } else { + addToStack(route) + } + } + } + + /** + * Добавление рутового фрагмента + */ + private fun addRoot( + fragmentRoute: FragmentRoute, + transition: Int = FragmentTransaction.TRANSIT_FRAGMENT_OPEN + ) { + val fragment = fragmentRoute.createFragment() + activeTabTag = fragmentRoute.tag + replace(fragment, fragmentRoute.tag, transition) + + val stack = Stack() + stack.push(fragment) + fragmentMap[fragmentRoute.tag] = stack + } + + /** + * Показывает существующий фрагмент + */ + private fun showExistent(routeTag: String) { + activeTabTag = routeTag + replace(activeStack.peek(), activeStack.peek().tag) + } + + private fun replace( + fragment: Fragment, + routeTag: String?, + transition: Int = FragmentTransaction.TRANSIT_FRAGMENT_OPEN + ) { + fragmentManager.executePendingTransactions() + + val fragmentTransaction = fragmentManager.beginTransaction() + detachAll(fragmentTransaction) + if (isFragmentExistInMap(fragment)) { + fragmentTransaction.attach(fragment) + } else { + add(fragment, routeTag, fragmentTransaction, transition) + } + + fragmentTransaction.commitAllowingStateLoss() + //commitAllowingStateLoss жёстко решает проблему редкого непонятного крэша + //https://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html + } + + private fun add( + fragment: Fragment, + routeTag: String?, + fragmentTransaction: FragmentTransaction, + transition: Int = FragmentTransaction.TRANSIT_FRAGMENT_FADE + ) { + val viewContainerId = getViewContainerIdOrThrow() + fragmentTransaction.add(viewContainerId, fragment, routeTag) + fragmentTransaction.setTransition(transition) + } + + private fun detachAll(fragmentTransaction: FragmentTransaction) { + fragmentMap.forEach { (_, stack) -> + stack.forEach { + Logger.i("Detach : ${it.tag}") + fragmentTransaction.detach(it) + } + } + } + + private fun isFragmentExistInMap(fragment: Fragment): Boolean = fragmentMap.values.any { it.any { it.tag == fragment.tag } } + + private fun remove(routeTag: String?, transition: Int = FragmentTransaction.TRANSIT_FRAGMENT_CLOSE): Boolean { + fragmentManager.executePendingTransactions() + + val fragment = fragmentManager.findFragmentByTag(routeTag) ?: return false + + fragmentManager.beginTransaction() + .setTransition(transition) + .remove(fragment) + .commitAllowingStateLoss() + //commitAllowingStateLoss жёстко решает проблему редкого непонятного крэша + //https://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html + + val fragmentToShow = activeStack.peek() + replace(fragmentToShow, activeStack.peek().tag) + + return true + } + + @IdRes + protected fun getViewContainerIdOrThrow(): Int { + val contentContainerView = activityProvider.get() + if (contentContainerView is FragmentContainer) { + val viewContainerId = (contentContainerView as FragmentContainer).contentContainerViewId + if (viewContainerId > 0) { + return viewContainerId + } + } + + throw IllegalStateException("Container has to have a ContentViewContainer " + "implementation in order to make fragment navigation") + } + + override fun onBackPressed(): Boolean { + if (activeStack.size <= 1) { + onBackPressEventSubject.onNext(OnBackPressedEvent()) + } else { + popStack() + } + + return true + } + + override fun onSaveState(outState: Bundle?) { + Logger.i("onSaveState TabFragmentNavigator bundle = $outState") + if (outState != null) onSaveInstanceState(outState) + } + + override fun onRestoreState(savedInstanceState: Bundle?) { + Logger.i("onRestoreState TabFragmentNavigator bundle = $savedInstanceState") + restoreFromBundle(savedInstanceState) + } + + private fun onSaveInstanceState(outState: Bundle) { + outState.putString(EXTRA_CURRENT_TAB_TAG, activeTabTag) + + currentFragment?.let { + outState.putString(EXTRA_CURRENT_FRAGMENT, it.tag) + } + + try { + val stackArrays = JSONArray() + + fragmentMap.forEach { (_, stack) -> + val stackArray = JSONArray() + val stackIterator = stack.iterator() + + while (stackIterator.hasNext()) { + val fragment = stackIterator.next() as Fragment + stackArray.put(fragment.tag) + } + stackArrays.put(stackArray) + } + + outState.putString(EXTRA_FRAGMENT_STACK, stackArrays.toString()) + } catch (t: Throwable) { + Logger.e(t) + } + } + + private fun restoreFromBundle(savedInstanceState: Bundle?): Boolean { + if (savedInstanceState == null) { + return false + } else { + return try { + val stackArrays = JSONArray(savedInstanceState.getString(EXTRA_FRAGMENT_STACK)) + + var x = 0 + while (x < stackArrays.length()) { + val stackArray = stackArrays.getJSONArray(x) + val stack = Stack() + val tagList = mutableListOf() + + (0 until stackArray.length()) + .map { stackArray.getString(it) } + .filter { it != null && !"null".equals(it, ignoreCase = true) } + .onEach { tagList.add(it) } + .mapNotNull { fragmentManager.findFragmentByTag(it) } + .forEach { stack.push(it) } + + fragmentMap[tagList.first()] = stack + ++x + } + + Logger.i("TabFragmentNavigator restoreFromBundle after restore map = $fragmentMap") + + val savedCurrentTabTag = savedInstanceState.getString(EXTRA_CURRENT_TAB_TAG) ?: "" + showExistent(savedCurrentTabTag) + + true + } catch (t: Throwable) { + Logger.e(t) + false + } + } + } +} diff --git a/dagger-scope/sample/src/main/java/ru/surfstudio/android/custom_scope_sample/ui/base/dagger/activity/ActivityModule.kt b/dagger-scope/sample/src/main/java/ru/surfstudio/android/custom_scope_sample/ui/base/dagger/activity/ActivityModule.kt index 9a6fc43df1..41ce51ebb4 100755 --- a/dagger-scope/sample/src/main/java/ru/surfstudio/android/custom_scope_sample/ui/base/dagger/activity/ActivityModule.kt +++ b/dagger-scope/sample/src/main/java/ru/surfstudio/android/custom_scope_sample/ui/base/dagger/activity/ActivityModule.kt @@ -7,6 +7,7 @@ import ru.surfstudio.android.core.ui.event.ScreenEventDelegateManager import ru.surfstudio.android.core.ui.navigation.activity.navigator.ActivityNavigator import ru.surfstudio.android.core.ui.navigation.activity.navigator.ActivityNavigatorForActivity import ru.surfstudio.android.core.ui.navigation.fragment.FragmentNavigator +import ru.surfstudio.android.core.ui.navigation.fragment.FragmentNavigatorImpl import ru.surfstudio.android.core.ui.permission.PermissionManager import ru.surfstudio.android.core.ui.permission.PermissionManagerForActivity import ru.surfstudio.android.core.ui.provider.ActivityProvider @@ -91,6 +92,6 @@ class ActivityModule(private val persistentScope: ActivityPersistentScope) { @Provides @PerActivity internal fun provideFragmentNavigator(activityProvider: ActivityProvider): FragmentNavigator { - return FragmentNavigator(activityProvider) + return FragmentNavigatorImpl(activityProvider) } } diff --git a/mvi/sample/src/main/java/ru/surfstudio/android/core/mvi/sample/ui/base/di/NavigationScreenModule.kt b/mvi/sample/src/main/java/ru/surfstudio/android/core/mvi/sample/ui/base/di/NavigationScreenModule.kt index 456c08df7c..2085d72501 100644 --- a/mvi/sample/src/main/java/ru/surfstudio/android/core/mvi/sample/ui/base/di/NavigationScreenModule.kt +++ b/mvi/sample/src/main/java/ru/surfstudio/android/core/mvi/sample/ui/base/di/NavigationScreenModule.kt @@ -7,6 +7,7 @@ import ru.surfstudio.android.core.mvi.impls.ui.middleware.navigation.composition import ru.surfstudio.android.core.mvi.impls.ui.middleware.navigation.ScreenNavigator import ru.surfstudio.android.core.ui.navigation.activity.navigator.ActivityNavigator import ru.surfstudio.android.core.ui.navigation.fragment.FragmentNavigator +import ru.surfstudio.android.core.ui.navigation.fragment.FragmentNavigatorImpl import ru.surfstudio.android.core.ui.provider.ActivityProvider import ru.surfstudio.android.dagger.scope.PerScreen import ru.surfstudio.android.mvp.dialog.navigation.navigator.DialogNavigator @@ -36,5 +37,5 @@ class NavigationScreenModule { @Provides @PerScreen - fun provideFragmentNavigator(activityProvider: ActivityProvider): FragmentNavigator = FragmentNavigator(activityProvider) + fun provideFragmentNavigator(activityProvider: ActivityProvider): FragmentNavigator = FragmentNavigatorImpl(activityProvider) } \ No newline at end of file diff --git a/mvp/sample-mvp-widget/src/main/java/ru/surfstudio/android/mvpwidget/sample/ui/screen/main/MainPresenter.kt b/mvp/sample-mvp-widget/src/main/java/ru/surfstudio/android/mvpwidget/sample/ui/screen/main/MainPresenter.kt index 5da8f4c369..631d60d0d4 100755 --- a/mvp/sample-mvp-widget/src/main/java/ru/surfstudio/android/mvpwidget/sample/ui/screen/main/MainPresenter.kt +++ b/mvp/sample-mvp-widget/src/main/java/ru/surfstudio/android/mvpwidget/sample/ui/screen/main/MainPresenter.kt @@ -5,7 +5,7 @@ import android.util.Log import ru.surfstudio.android.core.mvp.presenter.BasePresenter import ru.surfstudio.android.core.mvp.presenter.BasePresenterDependency import ru.surfstudio.android.core.ui.navigation.activity.navigator.ActivityNavigator -import ru.surfstudio.android.core.ui.navigation.fragment.FragmentNavigator +import ru.surfstudio.android.core.ui.navigation.fragment.FragmentNavigatorImpl import ru.surfstudio.android.core.ui.provider.ActivityProvider import ru.surfstudio.android.dagger.scope.PerScreen import ru.surfstudio.android.mvpwidget.sample.ui.screen.main.list.ListActivityRoute @@ -22,7 +22,7 @@ internal class MainPresenter @Inject constructor( private val activityNavigator: ActivityNavigator ) : BasePresenter(basePresenterDependency) { - private val fragmentNavigator = FragmentNavigator(activityProvider) + private val fragmentNavigator = FragmentNavigatorImpl(activityProvider) fun openWidgetFragment() { fragmentNavigator.add(MainFragmentRoute(), true, TRANSIT_FRAGMENT_FADE) diff --git a/template/base_feature/src/main/java/ru/surfstudio/standard/ui/activity/di/ActivityModule.kt b/template/base_feature/src/main/java/ru/surfstudio/standard/ui/activity/di/ActivityModule.kt index e132919def..2424a577cb 100644 --- a/template/base_feature/src/main/java/ru/surfstudio/standard/ui/activity/di/ActivityModule.kt +++ b/template/base_feature/src/main/java/ru/surfstudio/standard/ui/activity/di/ActivityModule.kt @@ -9,7 +9,9 @@ import ru.surfstudio.android.core.ui.navigation.activity.navigator.ActivityNavig import ru.surfstudio.android.core.ui.navigation.activity.navigator.ActivityNavigatorForActivity import ru.surfstudio.android.core.ui.navigation.feature.installer.SplitFeatureInstaller import ru.surfstudio.android.core.ui.navigation.fragment.FragmentNavigator +import ru.surfstudio.android.core.ui.navigation.fragment.FragmentNavigatorImpl import ru.surfstudio.android.core.ui.navigation.fragment.tabfragment.TabFragmentNavigator +import ru.surfstudio.android.core.ui.navigation.fragment.tabfragment.TabFragmentNavigatorImpl import ru.surfstudio.android.core.ui.permission.PermissionManager import ru.surfstudio.android.core.ui.permission.PermissionManagerForActivity import ru.surfstudio.android.core.ui.provider.ActivityProvider @@ -90,7 +92,7 @@ class ActivityModule(private val persistentScope: ActivityPersistentScope) { @Provides @PerActivity internal fun provideFragmentNavigator(activityProvider: ActivityProvider): FragmentNavigator { - return FragmentNavigator(activityProvider) + return FragmentNavigatorImpl(activityProvider) } @Provides @@ -124,10 +126,11 @@ class ActivityModule(private val persistentScope: ActivityPersistentScope) { @Provides @PerActivity internal fun provideTabFragmentNavigator( - activityProvider: ActivityProvider, - eventDelegateManager: ScreenEventDelegateManager + activityProvider: ActivityProvider, + fragmentNavigator: FragmentNavigator, + eventDelegateManager: ScreenEventDelegateManager ): TabFragmentNavigator { - return TabFragmentNavigator(activityProvider, eventDelegateManager) + return TabFragmentNavigatorImpl(activityProvider, fragmentNavigator, eventDelegateManager) } @Provides diff --git a/template/f-debug/src/main/java/ru/surfstudio/standard/f_debug/injector/ui/DebugActivityModule.kt b/template/f-debug/src/main/java/ru/surfstudio/standard/f_debug/injector/ui/DebugActivityModule.kt index 088a41cb4d..539dde45e9 100644 --- a/template/f-debug/src/main/java/ru/surfstudio/standard/f_debug/injector/ui/DebugActivityModule.kt +++ b/template/f-debug/src/main/java/ru/surfstudio/standard/f_debug/injector/ui/DebugActivityModule.kt @@ -7,7 +7,9 @@ import ru.surfstudio.android.core.ui.event.ScreenEventDelegateManager import ru.surfstudio.android.core.ui.navigation.activity.navigator.ActivityNavigator import ru.surfstudio.android.core.ui.navigation.activity.navigator.ActivityNavigatorForActivity import ru.surfstudio.android.core.ui.navigation.fragment.FragmentNavigator +import ru.surfstudio.android.core.ui.navigation.fragment.FragmentNavigatorImpl import ru.surfstudio.android.core.ui.navigation.fragment.tabfragment.TabFragmentNavigator +import ru.surfstudio.android.core.ui.navigation.fragment.tabfragment.TabFragmentNavigatorImpl import ru.surfstudio.android.core.ui.permission.PermissionManager import ru.surfstudio.android.core.ui.permission.PermissionManagerForActivity import ru.surfstudio.android.core.ui.provider.ActivityProvider @@ -71,7 +73,7 @@ class DebugActivityModule(private val persistentScope: ActivityPersistentScope) @Provides @PerActivity internal fun provideFragmentNavigator(activityProvider: ActivityProvider): FragmentNavigator { - return FragmentNavigator(activityProvider) + return FragmentNavigatorImpl(activityProvider) } @Provides @@ -105,10 +107,11 @@ class DebugActivityModule(private val persistentScope: ActivityPersistentScope) @Provides @PerActivity internal fun provideTabFragmentNavigator( - activityProvider: ActivityProvider, - eventDelegateManager: ScreenEventDelegateManager + activityProvider: ActivityProvider, + fragmentNavigator: FragmentNavigator, + eventDelegateManager: ScreenEventDelegateManager ): TabFragmentNavigator { - return TabFragmentNavigator(activityProvider, eventDelegateManager) + return TabFragmentNavigatorImpl(activityProvider, fragmentNavigator, eventDelegateManager) } @Provides From 5b0f74eeb324a7d23bf8a5fa83e2360da4e91a62 Mon Sep 17 00:00:00 2001 From: Kartashov Andrey Date: Wed, 7 Apr 2021 14:27:14 +0300 Subject: [PATCH 03/17] ANDDEP-1207 extracted DialogNavigator interface --- mvp/lib-mvp-dialog/README.md | 2 +- ...alogNavigator.java => BaseDialogNavigator.java} | 13 +++++-------- .../dialog/navigation/navigator/DialogNavigator.kt | 14 ++++++++++++++ .../navigator/DialogNavigatorForActivity.java | 2 +- .../navigator/DialogNavigatorForFragment.java | 2 +- .../navigator/DialogNavigatorForWidget.java | 2 +- 6 files changed, 23 insertions(+), 12 deletions(-) rename mvp/lib-mvp-dialog/src/main/java/ru/surfstudio/android/mvp/dialog/navigation/navigator/{DialogNavigator.java => BaseDialogNavigator.java} (87%) create mode 100644 mvp/lib-mvp-dialog/src/main/java/ru/surfstudio/android/mvp/dialog/navigation/navigator/DialogNavigator.kt diff --git a/mvp/lib-mvp-dialog/README.md b/mvp/lib-mvp-dialog/README.md index d1339a53c4..defee1b5fa 100755 --- a/mvp/lib-mvp-dialog/README.md +++ b/mvp/lib-mvp-dialog/README.md @@ -75,7 +75,7 @@ BottomSheetDialog с собственным презентером. [core]: src/main/java/ru/surfstudio/android/mvp/dialog/complex/CoreDialogFragmentView.java [bottom]: src/main/java/ru/surfstudio/android/mvp/dialog/complex/CoreBottomSheetDialogFragmentView.java [bottom_simple]: src/main/java/ru/surfstudio/android/mvp/dialog/simple/bottomsheet/CoreSimpleBottomSheetDialogFragment.kt -[nav]: src/main/java/ru/surfstudio/android/mvp/dialog/navigation/navigator/DialogNavigator.java +[nav]: src/main/java/ru/surfstudio/android/mvp/dialog/navigation/navigator/DialogNavigator.kt [dr]: src/main/java/ru/surfstudio/android/mvp/dialog/navigation/route/DialogRoute.java [dwpr]: src/main/java/ru/surfstudio/android/mvp/dialog/navigation/route/DialogWithParamsRoute.java [core_mvp]: ../../mvp/lib-core-mvp/ diff --git a/mvp/lib-mvp-dialog/src/main/java/ru/surfstudio/android/mvp/dialog/navigation/navigator/DialogNavigator.java b/mvp/lib-mvp-dialog/src/main/java/ru/surfstudio/android/mvp/dialog/navigation/navigator/BaseDialogNavigator.java similarity index 87% rename from mvp/lib-mvp-dialog/src/main/java/ru/surfstudio/android/mvp/dialog/navigation/navigator/DialogNavigator.java rename to mvp/lib-mvp-dialog/src/main/java/ru/surfstudio/android/mvp/dialog/navigation/navigator/BaseDialogNavigator.java index 3e9244372c..3df07a340c 100755 --- a/mvp/lib-mvp-dialog/src/main/java/ru/surfstudio/android/mvp/dialog/navigation/navigator/DialogNavigator.java +++ b/mvp/lib-mvp-dialog/src/main/java/ru/surfstudio/android/mvp/dialog/navigation/navigator/BaseDialogNavigator.java @@ -19,26 +19,23 @@ import androidx.fragment.app.DialogFragment; import androidx.fragment.app.FragmentManager; -import ru.surfstudio.android.core.ui.navigation.Navigator; import ru.surfstudio.android.core.ui.provider.ActivityProvider; import ru.surfstudio.android.core.ui.scope.ScreenPersistentScope; import ru.surfstudio.android.mvp.dialog.navigation.route.DialogRoute; import ru.surfstudio.android.mvp.dialog.simple.CoreSimpleDialogInterface; -/** - * позволяет открывать диалоги - */ -public abstract class DialogNavigator implements Navigator { +public abstract class BaseDialogNavigator implements DialogNavigator { private ActivityProvider activityProvider; private ScreenPersistentScope screenPersistentScope; - public DialogNavigator(ActivityProvider activityProvider, - ScreenPersistentScope screenPersistentScope) { + public BaseDialogNavigator(ActivityProvider activityProvider, + ScreenPersistentScope screenPersistentScope) { this.activityProvider = activityProvider; this.screenPersistentScope = screenPersistentScope; } + @Override public void show(DialogRoute dialogRoute) { DialogFragment dialog = dialogRoute.createFragment(); String tag = dialogRoute.getTag(); @@ -49,6 +46,7 @@ public void show(DialogRoute dialogRoute) { } } + @Override public void dismiss(DialogRoute dialogRoute) { FragmentManager fragmentManager = activityProvider.get().getSupportFragmentManager(); DialogFragment dialogFragment = (DialogFragment) fragmentManager @@ -60,5 +58,4 @@ protected abstract void s D fragment, String tag ); - } diff --git a/mvp/lib-mvp-dialog/src/main/java/ru/surfstudio/android/mvp/dialog/navigation/navigator/DialogNavigator.kt b/mvp/lib-mvp-dialog/src/main/java/ru/surfstudio/android/mvp/dialog/navigation/navigator/DialogNavigator.kt new file mode 100644 index 0000000000..672489d51c --- /dev/null +++ b/mvp/lib-mvp-dialog/src/main/java/ru/surfstudio/android/mvp/dialog/navigation/navigator/DialogNavigator.kt @@ -0,0 +1,14 @@ +package ru.surfstudio.android.mvp.dialog.navigation.navigator + +import ru.surfstudio.android.core.ui.navigation.Navigator +import ru.surfstudio.android.mvp.dialog.navigation.route.DialogRoute + +/** + * позволяет открывать диалоги + */ +interface DialogNavigator : Navigator { + + fun show(dialogRoute: DialogRoute) + + fun dismiss(dialogRoute: DialogRoute) +} \ No newline at end of file diff --git a/mvp/lib-mvp-dialog/src/main/java/ru/surfstudio/android/mvp/dialog/navigation/navigator/DialogNavigatorForActivity.java b/mvp/lib-mvp-dialog/src/main/java/ru/surfstudio/android/mvp/dialog/navigation/navigator/DialogNavigatorForActivity.java index 264dd68a10..b54167a40c 100755 --- a/mvp/lib-mvp-dialog/src/main/java/ru/surfstudio/android/mvp/dialog/navigation/navigator/DialogNavigatorForActivity.java +++ b/mvp/lib-mvp-dialog/src/main/java/ru/surfstudio/android/mvp/dialog/navigation/navigator/DialogNavigatorForActivity.java @@ -25,7 +25,7 @@ /** * DialogNavigator работающий из активити */ -public class DialogNavigatorForActivity extends DialogNavigator { +public class DialogNavigatorForActivity extends BaseDialogNavigator { private ActivityProvider activityProvider; private ActivityViewPersistentScope activityViewPersistentScope; diff --git a/mvp/lib-mvp-dialog/src/main/java/ru/surfstudio/android/mvp/dialog/navigation/navigator/DialogNavigatorForFragment.java b/mvp/lib-mvp-dialog/src/main/java/ru/surfstudio/android/mvp/dialog/navigation/navigator/DialogNavigatorForFragment.java index 49d4b0f3a4..f4c2996693 100755 --- a/mvp/lib-mvp-dialog/src/main/java/ru/surfstudio/android/mvp/dialog/navigation/navigator/DialogNavigatorForFragment.java +++ b/mvp/lib-mvp-dialog/src/main/java/ru/surfstudio/android/mvp/dialog/navigation/navigator/DialogNavigatorForFragment.java @@ -26,7 +26,7 @@ /** * DialogNavigator работающий из фрагмента */ -public class DialogNavigatorForFragment extends DialogNavigator { +public class DialogNavigatorForFragment extends BaseDialogNavigator { private FragmentProvider fragmentProvider; private FragmentViewPersistentScope fragmentViewPersistentScope; diff --git a/mvp/lib-mvp-dialog/src/main/java/ru/surfstudio/android/mvp/dialog/navigation/navigator/DialogNavigatorForWidget.java b/mvp/lib-mvp-dialog/src/main/java/ru/surfstudio/android/mvp/dialog/navigation/navigator/DialogNavigatorForWidget.java index 1c727d8435..2efffc3cc6 100755 --- a/mvp/lib-mvp-dialog/src/main/java/ru/surfstudio/android/mvp/dialog/navigation/navigator/DialogNavigatorForWidget.java +++ b/mvp/lib-mvp-dialog/src/main/java/ru/surfstudio/android/mvp/dialog/navigation/navigator/DialogNavigatorForWidget.java @@ -26,7 +26,7 @@ /** * DialogNavigator работающий из активити */ -public class DialogNavigatorForWidget extends DialogNavigator { +public class DialogNavigatorForWidget extends BaseDialogNavigator { private WidgetProvider widgetProvider; private WidgetViewPersistentScope widgetViewPersistentScope; From 2c57640a1c682146e63bd5920ec23893edabb626 Mon Sep 17 00:00:00 2001 From: Kartashov Andrey Date: Mon, 12 Apr 2021 10:33:06 +0300 Subject: [PATCH 04/17] ANDDEP-1207 extracted binding source/target relation interfaces --- .../android/core/mvp/binding/rx/relation/mvp/Action.kt | 6 +++--- .../core/mvp/binding/rx/relation/mvp/Command.kt | 4 ++-- .../android/core/mvp/binding/rx/relation/mvp/Entry.kt | 10 ++++++++-- .../android/core/mvp/binding/rx/relation/mvp/State.kt | 4 ++-- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/core-mvp-binding/lib-core-mvp-binding/src/main/java/ru/surfstudio/android/core/mvp/binding/rx/relation/mvp/Action.kt b/core-mvp-binding/lib-core-mvp-binding/src/main/java/ru/surfstudio/android/core/mvp/binding/rx/relation/mvp/Action.kt index 55d20ab7fc..3f98bc19dc 100644 --- a/core-mvp-binding/lib-core-mvp-binding/src/main/java/ru/surfstudio/android/core/mvp/binding/rx/relation/mvp/Action.kt +++ b/core-mvp-binding/lib-core-mvp-binding/src/main/java/ru/surfstudio/android/core/mvp/binding/rx/relation/mvp/Action.kt @@ -26,9 +26,9 @@ import ru.surfstudio.android.core.mvp.binding.rx.relation.BehaviorRelation * Хранит в себе последнее прошедшее значение. * При подписке сообщает это значение или initialValue */ -class Action(initialValue: T? = null) : BehaviorRelation(initialValue) { +class Action(initialValue: T? = null) : BehaviorRelation(initialValue) { - override fun getConsumer(source: VIEW): Consumer = relay + override fun getConsumer(source: ActionSource): Consumer = relay - override fun getObservable(target: PRESENTER): Observable = relay.share() + override fun getObservable(target: ActionTarget): Observable = relay.share() } \ No newline at end of file diff --git a/core-mvp-binding/lib-core-mvp-binding/src/main/java/ru/surfstudio/android/core/mvp/binding/rx/relation/mvp/Command.kt b/core-mvp-binding/lib-core-mvp-binding/src/main/java/ru/surfstudio/android/core/mvp/binding/rx/relation/mvp/Command.kt index 5c415350fb..879c15feae 100644 --- a/core-mvp-binding/lib-core-mvp-binding/src/main/java/ru/surfstudio/android/core/mvp/binding/rx/relation/mvp/Command.kt +++ b/core-mvp-binding/lib-core-mvp-binding/src/main/java/ru/surfstudio/android/core/mvp/binding/rx/relation/mvp/Command.kt @@ -27,11 +27,11 @@ import ru.surfstudio.android.core.mvp.binding.rx.relation.Relation * Еммит единичное событие. * В отличии от [State] не эммитит последне значение при подписке */ -class Command : Relation() { +class Command : Relation() { private val relay = PublishRelay.create() - override fun getConsumer(source: PRESENTER): Consumer = relay + override fun getConsumer(source: CommandSource): Consumer = relay override fun getObservable(target: CommandTarget): Observable = relay.share() } \ No newline at end of file diff --git a/core-mvp-binding/lib-core-mvp-binding/src/main/java/ru/surfstudio/android/core/mvp/binding/rx/relation/mvp/Entry.kt b/core-mvp-binding/lib-core-mvp-binding/src/main/java/ru/surfstudio/android/core/mvp/binding/rx/relation/mvp/Entry.kt index fa95579348..2a1d0c9ee6 100644 --- a/core-mvp-binding/lib-core-mvp-binding/src/main/java/ru/surfstudio/android/core/mvp/binding/rx/relation/mvp/Entry.kt +++ b/core-mvp-binding/lib-core-mvp-binding/src/main/java/ru/surfstudio/android/core/mvp/binding/rx/relation/mvp/Entry.kt @@ -25,8 +25,14 @@ import ru.surfstudio.android.core.mvp.binding.rx.relation.RelationEntity interface BondSource : RelationEntity interface BondTarget : RelationEntity +interface ActionSource : RelationEntity +interface ActionTarget : RelationEntity + +interface StateSource : RelationEntity interface StateTarget : RelationEntity + +interface CommandSource : RelationEntity interface CommandTarget : RelationEntity -object VIEW : BondSource, BondTarget, StateTarget, CommandTarget, RelationEntity -object PRESENTER : BondSource, BondTarget, StateTarget, CommandTarget, RelationEntity \ No newline at end of file +object VIEW : BondSource, BondTarget, ActionSource, StateTarget, CommandTarget, RelationEntity +object PRESENTER : BondSource, BondTarget, ActionTarget, StateSource, StateTarget, CommandSource, CommandTarget, RelationEntity \ No newline at end of file diff --git a/core-mvp-binding/lib-core-mvp-binding/src/main/java/ru/surfstudio/android/core/mvp/binding/rx/relation/mvp/State.kt b/core-mvp-binding/lib-core-mvp-binding/src/main/java/ru/surfstudio/android/core/mvp/binding/rx/relation/mvp/State.kt index c46a6d482f..da9f5c81d9 100644 --- a/core-mvp-binding/lib-core-mvp-binding/src/main/java/ru/surfstudio/android/core/mvp/binding/rx/relation/mvp/State.kt +++ b/core-mvp-binding/lib-core-mvp-binding/src/main/java/ru/surfstudio/android/core/mvp/binding/rx/relation/mvp/State.kt @@ -26,9 +26,9 @@ import ru.surfstudio.android.core.mvp.binding.rx.relation.BehaviorRelation * Хранит в себе последнее прошедшее значение. * При подписке сообщает это значение или initialValue */ -open class State(initialValue: T? = null) : BehaviorRelation(initialValue) { +open class State(initialValue: T? = null) : BehaviorRelation(initialValue) { - override fun getConsumer(source: PRESENTER): Consumer = relay + override fun getConsumer(source: StateSource): Consumer = relay override fun getObservable(target: StateTarget): Observable = relay.share() } \ No newline at end of file From 043812aa5d45606f92b8e028a1076d39deafd062 Mon Sep 17 00:00:00 2001 From: Kartashov Andrey Date: Mon, 12 Apr 2021 10:49:19 +0300 Subject: [PATCH 05/17] ANDDEP-1207 extracted ConnectionProvider interface --- .../dagger/app/dagger/DefaultAppModule.kt | 3 +- .../android/connection/ConnectionProvider.kt | 28 +++++++++++++++++++ ...vider.java => ConnectionProviderImpl.java} | 26 +++++++---------- .../app/dagger/AppModule.kt | 3 +- .../sample/app/dagger/CustomAppModule.kt | 3 +- .../standard/application/app/di/AppModule.kt | 3 +- .../f_debug/injector/DebugAppModule.kt | 3 +- .../di/modules/TestAppModule.kt | 3 +- 8 files changed, 50 insertions(+), 22 deletions(-) create mode 100644 connection/lib-connection/src/main/java/ru/surfstudio/android/connection/ConnectionProvider.kt rename connection/lib-connection/src/main/java/ru/surfstudio/android/connection/{ConnectionProvider.java => ConnectionProviderImpl.java} (91%) diff --git a/common/lib-sample-dagger/src/main/java/ru/surfstudio/android/sample/dagger/app/dagger/DefaultAppModule.kt b/common/lib-sample-dagger/src/main/java/ru/surfstudio/android/sample/dagger/app/dagger/DefaultAppModule.kt index fa918bc025..d9914d75a1 100755 --- a/common/lib-sample-dagger/src/main/java/ru/surfstudio/android/sample/dagger/app/dagger/DefaultAppModule.kt +++ b/common/lib-sample-dagger/src/main/java/ru/surfstudio/android/sample/dagger/app/dagger/DefaultAppModule.kt @@ -7,6 +7,7 @@ import dagger.Module import dagger.Provides import ru.surfstudio.android.activity.holder.ActiveActivityHolder import ru.surfstudio.android.connection.ConnectionProvider +import ru.surfstudio.android.connection.ConnectionProviderImpl import ru.surfstudio.android.core.ui.navigation.activity.navigator.GlobalNavigator import ru.surfstudio.android.core.ui.navigation.activity.navigator.GlobalNavigatorImpl import ru.surfstudio.android.dagger.scope.PerApplication @@ -51,6 +52,6 @@ class DefaultAppModule( @Provides @PerApplication internal fun provideConnectionQualityProvider(context: Context): ConnectionProvider { - return ConnectionProvider(context) + return ConnectionProviderImpl(context) } } diff --git a/connection/lib-connection/src/main/java/ru/surfstudio/android/connection/ConnectionProvider.kt b/connection/lib-connection/src/main/java/ru/surfstudio/android/connection/ConnectionProvider.kt new file mode 100644 index 0000000000..c00fd3437e --- /dev/null +++ b/connection/lib-connection/src/main/java/ru/surfstudio/android/connection/ConnectionProvider.kt @@ -0,0 +1,28 @@ +package ru.surfstudio.android.connection + +import android.content.Context +import android.net.NetworkInfo +import io.reactivex.Observable + +/** + * Provider, позволяющий подписаться на событие изменения состояния соединения + */ +interface ConnectionProvider { + + fun observeConnectionChanges(): Observable + + fun isConnected(): Boolean + + fun isDisconnected(): Boolean + + fun isConnectedFast(): Boolean + + /** + * Проверка на подключение к Wi-Fi + * + * @return подключен ли девайс к Wi-Fi, или к мобильной сети + */ + fun isConnectedToWifi(): Boolean + + fun getNetworkInfo(context: Context): NetworkInfo? +} \ No newline at end of file diff --git a/connection/lib-connection/src/main/java/ru/surfstudio/android/connection/ConnectionProvider.java b/connection/lib-connection/src/main/java/ru/surfstudio/android/connection/ConnectionProviderImpl.java similarity index 91% rename from connection/lib-connection/src/main/java/ru/surfstudio/android/connection/ConnectionProvider.java rename to connection/lib-connection/src/main/java/ru/surfstudio/android/connection/ConnectionProviderImpl.java index cf6a3a57b1..0514bba1bc 100755 --- a/connection/lib-connection/src/main/java/ru/surfstudio/android/connection/ConnectionProvider.java +++ b/connection/lib-connection/src/main/java/ru/surfstudio/android/connection/ConnectionProviderImpl.java @@ -25,10 +25,7 @@ import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; -/** - * Provider, позволяющий подписаться на событие изменения состояния соединения - */ -public class ConnectionProvider { +public class ConnectionProviderImpl implements ConnectionProvider { private static final long LAST_CONNECTION_QUALITY_RESULT_CACHE_TIME = 60L * 1000; //1 мин private ConnectionReceiver receiver; @@ -36,7 +33,7 @@ public class ConnectionProvider { private boolean lastConnectionResultFast = false; private long lastConnectionResultTime = 0; - public ConnectionProvider(Context context) { + public ConnectionProviderImpl(Context context) { this.context = context; this.receiver = new ConnectionReceiver(context); @@ -45,18 +42,22 @@ public ConnectionProvider(Context context) { context.registerReceiver(receiver, intentFilter); } + @Override public Observable observeConnectionChanges() { return receiver.observeConnectionChanges(); } + @Override public boolean isConnected() { return receiver.isConnected(); } + @Override public boolean isDisconnected() { return !receiver.isConnected(); } + @Override public boolean isConnectedFast() { long currentTime = System.currentTimeMillis(); if (currentTime - lastConnectionResultTime > LAST_CONNECTION_QUALITY_RESULT_CACHE_TIME) { @@ -66,22 +67,15 @@ public boolean isConnectedFast() { return lastConnectionResultFast; } - /** - * Проверка на подключение к Wi-Fi - * - * @return подключен ли девайс к Wi-Fi, или к мобильной сети - */ + + @Override public boolean isConnectedToWifi() { NetworkInfo info = getNetworkInfo(context); return info != null && info.isConnected() && info.getType() == ConnectivityManager.TYPE_WIFI; } - /** - * Get the network info - * - * @param context - * @return - */ + + @Override public NetworkInfo getNetworkInfo(Context context) { ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); return cm.getActiveNetworkInfo(); diff --git a/dagger-scope/sample/src/main/java/ru/surfstudio/android/custom_scope_sample/app/dagger/AppModule.kt b/dagger-scope/sample/src/main/java/ru/surfstudio/android/custom_scope_sample/app/dagger/AppModule.kt index ed6374c7be..a8c1963e80 100755 --- a/dagger-scope/sample/src/main/java/ru/surfstudio/android/custom_scope_sample/app/dagger/AppModule.kt +++ b/dagger-scope/sample/src/main/java/ru/surfstudio/android/custom_scope_sample/app/dagger/AppModule.kt @@ -6,6 +6,7 @@ import dagger.Module import dagger.Provides import ru.surfstudio.android.activity.holder.ActiveActivityHolder import ru.surfstudio.android.connection.ConnectionProvider +import ru.surfstudio.android.connection.ConnectionProviderImpl import ru.surfstudio.android.core.ui.navigation.activity.navigator.GlobalNavigator import ru.surfstudio.android.core.ui.navigation.activity.navigator.GlobalNavigatorImpl import ru.surfstudio.android.custom_scope_sample.app.App @@ -52,6 +53,6 @@ class AppModule(private val coreApp: App) { @Provides @PerApplication internal fun provideConnectionQualityProvider(context: Context): ConnectionProvider { - return ConnectionProvider(context) + return ConnectionProviderImpl(context) } } diff --git a/filestorage/sample/src/main/java/ru/surfstudio/android/filestorage/sample/app/dagger/CustomAppModule.kt b/filestorage/sample/src/main/java/ru/surfstudio/android/filestorage/sample/app/dagger/CustomAppModule.kt index 2a34bda436..121b29c8eb 100644 --- a/filestorage/sample/src/main/java/ru/surfstudio/android/filestorage/sample/app/dagger/CustomAppModule.kt +++ b/filestorage/sample/src/main/java/ru/surfstudio/android/filestorage/sample/app/dagger/CustomAppModule.kt @@ -5,6 +5,7 @@ import dagger.Module import dagger.Provides import ru.surfstudio.android.activity.holder.ActiveActivityHolder import ru.surfstudio.android.connection.ConnectionProvider +import ru.surfstudio.android.connection.ConnectionProviderImpl import ru.surfstudio.android.core.ui.navigation.activity.navigator.GlobalNavigator import ru.surfstudio.android.core.ui.navigation.activity.navigator.GlobalNavigatorImpl import ru.surfstudio.android.dagger.scope.PerApplication @@ -51,6 +52,6 @@ class CustomAppModule(private val coreApp: CustomApp) { @Provides @PerApplication internal fun provideConnectionQualityProvider(context: Context): ConnectionProvider { - return ConnectionProvider(context) + return ConnectionProviderImpl(context) } } \ No newline at end of file diff --git a/template/base_feature/src/main/java/ru/surfstudio/standard/application/app/di/AppModule.kt b/template/base_feature/src/main/java/ru/surfstudio/standard/application/app/di/AppModule.kt index ff0fd144d8..eda7cd793b 100644 --- a/template/base_feature/src/main/java/ru/surfstudio/standard/application/app/di/AppModule.kt +++ b/template/base_feature/src/main/java/ru/surfstudio/standard/application/app/di/AppModule.kt @@ -6,6 +6,7 @@ import dagger.Module import dagger.Provides import ru.surfstudio.android.activity.holder.ActiveActivityHolder import ru.surfstudio.android.connection.ConnectionProvider +import ru.surfstudio.android.connection.ConnectionProviderImpl import ru.surfstudio.android.core.ui.navigation.activity.navigator.GlobalNavigator import ru.surfstudio.android.core.ui.navigation.activity.navigator.GlobalNavigatorImpl import ru.surfstudio.android.dagger.scope.PerApplication @@ -54,6 +55,6 @@ class AppModule( @Provides @PerApplication internal fun provideConnectionQualityProvider(context: Context): ConnectionProvider { - return ConnectionProvider(context) + return ConnectionProviderImpl(context) } } \ No newline at end of file diff --git a/template/f-debug/src/main/java/ru/surfstudio/standard/f_debug/injector/DebugAppModule.kt b/template/f-debug/src/main/java/ru/surfstudio/standard/f_debug/injector/DebugAppModule.kt index c0175e2923..5ae792c520 100644 --- a/template/f-debug/src/main/java/ru/surfstudio/standard/f_debug/injector/DebugAppModule.kt +++ b/template/f-debug/src/main/java/ru/surfstudio/standard/f_debug/injector/DebugAppModule.kt @@ -6,6 +6,7 @@ import dagger.Module import dagger.Provides import ru.surfstudio.android.activity.holder.ActiveActivityHolder import ru.surfstudio.android.connection.ConnectionProvider +import ru.surfstudio.android.connection.ConnectionProviderImpl import ru.surfstudio.android.core.ui.navigation.activity.navigator.GlobalNavigator import ru.surfstudio.android.core.ui.navigation.activity.navigator.GlobalNavigatorImpl import ru.surfstudio.android.dagger.scope.PerApplication @@ -60,6 +61,6 @@ class DebugAppModule( @Provides @PerApplication internal fun provideConnectionQualityProvider(context: Context): ConnectionProvider { - return ConnectionProvider(context) + return ConnectionProviderImpl(context) } } \ No newline at end of file diff --git a/template/small-test-utils/src/main/java/ru/surfstudio/standard/small_test_utils/di/modules/TestAppModule.kt b/template/small-test-utils/src/main/java/ru/surfstudio/standard/small_test_utils/di/modules/TestAppModule.kt index 06c5193829..04ec10354e 100644 --- a/template/small-test-utils/src/main/java/ru/surfstudio/standard/small_test_utils/di/modules/TestAppModule.kt +++ b/template/small-test-utils/src/main/java/ru/surfstudio/standard/small_test_utils/di/modules/TestAppModule.kt @@ -6,6 +6,7 @@ import dagger.Module import dagger.Provides import ru.surfstudio.android.activity.holder.ActiveActivityHolder import ru.surfstudio.android.connection.ConnectionProvider +import ru.surfstudio.android.connection.ConnectionProviderImpl import ru.surfstudio.android.core.ui.navigation.activity.navigator.GlobalNavigator import ru.surfstudio.android.core.ui.navigation.activity.navigator.GlobalNavigatorImpl import ru.surfstudio.android.dagger.scope.PerApplication @@ -54,6 +55,6 @@ class TestAppModule( @Provides @PerApplication internal fun provideConnectionQualityProvider(context: Context): ConnectionProvider { - return ConnectionProvider(context) + return ConnectionProviderImpl(context) } } \ No newline at end of file From 5a720ad808d34074955a5a15bde627aa0b70dafd Mon Sep 17 00:00:00 2001 From: Kartashov Andrey Date: Mon, 12 Apr 2021 10:55:07 +0300 Subject: [PATCH 06/17] ANDDEP-1207 now observeResult returns ScreenResult with nullable value --- .../navigation/activity/navigator/ActivityNavigator.kt | 8 ++++---- .../impls/ui/middleware/navigation/ScreenNavigator.kt | 2 +- .../ui/screen/reactor_based/main/MainMiddleware.kt | 10 +++++----- .../java/ru/surfstudio/android/picturechooser/Utils.kt | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/activity/navigator/ActivityNavigator.kt b/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/activity/navigator/ActivityNavigator.kt index 06efeaff7e..dbf276b0d4 100644 --- a/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/activity/navigator/ActivityNavigator.kt +++ b/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/activity/navigator/ActivityNavigator.kt @@ -29,7 +29,7 @@ interface ActivityNavigator : Navigator { * @param routeClass класс маршрута экрана, который должен вернуть результат * @param тип возвращаемых данных */ - fun observeResult(routeClass: Class>): Observable> + fun observeResult(routeClass: Class>): Observable> /** * позволяет подписываться на событие OnActivityResult @@ -37,7 +37,7 @@ interface ActivityNavigator : Navigator { * @param route маршрут экрана, который должен вернуть результат * @param тип возвращаемых данных */ - fun observeResult(route: SupportOnActivityResultRoute): Observable> + fun observeResult(route: SupportOnActivityResultRoute): Observable> /** * Закрываает текущую активити @@ -65,7 +65,7 @@ interface ActivityNavigator : Navigator { * @param result возвращаемый результат * @param тип возвращаемого значения */ - fun finishWithResult(activeScreenRoute: SupportOnActivityResultRoute, result: T) + fun finishWithResult(activeScreenRoute: SupportOnActivityResultRoute, result: T?) /** * Закрываает текущую активити c результатом @@ -75,7 +75,7 @@ interface ActivityNavigator : Navigator { * @param success показывает успешное ли завершение * @param тип возвращаемого значения */ - fun finishWithResult(currentScreenRoute: SupportOnActivityResultRoute, result: T, success: Boolean) + fun finishWithResult(currentScreenRoute: SupportOnActivityResultRoute, result: T?, success: Boolean) /** * Launch a new activity. diff --git a/mvi/lib-mvi-impls/src/main/java/ru/surfstudio/android/core/mvi/impls/ui/middleware/navigation/ScreenNavigator.kt b/mvi/lib-mvi-impls/src/main/java/ru/surfstudio/android/core/mvi/impls/ui/middleware/navigation/ScreenNavigator.kt index c1daaefcd1..316337c8ad 100644 --- a/mvi/lib-mvi-impls/src/main/java/ru/surfstudio/android/core/mvi/impls/ui/middleware/navigation/ScreenNavigator.kt +++ b/mvi/lib-mvi-impls/src/main/java/ru/surfstudio/android/core/mvi/impls/ui/middleware/navigation/ScreenNavigator.kt @@ -102,7 +102,7 @@ open class ScreenNavigator( */ open fun observeResult( routeClass: Class> - ): Observable> { + ): Observable> { return activityNavigator.observeResult(routeClass) } diff --git a/mvi/sample/src/main/java/ru/surfstudio/android/core/mvi/sample/ui/screen/reactor_based/main/MainMiddleware.kt b/mvi/sample/src/main/java/ru/surfstudio/android/core/mvi/sample/ui/screen/reactor_based/main/MainMiddleware.kt index e0f971cfa4..bf18276f84 100644 --- a/mvi/sample/src/main/java/ru/surfstudio/android/core/mvi/sample/ui/screen/reactor_based/main/MainMiddleware.kt +++ b/mvi/sample/src/main/java/ru/surfstudio/android/core/mvi/sample/ui/screen/reactor_based/main/MainMiddleware.kt @@ -38,15 +38,15 @@ class MainMiddleware @Inject constructor( * Показ снека с результирующим текстом, либо показ диалога с сообщением о том, * что результата нет, и предложением открыть экран с вводом текста заново. */ - private fun showResultMap(result: ScreenResult): Observable { + private fun showResultMap(result: ScreenResult): Observable { val hasResultData = result.isSuccess && !result.data.isNullOrEmpty() return if (hasResultData) { - messageController.show(result.data).skip() + messageController.show(result.data!!).skip() } else { Navigation().open(StandardReactDialogRoute( - title = "No result", - message = "Try again?", - positiveButtonEvent = Navigation().open(InputFormActivityRoute())) + title = "No result", + message = "Try again?", + positiveButtonEvent = Navigation().open(InputFormActivityRoute())) ).toObservable() } } diff --git a/picture-provider/lib-picture-provider/src/main/java/ru/surfstudio/android/picturechooser/Utils.kt b/picture-provider/lib-picture-provider/src/main/java/ru/surfstudio/android/picturechooser/Utils.kt index cab4400993..bf1faab935 100644 --- a/picture-provider/lib-picture-provider/src/main/java/ru/surfstudio/android/picturechooser/Utils.kt +++ b/picture-provider/lib-picture-provider/src/main/java/ru/surfstudio/android/picturechooser/Utils.kt @@ -102,7 +102,7 @@ internal fun observeMultipleScreenResult( } internal fun parseScreenResult( - screenResult: ScreenResult, + screenResult: ScreenResult, throwable: () -> Throwable = { ActionInterruptedException() } ): Observable { return if (screenResult.isSuccess) { From 744ac9a81904b9523bf5646f0fce18be5fe73b54 Mon Sep 17 00:00:00 2001 From: Kartashov Andrey Date: Mon, 12 Apr 2021 10:57:14 +0300 Subject: [PATCH 07/17] ANDDEP-1207 removed nullable from routes in FragmentNavigator --- .../core/ui/navigation/fragment/FragmentNavigator.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/fragment/FragmentNavigator.kt b/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/fragment/FragmentNavigator.kt index 658a525d4b..351b04f302 100644 --- a/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/fragment/FragmentNavigator.kt +++ b/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/fragment/FragmentNavigator.kt @@ -10,24 +10,24 @@ import ru.surfstudio.android.core.ui.navigation.fragment.route.FragmentRoute */ interface FragmentNavigator : Navigator { - fun add(route: FragmentRoute?, stackable: Boolean, @Transit transition: Int) + fun add(route: FragmentRoute, stackable: Boolean, @Transit transition: Int) - fun replace(route: FragmentRoute?, stackable: Boolean, @Transit transition: Int) + fun replace(route: FragmentRoute, stackable: Boolean, @Transit transition: Int) /** * @return возвращает true если фрагмент был удален успешно */ - fun remove(route: FragmentRoute?, @Transit transition: Int): Boolean + fun remove(route: FragmentRoute, @Transit transition: Int): Boolean /** * @return возвращает true если фрагмент успешно отобразился */ - fun show(route: FragmentRoute?, @Transit transition: Int): Boolean + fun show(route: FragmentRoute, @Transit transition: Int): Boolean /** * @return возвращает true если фрагмент был скрыт успешно */ - fun hide(route: FragmentRoute?, @Transit transition: Int): Boolean + fun hide(route: FragmentRoute, @Transit transition: Int): Boolean /** * @return возвращает true если какой-либо фрагмент верхнего уровня был удален из стека From 30df1666361e2ada6b99437a83f4fc87b2ce8618 Mon Sep 17 00:00:00 2001 From: Kartashov Andrey Date: Tue, 13 Apr 2021 17:20:36 +0300 Subject: [PATCH 08/17] ANDDEP-1207 added BasePresenterTest, dependencies and test navigation --- buildSrc/components.json | 57 +++++++++ buildSrc/config.gradle | 2 + .../lib-core-mvp-binding-tests/.gitignore | 1 + .../lib-core-mvp-binding-tests/build.gradle | 1 + .../src/main/AndroidManifest.xml | 1 + .../mvp/binding/test/BasePresenterTest.kt | 114 ++++++++++++++++++ .../binding/test/TestConnectionProvider.kt | 58 +++++++++ .../core/mvp/binding/test/TestErrorHandler.kt | 13 ++ .../binding/test/TestSchedulersProvider.kt | 17 +++ .../test/TestScreenEventDelegateManager.kt | 47 ++++++++ .../core/mvp/binding/test/TestScreenState.kt | 9 ++ .../test/navigation/TestActivityNavigator.kt | 112 +++++++++++++++++ .../test/navigation/TestDialogNavigator.kt | 23 ++++ .../test/navigation/TestFragmentNavigator.kt | 52 ++++++++ .../test/navigation/TestGlobalNavigator.kt | 18 +++ .../navigation/TestTabFragmentNavigator.kt | 54 +++++++++ .../test/navigation/base/BaseTestNavigator.kt | 18 +++ .../navigation/base/TestNavigationEvent.kt | 70 +++++++++++ .../matchers/ActivityNavigationMatchers.kt | 64 ++++++++++ .../matchers/DialogNavigaitonMatchers.kt | 21 ++++ .../matchers/FragmentNavigationMatchers.kt | 61 ++++++++++ .../navigation/matchers/NavigationMatchers.kt | 12 ++ .../matchers/TabFragmentNavigationMatchers.kt | 62 ++++++++++ 23 files changed, 887 insertions(+) create mode 100644 core-mvp-binding/lib-core-mvp-binding-tests/.gitignore create mode 100644 core-mvp-binding/lib-core-mvp-binding-tests/build.gradle create mode 100644 core-mvp-binding/lib-core-mvp-binding-tests/src/main/AndroidManifest.xml create mode 100644 core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/BasePresenterTest.kt create mode 100644 core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/TestConnectionProvider.kt create mode 100644 core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/TestErrorHandler.kt create mode 100644 core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/TestSchedulersProvider.kt create mode 100644 core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/TestScreenEventDelegateManager.kt create mode 100644 core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/TestScreenState.kt create mode 100644 core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/TestActivityNavigator.kt create mode 100644 core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/TestDialogNavigator.kt create mode 100644 core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/TestFragmentNavigator.kt create mode 100644 core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/TestGlobalNavigator.kt create mode 100644 core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/TestTabFragmentNavigator.kt create mode 100644 core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/base/BaseTestNavigator.kt create mode 100644 core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/base/TestNavigationEvent.kt create mode 100644 core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/matchers/ActivityNavigationMatchers.kt create mode 100644 core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/matchers/DialogNavigaitonMatchers.kt create mode 100644 core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/matchers/FragmentNavigationMatchers.kt create mode 100644 core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/matchers/NavigationMatchers.kt create mode 100644 core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/matchers/TabFragmentNavigationMatchers.kt diff --git a/buildSrc/components.json b/buildSrc/components.json index 10f8e50cf9..dbe2ded6f2 100755 --- a/buildSrc/components.json +++ b/buildSrc/components.json @@ -760,6 +760,63 @@ "has_mirror": false, "mirror_repo": "" }, + { + "id": "core-mvp-binding-tests", + "version": "0.0.1", + "unstable_version": 1, + "stable": false, + "dir": "core-mvp-binding", + "libs": [ + { + "name": "core-mvp-binding-tests", + "dir": "lib-core-mvp-binding-tests", + "artifact_name": "core-mvp-binding-tests", + "third_party_dependencies": [ + { + "name": "io.kotest:kotest-runner-junit5", + "type": "implementation" + }, + { + "name": "io.mockk:mockk", + "type": "implementation" + } + ], + "android_standard_dependencies": [ + { + "name": "core-mvp-binding", + "type": "implementation" + }, + { + "name": "core-ui", + "type": "implementation" + }, + { + "name": "core-navigation", + "type": "implementation" + }, + { + "name": "core-mvp", + "type": "implementation" + }, + { + "name": "mvp-dialog", + "type": "implementation" + }, + { + "name": "connection", + "type": "implementation" + }, + { + "name": "rx-extension", + "type": "implementation" + } + ] + } + ], + "samples": [], + "has_mirror": false, + "mirror_repo": "" + }, { "id": "core-ui", "version": "0.5.0", diff --git a/buildSrc/config.gradle b/buildSrc/config.gradle index bc02e5db70..27fd02317d 100755 --- a/buildSrc/config.gradle +++ b/buildSrc/config.gradle @@ -85,6 +85,8 @@ ext { "com.squareup.okhttp3:logging-interceptor" : "4.4.1", //https://vk.cc/7UFwTd "junit:junit" : "4.13", //https://goo.gl/hEcfw1 "org.mockito:mockito-core" : "3.3.3", //https://bit.ly/3bw7XGe + "io.kotest:kotest-runner-junit5" : "4.4.1", //https://bit.ly/3g8bgJd + "io.mockk:mockk" : "1.10.6", //https://bit.ly/3wUGGc3 "org.powermock:powermock-classloading-xstream" : "2.0.7", //https://goo.gl/hEcfw1 "org.powermock:powermock-module-junit4" : "2.0.7", //https://goo.gl/hEcfw1 "org.powermock:powermock-module-junit4-rule" : "2.0.7", //https://goo.gl/hEcfw1 diff --git a/core-mvp-binding/lib-core-mvp-binding-tests/.gitignore b/core-mvp-binding/lib-core-mvp-binding-tests/.gitignore new file mode 100644 index 0000000000..796b96d1c4 --- /dev/null +++ b/core-mvp-binding/lib-core-mvp-binding-tests/.gitignore @@ -0,0 +1 @@ +/build diff --git a/core-mvp-binding/lib-core-mvp-binding-tests/build.gradle b/core-mvp-binding/lib-core-mvp-binding-tests/build.gradle new file mode 100644 index 0000000000..42d7e7822a --- /dev/null +++ b/core-mvp-binding/lib-core-mvp-binding-tests/build.gradle @@ -0,0 +1 @@ +apply from: "$rootDir/buildSrc/baseDeployBuild.gradle" \ No newline at end of file diff --git a/core-mvp-binding/lib-core-mvp-binding-tests/src/main/AndroidManifest.xml b/core-mvp-binding/lib-core-mvp-binding-tests/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..5bff044ed0 --- /dev/null +++ b/core-mvp-binding/lib-core-mvp-binding-tests/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/BasePresenterTest.kt b/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/BasePresenterTest.kt new file mode 100644 index 0000000000..6157c9dee4 --- /dev/null +++ b/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/BasePresenterTest.kt @@ -0,0 +1,114 @@ +package ru.surfstudio.android.core.mvp.binding.test + +import io.kotest.core.spec.style.AnnotationSpec +import io.reactivex.Observable +import io.reactivex.disposables.Disposable +import io.reactivex.functions.Consumer +import ru.surfstudio.android.connection.ConnectionProvider +import ru.surfstudio.android.core.mvp.binding.rx.relation.Related +import ru.surfstudio.android.core.mvp.binding.rx.relation.mvp.* +import ru.surfstudio.android.core.mvp.binding.rx.ui.BaseRxPresenter +import ru.surfstudio.android.core.mvp.binding.rx.ui.BindModel +import ru.surfstudio.android.core.mvp.error.ErrorHandler +import ru.surfstudio.android.core.mvp.presenter.BasePresenterDependency +import ru.surfstudio.android.core.ui.event.ScreenEventDelegateManager +import ru.surfstudio.android.core.ui.navigation.activity.navigator.ActivityNavigator +import ru.surfstudio.android.core.ui.state.ScreenState +import ru.surfstudio.android.rx.extension.scheduler.SchedulersProvider +import ru.surfstudio.android.core.mvp.binding.test.navigation.TestActivityNavigator +import ru.surfstudio.android.core.mvp.binding.test.navigation.TestDialogNavigator +import ru.surfstudio.android.core.mvp.binding.test.navigation.TestFragmentNavigator +import ru.surfstudio.android.core.mvp.binding.test.navigation.TestTabFragmentNavigator + +object TEST : ActionSource, ActionTarget, StateSource, StateTarget, CommandSource, CommandTarget + +/** + * Базовый класс для тестирования презентера + * @param PT тип презентера + * @param BMT тип bind-модели + */ +abstract class BasePresenterTest : AnnotationSpec(), Related { + + lateinit var presenter: PT + lateinit var bm: BMT + + var activityNavigator = TestActivityNavigator() + var fragmentNavigator = TestFragmentNavigator() + var tabFragmentNavigator = TestTabFragmentNavigator() + var dialogNavigator = TestDialogNavigator() + + var connectionProvider = TestConnectionProvider() + + var basePresenterDependency = createBasePresenterDependency() + + abstract fun createBindModel(): BMT + + abstract fun createPresenter(bm: BMT): PT + + + fun createBasePresenterDependency( + schedulersProvider: SchedulersProvider = TestSchedulersProvider(), + screenState: ScreenState = TestScreenState(), + eventDelegateManager: ScreenEventDelegateManager = TestScreenEventDelegateManager(), + errorHandler: ErrorHandler = TestErrorHandler(), + connectionProvider: ConnectionProvider = this.connectionProvider, + activityNavigator: ActivityNavigator = this.activityNavigator + ): BasePresenterDependency = BasePresenterDependency( + schedulersProvider, + screenState, + eventDelegateManager, + errorHandler, + connectionProvider, + activityNavigator + ) + + /** + * Инициализация презентера + * + * Следует вызывать перед выполением каждого теста + */ + fun setUpPresenter() { + bm = createBindModel() + presenter = createPresenter(bm) + presenter.onFirstLoad() + } + + /** + * Востановление превоначального состояния окружения + * + * Следует вызывать после каждого теста + */ + fun resetPresenter() { + resetNavigators() + connectionProvider.reset() + presenter.onDestroy() + } + + /** + * Пересоздание презентера + */ + fun recreatePresenter() { + if (::presenter.isInitialized) { + resetPresenter() + } + setUpPresenter() + } + + fun resetNavigators() { + activityNavigator.reset() + fragmentNavigator.reset() + tabFragmentNavigator.reset() + dialogNavigator.reset() + } + + + override fun relationEntity() = TEST + + override fun subscribe( + observable: Observable, + onNext: Consumer, + onError: (Throwable) -> Unit + ): Disposable { + throw NotImplementedError() + } +} diff --git a/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/TestConnectionProvider.kt b/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/TestConnectionProvider.kt new file mode 100644 index 0000000000..db90d779da --- /dev/null +++ b/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/TestConnectionProvider.kt @@ -0,0 +1,58 @@ +package ru.surfstudio.android.core.mvp.binding.test + +import android.content.Context +import android.net.NetworkInfo +import io.reactivex.Observable +import io.reactivex.subjects.PublishSubject +import ru.surfstudio.android.connection.ConnectionProvider + +/** + * Тестовая реализация [ConnectionProvider] + */ +class TestConnectionProvider : ConnectionProvider { + + private val connectionSubject = PublishSubject.create() + + private var isConnectedInternal: Boolean = DEFAULT_IS_CONNECTED + private var isConnectionFastInternal: Boolean = DEFAULT_IS_CONNECTION_FAST + private var isConnectedToWifiInternal: Boolean = DEFAULT_IS_CONNECTED_TO_WIFI + + var networkInfo: NetworkInfo? = null + + fun reset() { + isConnectedInternal = DEFAULT_IS_CONNECTED + isConnectionFastInternal = DEFAULT_IS_CONNECTION_FAST + isConnectedToWifiInternal = DEFAULT_IS_CONNECTED_TO_WIFI + } + + fun emitConnectionChanged(isConnected: Boolean) { + isConnectedInternal = isConnected + connectionSubject.onNext(isConnected) + } + + fun setIsConnectionFast(isConnectionFast: Boolean) { + isConnectionFastInternal = isConnectionFast + } + + fun setIsConnectedToWifi(isConnectedToWifi: Boolean) { + isConnectedToWifiInternal = isConnectedToWifi + } + + override fun observeConnectionChanges(): Observable = connectionSubject.hide() + + override fun isConnected(): Boolean = isConnectedInternal + + override fun isDisconnected(): Boolean = !isConnected() + + override fun isConnectedFast(): Boolean = isConnectionFastInternal + + override fun isConnectedToWifi(): Boolean = isConnectedToWifiInternal + + override fun getNetworkInfo(context: Context): NetworkInfo? = networkInfo + + companion object { + const val DEFAULT_IS_CONNECTED = true + const val DEFAULT_IS_CONNECTION_FAST = true + const val DEFAULT_IS_CONNECTED_TO_WIFI = true + } +} \ No newline at end of file diff --git a/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/TestErrorHandler.kt b/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/TestErrorHandler.kt new file mode 100644 index 0000000000..f6d301ac3d --- /dev/null +++ b/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/TestErrorHandler.kt @@ -0,0 +1,13 @@ +package ru.surfstudio.android.core.mvp.binding.test + +import ru.surfstudio.android.core.mvp.error.ErrorHandler + +/** + * Тестовая реализация [ErrorHandler] + */ +class TestErrorHandler : ErrorHandler { + + override fun handleError(err: Throwable?) { + // do nothing + } +} diff --git a/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/TestSchedulersProvider.kt b/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/TestSchedulersProvider.kt new file mode 100644 index 0000000000..91b2d70d82 --- /dev/null +++ b/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/TestSchedulersProvider.kt @@ -0,0 +1,17 @@ +package ru.surfstudio.android.core.mvp.binding.test + +import io.reactivex.Scheduler +import io.reactivex.schedulers.Schedulers +import ru.surfstudio.android.rx.extension.scheduler.SchedulersProvider + +/** + * Тестовая реализация [SchedulersProvider] + */ +class TestSchedulersProvider : SchedulersProvider { + + override fun main(): Scheduler = Schedulers.trampoline() + + override fun worker(): Scheduler = Schedulers.trampoline() + + override fun computation(): Scheduler = Schedulers.trampoline() +} diff --git a/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/TestScreenEventDelegateManager.kt b/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/TestScreenEventDelegateManager.kt new file mode 100644 index 0000000000..2d226560d0 --- /dev/null +++ b/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/TestScreenEventDelegateManager.kt @@ -0,0 +1,47 @@ +package ru.surfstudio.android.core.mvp.binding.test + +import ru.surfstudio.android.core.ui.ScreenType +import ru.surfstudio.android.core.ui.event.ScreenEventDelegateManager +import ru.surfstudio.android.core.ui.event.base.ScreenEvent +import ru.surfstudio.android.core.ui.event.base.ScreenEventDelegate + +/** + * Тестовая реализация [ScreenEventDelegateManager] + */ +class TestScreenEventDelegateManager : ScreenEventDelegateManager { + + override fun registerDelegate(delegate: ScreenEventDelegate?) { + /* do nothing */ + } + + override fun registerDelegate(delegate: ScreenEventDelegate?, emitterType: ScreenType?) { + /* do nothing */ + } + + override fun registerDelegate(delegate: ScreenEventDelegate?, emitterType: ScreenType?, eventType: Class?) { + /* do nothing */ + } + + override fun unregisterDelegate(delegate: ScreenEventDelegate?, event: Class?): Boolean { + /* do nothing */ + return true + } + + override fun unregisterDelegate(delegate: ScreenEventDelegate?): Boolean { + /* do nothing */ + return true + } + + override fun sendEvent(event: E): R? { + /* do nothing */ + return null + } + + override fun destroy() { + /* do nothing */ + } + + override fun sendUnhandledEvents() { + /* do nothing */ + } +} \ No newline at end of file diff --git a/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/TestScreenState.kt b/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/TestScreenState.kt new file mode 100644 index 0000000000..aec7d169d3 --- /dev/null +++ b/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/TestScreenState.kt @@ -0,0 +1,9 @@ +package ru.surfstudio.android.core.mvp.binding.test + +import ru.surfstudio.android.core.ui.state.BaseScreenState +import ru.surfstudio.android.core.ui.state.ScreenState + +/** + * Тестовая реализация [ScreenState] + */ +class TestScreenState : BaseScreenState() \ No newline at end of file diff --git a/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/TestActivityNavigator.kt b/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/TestActivityNavigator.kt new file mode 100644 index 0000000000..f168fcc152 --- /dev/null +++ b/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/TestActivityNavigator.kt @@ -0,0 +1,112 @@ +package ru.surfstudio.android.core.mvp.binding.test.navigation + +import io.reactivex.Observable +import io.reactivex.subjects.PublishSubject +import ru.surfstudio.android.core.mvp.binding.test.navigation.base.TestNavigationEvent.TestActivityNavigationEvent +import ru.surfstudio.android.core.mvp.binding.test.navigation.base.TestNavigationEvent.TestActivityNavigationEvent.FinishAffinity +import ru.surfstudio.android.core.mvp.binding.test.navigation.base.TestNavigationEvent.TestActivityNavigationEvent.FinishCurrent +import ru.surfstudio.android.core.mvp.binding.test.navigation.base.TestNavigationEvent.TestActivityNavigationEvent.RouteEvent.* +import ru.surfstudio.android.core.mvp.binding.test.navigation.base.BaseTestNavigator +import ru.surfstudio.android.core.ui.navigation.ScreenResult +import ru.surfstudio.android.core.ui.navigation.activity.navigator.ActivityNavigator +import ru.surfstudio.android.core.ui.navigation.activity.route.ActivityRoute +import ru.surfstudio.android.core.ui.navigation.activity.route.ActivityWithResultRoute +import ru.surfstudio.android.core.ui.navigation.activity.route.NewIntentRoute +import ru.surfstudio.android.core.ui.navigation.event.result.CrossFeatureSupportOnActivityResultRoute +import ru.surfstudio.android.core.ui.navigation.event.result.SupportOnActivityResultRoute +import ru.surfstudio.android.core.ui.navigation.feature.installer.SplitFeatureInstallState +import ru.surfstudio.android.core.ui.navigation.feature.route.feature.ActivityCrossFeatureRoute +import java.io.Serializable +import kotlin.reflect.KClass + +/** + * Тестовая реализация [ActivityNavigator] + */ +class TestActivityNavigator : BaseTestNavigator(), ActivityNavigator { + + private val activityResultMap = mutableMapOf>, PublishSubject>>() + private val newIntentMap = mutableMapOf, PublishSubject>() + + override fun reset() { + super.reset() + activityResultMap.clear() + newIntentMap.clear() + } + + fun emitActivityResult(routeClass: KClass>, result: T? = null, success: Boolean = true) { + val subject = activityResultMap[routeClass.java] + ?: throw RuntimeException("${routeClass.java} isn't being observed") + + subject.onNext(ScreenResult(success, result)) + } + + fun emitNewIntent(route: T) { + val subject = newIntentMap[route::class.java] + ?: throw RuntimeException("${route::class.java} isn't being observed") + + subject.onNext(route) + } + + override fun observeResult(routeClass: Class>): Observable> { + val subject = PublishSubject.create>() + activityResultMap[routeClass] = subject + @Suppress("UNCHECKED_CAST") + return subject.map { ScreenResult(it.isSuccess, it.data as T?) }.hide() + } + + override fun observeResult(route: SupportOnActivityResultRoute): Observable> { + return observeResult(route::class.java) + } + + override fun finishCurrent() { + mutableEvents.add(FinishCurrent) + } + + override fun finishAffinity() { + mutableEvents.add(FinishAffinity) + } + + override fun finishWithResult(route: ActivityWithResultRoute, success: Boolean) { + finishWithResult(route, null, success) + } + + override fun finishWithResult(activeScreenRoute: SupportOnActivityResultRoute, result: T?) { + finishWithResult(activeScreenRoute, result, true) + } + + override fun finishWithResult(currentScreenRoute: SupportOnActivityResultRoute, result: T?, success: Boolean) { + mutableEvents.add(FinishWithResult(currentScreenRoute, result, success)) + } + + override fun start(route: ActivityRoute): Boolean { + mutableEvents.add(Start(route)) + return true + } + + override fun start(route: ActivityCrossFeatureRoute): Observable { + mutableEvents.add(Start(route)) + return Observable.empty() + } + + override fun startForResult(route: CrossFeatureSupportOnActivityResultRoute<*>): Observable { + mutableEvents.add(StartForResult(route)) + return Observable.empty() + } + + override fun startForResult(route: SupportOnActivityResultRoute<*>): Boolean { + mutableEvents.add(StartForResult(route)) + return true + } + + override fun observeNewIntent(newIntentRouteClass: Class): Observable { + val subject = PublishSubject.create() + newIntentMap[newIntentRouteClass] = subject + @Suppress("UNCHECKED_CAST") + return subject.map { it as T }.hide() + } + + override fun observeNewIntent(newIntentRoute: T): Observable { + @Suppress("UNCHECKED_CAST") + return observeNewIntent(newIntentRoute::class.java) as Observable + } +} diff --git a/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/TestDialogNavigator.kt b/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/TestDialogNavigator.kt new file mode 100644 index 0000000000..a6c4993bd0 --- /dev/null +++ b/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/TestDialogNavigator.kt @@ -0,0 +1,23 @@ +package ru.surfstudio.android.core.mvp.binding.test.navigation + +import ru.surfstudio.android.mvp.dialog.navigation.navigator.DialogNavigator +import ru.surfstudio.android.mvp.dialog.navigation.route.DialogRoute +import ru.surfstudio.android.core.mvp.binding.test.navigation.base.TestNavigationEvent.TestDialogNavigationEvent +import ru.surfstudio.android.core.mvp.binding.test.navigation.base.TestNavigationEvent.TestDialogNavigationEvent.DismissDialog +import ru.surfstudio.android.core.mvp.binding.test.navigation.base.TestNavigationEvent.TestDialogNavigationEvent.ShowDialog +import ru.surfstudio.android.core.mvp.binding.test.navigation.base.BaseTestNavigator + +/** + * Тестовая реализация [DialogNavigator] + */ +class TestDialogNavigator : BaseTestNavigator>(), DialogNavigator { + + override fun show(dialogRoute: DialogRoute) { + mutableEvents.add(ShowDialog(dialogRoute)) + } + + override fun dismiss(dialogRoute: DialogRoute) { + mutableEvents.add(DismissDialog(dialogRoute)) + } + +} \ No newline at end of file diff --git a/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/TestFragmentNavigator.kt b/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/TestFragmentNavigator.kt new file mode 100644 index 0000000000..5069174686 --- /dev/null +++ b/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/TestFragmentNavigator.kt @@ -0,0 +1,52 @@ +package ru.surfstudio.android.core.mvp.binding.test.navigation + +import ru.surfstudio.android.core.ui.navigation.fragment.FragmentNavigator +import ru.surfstudio.android.core.ui.navigation.fragment.route.FragmentRoute +import ru.surfstudio.android.core.mvp.binding.test.navigation.base.TestNavigationEvent.TestFragmentNavigationEvent +import ru.surfstudio.android.core.mvp.binding.test.navigation.base.TestNavigationEvent.TestFragmentNavigationEvent.ClearBackStack +import ru.surfstudio.android.core.mvp.binding.test.navigation.base.TestNavigationEvent.TestFragmentNavigationEvent.RouteEvent.* +import ru.surfstudio.android.core.mvp.binding.test.navigation.base.BaseTestNavigator + +/** + * Тестовая реализация [FragmentNavigator] + */ +class TestFragmentNavigator : BaseTestNavigator(), FragmentNavigator { + + override fun add(route: FragmentRoute, stackable: Boolean, transition: Int) { + mutableEvents.add(Add(route)) + } + + override fun replace(route: FragmentRoute, stackable: Boolean, transition: Int) { + mutableEvents.add(Replace(route)) + } + + override fun remove(route: FragmentRoute, transition: Int): Boolean { + mutableEvents.add(Remove(route)) + return true + } + + override fun show(route: FragmentRoute, transition: Int): Boolean { + mutableEvents.add(Show(route)) + return true + } + + override fun hide(route: FragmentRoute, transition: Int): Boolean { + mutableEvents.add(Hide(route)) + return true + } + + override fun popBackStack(): Boolean { + mutableEvents.add(PopBackStack()) + return true + } + + override fun popBackStack(route: FragmentRoute, inclusive: Boolean): Boolean { + mutableEvents.add(PopBackStack(route, inclusive)) + return true + } + + override fun clearBackStack(): Boolean { + mutableEvents.add(ClearBackStack) + return true + } +} \ No newline at end of file diff --git a/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/TestGlobalNavigator.kt b/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/TestGlobalNavigator.kt new file mode 100644 index 0000000000..9bad0ed4dd --- /dev/null +++ b/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/TestGlobalNavigator.kt @@ -0,0 +1,18 @@ +package ru.surfstudio.android.core.mvp.binding.test.navigation + +import ru.surfstudio.android.core.ui.navigation.activity.navigator.GlobalNavigator +import ru.surfstudio.android.core.ui.navigation.activity.route.ActivityRoute +import ru.surfstudio.android.core.mvp.binding.test.navigation.base.TestNavigationEvent.TestActivityNavigationEvent +import ru.surfstudio.android.core.mvp.binding.test.navigation.base.TestNavigationEvent.TestActivityNavigationEvent.RouteEvent.Start +import ru.surfstudio.android.core.mvp.binding.test.navigation.base.BaseTestNavigator + +/** + * Тестовая реализация [GlobalNavigator] + */ +class TestGlobalNavigator : BaseTestNavigator(), GlobalNavigator { + + override fun start(route: ActivityRoute): Boolean { + mutableEvents.add(Start(route)) + return true + } +} \ No newline at end of file diff --git a/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/TestTabFragmentNavigator.kt b/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/TestTabFragmentNavigator.kt new file mode 100644 index 0000000000..d0957f12e2 --- /dev/null +++ b/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/TestTabFragmentNavigator.kt @@ -0,0 +1,54 @@ +package ru.surfstudio.android.core.mvp.binding.test.navigation + +import io.reactivex.Observable +import io.reactivex.subjects.PublishSubject +import ru.surfstudio.android.core.ui.event.back.OnBackPressedEvent +import ru.surfstudio.android.core.ui.navigation.fragment.route.FragmentRoute +import ru.surfstudio.android.core.ui.navigation.fragment.route.RootFragmentRoute +import ru.surfstudio.android.core.ui.navigation.fragment.tabfragment.TabFragmentNavigator +import ru.surfstudio.android.core.mvp.binding.test.navigation.base.TestNavigationEvent.TestTabFragmentNavigationEvent +import ru.surfstudio.android.core.mvp.binding.test.navigation.base.TestNavigationEvent.TestTabFragmentNavigationEvent.ClearStack +import ru.surfstudio.android.core.mvp.binding.test.navigation.base.TestNavigationEvent.TestTabFragmentNavigationEvent.ClearTabs +import ru.surfstudio.android.core.mvp.binding.test.navigation.base.TestNavigationEvent.TestTabFragmentNavigationEvent.RouteEvent.* +import ru.surfstudio.android.core.mvp.binding.test.navigation.base.BaseTestNavigator + +class TestTabFragmentNavigator : BaseTestNavigator(), TabFragmentNavigator { + + private val backPressedEventSubject = PublishSubject.create() + override val backPressedEventObservable: Observable = backPressedEventSubject.hide() + + private val activeTabReOpenSubject = PublishSubject.create() + override val activeTabReOpenObservable: Observable = activeTabReOpenSubject.hide() + + fun pressBack() { + backPressedEventSubject.onNext(OnBackPressedEvent()) + } + + fun reopenActiveTab() { + activeTabReOpenSubject.onNext(Unit) + } + + override fun open(route: FragmentRoute) { + mutableEvents.add(Open(route)) + } + + override fun showAtTab(tabRoute: T, fragmentRoute: FragmentRoute, clearStack: Boolean) where T : FragmentRoute, T : RootFragmentRoute { + mutableEvents.add(ShowAtTab(tabRoute, fragmentRoute, clearStack)) + } + + override fun replace(fragmentRoute: FragmentRoute) { + mutableEvents.add(Replace(fragmentRoute)) + } + + override fun clearTabs(vararg routes: T) where T : FragmentRoute, T : RootFragmentRoute { + mutableEvents.add(ClearTabs(routes)) + } + + override fun clearStack() { + mutableEvents.add(ClearStack) + } + + override fun clearStackTo(route: FragmentRoute) { + mutableEvents.add(ClearStackTo(route)) + } +} \ No newline at end of file diff --git a/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/base/BaseTestNavigator.kt b/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/base/BaseTestNavigator.kt new file mode 100644 index 0000000000..d13d2b7312 --- /dev/null +++ b/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/base/BaseTestNavigator.kt @@ -0,0 +1,18 @@ +package ru.surfstudio.android.core.mvp.binding.test.navigation.base + +import androidx.annotation.CallSuper + +/** + * Базовый класс для тестовых имплементаций навигаторов + */ +abstract class BaseTestNavigator { + + val events: List get() = mutableEvents + + protected val mutableEvents: MutableList = mutableListOf() + + @CallSuper + open fun reset() { + mutableEvents.clear() + } +} \ No newline at end of file diff --git a/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/base/TestNavigationEvent.kt b/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/base/TestNavigationEvent.kt new file mode 100644 index 0000000000..77dfe95b75 --- /dev/null +++ b/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/base/TestNavigationEvent.kt @@ -0,0 +1,70 @@ +package ru.surfstudio.android.core.mvp.binding.test.navigation.base + +import ru.surfstudio.android.core.ui.navigation.ActivityRouteInterface +import ru.surfstudio.android.core.ui.navigation.event.result.SupportOnActivityResultRoute +import ru.surfstudio.android.core.ui.navigation.fragment.route.FragmentRoute +import ru.surfstudio.android.core.ui.navigation.fragment.route.RootFragmentRoute +import ru.surfstudio.android.mvp.dialog.navigation.route.DialogRoute +import java.io.Serializable + +/** + * Тестовые события навигации + */ +sealed class TestNavigationEvent { + + sealed class TestActivityNavigationEvent : TestNavigationEvent() { + + sealed class RouteEvent : TestActivityNavigationEvent(), RouteEventInterface { + + class Start(override val route: RT) : RouteEvent() + class StartForResult(override val route: RT) : RouteEvent() + class FinishWithResult, T : Serializable>( + override val route: RT, + val result: T? = null, + val success: Boolean = true + ) : RouteEvent() + } + + object FinishCurrent : TestActivityNavigationEvent() + object FinishAffinity : TestActivityNavigationEvent() + } + + sealed class TestFragmentNavigationEvent : TestNavigationEvent() { + + sealed class RouteEvent : TestFragmentNavigationEvent(), RouteEventInterface { + + class Add(override val route: RT) : RouteEvent() + class Replace(override val route: RT) : RouteEvent() + class Remove(override val route: RT) : RouteEvent() + class Show(override val route: RT) : RouteEvent() + class Hide(override val route: RT) : RouteEvent() + class PopBackStack(override val route: RT? = null, val inclusive: Boolean = true) : RouteEvent() + } + + object ClearBackStack : TestFragmentNavigationEvent() + } + + sealed class TestDialogNavigationEvent : TestNavigationEvent(), RouteEventInterface { + + class ShowDialog(override val route: RT) : TestDialogNavigationEvent() + class DismissDialog(override val route: RT) : TestDialogNavigationEvent() + } + + sealed class TestTabFragmentNavigationEvent : TestNavigationEvent() { + + sealed class RouteEvent : TestTabFragmentNavigationEvent(), RouteEventInterface { + + class Open(override val route: RT) : RouteEvent() + class ShowAtTab(val tabRoute: FragmentRoute, override val route: RT, val clearStack: Boolean = false) : RouteEvent() + class ClearStackTo(override val route: RT) : RouteEvent() + class Replace(override val route: RT) : RouteEvent() + } + + class ClearTabs(val routes: Array) : TestTabFragmentNavigationEvent() + object ClearStack : TestTabFragmentNavigationEvent() + } + + interface RouteEventInterface { + val route: RT? + } +} diff --git a/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/matchers/ActivityNavigationMatchers.kt b/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/matchers/ActivityNavigationMatchers.kt new file mode 100644 index 0000000000..4e08c53b09 --- /dev/null +++ b/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/matchers/ActivityNavigationMatchers.kt @@ -0,0 +1,64 @@ +package ru.surfstudio.android.core.mvp.binding.test.navigation.matchers + +import io.kotest.matchers.shouldBe +import io.kotest.matchers.types.shouldBeTypeOf +import ru.surfstudio.android.core.mvp.binding.test.navigation.base.TestNavigationEvent.TestActivityNavigationEvent +import ru.surfstudio.android.core.mvp.binding.test.navigation.base.TestNavigationEvent.TestActivityNavigationEvent.FinishAffinity +import ru.surfstudio.android.core.mvp.binding.test.navigation.base.TestNavigationEvent.TestActivityNavigationEvent.FinishCurrent +import ru.surfstudio.android.core.mvp.binding.test.navigation.base.TestNavigationEvent.TestActivityNavigationEvent.RouteEvent.* +import ru.surfstudio.android.core.ui.navigation.ActivityRouteInterface +import ru.surfstudio.android.core.ui.navigation.activity.navigator.ActivityNavigator +import ru.surfstudio.android.core.ui.navigation.activity.navigator.GlobalNavigator +import ru.surfstudio.android.core.ui.navigation.event.result.SupportOnActivityResultRoute +import java.io.Serializable +import kotlin.reflect.KClass + +/** + * Проверка вызова метода [ActivityNavigator.start] или [GlobalNavigator.start] + */ +inline fun TestActivityNavigationEvent.shouldBeStart(): Start { + return shouldBeEventWithRoute, RT>() +} + +/** + * Проверка вызова метода [ActivityNavigator.startForResult] + */ +inline fun TestActivityNavigationEvent.shouldBeStartForResult(): StartForResult { + return shouldBeEventWithRoute, RT>() +} + +/** + * Проверка вызова метода [ActivityNavigator.finishWithResult] + */ +inline fun , T : Serializable> TestActivityNavigationEvent.shouldBeFinishWithResult(routeClass: KClass): FinishWithResult { + return shouldBeEventWithRoute, RT>() +} + +/** + * Проверка, что активити завершилась с успехом + */ +fun , T : Serializable> FinishWithResult.shouldBeSuccess() = apply { + success shouldBe true +} + +/** + * Проверка результата активити + */ +fun , T : Serializable> FinishWithResult.withResult(result: T?) = apply { + this.result shouldBe result +} + +/** + * Проверка вызова метода [ActivityNavigator.finishCurrent] + */ + +fun TestActivityNavigationEvent.shouldBeFinishCurrent(): FinishCurrent { + return shouldBeTypeOf() +} + +/** + * Проверка вызова метода [ActivityNavigator.finishAffinity] + */ +fun TestActivityNavigationEvent.shouldBeFinishAffinity(): FinishAffinity { + return shouldBeTypeOf() +} diff --git a/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/matchers/DialogNavigaitonMatchers.kt b/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/matchers/DialogNavigaitonMatchers.kt new file mode 100644 index 0000000000..64457257e2 --- /dev/null +++ b/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/matchers/DialogNavigaitonMatchers.kt @@ -0,0 +1,21 @@ +package ru.surfstudio.android.core.mvp.binding.test.navigation.matchers + +import ru.surfstudio.android.mvp.dialog.navigation.navigator.DialogNavigator +import ru.surfstudio.android.mvp.dialog.navigation.route.DialogRoute +import ru.surfstudio.android.core.mvp.binding.test.navigation.base.TestNavigationEvent.TestDialogNavigationEvent +import ru.surfstudio.android.core.mvp.binding.test.navigation.base.TestNavigationEvent.TestDialogNavigationEvent.DismissDialog +import ru.surfstudio.android.core.mvp.binding.test.navigation.base.TestNavigationEvent.TestDialogNavigationEvent.ShowDialog + +/** + * Проверка вызова метода [DialogNavigator.show] + */ +inline fun TestDialogNavigationEvent<*>.shouldBeShowDialog(): ShowDialog { + return shouldBeEventWithRoute, RT>() +} + +/** + * Проверка вызова метода [DialogNavigator.dismiss] + */ +inline fun TestDialogNavigationEvent<*>.shouldBeDismissDialog(): DismissDialog { + return shouldBeEventWithRoute, RT>() +} diff --git a/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/matchers/FragmentNavigationMatchers.kt b/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/matchers/FragmentNavigationMatchers.kt new file mode 100644 index 0000000000..094ceda151 --- /dev/null +++ b/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/matchers/FragmentNavigationMatchers.kt @@ -0,0 +1,61 @@ +package ru.surfstudio.android.core.mvp.binding.test.navigation.matchers + +import io.kotest.matchers.shouldBe +import io.kotest.matchers.types.shouldBeTypeOf +import ru.surfstudio.android.core.ui.navigation.fragment.FragmentNavigator +import ru.surfstudio.android.core.ui.navigation.fragment.route.FragmentRoute +import ru.surfstudio.android.core.mvp.binding.test.navigation.base.TestNavigationEvent.TestFragmentNavigationEvent +import ru.surfstudio.android.core.mvp.binding.test.navigation.base.TestNavigationEvent.TestFragmentNavigationEvent.ClearBackStack +import ru.surfstudio.android.core.mvp.binding.test.navigation.base.TestNavigationEvent.TestFragmentNavigationEvent.RouteEvent.* + + +/** + * Проверка вызова метода [FragmentNavigator.add] + */ +inline fun TestFragmentNavigationEvent.shouldBeAdd(): Add { + return shouldBeEventWithRoute, RT>() +} + +/** + * Проверка вызова метода [FragmentNavigator.replace] + */ +inline fun TestFragmentNavigationEvent.shouldBeReplace(): Replace { + return shouldBeEventWithRoute, RT>() +} + +/** + * Проверка вызова метода [FragmentNavigator.remove] + */ +inline fun TestFragmentNavigationEvent.shouldBeRemove(): Remove { + return shouldBeEventWithRoute, RT>() +} + +/** + * Проверка вызова метода [FragmentNavigator.show] + */ +inline fun TestFragmentNavigationEvent.shouldBeShow(): Show { + return shouldBeEventWithRoute, RT>() +} + +/** + * Проверка вызова метода [FragmentNavigator.hide] + */ +inline fun TestFragmentNavigationEvent.shouldBeHide(): Hide { + return shouldBeEventWithRoute, RT>() +} + +/** + * Проверка вызова метода [FragmentNavigator.popBackStack] + */ +inline fun TestFragmentNavigationEvent.shouldBePopBackStack(inclusive: Boolean = true): PopBackStack { + return shouldBeEventWithRoute, RT>().also { + it.inclusive shouldBe inclusive + } +} + +/** + * Проверка вызова метода [FragmentNavigator.clearBackStack] + */ +fun TestFragmentNavigationEvent.shouldBeClearBackStack(): ClearBackStack { + return shouldBeTypeOf() +} diff --git a/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/matchers/NavigationMatchers.kt b/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/matchers/NavigationMatchers.kt new file mode 100644 index 0000000000..c3cba3fd6b --- /dev/null +++ b/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/matchers/NavigationMatchers.kt @@ -0,0 +1,12 @@ +package ru.surfstudio.android.core.mvp.binding.test.navigation.matchers + +import io.kotest.matchers.types.shouldBeTypeOf +import ru.surfstudio.android.core.ui.navigation.Route +import ru.surfstudio.android.core.mvp.binding.test.navigation.base.TestNavigationEvent +import ru.surfstudio.android.core.mvp.binding.test.navigation.base.TestNavigationEvent.RouteEventInterface + +inline fun , reified RT : Route> TestNavigationEvent.shouldBeEventWithRoute(): ET { + return shouldBeTypeOf().also { + it.route.shouldBeTypeOf() + } +} diff --git a/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/matchers/TabFragmentNavigationMatchers.kt b/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/matchers/TabFragmentNavigationMatchers.kt new file mode 100644 index 0000000000..4c3962a921 --- /dev/null +++ b/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/matchers/TabFragmentNavigationMatchers.kt @@ -0,0 +1,62 @@ +package ru.surfstudio.android.core.mvp.binding.test.navigation.matchers + +import io.kotest.matchers.shouldBe +import io.kotest.matchers.types.shouldBeTypeOf +import ru.surfstudio.android.core.ui.navigation.Route +import ru.surfstudio.android.core.ui.navigation.fragment.route.FragmentRoute +import ru.surfstudio.android.core.ui.navigation.fragment.tabfragment.TabFragmentNavigator +import ru.surfstudio.android.core.mvp.binding.test.navigation.base.TestNavigationEvent.TestTabFragmentNavigationEvent +import ru.surfstudio.android.core.mvp.binding.test.navigation.base.TestNavigationEvent.TestTabFragmentNavigationEvent.ClearStack +import ru.surfstudio.android.core.mvp.binding.test.navigation.base.TestNavigationEvent.TestTabFragmentNavigationEvent.ClearTabs +import ru.surfstudio.android.core.mvp.binding.test.navigation.base.TestNavigationEvent.TestTabFragmentNavigationEvent.RouteEvent.* +import kotlin.reflect.KClass + +/** + * Проверка вызова метода [TabFragmentNavigator.open] + */ +inline fun TestTabFragmentNavigationEvent.shouldBeOpen(): Open { + return shouldBeEventWithRoute, RT>() +} + +/** + * Проверка вызова метода [TabFragmentNavigator.showAtTab] + * @param TRT класс роута таба + * @param RT класс роута экрана + */ +inline fun TestTabFragmentNavigationEvent.shouldBeShowAtTab(): ShowAtTab { + return shouldBeEventWithRoute, RT>().also { + it.tabRoute.shouldBeTypeOf() + } +} + +/** + * Проверка вызова метода [TabFragmentNavigator.clearTabs] + * @param expectedRoutesToClean множество роутов табов, которые должны быть очищены + */ +inline fun TestTabFragmentNavigationEvent.shouldBeClearTabs(expectedRoutesToClean: Set>): ClearTabs { + val event = shouldBeTypeOf() + val routesClasses = event.routes.map { it::class }.toSet() + routesClasses shouldBe expectedRoutesToClean + return event +} + +/** + * Проверка вызова метода [TabFragmentNavigator.clearStackTo] + */ +inline fun TestTabFragmentNavigationEvent.shouldBeClearStackTo(): ClearStackTo { + return shouldBeEventWithRoute, RT>() +} + +/** + * Проверка вызова метода [TabFragmentNavigator.replace] + */ +inline fun TestTabFragmentNavigationEvent.shouldBeReplace(): Replace { + return shouldBeEventWithRoute, RT>() +} + +/** + * Проверка вызова метода [TabFragmentNavigator.clearStack] + */ +fun TestTabFragmentNavigationEvent.shouldBeClearStack(): ClearStack { + return shouldBeTypeOf() +} From 9324dd71fce45831012f2539f91343c3c8c83939 Mon Sep 17 00:00:00 2001 From: Kartashov Andrey Date: Tue, 13 Apr 2021 17:56:16 +0300 Subject: [PATCH 09/17] ANDDEP-1207 added documentation --- .../mvp/binding/test/BasePresenterTest.kt | 10 +- docs/common/unit_testing.md | 123 +++++++++++++++++- 2 files changed, 126 insertions(+), 7 deletions(-) diff --git a/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/BasePresenterTest.kt b/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/BasePresenterTest.kt index 6157c9dee4..5816832c0f 100644 --- a/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/BasePresenterTest.kt +++ b/core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/BasePresenterTest.kt @@ -9,16 +9,16 @@ import ru.surfstudio.android.core.mvp.binding.rx.relation.Related import ru.surfstudio.android.core.mvp.binding.rx.relation.mvp.* import ru.surfstudio.android.core.mvp.binding.rx.ui.BaseRxPresenter import ru.surfstudio.android.core.mvp.binding.rx.ui.BindModel +import ru.surfstudio.android.core.mvp.binding.test.navigation.TestActivityNavigator +import ru.surfstudio.android.core.mvp.binding.test.navigation.TestDialogNavigator +import ru.surfstudio.android.core.mvp.binding.test.navigation.TestFragmentNavigator +import ru.surfstudio.android.core.mvp.binding.test.navigation.TestTabFragmentNavigator import ru.surfstudio.android.core.mvp.error.ErrorHandler import ru.surfstudio.android.core.mvp.presenter.BasePresenterDependency import ru.surfstudio.android.core.ui.event.ScreenEventDelegateManager import ru.surfstudio.android.core.ui.navigation.activity.navigator.ActivityNavigator import ru.surfstudio.android.core.ui.state.ScreenState import ru.surfstudio.android.rx.extension.scheduler.SchedulersProvider -import ru.surfstudio.android.core.mvp.binding.test.navigation.TestActivityNavigator -import ru.surfstudio.android.core.mvp.binding.test.navigation.TestDialogNavigator -import ru.surfstudio.android.core.mvp.binding.test.navigation.TestFragmentNavigator -import ru.surfstudio.android.core.mvp.binding.test.navigation.TestTabFragmentNavigator object TEST : ActionSource, ActionTarget, StateSource, StateTarget, CommandSource, CommandTarget @@ -45,7 +45,6 @@ abstract class BasePresenterTest : Annota abstract fun createPresenter(bm: BMT): PT - fun createBasePresenterDependency( schedulersProvider: SchedulersProvider = TestSchedulersProvider(), screenState: ScreenState = TestScreenState(), @@ -101,7 +100,6 @@ abstract class BasePresenterTest : Annota dialogNavigator.reset() } - override fun relationEntity() = TEST override fun subscribe( diff --git a/docs/common/unit_testing.md b/docs/common/unit_testing.md index c90eef51db..4a366773d3 100644 --- a/docs/common/unit_testing.md +++ b/docs/common/unit_testing.md @@ -8,7 +8,7 @@ 1. [Middleware](#middleware) 2. [Reducer](#reducer) 3. [CommandHolder](#commandholder) - 2. Binding + 2. [Binding](#binding) 3. MVP 4. [Best practices](#best-practices) 5. [Материалы](#Материалы) @@ -372,6 +372,110 @@ fun `when pin is wrong, should show error under pins`() = forAll( } ``` +#### Binding +Для реализации тест-класса презентера существует базовый класс [`BasePresenterTest`][basePresenterTest] , который содержит базовые зависимости для презентера. +Тестирование презентера сводится к 4 шагам: +1. Вызов метода `Presenter#onFirstLoad()` для биндинга к `BindModel` +1. Триггер нужных экшенов в `BindModel` +1. Провертка результата - данные с стейте или выполненная навигация +1. Вызов метода `Presenter#onDestroy()` + +Первый и последний шаги вынесены в `BasePresenterTest`. +**Пример тест-класса** +```kotlin +internal class ActivateCardsPresenterTest : BasePresenterTest() { + + override fun createBindModel() = ActivateCardsBindModel() + + override fun createPresenter(bm: ActivateCardsBindModel): ActivateCardsPresenter { + return ActivateCardsPresenter( + ... + bm, + basePresenterDependency + ) + } + + @Before + fun setUp() { + setUpPresenter() // создает презентер и bind-модель, вызывает presenter.onFirstLoad() + } + + @After + fun tearDown() { + resetPresenter() // очищает подписки презентера, приводит навигаторы в изначальное состояние + } + + @Test + fun `when dismiss action is accepted should accept dismiss command`() { + val testObserver = bm.onDismissCommand.observable.test() + + bm.onDismissAction.accept() + + testObserver.assertValue(Unit) + testObserver.dispose() + } +} +``` + +**Получение данных из стейта** +Из стейта можно получить как последнее значение, так и историю его изменения. В первом случае достаточно обратиться к полю `State#value`. +```kotlin + val currentStateValue = bm.someState.value +``` + +Для получения истории изменений нужно подписаться на стейт. +```koltin + @Test + fun `when card is being activated should show transparent loading`() { + val loadStateObserver = bm.loadState.observable.test() + bm.activateCardAction.accept() + + val (initialState, loadingState, finalState) = loadStateObserver.values() + assertSoftly { + initialState shouldBe LoadState.NONE + loadingState shouldBe LoadState.TRANSPARENT_LOADING + finalState shouldBe LoadState.NONE + } + + loadStateObserver.dispose() + } +``` + +**Навигация** +Для каждого навигатора предусмотрена реализация c префиксом `Test`, которая сохраняет события навигации в список. После чего можно проверить наличие нужных событий с помощью соответствующих мэтчеров. + +| Navigator | Matchers | +| --------- | -------- | +| [`TestActivityNavigator`][activityNavigator] | [`ActivityNavigationMatchers.kt`][activityNavigationMatchers] | +| [`TestFragmentNavigator`][fragmentNavigator] | [`FragmentNavigationMatchers.kt`][fragmentNavigationMatchers] | +| [`TestTabFragmentnavigator`][tabFragmentNavigator] | [`TabFragmentNavigationMatchers.kt`][tabFragmentNavigationMatchers] | +| [`TestDialogNavigator`][dialogNavigator] | [`DialogNavigationMatchers.kt`][dialogNavigationMatchers] | +| [`TestGlobalNavigator`][globalNavigator] | [`ActivityNavigationMatchers.kt`][activityNavigationMatchers] | + + +```kotlin + @Test + fun `when delay elapsed should finish affinity and start fast auth activity`() { + val testScheduler = TestScheduler() + RxJavaPlugins.setComputationSchedulerHandler { testScheduler } + recreatePresenter() // пересоздаем презентер, чтобы применился testScheduler + + testScheduler.advanceTimeBy(TRANSITION_DELAY_MS, TimeUnit.MILLISECONDS) + + val (finishEvent, navEvent) = activityNavigator.events + assertSoftly { + finishEvent.shouldBeFinishAffinity() + navEvent.shouldBeStart() + } + RxJavaPlugins.reset() + } +``` +**Обработка результата экрана** +Если нужно проверить поведение при получении результата с другого экрана, в `TestActivityNavigator` предусмотрен метод `emitActivityResult`. Например эмит результата с экрана `InputFormActivityView` будет выглядеть следующим образом: +```kotlin +activityNavigator.emitActivityResult(InputFormActivityRoute::class, inputFormResult, success = true) +``` + #### Best practices - В конце теста необходимо выполнять `testObserver.dispose()` @@ -381,6 +485,11 @@ fun `when pin is wrong, should show error under pins`() = forAll( ```kotlin mockkStatic(HtmlCompat::class) ``` +- Для мокирования `object`-классов используется `mockkObject`, но он не поддреживает `relaxed` режим. Для предотвращения вызова реального метода нужно замокировать его отдельно. +```kotlin +mockkObject(Foo) +every { Foo.doSomething() } returns Unit +``` - Когда необходимо гарантировать выполнение определенных инструкций внутри тестируемого метода, можно использовать `verify`: ```kotlin @@ -409,3 +518,15 @@ mockkStatic(HtmlCompat::class) [bestReactorTest]: ../../template/base_feature/src/main/java/ru/surfstudio/standard/ui/test/base/BaseReactorTest.kt [navigationMatchers]: ../../template/base_feature/src/main/java/ru/surfstudio/standard/ui/test/matcher/NavigationMatchers.kt [requestMatchers]: ../../template/base/src/main/java/ru/surfstudio/standard/base/test/matcher/RequestMatchers.kt + +[basePresenterTest]: ../../core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/BasePresenterTest.kt +[activityNavigator]: ../../core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/TestActivityNavigator.kt +[fragmentNavigator]: ../../core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/TestFragmentNavigator.kt +[tabFragmentNavigator]: ../../core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/TestTabFragmentNavigator.kt +[dialogNavigator]: ../../core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/TestDialogNavigator.kt +[globalNavigator]: ../../core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/TestGlobalNavigator.kt + +[activityNavigationMatchers]: ../../core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/matchers/ActivityNavigationMatchers.kt +[fragmentNavigationMatchers]: ../../core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/matchers/FragmentNavigationMatchers.kt +[tabFragmentNavigationMatchers]: ../../core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/matchers/TabFragmentNavigationMatchers.kt +[dialogNavigationMatchers]: ../../core-mvp-binding/lib-core-mvp-binding-tests/src/main/java/ru/surfstudio/android/core/mvp/binding/test/navigation/matchers/DialogNavigaitonMatchers.kt From 2f43dfe0476db049f064e38fe2e277716c25fed7 Mon Sep 17 00:00:00 2001 From: Kartashov Andrey Date: Tue, 13 Apr 2021 18:06:20 +0300 Subject: [PATCH 10/17] ANDDEP-1207 documentation fixes --- docs/common/unit_testing.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/common/unit_testing.md b/docs/common/unit_testing.md index 4a366773d3..fce0cfc0b1 100644 --- a/docs/common/unit_testing.md +++ b/docs/common/unit_testing.md @@ -381,6 +381,7 @@ fun `when pin is wrong, should show error under pins`() = forAll( 1. Вызов метода `Presenter#onDestroy()` Первый и последний шаги вынесены в `BasePresenterTest`. + **Пример тест-класса** ```kotlin internal class ActivateCardsPresenterTest : BasePresenterTest() { @@ -418,6 +419,7 @@ internal class ActivateCardsPresenterTest : BasePresenterTest Date: Wed, 14 Apr 2021 09:36:18 +0300 Subject: [PATCH 11/17] ANDDEP-1207 added release notes and removed extra lines --- common/RELEASE_NOTES.md | 1 + connection/RELEASE_NOTES.md | 4 ++-- .../android/connection/ConnectionProviderImpl.java | 2 -- core-mvp-binding/RELEASE_NOTES.md | 1 + core-navigation/RELEASE_NOTES.md | 1 + .../activity/navigator/BaseActivityNavigator.java | 12 +----------- dagger-scope/RELEASE_NOTES.md | 1 + filestorage/RELEASE_NOTES.md | 1 + mvi/RELEASE_NOTES.md | 1 + mvp/RELEASE_NOTES.md | 1 + permission/RELEASE_NOTES.md | 1 + picture-provider/RELEASE_NOTES.md | 1 + 12 files changed, 12 insertions(+), 15 deletions(-) diff --git a/common/RELEASE_NOTES.md b/common/RELEASE_NOTES.md index 6d4cb2174d..055eb4ca13 100644 --- a/common/RELEASE_NOTES.md +++ b/common/RELEASE_NOTES.md @@ -5,6 +5,7 @@ ## 0.5.0-alpha ##### Sample-common +* ANDDEP-1207 Fixed sample's dependencies * ANDDEP-785 added method scrollToBottom() in RecyclerViewUtils * ANDDEP-991 added method checkIfSnackbarIsVisible(String) * ANDDEP-978 Fix common dependency diff --git a/connection/RELEASE_NOTES.md b/connection/RELEASE_NOTES.md index 31a376808b..1d1f846d03 100644 --- a/connection/RELEASE_NOTES.md +++ b/connection/RELEASE_NOTES.md @@ -5,7 +5,7 @@ ## 0.5.0-alpha ##### Connection -* TODO +* ANDDEP-1207 extracted ConnectionProvider interface ## 0.4.0 ##### Connection -* Added method for checking Wi-Fi connection in [ConnectionProvider](lib-connection/src/main/java/ru/surfstudio/android/connection/ConnectionProvider.java) \ No newline at end of file +* Added method for checking Wi-Fi connection in [ConnectionProvider](lib-connection/src/main/java/ru/surfstudio/android/connection/ConnectionProvider.kt) \ No newline at end of file diff --git a/connection/lib-connection/src/main/java/ru/surfstudio/android/connection/ConnectionProviderImpl.java b/connection/lib-connection/src/main/java/ru/surfstudio/android/connection/ConnectionProviderImpl.java index 0514bba1bc..0b6d76c86b 100755 --- a/connection/lib-connection/src/main/java/ru/surfstudio/android/connection/ConnectionProviderImpl.java +++ b/connection/lib-connection/src/main/java/ru/surfstudio/android/connection/ConnectionProviderImpl.java @@ -67,14 +67,12 @@ public boolean isConnectedFast() { return lastConnectionResultFast; } - @Override public boolean isConnectedToWifi() { NetworkInfo info = getNetworkInfo(context); return info != null && info.isConnected() && info.getType() == ConnectivityManager.TYPE_WIFI; } - @Override public NetworkInfo getNetworkInfo(Context context) { ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); diff --git a/core-mvp-binding/RELEASE_NOTES.md b/core-mvp-binding/RELEASE_NOTES.md index 1c6d4afe25..a500f896e3 100644 --- a/core-mvp-binding/RELEASE_NOTES.md +++ b/core-mvp-binding/RELEASE_NOTES.md @@ -5,6 +5,7 @@ ## 0.5.0-alpha ##### Core-mvp-binding +* ANDDEP-1207 extracted relation's source/target interfaces * ANDDEP-687 Added "androidx.constraintlayout:constraintlayout" dependency with "implementation" type * Clearing `BaseRxFragmentView.viewDisposable` move from onDestroy to onDestroyView * ANDDEP-671 Renamed loadable to response diff --git a/core-navigation/RELEASE_NOTES.md b/core-navigation/RELEASE_NOTES.md index 9533d8827e..cebbf5f4ed 100644 --- a/core-navigation/RELEASE_NOTES.md +++ b/core-navigation/RELEASE_NOTES.md @@ -4,6 +4,7 @@ ## 0.5.0-alpha ##### Core-navigation +* ANDDEP-1207 Extracted navigation interfaces * ANDDEP-1049 All classes responsible for navigation migrated from `core-ui` to `core-navigation` * **NO BACKWARD COMPATIBILITY** ANDDEP-1049 Package for `BaseActivityResultDelegate`,`SupportOnActivityResultRoute` and `CrossFeatureSupportOnActivityResultRoute` is changed from `ru.surfstudio.android.core.ui.event.result` to: `ru.surfstudio.android.core.ui.navigation.event.result` diff --git a/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/activity/navigator/BaseActivityNavigator.java b/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/activity/navigator/BaseActivityNavigator.java index a24c11de2a..65566d5114 100755 --- a/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/activity/navigator/BaseActivityNavigator.java +++ b/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/activity/navigator/BaseActivityNavigator.java @@ -124,7 +124,6 @@ public void onPause() { freezeSelector.onNext(true); } - @NotNull @Override public Observable> observeResult( @@ -137,7 +136,6 @@ public Observable> observeResult( } } - @NotNull @Override public Observable> observeResult( @@ -145,7 +143,6 @@ public Observable> observeResult( return super.observeOnActivityResult(route); } - @Override public void finishCurrent() { activityProvider.get().finish(); @@ -156,7 +153,6 @@ public void finishAffinity() { ActivityCompat.finishAffinity(activityProvider.get()); } - @Override public void finishWithResult(@NotNull ActivityWithResultRoute activeScreenRoute, boolean success) { @@ -166,11 +162,10 @@ public void finishWithResult(@NotNull ActivityWithResul @Override public void finishWithResult(@NotNull SupportOnActivityResultRoute activeScreenRoute, - @NotNull T result) { + T result) { finishWithResult(activeScreenRoute, result, true); } - @Override public void finishWithResult(SupportOnActivityResultRoute currentScreenRoute, T result, boolean success) { @@ -181,7 +176,6 @@ public void finishWithResult(SupportOnActivityResultRou finishCurrent(); } - @Override public boolean start(ActivityRoute route) { Context context = activityProvider.get(); @@ -193,14 +187,12 @@ public boolean start(ActivityRoute route) { return false; } - @NotNull @Override public Observable start(@NotNull ActivityCrossFeatureRoute route) { return startCrossFeature(route, startStatusSubject -> performStart(route, startStatusSubject)); } - @NotNull @Override public Observable startForResult(@NotNull CrossFeatureSupportOnActivityResultRoute route) { @@ -297,7 +289,6 @@ public boolean onNewIntent(Intent intent) { return false; } - @NotNull @Override public Observable observeNewIntent(Class newIntentRouteClass) { @@ -309,7 +300,6 @@ public Observable observeNewIntent(Class newInt } } - @NotNull @Override public Observable observeNewIntent(@NotNull T newIntentRoute) { diff --git a/dagger-scope/RELEASE_NOTES.md b/dagger-scope/RELEASE_NOTES.md index d0aa46d75d..e9e1791d07 100644 --- a/dagger-scope/RELEASE_NOTES.md +++ b/dagger-scope/RELEASE_NOTES.md @@ -4,6 +4,7 @@ ## 0.5.0-alpha ##### Dagger-scope +* ANDDEP-1207 Fixed sample's dependencies * ANDDEP-687 Changed "javax.inject:javax.inject" dependency from "api" to "implementation" type * ANDDEP-687 Changed "com.google.dagger:dagger" dependency from "api" to "implementation" type * ANDDEP-1048 Fixing wrong docs links and docs structure diff --git a/filestorage/RELEASE_NOTES.md b/filestorage/RELEASE_NOTES.md index 0d33938252..fede9400f4 100644 --- a/filestorage/RELEASE_NOTES.md +++ b/filestorage/RELEASE_NOTES.md @@ -5,6 +5,7 @@ ## 0.5.0-alpha ##### Filestorage +* ANDDEP-1207 Fixed sample's dependencies * ANDDEP-687 Changed "com.google.code.gson:gson" dependency from "api" to "implementation" type ## 0.3.0 ##### Filestorage diff --git a/mvi/RELEASE_NOTES.md b/mvi/RELEASE_NOTES.md index 155871acd0..352c62cbe8 100644 --- a/mvi/RELEASE_NOTES.md +++ b/mvi/RELEASE_NOTES.md @@ -4,6 +4,7 @@ ## 0.5.0-alpha ##### Core-mvi +* ANDDEP-1207 Fixed sample's dependencies * ANDDEP-1008 License added * ANDDEP-671 Core mvi refactor, add comments * ANDDEP-671 Add navigation middleware, add dsl diff --git a/mvp/RELEASE_NOTES.md b/mvp/RELEASE_NOTES.md index 238eb238b8..a1af95b2d3 100644 --- a/mvp/RELEASE_NOTES.md +++ b/mvp/RELEASE_NOTES.md @@ -9,6 +9,7 @@ ##### Core-mvp * ANDDEP-1048 Fixing wrong docs links and docs structure ##### Mvp-widget +* ANDDEP-1207 Fixed sample * ANDDEP-687 Changed "javax.inject:javax.inject" dependency from "api" to "implementation" type * ANDDEP-687 Changed "androidx.constraintlayout:constraintlayout" dependency from "api" to "implementation" type * ANDDEP-633 Fixed widget manual destroy method diff --git a/permission/RELEASE_NOTES.md b/permission/RELEASE_NOTES.md index ecfe7b2a52..52013f6791 100644 --- a/permission/RELEASE_NOTES.md +++ b/permission/RELEASE_NOTES.md @@ -4,6 +4,7 @@ ## 0.5.0-alpha ##### Permission +* ANDDEP-1207 Removed generic parameter from `observeResult` call * ANDDEP-1049 All classes responsible for permissions migrated from `core-ui` to `permission` * **NO BACKWARD COMPATIBILITY** ANDDEP-1049 Package for `BaseActivityResultDelegate`,`SupportOnActivityResultRoute` and `CrossFeatureSupportOnActivityResultRoute` is changed from `ru.surfstudio.android.core.ui.event.result` to: `ru.surfstudio.android.core.ui.navigation.event.result` diff --git a/picture-provider/RELEASE_NOTES.md b/picture-provider/RELEASE_NOTES.md index 0a9b26f18a..3c5bd5b4b9 100644 --- a/picture-provider/RELEASE_NOTES.md +++ b/picture-provider/RELEASE_NOTES.md @@ -7,6 +7,7 @@ ## 0.5.0-alpha ##### Picture-provider +* ANDDEP-1207 Util fix * ANDDEP-687 Changed "androidx.exifinterface:exifinterface" dependency from "api" to "implementation" type * ANDDEP-729 Removed camera-view dependency from picture-provider-sample * ANDDEP-1148 Adding support for android 29 and higher. Added the ability to customize the file location for saving photos from an external camera application From 0d77d54905344bc50b0d4fa1d02961b5d64faa61 Mon Sep 17 00:00:00 2001 From: Kartashov Andrey Date: Wed, 14 Apr 2021 09:40:16 +0300 Subject: [PATCH 12/17] ANDDEP-1207 added release notes for template --- template/RELEASE_NOTES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/template/RELEASE_NOTES.md b/template/RELEASE_NOTES.md index a135eb8ca8..d6da82f869 100644 --- a/template/RELEASE_NOTES.md +++ b/template/RELEASE_NOTES.md @@ -6,6 +6,7 @@ ## 0.5.0-alpha ##### Template +* ANDDEP-1207 Fixed dependencies * Added unit-tests for SplashMiddleware and MainBarMiddleware * Added utility classes for unit testing * `kotlin` version raised: `1.3.71 -> 1.4.31`; From 098e0cff53345f1ada2df672853139610e85c252 Mon Sep 17 00:00:00 2001 From: Kartashov Andrey Date: Wed, 14 Apr 2021 09:50:24 +0300 Subject: [PATCH 13/17] ANDDEP-1207 removed extra lines --- .../ui/navigation/activity/navigator/BaseActivityNavigator.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/activity/navigator/BaseActivityNavigator.java b/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/activity/navigator/BaseActivityNavigator.java index 65566d5114..faa18a7120 100755 --- a/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/activity/navigator/BaseActivityNavigator.java +++ b/core-navigation/lib-core-navigation/src/main/java/ru/surfstudio/android/core/ui/navigation/activity/navigator/BaseActivityNavigator.java @@ -159,7 +159,6 @@ public void finishWithResult(@NotNull ActivityWithResul finishWithResult(activeScreenRoute, null, success); } - @Override public void finishWithResult(@NotNull SupportOnActivityResultRoute activeScreenRoute, T result) { @@ -237,7 +236,6 @@ private void emitFeatureInstallState(BehaviorSubject s splitFeatureInstallStateSubject.onNext(new SplitFeatureInstallState(status)); } - @Override public boolean startForResult(@NotNull SupportOnActivityResultRoute route) { if (!super.isObserved(route)) { From dfafed059a6cc52acbb778bf1dead6069bec0258 Mon Sep 17 00:00:00 2001 From: Kartashov Andrey Date: Wed, 14 Apr 2021 11:15:17 +0300 Subject: [PATCH 14/17] ANDDEP-1207 fixed release notes errors --- buildSrc/components.json | 2 +- core-mvp-binding/RELEASE_NOTES.md | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/buildSrc/components.json b/buildSrc/components.json index dbe2ded6f2..83f3953fbc 100755 --- a/buildSrc/components.json +++ b/buildSrc/components.json @@ -762,7 +762,7 @@ }, { "id": "core-mvp-binding-tests", - "version": "0.0.1", + "version": "0.5.0", "unstable_version": 1, "stable": false, "dir": "core-mvp-binding", diff --git a/core-mvp-binding/RELEASE_NOTES.md b/core-mvp-binding/RELEASE_NOTES.md index a500f896e3..37ff14127d 100644 --- a/core-mvp-binding/RELEASE_NOTES.md +++ b/core-mvp-binding/RELEASE_NOTES.md @@ -20,6 +20,9 @@ Added state checker methods to Request. * ANDDEP-968 `Loading.kt`: added class `SimpleLoading`; * ANDDEP-968 `RequestUi.kt`: added fields `isLoading`, `hasData`, `hasError`; * ANDDEP-1048 Fixing wrong docs links and docs structure + +##### Core-mvp-binding-tests +* TODO ## 0.3.0 ##### Core-mvp-binding * Renamed `onViewDetached ()` -> `onViewDetach ()` \ No newline at end of file From ded65fabdfe7ff00c0f36f5b197bf2eae789966c Mon Sep 17 00:00:00 2001 From: Kartashov Andrey Date: Wed, 14 Apr 2021 11:25:24 +0300 Subject: [PATCH 15/17] ANDDEP-1207 fixed release notes --- core-mvp-binding/RELEASE_NOTES.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core-mvp-binding/RELEASE_NOTES.md b/core-mvp-binding/RELEASE_NOTES.md index 37ff14127d..f3851b1ac1 100644 --- a/core-mvp-binding/RELEASE_NOTES.md +++ b/core-mvp-binding/RELEASE_NOTES.md @@ -21,8 +21,10 @@ Added state checker methods to Request. * ANDDEP-968 `RequestUi.kt`: added fields `isLoading`, `hasData`, `hasError`; * ANDDEP-1048 Fixing wrong docs links and docs structure +## 0.5.0-alpha ##### Core-mvp-binding-tests -* TODO +* ANDDEP-1207 Init module + ## 0.3.0 ##### Core-mvp-binding * Renamed `onViewDetached ()` -> `onViewDetach ()` \ No newline at end of file From 940f5f88bed1c64f3b2d8ca19482fddb3e3fe228 Mon Sep 17 00:00:00 2001 From: Kartashov Andrey Date: Wed, 14 Apr 2021 12:54:16 +0300 Subject: [PATCH 16/17] ANDDEP-1207 fixed component declaration --- buildSrc/components.json | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/buildSrc/components.json b/buildSrc/components.json index 83f3953fbc..c212c8d497 100755 --- a/buildSrc/components.json +++ b/buildSrc/components.json @@ -749,24 +749,7 @@ "type": "implementation" } ] - } - ], - "samples": [ - { - "name": "core-mvp-binding-sample", - "dir": "sample" - } - ], - "has_mirror": false, - "mirror_repo": "" - }, - { - "id": "core-mvp-binding-tests", - "version": "0.5.0", - "unstable_version": 1, - "stable": false, - "dir": "core-mvp-binding", - "libs": [ + }, { "name": "core-mvp-binding-tests", "dir": "lib-core-mvp-binding-tests", @@ -813,7 +796,12 @@ ] } ], - "samples": [], + "samples": [ + { + "name": "core-mvp-binding-sample", + "dir": "sample" + } + ], "has_mirror": false, "mirror_repo": "" }, From d93b95c12536535b40e7fcbfbfb69536f8d1fd8b Mon Sep 17 00:00:00 2001 From: Kartashov Andrey Date: Mon, 19 Apr 2021 09:38:40 +0300 Subject: [PATCH 17/17] ANDDEP-1207 doc fixes --- docs/common/unit_testing.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/common/unit_testing.md b/docs/common/unit_testing.md index fce0cfc0b1..71e8b9a706 100644 --- a/docs/common/unit_testing.md +++ b/docs/common/unit_testing.md @@ -373,11 +373,11 @@ fun `when pin is wrong, should show error under pins`() = forAll( ``` #### Binding -Для реализации тест-класса презентера существует базовый класс [`BasePresenterTest`][basePresenterTest] , который содержит базовые зависимости для презентера. +Для реализации тест-класса презентера существует базовый класс [`BasePresenterTest`][basePresenterTest], который содержит базовые зависимости для презентера. Тестирование презентера сводится к 4 шагам: 1. Вызов метода `Presenter#onFirstLoad()` для биндинга к `BindModel` 1. Триггер нужных экшенов в `BindModel` -1. Провертка результата - данные с стейте или выполненная навигация +1. Провертка результата - данные в стейте или выполненная навигация 1. Вызов метода `Presenter#onDestroy()` Первый и последний шаги вынесены в `BasePresenterTest`.