diff --git a/navigation/RELEASE_NOTES.md b/navigation/RELEASE_NOTES.md index 639a6546c9..2f89ea09e3 100644 --- a/navigation/RELEASE_NOTES.md +++ b/navigation/RELEASE_NOTES.md @@ -12,4 +12,5 @@ * ANDDEP-1154 Fixed crashes "You must specify unique tag" * ANDDEP-1153 Fixed wrong behavior RemoveLast * ANDDEP-1100 Added command StartForResult to open system activities for getting result +* ANDDEP-1103 Added sequential async and sync command execution * ANDDEP-1148 code updated for target sdk 30 diff --git a/navigation/lib-navigation-observer/src/main/java/ru/surfstudio/android/navigation/observer/command/activity/StartForResult.kt b/navigation/lib-navigation-observer/src/main/java/ru/surfstudio/android/navigation/observer/command/activity/StartForResult.kt index f1586842d9..fde4af9149 100644 --- a/navigation/lib-navigation-observer/src/main/java/ru/surfstudio/android/navigation/observer/command/activity/StartForResult.kt +++ b/navigation/lib-navigation-observer/src/main/java/ru/surfstudio/android/navigation/observer/command/activity/StartForResult.kt @@ -14,5 +14,5 @@ import java.io.Serializable class StartForResult( override val route: R, override val animations: Animations = DefaultAnimations.activity, - val activityOptions: Bundle? = null + override val activityOptions: Bundle? = null ) : ActivityNavigationCommand where R : ActivityWithResultRoute diff --git a/navigation/lib-navigation/README.md b/navigation/lib-navigation/README.md index 8b179c3a8d..6da93795b6 100644 --- a/navigation/lib-navigation/README.md +++ b/navigation/lib-navigation/README.md @@ -104,7 +104,7 @@ ACTIVITY_NAVIGATION_TAG. * [RemoveUntil][removeuntilcom] - удаление фрагментов вплоть до указанного в команде. * [RemoveAll][removeallcom] - очистка бекстека. Возможна очистка всех фрагментов с сохранением последнего: необходимо явно указать параметр shouldRemoveLast=false. - Чтобы выполнить эту команду для [TabFragmentNavigator][tfnav], необходимо явно указать параметр isTab=true. + В случае с [TabFragmentNavigator][tfnav], команда [RemoveAll][removeallcom] очистит стек текущего активного таба. - [DialogNavigationCommand][dcom] * [Show][showcom] - показ диалога * [Dismiss][dismisscom] - скрытие диалога @@ -198,6 +198,51 @@ noBackupFilesDir. 1. Вызвать метод NavigationCommandExecutor.execute и передать в него команду Add(routeA, sourceTag), где routeA - route, созданный на первом шаге, sourceTag - тег экрана, созданный на 3 шаге. +### Цепочное выполнение команд +[AppCommandExecutor][appexec] поддерживает выполнение цепочек, состоящих как из синхронных, так и +асинхронных команд. +В зависимости от того какие команды есть в цепочке логика работы [AppCommandExecutor][appexec] будет +немного отличаться. Но основной принцип - выполняется асинхронная команда, выполнение команд идущих +после нее откладывается до момента, когда будет доступен новый [ActivityNavigationHolder][anavholdercom]. +И как только холдер станет доступен - срабатывает та же логика. +Исключения из этого: + * Выполнение цепочки команд [Start][startcom], открывающее несколько активити сразу. + * Выполнение асинхронных команд Replace и Start для активити после выполнения команд Finish и FinishAffinity. + Replace и Start выполняются сразу после команд Finish и FinishAffinity в том же [ActivityNavigationHolder][anavholdercom], + так как после выполнения команд закрывающих активити или стек активити в стеке может не остаться + активити и последующие команды не выполнятся. + +1. Можно запускать несколько активити за один раз, передавая список из команд Start, например +`listOf(Start(ActivityRoute1()), Start(ActivityRoute2()), Start(ActivityRoute3()))`. +У [ActivityNavigator][anav] для выполнения этой цепочки команд будет вызван метод `Context.startActivities`. +1. Можно асинхронно запустить активити-контейнер фрагментов и сразу добавить в него несколько фрагментов, +[AppCommandExecutor][appexec] отложит выполнение всех команд, идущих после команды старта активити, +дождется пока активити запутится и последовательно выполнит все синхронные команды, идущие после +команды запуска активити. +1. Можно закрыть несколько активити (без использования [FinishAffinity][finishacom]). Для этого нужно +передать список комманд [Finish][finishcom]. Первая команда [Finish][finishcom] из этого списка +выполнится для текущей активити, последующие команды будут выполняться по мере того как будут +становиться доступны холдеры [ActivityNavigationHolder][anavholdercom] ранее запущенных активити. +1. Можно выполнять запуск нескольких активити с добавлением в них фрагментов. +listOf( + Start(FirstActivityRoute()), + Replace(FirstFragmentRoute()), + Replace(SecondFragmentRoute()), + Start(SecondActivityRoute()), + Start(ThirdActivityRoute()), + Replace(FirstFragmentRoute()), + Replace(SecondFragmentRoute()) +) +1. Закрываем несколько активити из стека с последующим открытием новых активити. +В этом случае нужно быть очень внимательным, так как если в стеке всего одна активити, а переданы +две команды [Finish][finishcom] и после них есть еще какие-то команды - выполнится только первая +команда, которая закроет активити и вместе с этим все приложение и все последующие команды не будут +выполнены. +listOf( + Finish(), + Finish(), + Start(SomeActivityRoute()) +) [route]: src/main/java/ru/surfstudio/android/navigation/route/Route.kt [baseroute]: src/main/java/ru/surfstudio/android/navigation/route/BaseRoute.kt @@ -250,3 +295,5 @@ noBackupFilesDir. [dcom]: src/main/java/ru/surfstudio/android/navigation/command/dialog/base/DialogNavigationCommand.kt [showcom]: src/main/java/ru/surfstudio/android/navigation/command/dialog/Show.kt [dismisscom]: src/main/java/ru/surfstudio/android/navigation/command/dialog/Dismiss.kt + +[anavholdercom]: src/main/java/ru/surfstudio/android/navigation/provider/holder/ActivityNavigationHolder.kt diff --git a/navigation/lib-navigation/src/main/java/ru/surfstudio/android/navigation/command/activity/Finish.kt b/navigation/lib-navigation/src/main/java/ru/surfstudio/android/navigation/command/activity/Finish.kt index 1a02c0a960..8e6ee21ffc 100644 --- a/navigation/lib-navigation/src/main/java/ru/surfstudio/android/navigation/command/activity/Finish.kt +++ b/navigation/lib-navigation/src/main/java/ru/surfstudio/android/navigation/command/activity/Finish.kt @@ -1,5 +1,6 @@ package ru.surfstudio.android.navigation.command.activity +import android.os.Bundle import ru.surfstudio.android.navigation.animation.Animations import ru.surfstudio.android.navigation.animation.DefaultAnimations import ru.surfstudio.android.navigation.command.activity.base.ActivityNavigationCommand @@ -14,4 +15,5 @@ data class Finish( ) : ActivityNavigationCommand { override val route: ActivityRoute = StubActivityRoute + override val activityOptions: Bundle? = null } \ No newline at end of file diff --git a/navigation/lib-navigation/src/main/java/ru/surfstudio/android/navigation/command/activity/FinishAffinity.kt b/navigation/lib-navigation/src/main/java/ru/surfstudio/android/navigation/command/activity/FinishAffinity.kt index 361f06972b..14b38570ba 100644 --- a/navigation/lib-navigation/src/main/java/ru/surfstudio/android/navigation/command/activity/FinishAffinity.kt +++ b/navigation/lib-navigation/src/main/java/ru/surfstudio/android/navigation/command/activity/FinishAffinity.kt @@ -1,5 +1,6 @@ package ru.surfstudio.android.navigation.command.activity +import android.os.Bundle import ru.surfstudio.android.navigation.animation.Animations import ru.surfstudio.android.navigation.animation.DefaultAnimations import ru.surfstudio.android.navigation.command.activity.base.ActivityNavigationCommand @@ -14,4 +15,5 @@ data class FinishAffinity( ) : ActivityNavigationCommand { override val route: ActivityRoute = StubActivityRoute + override val activityOptions: Bundle? = null } \ No newline at end of file diff --git a/navigation/lib-navigation/src/main/java/ru/surfstudio/android/navigation/command/activity/Replace.kt b/navigation/lib-navigation/src/main/java/ru/surfstudio/android/navigation/command/activity/Replace.kt index 369ebfee90..0578d6615c 100644 --- a/navigation/lib-navigation/src/main/java/ru/surfstudio/android/navigation/command/activity/Replace.kt +++ b/navigation/lib-navigation/src/main/java/ru/surfstudio/android/navigation/command/activity/Replace.kt @@ -12,5 +12,5 @@ import ru.surfstudio.android.navigation.route.activity.ActivityRoute data class Replace( override val route: ActivityRoute, override val animations: Animations = DefaultAnimations.activity, - val activityOptions: Bundle? = null + override val activityOptions: Bundle? = null ) : ActivityNavigationCommand \ No newline at end of file diff --git a/navigation/lib-navigation/src/main/java/ru/surfstudio/android/navigation/command/activity/Start.kt b/navigation/lib-navigation/src/main/java/ru/surfstudio/android/navigation/command/activity/Start.kt index 8e85a73a9b..a330d66afa 100644 --- a/navigation/lib-navigation/src/main/java/ru/surfstudio/android/navigation/command/activity/Start.kt +++ b/navigation/lib-navigation/src/main/java/ru/surfstudio/android/navigation/command/activity/Start.kt @@ -12,5 +12,5 @@ import ru.surfstudio.android.navigation.route.activity.ActivityRoute data class Start( override val route: ActivityRoute, override val animations: Animations = DefaultAnimations.activity, - val activityOptions: Bundle? = null + override val activityOptions: Bundle? = null ) : ActivityNavigationCommand \ No newline at end of file diff --git a/navigation/lib-navigation/src/main/java/ru/surfstudio/android/navigation/command/activity/base/ActivityNavigationCommand.kt b/navigation/lib-navigation/src/main/java/ru/surfstudio/android/navigation/command/activity/base/ActivityNavigationCommand.kt index 72f0851ea3..870bc8c27b 100644 --- a/navigation/lib-navigation/src/main/java/ru/surfstudio/android/navigation/command/activity/base/ActivityNavigationCommand.kt +++ b/navigation/lib-navigation/src/main/java/ru/surfstudio/android/navigation/command/activity/base/ActivityNavigationCommand.kt @@ -1,5 +1,7 @@ package ru.surfstudio.android.navigation.command.activity.base +import android.os.Bundle +import ru.surfstudio.android.navigation.animation.Animations import ru.surfstudio.android.navigation.command.NavigationCommand import ru.surfstudio.android.navigation.route.activity.ActivityRoute @@ -8,4 +10,6 @@ import ru.surfstudio.android.navigation.route.activity.ActivityRoute */ interface ActivityNavigationCommand: NavigationCommand { override val route: ActivityRoute + override val animations: Animations + val activityOptions: Bundle? } \ No newline at end of file diff --git a/navigation/lib-navigation/src/main/java/ru/surfstudio/android/navigation/executor/AppCommandExecutor.kt b/navigation/lib-navigation/src/main/java/ru/surfstudio/android/navigation/executor/AppCommandExecutor.kt index d8f04fed86..45951539f9 100644 --- a/navigation/lib-navigation/src/main/java/ru/surfstudio/android/navigation/executor/AppCommandExecutor.kt +++ b/navigation/lib-navigation/src/main/java/ru/surfstudio/android/navigation/executor/AppCommandExecutor.kt @@ -1,7 +1,11 @@ package ru.surfstudio.android.navigation.executor import android.os.Handler +import android.os.Looper import ru.surfstudio.android.navigation.command.NavigationCommand +import ru.surfstudio.android.navigation.command.activity.Finish +import ru.surfstudio.android.navigation.command.activity.FinishAffinity +import ru.surfstudio.android.navigation.command.activity.Start import ru.surfstudio.android.navigation.command.activity.base.ActivityNavigationCommand import ru.surfstudio.android.navigation.command.dialog.base.DialogNavigationCommand import ru.surfstudio.android.navigation.command.fragment.base.FragmentNavigationCommand @@ -23,6 +27,7 @@ open class AppCommandExecutor( protected val dialogCommandExecutor: DialogCommandExecutor = DialogCommandExecutor(activityNavigationProvider) ) : NavigationCommandExecutor { + protected val handler = Handler(Looper.getMainLooper()) protected val buffer = mutableListOf() protected val commandQueue: Queue = LinkedList() @@ -43,8 +48,19 @@ open class AppCommandExecutor( */ protected open fun safeExecuteWithBuffer(commands: List) { if (activityNavigationProvider.hasCurrentHolder()) { - divideExecution(commands) + splitExecutionIntoChunks(commands) } else { + postponeExecution(commands) + } + } + + + /** + * Postpones command execution to the end of the Message Queue + * and executes [commands] with the next available navigator + */ + protected fun postponeExecution(commands: List) { + handler.post { buffer.addAll(commands) activityNavigationProvider.setOnHolderActiveListenerSingle { utilizeBuffer() } } @@ -63,40 +79,76 @@ open class AppCommandExecutor( * and then call this method again with remaining commands (returning to step 1) * * TODO: Dispatch commands in batches, to, for example, - * TODO: execute multiple commands in one FragmentTransaction, - * TODO: or use Context.startActivities(Intent, Intent...) to launch multiple activities. + * TODO: execute multiple commands in one FragmentTransaction * */ - protected open fun divideExecution(commands: List) { + protected open fun splitExecutionIntoChunks(commands: List) { if (commands.isEmpty()) return - val isStartingWithAsyncCommand = checkCommandAsync(commands.first()) - val asyncCommands: List - val syncCommands: List + val firstCommand = commands.first() + val isStartingWithAsyncCommand = checkCommandAsync(firstCommand) if (isStartingWithAsyncCommand) { - asyncCommands = commands.takeWhile(::checkCommandAsync) - syncCommands = commands.takeLast(commands.size - asyncCommands.size) - - val hasAsyncCommands = asyncCommands.isNotEmpty() - val hasSyncCommands = syncCommands.isNotEmpty() + splitStartingFromAsync(commands, firstCommand) + } else { + splitStartingFromSync(commands) + } + } - if (hasAsyncCommands) queueCommands(asyncCommands) + private fun splitStartingFromSync(commands: List) { + val firstAsyncCommandIndex = commands.indexOfFirst { checkCommandAsync(it) } + if (firstAsyncCommandIndex >= 1) { + queueCommands(commands.take(firstAsyncCommandIndex)) + safeExecuteWithBuffer(commands.subList(firstAsyncCommandIndex, commands.size)) + } else { + queueCommands(commands) + } + } - when { - hasAsyncCommands && hasSyncCommands -> postponeExecution(syncCommands) - hasSyncCommands -> divideExecution(syncCommands) + private fun splitStartingFromAsync(commands: List, firstCommand: NavigationCommand) { + val firstNotStartIndex = commands.indexOfFirst { command -> command !is Start } + when { + /** We have only Start commands, just execute them */ + firstNotStartIndex == -1 -> { + startSeveralActivities(commands) } - } else { - syncCommands = commands.takeWhile { !checkCommandAsync(it) } - asyncCommands = commands.takeLast(commands.size - syncCommands.size) + /** + * We have several Start commands and commands of other types right after them, so + * we start activities first and postpone the execution of the remaining commands until + * new navigation holder become available + */ + firstNotStartIndex > 1 -> { + startSeveralActivities(commands.take(firstNotStartIndex)) + postponeExecution(commands.subList(firstNotStartIndex, commands.size)) + } + /** + * Here are other cases, where we just execute first command. + * If we have to Finish activity or affinity we need to check the next command after + * it. If it's async but not Finish/FinishAffinity command we can immediately execute it + * with current navigation holder. Otherwise we postpone execution until new navigation + * holder becomes available. + */ + else -> { + dispatchCommand(firstCommand) + val restCommands = commands.drop(1) + val canExecuteImmediately: Boolean = restCommands.isNotEmpty() + && checkCommandAsync(restCommands[0]) + && !isFinishCommand(restCommands[0]) + if (canExecuteImmediately) { + splitExecutionIntoChunks(restCommands) + } else { + postponeExecution(restCommands) + } + } + } + } - val hasAsyncCommands = asyncCommands.isNotEmpty() - val hasSyncCommands = syncCommands.isNotEmpty() + private fun isFinishCommand(command: NavigationCommand): Boolean { + return command is Finish || command is FinishAffinity + } - if (hasSyncCommands) queueCommands(syncCommands) - if (hasAsyncCommands) safeExecuteWithBuffer(asyncCommands) - } + private fun startSeveralActivities(commands: List) { + activityCommandExecutor.execute(commands.map { it as ActivityNavigationCommand }) } /** @@ -139,13 +191,6 @@ open class AppCommandExecutor( } } - /** - * Postpones command execution to the end of the Message Queue. - */ - protected open fun postponeExecution(commands: List) { - Handler().post { safeExecuteWithBuffer(commands) } - } - /** * Checks whether the effect of this command will be shown immediately after execution, * or it will take some time to appear. @@ -157,7 +202,10 @@ open class AppCommandExecutor( * Executes everything in buffer and cleans it. */ protected fun utilizeBuffer() { - execute(buffer) + val commands = ArrayList(buffer) buffer.clear() + handler.post { + execute(commands) + } } } \ No newline at end of file diff --git a/navigation/lib-navigation/src/main/java/ru/surfstudio/android/navigation/executor/screen/activity/ActivityCommandExecutor.kt b/navigation/lib-navigation/src/main/java/ru/surfstudio/android/navigation/executor/screen/activity/ActivityCommandExecutor.kt index b00ecf2664..a62bdab31a 100644 --- a/navigation/lib-navigation/src/main/java/ru/surfstudio/android/navigation/executor/screen/activity/ActivityCommandExecutor.kt +++ b/navigation/lib-navigation/src/main/java/ru/surfstudio/android/navigation/executor/screen/activity/ActivityCommandExecutor.kt @@ -4,6 +4,7 @@ import ru.surfstudio.android.navigation.command.activity.* import ru.surfstudio.android.navigation.command.activity.base.ActivityNavigationCommand import ru.surfstudio.android.navigation.provider.ActivityNavigationProvider import ru.surfstudio.android.navigation.executor.CommandExecutor +import ru.surfstudio.android.navigation.navigator.activity.ActivityNavigatorInterface /** * Command executor for [ActivityNavigationCommand]s. @@ -14,8 +15,10 @@ open class ActivityCommandExecutor( private val activityNavigationProvider: ActivityNavigationProvider ) : CommandExecutor { + private val navigator: ActivityNavigatorInterface + get() = activityNavigationProvider.provide().activityNavigator + override fun execute(command: ActivityNavigationCommand) { - val navigator = activityNavigationProvider.provide().activityNavigator when (command) { is Start -> navigator.start(command.route, command.animations, command.activityOptions) is Replace -> navigator.replace(command.route, command.animations, command.activityOptions) @@ -25,6 +28,13 @@ open class ActivityCommandExecutor( } override fun execute(commands: List) { - TODO("Activity navigation command list execution") + if (commands.isEmpty()) return + + val lastCommand = commands.first() as Start + navigator.start( + routes = commands.map { it.route }, + animations = lastCommand.animations, + activityOptions = lastCommand.activityOptions + ) } } \ No newline at end of file diff --git a/navigation/lib-navigation/src/main/java/ru/surfstudio/android/navigation/navigator/activity/ActivityNavigator.kt b/navigation/lib-navigation/src/main/java/ru/surfstudio/android/navigation/navigator/activity/ActivityNavigator.kt index fde8486645..79d2952731 100644 --- a/navigation/lib-navigation/src/main/java/ru/surfstudio/android/navigation/navigator/activity/ActivityNavigator.kt +++ b/navigation/lib-navigation/src/main/java/ru/surfstudio/android/navigation/navigator/activity/ActivityNavigator.kt @@ -10,6 +10,16 @@ open class ActivityNavigator(val activity: AppCompatActivity) : ActivityNavigato protected open val animationSupplier = ActivityAnimationSupplier() + override fun start(routes: List, animations: Animations, activityOptions: Bundle?) { + val intents = Array(routes.size) { index: Int -> + val route = routes[index] + route.createIntent(activity) + } + val optionsWithAnimations: Bundle? = + animationSupplier.supplyWithAnimations(activity, activityOptions, animations) + activity.startActivities(intents, optionsWithAnimations) + } + override fun start(route: ActivityRoute, animations: Animations, activityOptions: Bundle?) { val optionsWithAnimations = animationSupplier.supplyWithAnimations(activity, activityOptions, animations) diff --git a/navigation/lib-navigation/src/main/java/ru/surfstudio/android/navigation/navigator/activity/ActivityNavigatorInterface.kt b/navigation/lib-navigation/src/main/java/ru/surfstudio/android/navigation/navigator/activity/ActivityNavigatorInterface.kt index 890c3ba6bb..e78f4f3058 100644 --- a/navigation/lib-navigation/src/main/java/ru/surfstudio/android/navigation/navigator/activity/ActivityNavigatorInterface.kt +++ b/navigation/lib-navigation/src/main/java/ru/surfstudio/android/navigation/navigator/activity/ActivityNavigatorInterface.kt @@ -9,6 +9,8 @@ import ru.surfstudio.android.navigation.route.activity.ActivityRoute */ interface ActivityNavigatorInterface { + fun start(routes: List, animations: Animations, activityOptions: Bundle?) + fun start(route: ActivityRoute, animations: Animations, activityOptions: Bundle?) fun replace(route: ActivityRoute, animations: Animations, activityOptions: Bundle?) diff --git a/navigation/lib-navigation/src/main/java/ru/surfstudio/android/navigation/provider/callbacks/ActivityNavigationProviderCallbacks.kt b/navigation/lib-navigation/src/main/java/ru/surfstudio/android/navigation/provider/callbacks/ActivityNavigationProviderCallbacks.kt index 9b69f909fc..433ed03994 100644 --- a/navigation/lib-navigation/src/main/java/ru/surfstudio/android/navigation/provider/callbacks/ActivityNavigationProviderCallbacks.kt +++ b/navigation/lib-navigation/src/main/java/ru/surfstudio/android/navigation/provider/callbacks/ActivityNavigationProviderCallbacks.kt @@ -88,7 +88,7 @@ open class ActivityNavigationProviderCallbacks( // So, we're just waiting until it executes all actions, and then updating holder status. handler.post { updateCurrentHolder(id) } } else { - updateCurrentHolder(id) + handler.post { updateCurrentHolder(id) } } } } diff --git a/navigation/sample-standard/src/main/java/ru/surfstudio/android/navigation/sample_standard/App.kt b/navigation/sample-standard/src/main/java/ru/surfstudio/android/navigation/sample_standard/App.kt index 7b17b59890..1d66ac1234 100644 --- a/navigation/sample-standard/src/main/java/ru/surfstudio/android/navigation/sample_standard/App.kt +++ b/navigation/sample-standard/src/main/java/ru/surfstudio/android/navigation/sample_standard/App.kt @@ -3,6 +3,7 @@ package ru.surfstudio.android.navigation.sample_standard import android.app.Activity import android.app.Application import ru.surfstudio.android.activity.holder.ActiveActivityHolder +import ru.surfstudio.android.logger.Logger import ru.surfstudio.android.navigation.animation.DefaultAnimations import ru.surfstudio.android.navigation.provider.callbacks.ActivityNavigationProviderCallbacks import ru.surfstudio.android.navigation.sample_standard.di.AppComponent diff --git a/navigation/sample-standard/src/main/java/ru/surfstudio/android/navigation/sample_standard/screen/bottom_nav/profile/ProfileBindModel.kt b/navigation/sample-standard/src/main/java/ru/surfstudio/android/navigation/sample_standard/screen/bottom_nav/profile/ProfileBindModel.kt index d22fc37799..da1f1a7bbe 100644 --- a/navigation/sample-standard/src/main/java/ru/surfstudio/android/navigation/sample_standard/screen/bottom_nav/profile/ProfileBindModel.kt +++ b/navigation/sample-standard/src/main/java/ru/surfstudio/android/navigation/sample_standard/screen/bottom_nav/profile/ProfileBindModel.kt @@ -10,4 +10,5 @@ class ProfileBindModel @Inject constructor() : BindModel { val openSettings = Action() val openConfirmLogoutScreen = Action() + val openSearch = Action() } \ No newline at end of file diff --git a/navigation/sample-standard/src/main/java/ru/surfstudio/android/navigation/sample_standard/screen/bottom_nav/profile/ProfileFragmentView.kt b/navigation/sample-standard/src/main/java/ru/surfstudio/android/navigation/sample_standard/screen/bottom_nav/profile/ProfileFragmentView.kt index 139000a3ca..0d2e23ebb0 100644 --- a/navigation/sample-standard/src/main/java/ru/surfstudio/android/navigation/sample_standard/screen/bottom_nav/profile/ProfileFragmentView.kt +++ b/navigation/sample-standard/src/main/java/ru/surfstudio/android/navigation/sample_standard/screen/bottom_nav/profile/ProfileFragmentView.kt @@ -23,5 +23,6 @@ class ProfileFragmentView : BaseRxFragmentView() { override fun onActivityCreated(savedInstanceState: Bundle?, viewRecreated: Boolean) { profile_settings_btn.setOnClickListener { bm.openSettings.accept() } profile_logout_btn.setOnClickListener { bm.openConfirmLogoutScreen.accept() } + profile_search_btn.setOnClickListener { bm.openSearch.accept() } } } \ No newline at end of file diff --git a/navigation/sample-standard/src/main/java/ru/surfstudio/android/navigation/sample_standard/screen/bottom_nav/profile/ProfilePresenter.kt b/navigation/sample-standard/src/main/java/ru/surfstudio/android/navigation/sample_standard/screen/bottom_nav/profile/ProfilePresenter.kt index 1e22886628..93559bbce8 100644 --- a/navigation/sample-standard/src/main/java/ru/surfstudio/android/navigation/sample_standard/screen/bottom_nav/profile/ProfilePresenter.kt +++ b/navigation/sample-standard/src/main/java/ru/surfstudio/android/navigation/sample_standard/screen/bottom_nav/profile/ProfilePresenter.kt @@ -6,6 +6,7 @@ import ru.surfstudio.android.dagger.scope.PerScreen import ru.surfstudio.android.navigation.command.activity.Start import ru.surfstudio.android.navigation.command.dialog.Show import ru.surfstudio.android.navigation.command.fragment.RemoveLast +import ru.surfstudio.android.navigation.command.fragment.Replace import ru.surfstudio.android.navigation.command.fragment.base.FragmentNavigationCommand.Companion.ACTIVITY_NAVIGATION_TAG import ru.surfstudio.android.navigation.executor.NavigationCommandExecutor import ru.surfstudio.android.navigation.observer.ScreenResultObserver @@ -14,6 +15,9 @@ import ru.surfstudio.android.navigation.sample_standard.screen.base.dialog.simpl import ru.surfstudio.android.navigation.sample_standard.screen.base.presenter.CommandExecutionPresenter import ru.surfstudio.android.navigation.sample_standard.screen.bottom_nav.profile.logout.LogoutConfirmationRoute import ru.surfstudio.android.navigation.sample_standard.screen.bottom_nav.profile.settings.ApplicationSettingsRoute +import ru.surfstudio.android.navigation.sample_standard.screen.search.request.SearchRequestRoute +import ru.surfstudio.android.navigation.sample_standard.screen.search.results.SearchResultRoute +import ru.surfstudio.android.navigation.sample_standard.utils.animations.ModalAnimations import javax.inject.Inject @PerScreen @@ -27,8 +31,22 @@ class ProfilePresenter @Inject constructor( override fun onFirstLoad() { val confirmLogoutRoute = LogoutConfirmationRoute() subscribeToLogoutResult(confirmLogoutRoute) + subscribeOnSearchResults() bm.openConfirmLogoutScreen bindTo { Show(confirmLogoutRoute).execute() } bm.openSettings.bindTo { Start(ApplicationSettingsRoute()).execute() } + bm.openSearch.bindTo { + Replace( + route = SearchRequestRoute(), + animations = ModalAnimations, + sourceTag = ACTIVITY_NAVIGATION_TAG + ).execute() + } + } + + private fun subscribeOnSearchResults() { + subscribe(screenResultObserver.observeScreenResult(SearchRequestRoute())){ result: String -> + Replace(SearchResultRoute(result)).execute() + } } private fun subscribeToLogoutResult(confirmLogoutRoute: LogoutConfirmationRoute) { diff --git a/navigation/sample-standard/src/main/java/ru/surfstudio/android/navigation/sample_standard/screen/search/request/SearchRequestBindModel.kt b/navigation/sample-standard/src/main/java/ru/surfstudio/android/navigation/sample_standard/screen/search/request/SearchRequestBindModel.kt new file mode 100644 index 0000000000..e9cae0097b --- /dev/null +++ b/navigation/sample-standard/src/main/java/ru/surfstudio/android/navigation/sample_standard/screen/search/request/SearchRequestBindModel.kt @@ -0,0 +1,17 @@ +package ru.surfstudio.android.navigation.sample_standard.screen.search.request + +import ru.surfstudio.android.core.mvp.binding.rx.relation.mvp.Action +import ru.surfstudio.android.core.mvp.binding.rx.relation.mvp.State +import ru.surfstudio.android.core.mvp.binding.rx.ui.BindModel +import ru.surfstudio.android.dagger.scope.PerScreen +import javax.inject.Inject + +@PerScreen +class SearchRequestBindModel @Inject constructor( + route: SearchRequestRoute +) : BindModel { + val resultClick = Action() + val textChanges = Action() + val textState = State(route.searchInput) + val closeClick = Action() +} diff --git a/navigation/sample-standard/src/main/java/ru/surfstudio/android/navigation/sample_standard/screen/search/request/SearchRequestFragmentView.kt b/navigation/sample-standard/src/main/java/ru/surfstudio/android/navigation/sample_standard/screen/search/request/SearchRequestFragmentView.kt new file mode 100644 index 0000000000..4c3268f141 --- /dev/null +++ b/navigation/sample-standard/src/main/java/ru/surfstudio/android/navigation/sample_standard/screen/search/request/SearchRequestFragmentView.kt @@ -0,0 +1,44 @@ +package ru.surfstudio.android.navigation.sample_standard.screen.search.request + +import android.os.Bundle +import android.text.TextUtils +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.core.widget.doAfterTextChanged +import kotlinx.android.synthetic.main.fragment_search_request.* +import ru.surfstudio.android.core.mvp.binding.rx.ui.BaseRxFragmentView +import ru.surfstudio.android.navigation.sample_standard.R +import ru.surfstudio.android.navigation.sample_standard.utils.addOnBackPressedListener +import ru.surfstudio.android.utilktx.ktx.text.EMPTY_STRING +import javax.inject.Inject + +/** + * Экран ввода поискового запроса. Иммитирует экран поиска с подсказками + */ +class SearchRequestFragmentView : BaseRxFragmentView() { + + @Inject + lateinit var bm: SearchRequestBindModel + + override fun createConfigurator() = SearchRequestScreenConfigurator(requireArguments()) + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.fragment_search_request, container, false) + } + + override fun onActivityCreated(savedInstanceState: Bundle?, viewRecreated: Boolean) { + addOnBackPressedListener { bm.closeClick.accept() } + search_request_close_btn.setOnClickListener { bm.closeClick.accept() } + search_request_results_btn.setOnClickListener { bm.resultClick.accept() } + search_request_input_et.doAfterTextChanged { + bm.textChanges.accept(it?.toString() ?: EMPTY_STRING) + } + + bm.textState.bindTo { text: String -> + if (!TextUtils.equals(text, search_request_input_et.text)) + search_request_input_et.setText(text, TextView.BufferType.EDITABLE) + } + } +} \ No newline at end of file diff --git a/navigation/sample-standard/src/main/java/ru/surfstudio/android/navigation/sample_standard/screen/search/request/SearchRequestPresenter.kt b/navigation/sample-standard/src/main/java/ru/surfstudio/android/navigation/sample_standard/screen/search/request/SearchRequestPresenter.kt new file mode 100644 index 0000000000..d889823475 --- /dev/null +++ b/navigation/sample-standard/src/main/java/ru/surfstudio/android/navigation/sample_standard/screen/search/request/SearchRequestPresenter.kt @@ -0,0 +1,30 @@ +package ru.surfstudio.android.navigation.sample_standard.screen.search.request + +import ru.surfstudio.android.core.mvp.binding.rx.ui.BaseRxPresenter +import ru.surfstudio.android.core.mvp.presenter.BasePresenterDependency +import ru.surfstudio.android.dagger.scope.PerScreen +import ru.surfstudio.android.navigation.command.fragment.RemoveLast +import ru.surfstudio.android.navigation.executor.NavigationCommandExecutor +import ru.surfstudio.android.navigation.observer.command.EmitScreenResult +import ru.surfstudio.android.navigation.sample_standard.screen.base.presenter.CommandExecutionPresenter +import javax.inject.Inject + +@PerScreen +class SearchRequestPresenter @Inject constructor( + basePresenterDependency: BasePresenterDependency, + override val commandExecutor: NavigationCommandExecutor, + private val bm: SearchRequestBindModel, + private val route: SearchRequestRoute +) : BaseRxPresenter(basePresenterDependency), CommandExecutionPresenter { + + override fun onFirstLoad() { + bm.closeClick.bindTo { RemoveLast().execute() } + bm.textChanges.bindTo { bm.textState.accept(it) } + bm.resultClick.bindTo { + listOf( + RemoveLast(), + EmitScreenResult(route, bm.textState.value.toString()) + ).execute() + } + } +} \ No newline at end of file diff --git a/navigation/sample-standard/src/main/java/ru/surfstudio/android/navigation/sample_standard/screen/search/request/SearchRequestRoute.kt b/navigation/sample-standard/src/main/java/ru/surfstudio/android/navigation/sample_standard/screen/search/request/SearchRequestRoute.kt new file mode 100644 index 0000000000..8dacb384a5 --- /dev/null +++ b/navigation/sample-standard/src/main/java/ru/surfstudio/android/navigation/sample_standard/screen/search/request/SearchRequestRoute.kt @@ -0,0 +1,27 @@ +package ru.surfstudio.android.navigation.sample_standard.screen.search.request + +import android.os.Bundle +import androidx.core.os.bundleOf +import androidx.fragment.app.Fragment +import ru.surfstudio.android.navigation.observer.route.ResultRoute +import ru.surfstudio.android.navigation.route.Route +import ru.surfstudio.android.navigation.route.fragment.FragmentRoute +import ru.surfstudio.android.utilktx.ktx.text.EMPTY_STRING + +/** + * Роут экрана на котором осуществляется поиск + * + * @param searchInput строка с поисковым запросом, предзаполненная на старте экрана + */ +class SearchRequestRoute(val searchInput: String = EMPTY_STRING): FragmentRoute(), ResultRoute { + + constructor(bundle: Bundle): this(bundle.getString(Route.EXTRA_FIRST, EMPTY_STRING)) + + override fun prepareData(): Bundle { + return bundleOf(Route.EXTRA_FIRST to searchInput) + } + + override fun getScreenClass(): Class { + return SearchRequestFragmentView::class.java + } +} \ No newline at end of file diff --git a/navigation/sample-standard/src/main/java/ru/surfstudio/android/navigation/sample_standard/screen/search/request/SearchRequestScreenConfigurator.kt b/navigation/sample-standard/src/main/java/ru/surfstudio/android/navigation/sample_standard/screen/search/request/SearchRequestScreenConfigurator.kt new file mode 100644 index 0000000000..085053e963 --- /dev/null +++ b/navigation/sample-standard/src/main/java/ru/surfstudio/android/navigation/sample_standard/screen/search/request/SearchRequestScreenConfigurator.kt @@ -0,0 +1,44 @@ +package ru.surfstudio.android.navigation.sample_standard.screen.search.request + +import android.os.Bundle +import dagger.Component +import dagger.Module +import dagger.Provides +import ru.surfstudio.android.core.mvp.configurator.BindableScreenComponent +import ru.surfstudio.android.core.mvp.configurator.ScreenComponent +import ru.surfstudio.android.dagger.scope.PerScreen +import ru.surfstudio.android.navigation.sample_standard.di.ui.ActivityComponent +import ru.surfstudio.android.navigation.sample_standard.di.ui.configurator.FragmentScreenConfigurator +import ru.surfstudio.android.navigation.sample_standard.di.ui.screen.RouteScreenModule +import ru.surfstudio.android.sample.dagger.ui.base.dagger.screen.DefaultFragmentScreenModule + +class SearchRequestScreenConfigurator(args: Bundle): FragmentScreenConfigurator(args) { + + @PerScreen + @Component(dependencies = [ActivityComponent::class], + modules = [DefaultFragmentScreenModule::class, SearchRequestScreenModule::class]) + internal interface SearchRequestFragmentScreenComponent + : BindableScreenComponent + + @Module + internal class SearchRequestScreenModule(route: SearchRequestRoute) : RouteScreenModule(route) { + + @Provides + @PerScreen + fun providePresenters(presenter: SearchRequestPresenter): Any = presenter + } + + + @Suppress("DEPRECATION") + override fun createScreenComponent( + parentComponent: ActivityComponent?, + fragmentScreenModule: DefaultFragmentScreenModule?, + args: Bundle + ): ScreenComponent<*> { + return DaggerSearchRequestScreenConfigurator_SearchRequestFragmentScreenComponent.builder() + .activityComponent(parentComponent) + .defaultFragmentScreenModule(fragmentScreenModule) + .searchRequestScreenModule(SearchRequestScreenModule(SearchRequestRoute(args))) + .build() + } +} \ No newline at end of file diff --git a/navigation/sample-standard/src/main/java/ru/surfstudio/android/navigation/sample_standard/screen/search/results/SearchResultBindModel.kt b/navigation/sample-standard/src/main/java/ru/surfstudio/android/navigation/sample_standard/screen/search/results/SearchResultBindModel.kt new file mode 100644 index 0000000000..8a23e73fbc --- /dev/null +++ b/navigation/sample-standard/src/main/java/ru/surfstudio/android/navigation/sample_standard/screen/search/results/SearchResultBindModel.kt @@ -0,0 +1,15 @@ +package ru.surfstudio.android.navigation.sample_standard.screen.search.results + +import ru.surfstudio.android.core.mvp.binding.rx.relation.mvp.Action +import ru.surfstudio.android.core.mvp.binding.rx.relation.mvp.State +import ru.surfstudio.android.core.mvp.binding.rx.ui.BindModel +import ru.surfstudio.android.dagger.scope.PerScreen +import javax.inject.Inject + +@PerScreen +class SearchResultBindModel @Inject constructor( + route: SearchResultRoute +) : BindModel { + val textState = State(route.searchInput) + val backClick = Action() +} diff --git a/navigation/sample-standard/src/main/java/ru/surfstudio/android/navigation/sample_standard/screen/search/results/SearchResultFragmentView.kt b/navigation/sample-standard/src/main/java/ru/surfstudio/android/navigation/sample_standard/screen/search/results/SearchResultFragmentView.kt new file mode 100644 index 0000000000..cb6937cbd4 --- /dev/null +++ b/navigation/sample-standard/src/main/java/ru/surfstudio/android/navigation/sample_standard/screen/search/results/SearchResultFragmentView.kt @@ -0,0 +1,32 @@ +package ru.surfstudio.android.navigation.sample_standard.screen.search.results + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import kotlinx.android.synthetic.main.fragment_search_result.* +import ru.surfstudio.android.core.mvp.binding.rx.ui.BaseRxFragmentView +import ru.surfstudio.android.navigation.sample_standard.R +import ru.surfstudio.android.navigation.sample_standard.utils.addOnBackPressedListener +import javax.inject.Inject + +class SearchResultFragmentView: BaseRxFragmentView() { + + @Inject + lateinit var bm: SearchResultBindModel + + override fun createConfigurator() = SearchResultScreenConfigurator(requireArguments()) + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.fragment_search_result, container, false) + } + + override fun onActivityCreated(savedInstanceState: Bundle?, viewRecreated: Boolean) { + addOnBackPressedListener { bm.backClick.accept() } + search_result_back_btn.setOnClickListener { bm.backClick.accept() } + + bm.textState.bindTo { text: String -> + search_result_tv.text = text + } + } +} \ No newline at end of file diff --git a/navigation/sample-standard/src/main/java/ru/surfstudio/android/navigation/sample_standard/screen/search/results/SearchResultPresenter.kt b/navigation/sample-standard/src/main/java/ru/surfstudio/android/navigation/sample_standard/screen/search/results/SearchResultPresenter.kt new file mode 100644 index 0000000000..5b61c6f04d --- /dev/null +++ b/navigation/sample-standard/src/main/java/ru/surfstudio/android/navigation/sample_standard/screen/search/results/SearchResultPresenter.kt @@ -0,0 +1,35 @@ +package ru.surfstudio.android.navigation.sample_standard.screen.search.results + +import ru.surfstudio.android.core.mvp.binding.rx.ui.BaseRxPresenter +import ru.surfstudio.android.core.mvp.presenter.BasePresenterDependency +import ru.surfstudio.android.dagger.scope.PerScreen +import ru.surfstudio.android.navigation.command.fragment.RemoveAll +import ru.surfstudio.android.navigation.command.fragment.Replace +import ru.surfstudio.android.navigation.command.fragment.base.FragmentNavigationCommand.Companion.ACTIVITY_NAVIGATION_TAG +import ru.surfstudio.android.navigation.executor.NavigationCommandExecutor +import ru.surfstudio.android.navigation.sample_standard.screen.base.presenter.CommandExecutionPresenter +import ru.surfstudio.android.navigation.sample_standard.screen.search.request.SearchRequestRoute +import ru.surfstudio.android.navigation.sample_standard.utils.animations.ModalAnimations +import javax.inject.Inject + +@PerScreen +class SearchResultPresenter @Inject constructor( + basePresenterDependency: BasePresenterDependency, + override val commandExecutor: NavigationCommandExecutor, + private val bm: SearchResultBindModel, + private val route: SearchResultRoute +) : BaseRxPresenter(basePresenterDependency), CommandExecutionPresenter { + + override fun onFirstLoad() { + bm.backClick.bindTo { + listOf( + RemoveAll(), + Replace( + route = SearchRequestRoute(bm.textState.value), + animations = ModalAnimations, + sourceTag = ACTIVITY_NAVIGATION_TAG + ) + ).execute() + } + } +} \ No newline at end of file diff --git a/navigation/sample-standard/src/main/java/ru/surfstudio/android/navigation/sample_standard/screen/search/results/SearchResultRoute.kt b/navigation/sample-standard/src/main/java/ru/surfstudio/android/navigation/sample_standard/screen/search/results/SearchResultRoute.kt new file mode 100644 index 0000000000..80f96bb197 --- /dev/null +++ b/navigation/sample-standard/src/main/java/ru/surfstudio/android/navigation/sample_standard/screen/search/results/SearchResultRoute.kt @@ -0,0 +1,26 @@ +package ru.surfstudio.android.navigation.sample_standard.screen.search.results + +import android.os.Bundle +import androidx.core.os.bundleOf +import androidx.fragment.app.Fragment +import ru.surfstudio.android.navigation.route.Route +import ru.surfstudio.android.navigation.route.fragment.FragmentRoute +import ru.surfstudio.android.utilktx.ktx.text.EMPTY_STRING + +class SearchResultRoute(val searchInput: String): FragmentRoute() { + + constructor(bundle: Bundle): this(bundle.getString(Route.EXTRA_FIRST, EMPTY_STRING)) + + override fun getId(): String { + return super.getId() + searchInput + } + + override fun prepareData(): Bundle { + return bundleOf(Route.EXTRA_FIRST to searchInput) + } + + override fun getScreenClass(): Class { + return SearchResultFragmentView::class.java + } + +} diff --git a/navigation/sample-standard/src/main/java/ru/surfstudio/android/navigation/sample_standard/screen/search/results/SearchResultScreenConfigurator.kt b/navigation/sample-standard/src/main/java/ru/surfstudio/android/navigation/sample_standard/screen/search/results/SearchResultScreenConfigurator.kt new file mode 100644 index 0000000000..b46484dc04 --- /dev/null +++ b/navigation/sample-standard/src/main/java/ru/surfstudio/android/navigation/sample_standard/screen/search/results/SearchResultScreenConfigurator.kt @@ -0,0 +1,44 @@ +package ru.surfstudio.android.navigation.sample_standard.screen.search.results + +import android.os.Bundle +import dagger.Component +import dagger.Module +import dagger.Provides +import ru.surfstudio.android.core.mvp.configurator.BindableScreenComponent +import ru.surfstudio.android.core.mvp.configurator.ScreenComponent +import ru.surfstudio.android.dagger.scope.PerScreen +import ru.surfstudio.android.navigation.sample_standard.di.ui.ActivityComponent +import ru.surfstudio.android.navigation.sample_standard.di.ui.configurator.FragmentScreenConfigurator +import ru.surfstudio.android.navigation.sample_standard.di.ui.screen.RouteScreenModule +import ru.surfstudio.android.sample.dagger.ui.base.dagger.screen.DefaultFragmentScreenModule + +class SearchResultScreenConfigurator(args: Bundle): FragmentScreenConfigurator(args) { + + @PerScreen + @Component(dependencies = [ActivityComponent::class], + modules = [DefaultFragmentScreenModule::class, SearchResultScreenModule::class]) + internal interface SearchResultFragmentScreenComponent + : BindableScreenComponent + + @Module + internal class SearchResultScreenModule(route: SearchResultRoute) : RouteScreenModule(route) { + + @Provides + @PerScreen + fun providePresenters(presenter: SearchResultPresenter): Any = presenter + } + + + @Suppress("DEPRECATION") + override fun createScreenComponent( + parentComponent: ActivityComponent?, + fragmentScreenModule: DefaultFragmentScreenModule?, + args: Bundle + ): ScreenComponent<*> { + return DaggerSearchResultScreenConfigurator_SearchResultFragmentScreenComponent.builder() + .activityComponent(parentComponent) + .defaultFragmentScreenModule(fragmentScreenModule) + .searchResultScreenModule(SearchResultScreenModule(SearchResultRoute(args))) + .build() + } +} diff --git a/navigation/sample-standard/src/main/java/ru/surfstudio/android/navigation/sample_standard/utils/animations/ModalAnimations.kt b/navigation/sample-standard/src/main/java/ru/surfstudio/android/navigation/sample_standard/utils/animations/ModalAnimations.kt new file mode 100644 index 0000000000..4616a73577 --- /dev/null +++ b/navigation/sample-standard/src/main/java/ru/surfstudio/android/navigation/sample_standard/utils/animations/ModalAnimations.kt @@ -0,0 +1,11 @@ +package ru.surfstudio.android.navigation.sample_standard.utils.animations + +import ru.surfstudio.android.navigation.animation.resource.BaseResourceAnimations +import ru.surfstudio.android.navigation.sample_standard.R + +object ModalAnimations: BaseResourceAnimations( + enterAnimation = R.anim.slide_in_from_bottom, + exitAnimation = R.anim.fade_out_fast, + popEnterAnimation = R.anim.fade_in_fast, + popExitAnimation = R.anim.slide_out_to_bottom +) \ No newline at end of file diff --git a/navigation/sample-standard/src/main/res/anim/slide_in_from_bottom.xml b/navigation/sample-standard/src/main/res/anim/slide_in_from_bottom.xml new file mode 100644 index 0000000000..48167b4fa5 --- /dev/null +++ b/navigation/sample-standard/src/main/res/anim/slide_in_from_bottom.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/navigation/sample-standard/src/main/res/anim/slide_out_to_bottom.xml b/navigation/sample-standard/src/main/res/anim/slide_out_to_bottom.xml new file mode 100644 index 0000000000..7352490789 --- /dev/null +++ b/navigation/sample-standard/src/main/res/anim/slide_out_to_bottom.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/navigation/sample-standard/src/main/res/layout/fragment_profile.xml b/navigation/sample-standard/src/main/res/layout/fragment_profile.xml index 4a457fdce2..1c7f6be89c 100644 --- a/navigation/sample-standard/src/main/res/layout/fragment_profile.xml +++ b/navigation/sample-standard/src/main/res/layout/fragment_profile.xml @@ -18,6 +18,14 @@ android:layout_gravity="bottom" android:orientation="vertical"> +