Added navigation-new module
This commit is contained in:
parent
24eca02b5d
commit
53ffc2d2fc
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
navigation
|
||||
====
|
||||
|
||||
Модуль содержит классы для организации навигации в приложении.
|
||||
|
||||
### Основные интерфейсы и классы
|
||||
|
||||
#### Пакет `activities`
|
||||
|
||||
`BaseActivity` - абстрактный класс, в котором выполняется логгирование с помощью модуля [logging](https://github.com/TouchInstinct/RoboSwag/tree/master/logging) при выполнении некоторых методов. Класс позволяет добавлять новые `OnBackPressedListener` и удалять их с помощью методов *addOnBackPressedListener* и *removeOnBackPressedListener* (*removeAllOnBackPressedListeners*) соответственно.
|
||||
|
||||
Интерфейс `OnBackPressedListener` - интерфейс с одним методом *onBackPressed*. Используется в `BaseActivity`.
|
||||
|
||||
#### Пакет `fragments`
|
||||
|
||||
Класс `ViewControllerFragment` наследуется от `Fragment`. Через статический метод *args* получается `Bundle` с классом `ViewController`(а) и состоянием, которое наследуется от `Parcelable`. В методе *onCreate* инициализируются поля *state* и *viewControllerClass* используя данные из `Bundle`. В методе *onCreateView* создается `ViewController`.
|
||||
|
||||
#### Пакет `viewcontrollers`
|
||||
|
||||
`ViewController` - обертка над Fragment. Один ViewController - один экран. К моменту инициализации вашего класса уже будут доступны следующие поля из `ViewController`: *state*, *activity*, *fragment*, *view*. Это означает, что можно выполнять всю настройку экрана в `init { }`.
|
||||
|
||||
У класса есть два параметра `TActivity: FragmentActivity` и `TState: Parcelable`, которые нужно указывать при инициализации класса `ViewController`. В конструкторе данный класс принимает `CreationContext` и идентификатор layout-ресурса.
|
||||
|
||||
`ViewControllerNavigation` отвечает за навигацию по `ViewController`(ам). В конструкторе принимает `Context`, `FragmentManager` и идентификатор ресурса, который является контейнером для других фрагментов. Имеет параметр `TActivity : FragmentActivity`.
|
||||
|
||||
`EmptyState` - пустое состояние. Использутся, когда при переходе к новому `ViewController` не нужно передавать никаких инициализирующих данных.
|
||||
|
||||
`LifecycleLoggingObserver` подписывается на вызовы методов жизненного цикла и логгирует номер строки, из которой был вызваны эти методы.
|
||||
|
||||
Методы для навигации:
|
||||
|
||||
* *pushViewController* добавляет `ViewController` в стек. Имеет два обязательных параметра *viewControllerClass* - класс, унаследованный от `ViewController` и *state* - объект описывающий состояние.
|
||||
|
||||
* *pushViewControllerForResult* аналогичен предыдущему методу, используется, когда необходимо запустить какой-то фрагмент и при его завершении получить код. Для этого передаются еще два параметра: *requestCode* - код, который нужно получить при закрытии фрагмента и *targetFragment* - фрагмент, который должен получить этот код.
|
||||
|
||||
* *setViewControllerAsTop* работает так же как и *pushViewController* но еще добавляет в качестве *backStackName* тег `TOP_FRAGMENT_TAG_MARK`. При выполнении возврата с помощью метода `up` будет выполнен возврат данному фрагменту.
|
||||
|
||||
* *setInitialViewController* очищает стек и добавляет туда переданный `ViewController`.
|
||||
|
||||
`ViewControllerNavigation` является наследником класса `FragmentNavigation` и для возвратов необходимо использовать методы из родительского класса:
|
||||
|
||||
* *back* - вернуться к фрагменту, который лежит ниже в стеке.
|
||||
* *up* - вернуться к самому низу стека, если в стеке нет фрагментов, помеченных тегом `TOP_FRAGMENT_TAG_MARK`. Если есть, то выполнить возврат к нему. Имеет два необязательных параметра: *name* - имя класса до которого нужно сделать возврат, если он не будет найден, то будет произведен возврат к самому низу стека; *inclusive* - если установить этот флаг, то будет произведен возврат к самому низу стека несмотря на фрагменты с тегом `TOP_FRAGMENT_TAG_MARK`. Если будет установлен и *name* и *inclusive*, то будет произведен возврат к фрагменту, который стоит ниже фрагмента с переданным *name*.
|
||||
|
||||
### Примеры
|
||||
|
||||
Файл `MainActivity.kt`
|
||||
```Kotlin
|
||||
class MainActivity : BaseActivity() {
|
||||
|
||||
private val screenNavigation by lazy {
|
||||
ViewControllerNavigation<MainActivity>(
|
||||
this,
|
||||
supportFragmentManager,
|
||||
R.id.fragment_container
|
||||
)
|
||||
}
|
||||
|
||||
fun getNavigation() = screenNavigation
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
|
||||
screenNavigation.setInitialViewController(
|
||||
MainViewController::class.java,
|
||||
MainScreenState(true)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Файл `MainViewController.kt`
|
||||
```Kotlin
|
||||
class MainViewController(
|
||||
creationContext: CreationContext
|
||||
) : ViewController<MainActivity, MainScreenState>(
|
||||
creationContext,
|
||||
R.layout.view_controller_main
|
||||
) {
|
||||
|
||||
private val button: View = findViewById(R.id.view_controller_main_button)
|
||||
|
||||
init {
|
||||
button.setOnClickListener {
|
||||
activity.getNavigation().pushViewController(
|
||||
TutorialViewController::class.java,
|
||||
EmptyState
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Файл `activity_main.xml`
|
||||
```xml
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/fragment_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
</FrameLayout>
|
||||
```
|
||||
|
||||
### Рекомендации
|
||||
|
||||
Рекомендуется делать состояния, которые передаются во `ViewController` неизменяемыми, чтобы при навигации обратно `ViewController` корректно восстанавливались с изначально заданным состоянием.
|
||||
|
||||
### Зависимости
|
||||
|
||||
Для работы с данным модулем необходимо так же подключить модуль [logging](https://github.com/TouchInstinct/RoboSwag/tree/master/logging).
|
||||
|
||||
```gradle
|
||||
implementation project(':logging')
|
||||
```
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
|
||||
android {
|
||||
compileSdkVersion versions.compileSdk
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api project(":utils")
|
||||
api project(":logging")
|
||||
api project(":api-logansquare")
|
||||
|
||||
api 'androidx.multidex:multidex:2.0.1'
|
||||
|
||||
api 'net.danlew:android.joda:2.10.2'
|
||||
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
|
||||
implementation "androidx.appcompat:appcompat:$versions.appcompat"
|
||||
|
||||
implementation "androidx.fragment:fragment:$versions.fragment"
|
||||
implementation "androidx.fragment:fragment-ktx:$versions.fragment"
|
||||
|
||||
implementation "com.jakewharton:butterknife:$versions.butterknife"
|
||||
kapt "com.jakewharton:butterknife-compiler:$versions.butterknife"
|
||||
|
||||
implementation("com.crashlytics.sdk.android:crashlytics:$versions.crashlytics@aar") {
|
||||
transitive = true
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="ru.touchin.roboswag.components.navigation"/>
|
||||
|
|
@ -0,0 +1,288 @@
|
|||
/*
|
||||
* Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package ru.touchin.roboswag.components.navigation
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.view.MenuItem
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.fragment.app.FragmentTransaction
|
||||
import ru.touchin.roboswag.core.log.Lc
|
||||
import ru.touchin.roboswag.components.navigation.fragments.BaseFragment
|
||||
import ru.touchin.roboswag.components.navigation.viewcontrollers.EmptyState
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 07/03/2016.
|
||||
* Navigation which is controlling fragments on activity using [FragmentManager].
|
||||
* Basically there are 4 main actions to add fragments to activity.
|
||||
* 1) [.setInitial] means to set fragment on top and remove all previously added fragments from stack;
|
||||
* 2) [.push] means to simply add fragment on top of the stack;
|
||||
* 3) [.setAsTop] means to push fragment on top of the stack with specific [.TOP_FRAGMENT_TAG_MARK] tag.
|
||||
* It is useful to realize up/back navigation: if [.up] method will be called then stack will go to nearest fragment with TOP tag.
|
||||
* If [.back] method will be called then stack will go to previous fragment.
|
||||
* Usually such logic using to set as top fragments from sidebar and show hamburger when some of them appeared;
|
||||
* 4) [.pushForResult] means to push fragment with target fragment. It is also adding [.WITH_TARGET_FRAGMENT_TAG_MARK] tag.
|
||||
* Also if such up/back navigation logic is not OK then [.backTo] method could be used with any condition to back to.
|
||||
* In that case in any stack-change method it is allowed to setup fragment transactions.
|
||||
*/
|
||||
open class FragmentNavigation(
|
||||
private val context: Context,
|
||||
private val fragmentManager: FragmentManager,
|
||||
@IdRes private val containerViewId: Int,
|
||||
private val transition: Int = FragmentTransaction.TRANSIT_FRAGMENT_OPEN
|
||||
) {
|
||||
|
||||
companion object {
|
||||
const val TOP_FRAGMENT_TAG_MARK = "TOP_FRAGMENT"
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if last fragment in stack is top (added by [.setAsTop] or [.setInitial]) like fragment from sidebar menu.
|
||||
*
|
||||
* @return True if last fragment on stack has TOP_FRAGMENT_TAG_MARK.
|
||||
*/
|
||||
fun isCurrentFragmentTop(): Boolean = if (fragmentManager.backStackEntryCount == 0) {
|
||||
true
|
||||
} else {
|
||||
fragmentManager
|
||||
.getBackStackEntryAt(fragmentManager.backStackEntryCount - 1)
|
||||
.name
|
||||
?.contains(TOP_FRAGMENT_TAG_MARK) ?: false
|
||||
}
|
||||
|
||||
/**
|
||||
* Allowed to react on [android.app.Activity]'s menu item selection.
|
||||
*
|
||||
* @param item Selected menu item;
|
||||
* @return True if reaction fired.
|
||||
*/
|
||||
fun onOptionsItemSelected(item: MenuItem): Boolean = item.itemId == android.R.id.home && back()
|
||||
|
||||
/**
|
||||
* Base method which is adding fragment to stack.
|
||||
*
|
||||
* @param fragmentClass Class of [Fragment] to instantiate;
|
||||
* @param targetFragment Target fragment to be set as [Fragment.getTargetFragment] of instantiated [Fragment];
|
||||
* @param addToStack Flag to add this transaction to the back stack;
|
||||
* @param args Bundle to be set as [Fragment.getArguments] of instantiated [Fragment];
|
||||
* @param backStackName Name of [Fragment] in back stack;
|
||||
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info.
|
||||
*/
|
||||
fun addToStack(
|
||||
fragmentClass: Class<out Fragment>,
|
||||
targetFragment: Fragment?,
|
||||
targetRequestCode: Int,
|
||||
addToStack: Boolean,
|
||||
args: Bundle?,
|
||||
backStackName: String?,
|
||||
transactionSetup: ((FragmentTransaction) -> Unit)?
|
||||
) {
|
||||
if (fragmentManager.isDestroyed) {
|
||||
Lc.assertion("FragmentManager is destroyed")
|
||||
return
|
||||
}
|
||||
|
||||
val fragment = Fragment.instantiate(context, fragmentClass.name, args)
|
||||
fragment.setTargetFragment(targetFragment, targetRequestCode)
|
||||
|
||||
val fragmentTransaction = fragmentManager.beginTransaction()
|
||||
transactionSetup?.invoke(fragmentTransaction)
|
||||
fragmentTransaction.replace(containerViewId, fragment, null)
|
||||
if (addToStack) {
|
||||
fragmentTransaction
|
||||
.addToBackStack(backStackName)
|
||||
.setTransition(transition)
|
||||
}
|
||||
fragmentTransaction
|
||||
.setPrimaryNavigationFragment(fragment)
|
||||
.commit()
|
||||
}
|
||||
|
||||
/**
|
||||
* Simply calls [FragmentManager.popBackStack].
|
||||
*
|
||||
* @return True if it have back to some entry in stack.
|
||||
*/
|
||||
fun back(): Boolean {
|
||||
if (fragmentManager.backStackEntryCount >= 1) {
|
||||
fragmentManager.popBackStack()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Backs to fragment with specific [.TOP_FRAGMENT_TAG_MARK] tag.
|
||||
* This tag is adding if fragment added to stack via [.setInitial] or [.setAsTop] methods.
|
||||
* It can be used to create simple up/back navigation.
|
||||
*
|
||||
* @return True if it have back to some entry in stack.
|
||||
*/
|
||||
fun up(name: String? = null, inclusive: Boolean = false) {
|
||||
fragmentManager.popBackStack(name, if (inclusive) FragmentManager.POP_BACK_STACK_INCLUSIVE else 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushes [Fragment] on top of stack with specific arguments and transaction setup.
|
||||
*
|
||||
* @param fragmentClass Class of [Fragment] to instantiate;
|
||||
* @param args Bundle to be set as [Fragment.getArguments] of instantiated [Fragment];
|
||||
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info.
|
||||
*/
|
||||
fun push(
|
||||
fragmentClass: Class<out Fragment>,
|
||||
args: Bundle? = null,
|
||||
addToStack: Boolean = true,
|
||||
backStackName: String? = null,
|
||||
transactionSetup: ((FragmentTransaction) -> Unit)? = null
|
||||
) {
|
||||
addToStack(fragmentClass, null, 0, addToStack, args, backStackName, transactionSetup)
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushes [Fragment] on top of stack with specific target fragment, arguments and transaction setup.
|
||||
*
|
||||
* @param fragmentClass KClass of [Fragment] to instantiate;
|
||||
* @param state State of instantiated [Fragment];
|
||||
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info.
|
||||
*/
|
||||
fun <T: Parcelable> push(
|
||||
fragmentClass: KClass<out BaseFragment<*, out T>>,
|
||||
state: T? = null,
|
||||
addToStack: Boolean = true,
|
||||
backStackName: String? = null,
|
||||
transactionSetup: ((FragmentTransaction) -> Unit)? = null
|
||||
) {
|
||||
push(fragmentClass.java, BaseFragment.args(state ?: EmptyState), addToStack, backStackName, transactionSetup)
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushes [Fragment] on top of stack with specific target fragment, arguments and transaction setup.
|
||||
*
|
||||
* @param fragmentClass Class of [Fragment] to instantiate;
|
||||
* @param targetFragment Target fragment to be set as [Fragment.getTargetFragment] of instantiated [Fragment];
|
||||
* @param args Bundle to be set as [Fragment.getArguments] of instantiated [Fragment];
|
||||
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info.
|
||||
*/
|
||||
fun pushForResult(
|
||||
fragmentClass: Class<out Fragment>,
|
||||
targetFragment: Fragment,
|
||||
targetRequestCode: Int,
|
||||
args: Bundle? = null,
|
||||
transactionSetup: ((FragmentTransaction) -> Unit)? = null
|
||||
) {
|
||||
addToStack(
|
||||
fragmentClass,
|
||||
targetFragment,
|
||||
targetRequestCode,
|
||||
true,
|
||||
args,
|
||||
null,
|
||||
transactionSetup
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushes [Fragment] on top of stack with specific target fragment, arguments and transaction setup.
|
||||
*
|
||||
* @param fragmentClass KClass of [Fragment] to instantiate;
|
||||
* @param targetFragment Target fragment to be set as [Fragment.getTargetFragment] of instantiated [Fragment];
|
||||
* @param state State of instantiated [Fragment];
|
||||
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info.
|
||||
*/
|
||||
fun <T: Parcelable> pushForResult(
|
||||
fragmentClass: KClass<out BaseFragment<*, out T>>,
|
||||
targetFragment: Fragment,
|
||||
targetRequestCode: Int,
|
||||
state: T? = null,
|
||||
transactionSetup: ((FragmentTransaction) -> Unit)? = null
|
||||
) {
|
||||
pushForResult(fragmentClass.java, targetFragment, targetRequestCode, BaseFragment.args(state ?: EmptyState), transactionSetup)
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushes [Fragment] on top of stack with specific transaction setup, arguments
|
||||
* and with [.TOP_FRAGMENT_TAG_MARK] tag used for simple up/back navigation.
|
||||
*
|
||||
* @param fragmentClass Class of [Fragment] to instantiate;
|
||||
* @param args Bundle to be set as [Fragment.getArguments] of instantiated [Fragment];
|
||||
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info.
|
||||
*/
|
||||
fun setAsTop(
|
||||
fragmentClass: Class<out Fragment>,
|
||||
args: Bundle? = null,
|
||||
addToStack: Boolean = true,
|
||||
transactionSetup: ((FragmentTransaction) -> Unit)? = null
|
||||
) {
|
||||
addToStack(fragmentClass, null, 0, addToStack, args, TOP_FRAGMENT_TAG_MARK, transactionSetup)
|
||||
}
|
||||
|
||||
/**
|
||||
* Pops all [Fragment]s and places new initial [Fragment] on top of stack with specific transaction setup and arguments.
|
||||
*
|
||||
* @param fragmentClass Class of [Fragment] to instantiate;
|
||||
* @param args Bundle to be set as [Fragment.getArguments] of instantiated [Fragment];
|
||||
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info.
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun setInitial(
|
||||
fragmentClass: Class<out Fragment>,
|
||||
args: Bundle? = null,
|
||||
transactionSetup: ((FragmentTransaction) -> Unit)? = null
|
||||
) {
|
||||
beforeSetInitialActions()
|
||||
setAsTop(fragmentClass, args, false, transactionSetup)
|
||||
}
|
||||
|
||||
/**
|
||||
* Pops all [Fragment]s and places new initial [Fragment] on top of stack with specific transaction setup and arguments.
|
||||
*
|
||||
* @param fragmentClass Class of [Fragment] to instantiate;
|
||||
* @param state State of instantiated [Fragment];
|
||||
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info.
|
||||
*/
|
||||
fun <T: Parcelable> setInitial(
|
||||
fragmentClass: KClass<out BaseFragment<*, out T>>,
|
||||
state: T? = null,
|
||||
transactionSetup: ((FragmentTransaction) -> Unit)? = null
|
||||
) {
|
||||
beforeSetInitialActions()
|
||||
setAsTop(fragmentClass.java, BaseFragment.args(state ?: EmptyState), false, transactionSetup)
|
||||
}
|
||||
|
||||
/**
|
||||
* Method calls every time before initial [Fragment] will be placed.
|
||||
*/
|
||||
protected fun beforeSetInitialActions() {
|
||||
if (fragmentManager.isDestroyed) {
|
||||
Lc.assertion("FragmentManager is destroyed")
|
||||
return
|
||||
}
|
||||
|
||||
if (fragmentManager.backStackEntryCount > 0) {
|
||||
fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,208 @@
|
|||
/*
|
||||
* Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package ru.touchin.roboswag.components.navigation
|
||||
|
||||
import android.animation.ValueAnimator
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.ActionBarDrawerToggle
|
||||
import androidx.drawerlayout.widget.DrawerLayout
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import ru.touchin.roboswag.components.navigation.activities.BaseActivity
|
||||
import ru.touchin.roboswag.components.navigation.activities.OnBackPressedListener
|
||||
import ru.touchin.roboswag.components.utils.UiUtils
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 11/03/16.
|
||||
* Simple realization of one-side [ActionBarDrawerToggle].
|
||||
*/
|
||||
class SimpleActionBarDrawerToggle(
|
||||
private val activity: BaseActivity,
|
||||
val drawerLayout: DrawerLayout,
|
||||
private val sidebar: View
|
||||
) : ActionBarDrawerToggle(activity, drawerLayout, 0, 0), FragmentManager.OnBackStackChangedListener, OnBackPressedListener {
|
||||
|
||||
private var isInvalidateOptionsMenuSupported = true
|
||||
|
||||
private var hamburgerShowed: Boolean = false
|
||||
private var sidebarDisabled: Boolean = false
|
||||
|
||||
private var slideOffset: Float = 0f
|
||||
private var slidePosition: Float = 0f
|
||||
|
||||
private var hamburgerAnimator: ValueAnimator? = null
|
||||
private var firstAnimation = true
|
||||
|
||||
init {
|
||||
drawerLayout.addDrawerListener(this)
|
||||
activity.supportFragmentManager.addOnBackStackChangedListener(this)
|
||||
activity.addOnBackPressedListener(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set turn on/off invocation of supportInvalidateOptionsMenu
|
||||
*
|
||||
* @param isInvalidateOptionsMenuSupported flag for turning on/off invocation.
|
||||
*/
|
||||
fun setInvalidateOptionsMenuSupported(isInvalidateOptionsMenuSupported: Boolean) {
|
||||
this.isInvalidateOptionsMenuSupported = isInvalidateOptionsMenuSupported
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if sidebar is opened.
|
||||
*
|
||||
* @return True if sidebar is opened.
|
||||
*/
|
||||
fun isOpened(): Boolean = drawerLayout.isDrawerOpen(sidebar)
|
||||
|
||||
/**
|
||||
* Disables sidebar. So it will be in closed state and couldn't be opened.
|
||||
*/
|
||||
fun disableSidebar() {
|
||||
sidebarDisabled = true
|
||||
close()
|
||||
update()
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables sidebar. So it could be opened.
|
||||
*/
|
||||
fun enableSidebar() {
|
||||
sidebarDisabled = false
|
||||
update()
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides hamburger icon. Use it if there are some fragments in activity's stack.
|
||||
*/
|
||||
fun hideHamburger() {
|
||||
syncState()
|
||||
hamburgerShowed = true
|
||||
update()
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows hamburger icon. Use it if there are no fragments in activity's stack or current fragment is like top.
|
||||
*/
|
||||
fun showHamburger() {
|
||||
syncState()
|
||||
hamburgerShowed = false
|
||||
update()
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens sidebar.
|
||||
*/
|
||||
fun open() {
|
||||
if (!sidebarDisabled && !drawerLayout.isDrawerOpen(sidebar)) {
|
||||
drawerLayout.openDrawer(sidebar)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes sidebar.
|
||||
*/
|
||||
fun close() {
|
||||
if (drawerLayout.isDrawerOpen(sidebar)) {
|
||||
drawerLayout.closeDrawer(sidebar)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to process clicking on hamburger. It is needed to be called from [android.app.Activity.onOptionsItemSelected].
|
||||
* If this method won't be called then opening-closing won't work.
|
||||
*
|
||||
* @param item Selected item.
|
||||
* @return True if item clicking processed.
|
||||
*/
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean = shouldShowHamburger() && super.onOptionsItemSelected(item)
|
||||
|
||||
/**
|
||||
* Call it when back stack of activity's fragments have changed.
|
||||
*/
|
||||
override fun onBackStackChanged() {
|
||||
close()
|
||||
}
|
||||
|
||||
/**
|
||||
* Call it when system back button have pressed.
|
||||
*/
|
||||
override fun onBackPressed(): Boolean = if (isOpened()) {
|
||||
close()
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
override fun onDrawerClosed(view: View) {
|
||||
if (isInvalidateOptionsMenuSupported) {
|
||||
activity.invalidateOptionsMenu()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDrawerSlide(drawerView: View, offset: Float) {
|
||||
if (offset in slideOffset..slidePosition
|
||||
|| offset in slidePosition..slideOffset) {
|
||||
slideOffset = offset
|
||||
}
|
||||
super.onDrawerSlide(drawerView, slideOffset)
|
||||
}
|
||||
|
||||
/**
|
||||
* Call it at [android.app.Activity.onPostCreate].
|
||||
*/
|
||||
override fun syncState() {
|
||||
cancelAnimation()
|
||||
super.syncState()
|
||||
}
|
||||
|
||||
override fun onDrawerOpened(drawerView: View) {
|
||||
UiUtils.OfViews.hideSoftInput(activity)
|
||||
if (isInvalidateOptionsMenuSupported) {
|
||||
activity.invalidateOptionsMenu()
|
||||
}
|
||||
}
|
||||
|
||||
private fun shouldShowHamburger(): Boolean = !hamburgerShowed && !sidebarDisabled
|
||||
|
||||
private fun update() {
|
||||
setHamburgerState(shouldShowHamburger())
|
||||
drawerLayout.setDrawerLockMode(if (sidebarDisabled) DrawerLayout.LOCK_MODE_LOCKED_CLOSED else DrawerLayout.LOCK_MODE_UNLOCKED)
|
||||
}
|
||||
|
||||
private fun setHamburgerState(showHamburger: Boolean) {
|
||||
if (!firstAnimation) {
|
||||
cancelAnimation()
|
||||
hamburgerAnimator = ValueAnimator.ofFloat(slideOffset, if (showHamburger) 0f else 1f)
|
||||
hamburgerAnimator!!.addUpdateListener { animation -> onDrawerSlide(drawerLayout, animation.animatedValue as Float) }
|
||||
hamburgerAnimator!!.start()
|
||||
} else {
|
||||
slideOffset = if (showHamburger) 0f else 1f
|
||||
onDrawerSlide(drawerLayout, slideOffset)
|
||||
}
|
||||
slidePosition = if (showHamburger) 0f else 1f
|
||||
firstAnimation = false
|
||||
}
|
||||
|
||||
private fun cancelAnimation() {
|
||||
hamburgerAnimator?.cancel()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* Copyright (c) 2016 Touch Instinct
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package ru.touchin.roboswag.components.navigation;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.os.StrictMode;
|
||||
import android.util.Log;
|
||||
|
||||
import com.crashlytics.android.Crashlytics;
|
||||
|
||||
import net.danlew.android.joda.JodaTimeAndroid;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.multidex.MultiDex;
|
||||
import io.fabric.sdk.android.Fabric;
|
||||
import ru.touchin.roboswag.core.log.ConsoleLogProcessor;
|
||||
import ru.touchin.roboswag.core.log.Lc;
|
||||
import ru.touchin.roboswag.core.log.LcGroup;
|
||||
import ru.touchin.roboswag.core.log.LcLevel;
|
||||
import ru.touchin.roboswag.core.log.LogProcessor;
|
||||
import ru.touchin.roboswag.core.utils.ShouldNotHappenException;
|
||||
import ru.touchin.templates.ApiModel;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 10/03/16.
|
||||
* Base class of application to extends for Touch Instinct related projects.
|
||||
*/
|
||||
public abstract class TouchinApp extends Application {
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(@NonNull final Context base) {
|
||||
super.attachBaseContext(base);
|
||||
MultiDex.install(base);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
JodaTimeAndroid.init(this);
|
||||
if (BuildConfig.DEBUG) {
|
||||
enableStrictMode();
|
||||
Lc.initialize(new ConsoleLogProcessor(LcLevel.VERBOSE), true);
|
||||
LcGroup.UI_LIFECYCLE.disable();
|
||||
} else {
|
||||
try {
|
||||
final Crashlytics crashlytics = new Crashlytics();
|
||||
Fabric.with(this, crashlytics);
|
||||
Fabric.getLogger().setLogLevel(Log.ERROR);
|
||||
Lc.initialize(new CrashlyticsLogProcessor(crashlytics), false);
|
||||
} catch (final NoClassDefFoundError error) {
|
||||
Lc.initialize(new ConsoleLogProcessor(LcLevel.INFO), false);
|
||||
Lc.e("Crashlytics initialization error! Did you forget to add\n"
|
||||
+ "compile('com.crashlytics.sdk.android:crashlytics:+@aar') {\n"
|
||||
+ " transitive = true;\n"
|
||||
+ "}\n"
|
||||
+ "to your build.gradle?", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void enableStrictMode() {
|
||||
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
|
||||
.detectAll()
|
||||
.permitDiskReads()
|
||||
.permitDiskWrites()
|
||||
.penaltyLog()
|
||||
.build());
|
||||
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
|
||||
.detectAll()
|
||||
.penaltyLog()
|
||||
.build());
|
||||
}
|
||||
|
||||
private static class CrashlyticsLogProcessor extends LogProcessor {
|
||||
|
||||
@NonNull
|
||||
private final Crashlytics crashlytics;
|
||||
|
||||
public CrashlyticsLogProcessor(@NonNull final Crashlytics crashlytics) {
|
||||
super(LcLevel.INFO);
|
||||
this.crashlytics = crashlytics;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processLogMessage(@NonNull final LcGroup group,
|
||||
@NonNull final LcLevel level,
|
||||
@NonNull final String tag,
|
||||
@NonNull final String message,
|
||||
@Nullable final Throwable throwable) {
|
||||
if (group == LcGroup.UI_LIFECYCLE) {
|
||||
crashlytics.core.log(level.getPriority(), tag, message);
|
||||
} else if (!level.lessThan(LcLevel.ASSERT)
|
||||
|| (group == ApiModel.API_VALIDATION_LC_GROUP && level == LcLevel.ERROR)) {
|
||||
Log.e(tag, message);
|
||||
if (throwable != null) {
|
||||
crashlytics.core.log(level.getPriority(), tag, message);
|
||||
crashlytics.core.logException(throwable);
|
||||
} else {
|
||||
final ShouldNotHappenException exceptionToLog = new ShouldNotHappenException(tag + ':' + message);
|
||||
reduceStackTrace(exceptionToLog);
|
||||
crashlytics.core.logException(exceptionToLog);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void reduceStackTrace(@NonNull final Throwable throwable) {
|
||||
final StackTraceElement[] stackTrace = throwable.getStackTrace();
|
||||
final List<StackTraceElement> reducedStackTraceList = new ArrayList<>();
|
||||
for (int i = stackTrace.length - 1; i >= 0; i--) {
|
||||
final StackTraceElement stackTraceElement = stackTrace[i];
|
||||
if (stackTraceElement.getClassName().contains(getClass().getSimpleName())
|
||||
|| stackTraceElement.getClassName().contains(LcGroup.class.getName())
|
||||
|| stackTraceElement.getClassName().contains(Lc.class.getName())) {
|
||||
break;
|
||||
}
|
||||
reducedStackTraceList.add(0, stackTraceElement);
|
||||
}
|
||||
StackTraceElement[] reducedStackTrace = new StackTraceElement[reducedStackTraceList.size()];
|
||||
reducedStackTrace = reducedStackTraceList.toArray(reducedStackTrace);
|
||||
throwable.setStackTrace(reducedStackTrace);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package ru.touchin.roboswag.components.navigation.activities
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.os.PersistableBundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import ru.touchin.roboswag.components.navigation.keyboard_resizeable.KeyboardBehaviorDetector
|
||||
import ru.touchin.roboswag.components.navigation.viewcontrollers.LifecycleLoggingObserver
|
||||
import ru.touchin.roboswag.core.log.Lc
|
||||
import ru.touchin.roboswag.core.log.LcGroup
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 08/03/2016.
|
||||
* Base activity to use in components repository.
|
||||
*/
|
||||
abstract class BaseActivity : AppCompatActivity() {
|
||||
|
||||
private val onBackPressedListeners = ArrayList<OnBackPressedListener>()
|
||||
|
||||
open val keyboardBehaviorDetector: KeyboardBehaviorDetector? = null
|
||||
|
||||
init {
|
||||
lifecycle.addObserver(LifecycleLoggingObserver())
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
|
||||
super.onCreate(savedInstanceState, persistentState)
|
||||
// Possible work around for market launches. See http://code.google.com/p/android/issues/detail?id=2373
|
||||
// for more details. Essentially, the market launches the main activity on top of other activities.
|
||||
// we never want this to happen. Instead, we check if we are the root and if not, we finish.
|
||||
if (!isTaskRoot && intent.hasCategory(Intent.CATEGORY_LAUNCHER) && Intent.ACTION_MAIN == intent.action) {
|
||||
Lc.e("Finishing activity as it is launcher but not root")
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
LcGroup.UI_LIFECYCLE.i("${Lc.getCodePoint(this)} requestCode: $requestCode; resultCode: $resultCode")
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(stateToSave: Bundle) {
|
||||
super.onSaveInstanceState(stateToSave)
|
||||
LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(this))
|
||||
}
|
||||
|
||||
override fun onSupportNavigateUp(): Boolean {
|
||||
onBackPressed()
|
||||
return true
|
||||
}
|
||||
|
||||
open fun addOnBackPressedListener(onBackPressedListener: OnBackPressedListener) {
|
||||
onBackPressedListeners.add(onBackPressedListener)
|
||||
}
|
||||
|
||||
open fun removeOnBackPressedListener(onBackPressedListener: OnBackPressedListener) {
|
||||
onBackPressedListeners.remove(onBackPressedListener)
|
||||
}
|
||||
|
||||
open fun removeAllOnBackPressedListeners() {
|
||||
onBackPressedListeners.clear()
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
onBackPressedListeners.reversed().forEach { onBackPressedListener ->
|
||||
if (onBackPressedListener.onBackPressed()) {
|
||||
return
|
||||
}
|
||||
}
|
||||
super.onBackPressed()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package ru.touchin.roboswag.components.navigation.activities
|
||||
|
||||
import androidx.fragment.app.FragmentTransaction
|
||||
import ru.touchin.roboswag.components.navigation.viewcontrollers.ViewControllerNavigation
|
||||
|
||||
/**
|
||||
* Created by Daniil Borisovskii on 15/08/2019.
|
||||
* Base activity with nested navigation.
|
||||
*/
|
||||
abstract class NavigationActivity : BaseActivity() {
|
||||
|
||||
protected abstract val fragmentContainerViewId: Int
|
||||
|
||||
protected open val transition = FragmentTransaction.TRANSIT_NONE
|
||||
|
||||
open val navigation by lazy {
|
||||
ViewControllerNavigation<NavigationActivity>(
|
||||
this,
|
||||
supportFragmentManager,
|
||||
fragmentContainerViewId,
|
||||
transition
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package ru.touchin.roboswag.components.navigation.activities;
|
||||
|
||||
public interface OnBackPressedListener {
|
||||
|
||||
boolean onBackPressed();
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,259 @@
|
|||
/*
|
||||
* Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package ru.touchin.roboswag.components.navigation.fragments
|
||||
|
||||
import android.animation.Animator
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.animation.Animation
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import ru.touchin.roboswag.components.navigation.BuildConfig
|
||||
import ru.touchin.roboswag.components.navigation.viewcontrollers.ViewController
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 21/10/2015.
|
||||
* Fragment instantiated in specific activity of [TActivity] type that is holding [ViewController] inside.
|
||||
*
|
||||
* @param <TState> Type of object which is representing it's fragment state;
|
||||
* @param <TActivity> Type of [FragmentActivity] where fragment could be attached to.
|
||||
</TActivity></TState> */
|
||||
@Suppress("detekt.TooManyFunctions", "UNCHECKED_CAST")
|
||||
open class ViewControllerFragment<TActivity : FragmentActivity, TState : Parcelable> : Fragment() {
|
||||
|
||||
companion object {
|
||||
|
||||
private const val VIEW_CONTROLLER_CLASS_EXTRA = "VIEW_CONTROLLER_CLASS_EXTRA"
|
||||
private const val VIEW_CONTROLLER_STATE_EXTRA = "VIEW_CONTROLLER_STATE_EXTRA"
|
||||
|
||||
/**
|
||||
* Creates [Bundle] which will store state.
|
||||
*
|
||||
* @param state State to use into ViewController.
|
||||
* @return Returns bundle with state inside.
|
||||
*/
|
||||
fun args(viewControllerClass: Class<out ViewController<*, *>>, state: Parcelable?): Bundle = Bundle().apply {
|
||||
putSerializable(VIEW_CONTROLLER_CLASS_EXTRA, viewControllerClass)
|
||||
putParcelable(VIEW_CONTROLLER_STATE_EXTRA, state)
|
||||
}
|
||||
|
||||
private fun <T : Parcelable> reserialize(parcelable: T, classLoader: ClassLoader): T {
|
||||
var parcel = Parcel.obtain()
|
||||
parcel.writeParcelable(parcelable, 0)
|
||||
val serializableBytes = parcel.marshall()
|
||||
parcel.recycle()
|
||||
parcel = Parcel.obtain()
|
||||
parcel.unmarshall(serializableBytes, 0, serializableBytes.size)
|
||||
parcel.setDataPosition(0)
|
||||
val result = parcel.readParcelable<T>(classLoader) ?: throw IllegalStateException("It must not be null")
|
||||
parcel.recycle()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
lateinit var state: TState private set
|
||||
|
||||
lateinit var viewControllerClass: Class<ViewController<TActivity, TState>> private set
|
||||
|
||||
private var viewController: ViewController<out TActivity, out TState>? = null
|
||||
|
||||
private var pendingActivityResult: ActivityResult? = null
|
||||
|
||||
private var appeared: Boolean = false
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setHasOptionsMenu(true)
|
||||
|
||||
viewControllerClass = arguments?.getSerializable(VIEW_CONTROLLER_CLASS_EXTRA) as? Class<ViewController<TActivity, TState>>
|
||||
?: throw IllegalArgumentException("View controller class must be not-null")
|
||||
|
||||
state = savedInstanceState?.getParcelable<TState>(VIEW_CONTROLLER_STATE_EXTRA)
|
||||
?: arguments?.getParcelable(VIEW_CONTROLLER_STATE_EXTRA)
|
||||
?: throw IllegalStateException("State is required and null")
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
state = reserialize(state, state.javaClass.classLoader ?: Thread.currentThread().contextClassLoader)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
val newViewController = createViewController(
|
||||
ViewController.CreationContext(requireActivity(), this, inflater, container),
|
||||
savedInstanceState
|
||||
)
|
||||
viewController = newViewController
|
||||
newViewController.onCreate()
|
||||
return newViewController.view
|
||||
}
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
val activityResult = pendingActivityResult
|
||||
if (viewController != null && activityResult != null) {
|
||||
viewController?.onActivityResult(activityResult.requestCode, activityResult.resultCode, activityResult.data)
|
||||
pendingActivityResult = null
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateAnimation(transit: Int, enter: Boolean, nextAnim: Int): Animation? =
|
||||
viewController?.onCreateAnimation(transit, enter, nextAnim)
|
||||
|
||||
override fun onCreateAnimator(transit: Int, enter: Boolean, nextAnim: Int): Animator? =
|
||||
viewController?.onCreateAnimator(transit, enter, nextAnim)
|
||||
|
||||
override fun onViewStateRestored(savedInstanceState: Bundle?) {
|
||||
super.onViewStateRestored(savedInstanceState)
|
||||
viewController?.onViewStateRestored(savedInstanceState)
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
if (!appeared && isMenuVisible) {
|
||||
onAppear()
|
||||
}
|
||||
viewController?.onStart()
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when fragment is moved in started state and it's [.isMenuVisible] sets to true.
|
||||
* Usually it is indicating that user can't see fragment on screen and useful to track analytics events.
|
||||
*/
|
||||
private fun onAppear() {
|
||||
appeared = true
|
||||
viewController?.onAppear()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
viewController?.onResume()
|
||||
}
|
||||
|
||||
override fun onLowMemory() {
|
||||
super.onLowMemory()
|
||||
viewController?.onLowMemory()
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
viewController?.onCreateOptionsMenu(menu, inflater)
|
||||
}
|
||||
|
||||
override fun onPrepareOptionsMenu(menu: Menu) {
|
||||
viewController?.onPrepareOptionsMenu(menu)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean =
|
||||
viewController?.onOptionsItemSelected(item) == true || super.onOptionsItemSelected(item)
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
viewController?.onPause()
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(savedInstanceState: Bundle) {
|
||||
super.onSaveInstanceState(savedInstanceState)
|
||||
viewController?.onSaveInstanceState(savedInstanceState)
|
||||
savedInstanceState.putParcelable(VIEW_CONTROLLER_STATE_EXTRA, state)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when fragment is moved in stopped state or it's [.isMenuVisible] sets to false.
|
||||
* Usually it is indicating that user can't see fragment on screen and useful to track analytics events.
|
||||
*/
|
||||
private fun onDisappear() {
|
||||
appeared = false
|
||||
viewController?.onDisappear()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
if (appeared) {
|
||||
onDisappear()
|
||||
}
|
||||
viewController?.onStop()
|
||||
super.onStop()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
viewController?.onDestroy()
|
||||
viewController = null
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
|
||||
viewController?.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
viewController?.onActivityResult(requestCode, resultCode, data) ?: let {
|
||||
pendingActivityResult = ActivityResult(requestCode, resultCode, data)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun setMenuVisibility(menuVisible: Boolean) {
|
||||
super.setMenuVisibility(menuVisible)
|
||||
if (activity != null && view != null) {
|
||||
val started = lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)
|
||||
if (!appeared && menuVisible && started) {
|
||||
onAppear()
|
||||
}
|
||||
if (appeared && (!menuVisible || !started)) {
|
||||
onDisappear()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createViewController(
|
||||
creationContext: ViewController.CreationContext,
|
||||
savedInstanceState: Bundle?
|
||||
): ViewController<out TActivity, out TState> {
|
||||
if (viewControllerClass.constructors.size != 1) {
|
||||
throw IllegalStateException("There should be single constructor for $viewControllerClass")
|
||||
}
|
||||
val constructor = viewControllerClass.constructors[0]
|
||||
return when (constructor.parameterTypes.size) {
|
||||
1 -> constructor.newInstance(creationContext)
|
||||
2 -> constructor.newInstance(creationContext, savedInstanceState)
|
||||
else -> throw IllegalArgumentException("Wrong constructor parameters count: ${constructor.parameterTypes.size}")
|
||||
} as ViewController<out TActivity, out TState>
|
||||
}
|
||||
|
||||
override fun toString(): String = "${super.toString()} ViewController: $viewControllerClass"
|
||||
|
||||
private data class ActivityResult(val requestCode: Int, val resultCode: Int, val data: Intent?)
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
package ru.touchin.roboswag.components.navigation.keyboard_resizeable
|
||||
|
||||
import android.graphics.Rect
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import ru.touchin.roboswag.components.navigation.activities.BaseActivity
|
||||
|
||||
// The workaround forces an activity to resize when keyboard appears in the full-screen mode
|
||||
class KeyboardBehaviorDetector(
|
||||
activity: BaseActivity,
|
||||
fragmentContainerId: Int
|
||||
) {
|
||||
|
||||
companion object {
|
||||
private const val SCREEN_TO_KEYBOARD_HEIGHT_RATIO = 4.75
|
||||
}
|
||||
|
||||
private val contentContainer = activity.findViewById(android.R.id.content) as ViewGroup
|
||||
private val fragmentContainer = activity.findViewById(fragmentContainerId) as ViewGroup
|
||||
private lateinit var rootView: View
|
||||
private val listener = { possiblyResizeChildOfContent() }
|
||||
|
||||
private var keyboardHideListener: (() -> Unit)? = null
|
||||
private var keyboardShowListener: ((Int) -> Unit)? = null
|
||||
|
||||
fun setKeyboardHideListener(listener: () -> Unit) {
|
||||
keyboardHideListener = listener
|
||||
}
|
||||
|
||||
fun removeKeyboardHideListener() {
|
||||
keyboardHideListener = null
|
||||
}
|
||||
|
||||
fun setKeyboardShowListener(listener: (Int) -> Unit) {
|
||||
keyboardShowListener = listener
|
||||
}
|
||||
|
||||
fun removeKeyboardShowListener() {
|
||||
keyboardShowListener = null
|
||||
}
|
||||
|
||||
// Call this in "onResume()" of a fragment
|
||||
fun startDetection() {
|
||||
rootView = fragmentContainer.getChildAt(0)
|
||||
|
||||
contentContainer.viewTreeObserver.addOnGlobalLayoutListener(listener)
|
||||
}
|
||||
|
||||
// Call this in "onPause()" of a fragment
|
||||
fun stopDetection() {
|
||||
contentContainer.viewTreeObserver.removeOnGlobalLayoutListener(listener)
|
||||
}
|
||||
|
||||
//https://stackoverflow.com/questions/2150078/how-to-check-visibility-of-software-keyboard-in-android?rq=1
|
||||
private fun possiblyResizeChildOfContent() {
|
||||
val rect = Rect()
|
||||
rootView.getWindowVisibleDisplayFrame(rect)
|
||||
val height = rootView.context.resources.displayMetrics.heightPixels
|
||||
val diff = height - rect.bottom
|
||||
|
||||
if (diff > rootView.rootView.height / SCREEN_TO_KEYBOARD_HEIGHT_RATIO) {
|
||||
keyboardShowListener?.invoke(diff)
|
||||
} else {
|
||||
keyboardHideListener?.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
package ru.touchin.roboswag.components.navigation.keyboard_resizeable
|
||||
|
||||
import android.os.Build
|
||||
import android.os.Parcelable
|
||||
import androidx.annotation.LayoutRes
|
||||
import ru.touchin.roboswag.components.navigation.activities.BaseActivity
|
||||
import ru.touchin.roboswag.components.navigation.activities.OnBackPressedListener
|
||||
import ru.touchin.roboswag.components.navigation.viewcontrollers.ViewController
|
||||
import ru.touchin.roboswag.components.utils.UiUtils
|
||||
|
||||
abstract class KeyboardResizeableViewController<TActivity : BaseActivity, TState : Parcelable>(
|
||||
@LayoutRes layoutRes: Int,
|
||||
creationContext: CreationContext
|
||||
) : ViewController<TActivity, TState>(
|
||||
creationContext,
|
||||
layoutRes
|
||||
) {
|
||||
init {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {
|
||||
creationContext.container?.requestApplyInsets()
|
||||
}
|
||||
}
|
||||
|
||||
private var keyboardIsVisible: Boolean = false
|
||||
|
||||
private val keyboardHideListener = OnBackPressedListener {
|
||||
if (keyboardIsVisible) {
|
||||
UiUtils.OfViews.hideSoftInput(activity)
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private var isHideKeyboardOnBackEnabled = false
|
||||
|
||||
protected open fun onKeyboardShow(diff: Int = 0) {}
|
||||
|
||||
protected open fun onKeyboardHide() {}
|
||||
|
||||
protected fun hideKeyboardOnBackPressed() {
|
||||
isHideKeyboardOnBackEnabled = true
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
if (isHideKeyboardOnBackEnabled) activity.addOnBackPressedListener(keyboardHideListener)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
if (isHideKeyboardOnBackEnabled) activity.removeOnBackPressedListener(keyboardHideListener)
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
activity.keyboardBehaviorDetector?.apply {
|
||||
setKeyboardHideListener {
|
||||
if (keyboardIsVisible) {
|
||||
onKeyboardHide()
|
||||
}
|
||||
keyboardIsVisible = false
|
||||
}
|
||||
setKeyboardShowListener { diff ->
|
||||
if (!keyboardIsVisible) {
|
||||
onKeyboardShow(diff)
|
||||
}
|
||||
keyboardIsVisible = true
|
||||
}
|
||||
startDetection()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
activity.keyboardBehaviorDetector?.apply {
|
||||
removeKeyboardHideListener()
|
||||
removeKeyboardShowListener()
|
||||
stopDetection()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package ru.touchin.roboswag.components.navigation.viewcontrollers
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
|
||||
object EmptyState : Parcelable {
|
||||
|
||||
override fun writeToParcel(parcel: Parcel, flags: Int) = Unit
|
||||
|
||||
override fun describeContents() = 0
|
||||
|
||||
@JvmField
|
||||
val CREATOR = object : Parcelable.Creator<EmptyState> {
|
||||
override fun createFromParcel(parcel: Parcel) = EmptyState
|
||||
|
||||
override fun newArray(size: Int): Array<EmptyState?> = arrayOfNulls(size)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package ru.touchin.roboswag.components.navigation.viewcontrollers
|
||||
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleObserver
|
||||
import androidx.lifecycle.OnLifecycleEvent
|
||||
import ru.touchin.roboswag.core.log.Lc
|
||||
import ru.touchin.roboswag.core.log.LcGroup
|
||||
|
||||
class LifecycleLoggingObserver : LifecycleObserver {
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_ANY)
|
||||
fun onAnyLifecycleEvent() {
|
||||
LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(this))
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,309 @@
|
|||
/*
|
||||
* Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package ru.touchin.roboswag.components.navigation.viewcontrollers
|
||||
|
||||
import android.animation.Animator
|
||||
import android.content.Intent
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.animation.Animation
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.ColorRes
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.fragment.app.FragmentTransaction
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import ru.touchin.roboswag.components.navigation.fragments.ViewControllerFragment
|
||||
import ru.touchin.roboswag.components.utils.UiUtils
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 21/10/2015.
|
||||
* Class to control view of specific fragment, activity and application by logic bridge.
|
||||
*
|
||||
* @param <TActivity> Type of activity where such [ViewController] could be;
|
||||
* @param <TState> Type of state;
|
||||
</TState></TActivity> */
|
||||
@Suppress("detekt.TooManyFunctions", "UNCHECKED_CAST")
|
||||
open class ViewController<TActivity : FragmentActivity, TState : Parcelable>(
|
||||
creationContext: CreationContext,
|
||||
@LayoutRes layoutRes: Int
|
||||
) : LifecycleOwner {
|
||||
|
||||
val activity: TActivity = creationContext.activity as TActivity
|
||||
|
||||
val fragment: ViewControllerFragment<out TActivity, out TState> = creationContext.fragment as ViewControllerFragment<out TActivity, out TState>
|
||||
|
||||
val state = fragment.state
|
||||
|
||||
val view: View = creationContext.inflater.inflate(layoutRes, creationContext.container, false)
|
||||
|
||||
init {
|
||||
lifecycle.addObserver(LifecycleLoggingObserver())
|
||||
}
|
||||
|
||||
override fun getLifecycle(): Lifecycle = fragment.viewLifecycleOwner.lifecycle
|
||||
|
||||
/**
|
||||
* Look for a child view with the given id. If this view has the given id, return this view.
|
||||
*
|
||||
* @param id The id to search for;
|
||||
* @return The view that has the given id in the hierarchy.
|
||||
*/
|
||||
fun <T : View> findViewById(@IdRes id: Int): T = view.findViewById(id)
|
||||
|
||||
/**
|
||||
* Return a localized, styled CharSequence from the application's package's
|
||||
* default string table.
|
||||
*
|
||||
* @param resId Resource id for the CharSequence text
|
||||
*/
|
||||
fun getText(@StringRes resId: Int): CharSequence = activity.getText(resId)
|
||||
|
||||
/**
|
||||
* Return a localized string from the application's package's default string table.
|
||||
*
|
||||
* @param resId Resource id for the string
|
||||
*/
|
||||
fun getString(@StringRes resId: Int): String = activity.getString(resId)
|
||||
|
||||
/**
|
||||
* Return a localized formatted string from the application's package's default string table, substituting the format arguments as defined in
|
||||
* [java.util.Formatter] and [java.lang.String.format].
|
||||
*
|
||||
* @param resId Resource id for the format string
|
||||
* @param formatArgs The format arguments that will be used for substitution.
|
||||
*/
|
||||
fun getString(@StringRes resId: Int, vararg formatArgs: Any): String = activity.getString(resId, *formatArgs)
|
||||
|
||||
/**
|
||||
* Return the color value associated with a particular resource ID.
|
||||
* Starting in [android.os.Build.VERSION_CODES.M], the returned
|
||||
* color will be styled for the specified Context's theme.
|
||||
*
|
||||
* @param resId The resource id to search for data;
|
||||
* @return int A single color value in the form 0xAARRGGBB.
|
||||
*/
|
||||
@ColorInt
|
||||
fun getColor(@ColorRes resId: Int): Int = ContextCompat.getColor(activity, resId)
|
||||
|
||||
/**
|
||||
* Returns a color state list associated with a particular resource ID.
|
||||
*
|
||||
*
|
||||
* Starting in [android.os.Build.VERSION_CODES.M], the returned
|
||||
* color state list will be styled for the specified Context's theme.
|
||||
*
|
||||
* @param resId The desired resource identifier, as generated by the aapt
|
||||
* tool. This integer encodes the package, type, and resource
|
||||
* entry. The value 0 is an invalid identifier.
|
||||
* @return A color state list, or `null` if the resource could not be resolved.
|
||||
* @throws android.content.res.Resources.NotFoundException if the given ID
|
||||
* does not exist.
|
||||
*/
|
||||
fun getColorStateList(@ColorRes resId: Int): ColorStateList? = ContextCompat.getColorStateList(activity, resId)
|
||||
|
||||
/**
|
||||
* Returns a drawable object associated with a particular resource ID.
|
||||
* Starting in [android.os.Build.VERSION_CODES.LOLLIPOP], the
|
||||
* returned drawable will be styled for the specified Context's theme.
|
||||
*
|
||||
* @param resId The resource id to search for data;
|
||||
* @return Drawable An object that can be used to draw this resource.
|
||||
*/
|
||||
fun getDrawable(@DrawableRes resId: Int): Drawable? = ContextCompat.getDrawable(activity, resId)
|
||||
|
||||
fun startActivity(intent: Intent) {
|
||||
fragment.startActivity(intent)
|
||||
}
|
||||
|
||||
fun startActivityForResult(intent: Intent, requestCode: Int) {
|
||||
fragment.startActivityForResult(intent, requestCode)
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls when activity configuring ActionBar, Toolbar, Sidebar etc.
|
||||
* If it will be called or not depends on [Fragment.hasOptionsMenu] and [Fragment.isMenuVisible].
|
||||
*
|
||||
* @param menu The options menu in which you place your items;
|
||||
* @param inflater Helper to inflate menu items.
|
||||
*/
|
||||
open fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) = Unit
|
||||
|
||||
/**
|
||||
* Prepare the standard options menu to be displayed. This is
|
||||
* called right before the menu is shown, every time it is shown.
|
||||
* You can use this method to efficiently enable/disable items or otherwise
|
||||
* dynamically modify the contents.
|
||||
*
|
||||
* @param menu The options menu as last shown or first initialized by onCreateOptionsMenu().
|
||||
*
|
||||
* @see [Fragment.hasOptionsMenu]
|
||||
* @see [onCreateOptionsMenu]
|
||||
*/
|
||||
open fun onPrepareOptionsMenu(menu: Menu?) = Unit
|
||||
|
||||
/**
|
||||
* Calls right after construction of [ViewController].
|
||||
* Happens at [ViewControllerFragment.onActivityCreated].
|
||||
*/
|
||||
open fun onCreate() = Unit
|
||||
|
||||
/**
|
||||
* Called when a fragment loads an animation. Note that if
|
||||
* [FragmentTransaction.setCustomAnimations] was called with
|
||||
* [Animator] resources instead of [Animation] resources, `nextAnim`
|
||||
* will be an animator resource.
|
||||
*
|
||||
* @param transit The value set in [FragmentTransaction.setTransition] or 0 if not
|
||||
* set.
|
||||
* @param enter `true` when the fragment is added/attached/shown or `false` when
|
||||
* the fragment is removed/detached/hidden.
|
||||
* @param nextAnim The resource set in
|
||||
* [FragmentTransaction.setCustomAnimations],
|
||||
* [FragmentTransaction.setCustomAnimations], or
|
||||
* 0 if neither was called. The value will depend on the current operation.
|
||||
*/
|
||||
open fun onCreateAnimation(transit: Int, enter: Boolean, nextAnim: Int): Animation? = null
|
||||
|
||||
/**
|
||||
* Called when a fragment loads an animator. This will be called when
|
||||
* [.onCreateAnimation] returns null. Note that if
|
||||
* [FragmentTransaction.setCustomAnimations] was called with
|
||||
* [Animation] resources instead of [Animator] resources, `nextAnim`
|
||||
* will be an animation resource.
|
||||
*
|
||||
* @param transit The value set in [FragmentTransaction.setTransition] or 0 if not
|
||||
* set.
|
||||
* @param enter `true` when the fragment is added/attached/shown or `false` when
|
||||
* the fragment is removed/detached/hidden.
|
||||
* @param nextAnim The resource set in
|
||||
* [FragmentTransaction.setCustomAnimations],
|
||||
* [FragmentTransaction.setCustomAnimations], or
|
||||
* 0 if neither was called. The value will depend on the current operation.
|
||||
*/
|
||||
open fun onCreateAnimator(transit: Int, enter: Boolean, nextAnim: Int): Animator? = null
|
||||
|
||||
/**
|
||||
* Calls when [ViewController] saved state has been restored into the view hierarchy.
|
||||
* Happens at [ViewControllerFragment.onViewStateRestored].
|
||||
*/
|
||||
open fun onViewStateRestored(savedInstanceState: Bundle?) = Unit
|
||||
|
||||
/**
|
||||
* Calls when [ViewController] have started.
|
||||
* Happens at [ViewControllerFragment.onStart].
|
||||
*/
|
||||
open fun onStart() {
|
||||
UiUtils.OfViews.hideSoftInput(view)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when fragment is moved in started state and it's [.getFragment] sets to true.
|
||||
* Usually it is indicating that user can't see fragment on screen and useful to track analytics events.
|
||||
*/
|
||||
open fun onAppear() = Unit
|
||||
|
||||
/**
|
||||
* Calls when [ViewController] have resumed.
|
||||
* Happens at [ViewControllerFragment.onResume].
|
||||
*/
|
||||
open fun onResume() = Unit
|
||||
|
||||
/**
|
||||
* Calls when [ViewController] have goes near out of memory state.
|
||||
* Happens at [ViewControllerFragment.onLowMemory].
|
||||
*/
|
||||
open fun onLowMemory() = Unit
|
||||
|
||||
/**
|
||||
* Calls when [ViewController] have paused.
|
||||
* Happens at [ViewControllerFragment.onPause].
|
||||
*/
|
||||
open fun onPause() = Unit
|
||||
|
||||
/**
|
||||
* Calls when [ViewController] should save it's state.
|
||||
* Happens at [ViewControllerFragment.onSaveInstanceState].
|
||||
* Try not to use such method for saving state but use [ViewControllerFragment.state] from [.getFragment].
|
||||
*/
|
||||
open fun onSaveInstanceState(savedInstanceState: Bundle) = Unit
|
||||
|
||||
/**
|
||||
* Called when fragment is moved in stopped state or it's [.getFragment] sets to false.
|
||||
* Usually it is indicating that user can't see fragment on screen and useful to track analytics events.
|
||||
*/
|
||||
open fun onDisappear() = Unit
|
||||
|
||||
/**
|
||||
* Calls when [ViewController] have stopped.
|
||||
* Happens at [ViewControllerFragment.onStop].
|
||||
*/
|
||||
open fun onStop() = Unit
|
||||
|
||||
/**
|
||||
* Calls when [ViewController] have destroyed.
|
||||
* Happens usually at [ViewControllerFragment.onDestroyView]. In some cases at [ViewControllerFragment.onDestroy].
|
||||
*/
|
||||
open fun onDestroy() = Unit
|
||||
|
||||
/**
|
||||
* Calls when [ViewController] have requested permissions results.
|
||||
* Happens at [ViewControllerFragment.onRequestPermissionsResult] ()}.
|
||||
*/
|
||||
open fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) = Unit
|
||||
|
||||
/**
|
||||
* Callback from parent fragment.
|
||||
*/
|
||||
open fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) = Unit
|
||||
|
||||
/**
|
||||
* Similar to [ViewControllerFragment.onOptionsItemSelected].
|
||||
*
|
||||
* @param item Selected menu item;
|
||||
* @return True if selection processed.
|
||||
*/
|
||||
open fun onOptionsItemSelected(item: MenuItem): Boolean = false
|
||||
|
||||
/**
|
||||
* Helper class to simplify constructor override.
|
||||
*/
|
||||
data class CreationContext(
|
||||
val activity: FragmentActivity,
|
||||
val fragment: ViewControllerFragment<*, *>,
|
||||
val inflater: LayoutInflater,
|
||||
val container: ViewGroup?
|
||||
)
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
* Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
|
||||
*
|
||||
* This file is part of RoboSwag library.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package ru.touchin.roboswag.components.navigation.viewcontrollers
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Parcelable
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.fragment.app.FragmentTransaction
|
||||
|
||||
import ru.touchin.roboswag.components.navigation.FragmentNavigation
|
||||
import ru.touchin.roboswag.components.navigation.fragments.ViewControllerFragment
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 07/03/2016.
|
||||
* Navigation based on [ViewController]s which are creating by [Fragment]s.
|
||||
* So basically it is just [FragmentNavigation] where most of fragments should be inherited from [ViewControllerFragment].
|
||||
*
|
||||
* @param TActivity Type of activity where [ViewController]s should be showed.
|
||||
*/
|
||||
open class ViewControllerNavigation<TActivity : FragmentActivity>(
|
||||
context: Context,
|
||||
fragmentManager: FragmentManager,
|
||||
@IdRes containerViewId: Int,
|
||||
transition: Int = FragmentTransaction.TRANSIT_FRAGMENT_OPEN
|
||||
) : FragmentNavigation(context, fragmentManager, containerViewId, transition) {
|
||||
|
||||
/**
|
||||
* Pushes [ViewController] on top of stack with specific [ViewControllerFragment.getState] and with specific transaction setup.
|
||||
*
|
||||
* @param viewControllerClass Class of [ViewController] to be pushed;
|
||||
* @param state [Parcelable] of [ViewController]'s fragment;
|
||||
* @param addToStack Flag to add this transaction to the back stack;
|
||||
* @param backStackName Name of [Fragment] in back stack;
|
||||
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info;
|
||||
* @param TState Type of state of fragment.
|
||||
*/
|
||||
fun <TState : Parcelable> pushViewController(
|
||||
viewControllerClass: Class<out ViewController<out TActivity, TState>>,
|
||||
state: TState,
|
||||
addToStack: Boolean = true,
|
||||
backStackName: String? = null,
|
||||
transactionSetup: ((FragmentTransaction) -> Unit)? = null
|
||||
) {
|
||||
addToStack(
|
||||
ViewControllerFragment::class.java,
|
||||
null,
|
||||
0,
|
||||
addToStack,
|
||||
ViewControllerFragment.args(viewControllerClass, state),
|
||||
backStackName,
|
||||
transactionSetup
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushes [ViewController] on top of stack with specific [ViewControllerFragment.getState]
|
||||
* and with specific [TTargetFragment] and transaction setup.
|
||||
*
|
||||
* @param viewControllerClass Class of [ViewController] to be pushed;
|
||||
* @param targetFragment [ViewControllerFragment] to be set as target;
|
||||
* @param state [Parcelable] of [ViewController]'s fragment;
|
||||
* @param backStackName Name of [Fragment] in back stack;
|
||||
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info;
|
||||
* @param TState Type of state of fragment;
|
||||
* @param TTargetFragment Type of target fragment.
|
||||
*/
|
||||
fun <TState : Parcelable, TTargetFragment : Fragment> pushViewControllerForResult(
|
||||
viewControllerClass: Class<out ViewController<out TActivity, TState>>,
|
||||
state: TState,
|
||||
targetFragment: TTargetFragment,
|
||||
targetRequestCode: Int,
|
||||
backStackName: String? = null,
|
||||
transactionSetup: ((FragmentTransaction) -> Unit)? = null
|
||||
) {
|
||||
addToStack(
|
||||
ViewControllerFragment::class.java,
|
||||
targetFragment,
|
||||
targetRequestCode,
|
||||
true,
|
||||
ViewControllerFragment.args(viewControllerClass, state),
|
||||
backStackName,
|
||||
transactionSetup
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushes [ViewController] on top of stack with specific [ViewControllerFragment.getState] and with specific transaction setup
|
||||
* and with [.TOP_FRAGMENT_TAG_MARK] tag used for simple up/back navigation.
|
||||
*
|
||||
* @param viewControllerClass Class of [ViewController] to be pushed;
|
||||
* @param state [Parcelable] of [ViewController]'s fragment;
|
||||
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info;
|
||||
* @param TState Type of state of fragment.
|
||||
*/
|
||||
fun <TState : Parcelable> setViewControllerAsTop(
|
||||
viewControllerClass: Class<out ViewController<out TActivity, TState>>,
|
||||
state: TState,
|
||||
addToStack: Boolean = true,
|
||||
transactionSetup: ((FragmentTransaction) -> Unit)? = null
|
||||
) {
|
||||
addToStack(
|
||||
ViewControllerFragment::class.java,
|
||||
null,
|
||||
0,
|
||||
addToStack,
|
||||
ViewControllerFragment.args(viewControllerClass, state),
|
||||
TOP_FRAGMENT_TAG_MARK,
|
||||
transactionSetup
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Pops all [Fragment]s and places new initial [ViewController] on top of stack
|
||||
* with specific [ViewControllerFragment.getState] and specific transaction setup.
|
||||
*
|
||||
* @param viewControllerClass Class of [ViewController] to be pushed;
|
||||
* @param state [Parcelable] of [ViewController]'s fragment;
|
||||
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info;
|
||||
* @param TState Type of state of fragment.
|
||||
*/
|
||||
fun <TState : Parcelable> setInitialViewController(
|
||||
viewControllerClass: Class<out ViewController<out TActivity, TState>>,
|
||||
state: TState,
|
||||
transactionSetup: ((FragmentTransaction) -> Unit)? = null
|
||||
) {
|
||||
beforeSetInitialActions()
|
||||
setViewControllerAsTop(viewControllerClass, state, false, transactionSetup)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -28,12 +28,6 @@ dependencies {
|
|||
|
||||
implementation "androidx.appcompat:appcompat:$versions.appcompat"
|
||||
|
||||
implementation "androidx.fragment:fragment:$versions.fragment"
|
||||
implementation "androidx.fragment:fragment-ktx:$versions.fragment"
|
||||
|
||||
implementation "com.jakewharton:butterknife:$versions.butterknife"
|
||||
kapt "com.jakewharton:butterknife-compiler:$versions.butterknife"
|
||||
|
||||
implementation("com.crashlytics.sdk.android:crashlytics:$versions.crashlytics@aar") {
|
||||
transitive = true
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue