Removed from navigation-new most of copypasta

This commit is contained in:
alex 2019-09-12 14:53:34 +03:00
parent 5a31d2de87
commit e1465ff60e
15 changed files with 7 additions and 1361 deletions

View File

@ -18,6 +18,7 @@ android {
dependencies {
api project(":utils")
api project(":logging")
api project(":navigation")
api project(":api-logansquare")
api 'androidx.multidex:multidex:2.0.1'

View File

@ -29,7 +29,7 @@ import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentTransaction
import ru.touchin.roboswag.core.log.Lc
import ru.touchin.roboswag.components.navigation_new.fragments.BaseFragment
import ru.touchin.roboswag.components.navigation_new.viewcontrollers.EmptyState
import ru.touchin.roboswag.components.navigation.viewcontrollers.EmptyState
import kotlin.reflect.KClass
/**

View File

@ -1,208 +0,0 @@
/*
* Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
*
* This file is part of RoboSwag library.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package ru.touchin.roboswag.components.navigation_new
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_new.activities.BaseActivity
import ru.touchin.roboswag.components.navigation_new.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()
}
}

View File

@ -1,147 +0,0 @@
/*
* 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_new;
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);
}
}
}

View File

@ -1,92 +0,0 @@
/*
* Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
*
* This file is part of RoboSwag library.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package ru.touchin.roboswag.components.navigation_new.activities
import android.content.Intent
import android.os.Bundle
import android.os.PersistableBundle
import androidx.appcompat.app.AppCompatActivity
import ru.touchin.roboswag.components.navigation_new.keyboard_resizeable.KeyboardBehaviorDetector
import ru.touchin.roboswag.components.navigation_new.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()
}
}

View File

@ -1,7 +1,9 @@
package ru.touchin.roboswag.components.navigation_new.activities
import androidx.fragment.app.FragmentTransaction
import ru.touchin.roboswag.components.navigation_new.viewcontrollers.ViewControllerNavigation
import ru.touchin.roboswag.components.navigation.activities.BaseActivity
import ru.touchin.roboswag.components.navigation.viewcontrollers.ViewControllerNavigation
import ru.touchin.roboswag.components.navigation_new.FragmentNavigation
/**
* Created by Daniil Borisovskii on 15/08/2019.
@ -14,7 +16,7 @@ abstract class NavigationActivity : BaseActivity() {
protected open val transition = FragmentTransaction.TRANSIT_NONE
open val navigation by lazy {
ViewControllerNavigation<NavigationActivity>(
FragmentNavigation(
this,
supportFragmentManager,
fragmentContainerViewId,

View File

@ -1,7 +0,0 @@
package ru.touchin.roboswag.components.navigation_new.activities;
public interface OnBackPressedListener {
boolean onBackPressed();
}

View File

@ -18,7 +18,7 @@ import androidx.fragment.app.FragmentActivity
import butterknife.ButterKnife
import butterknife.Unbinder
import ru.touchin.roboswag.components.navigation_new.BuildConfig
import ru.touchin.roboswag.components.navigation_new.viewcontrollers.LifecycleLoggingObserver
import ru.touchin.roboswag.components.navigation.viewcontrollers.LifecycleLoggingObserver
open class BaseFragment<TActivity : FragmentActivity, TState : Parcelable>(@LayoutRes layoutRes: Int) : Fragment(layoutRes) {

View File

@ -1,260 +0,0 @@
/*
* Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
*
* This file is part of RoboSwag library.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package ru.touchin.roboswag.components.navigation_new.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_new.BuildConfig
import ru.touchin.roboswag.components.navigation_new.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?)
}

View File

@ -1,67 +0,0 @@
package ru.touchin.roboswag.components.navigation_new.keyboard_resizeable
import android.graphics.Rect
import android.view.View
import android.view.ViewGroup
import ru.touchin.roboswag.components.navigation_new.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()
}
}
}

View File

@ -1,82 +0,0 @@
package ru.touchin.roboswag.components.navigation_new.keyboard_resizeable
import android.os.Build
import android.os.Parcelable
import androidx.annotation.LayoutRes
import ru.touchin.roboswag.components.navigation_new.activities.BaseActivity
import ru.touchin.roboswag.components.navigation_new.activities.OnBackPressedListener
import ru.touchin.roboswag.components.navigation_new.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()
}
}
}

View File

@ -1,19 +0,0 @@
package ru.touchin.roboswag.components.navigation_new.viewcontrollers
import android.os.Parcel
import android.os.Parcelable
object EmptyState : Parcelable {
override fun writeToParcel(parcel: Parcel, flags: Int) = Unit
override fun describeContents() = 0
@JvmField
val CREATOR = object : Parcelable.Creator<EmptyState> {
override fun createFromParcel(parcel: Parcel) = EmptyState
override fun newArray(size: Int): Array<EmptyState?> = arrayOfNulls(size)
}
}

View File

@ -1,16 +0,0 @@
package ru.touchin.roboswag.components.navigation_new.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))
}
}

View File

@ -1,309 +0,0 @@
/*
* Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
*
* This file is part of RoboSwag library.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package ru.touchin.roboswag.components.navigation_new.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_new.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?
)
}

View File

@ -1,150 +0,0 @@
/*
* Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
*
* This file is part of RoboSwag library.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package ru.touchin.roboswag.components.navigation_new.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_new.FragmentNavigation
import ru.touchin.roboswag.components.navigation_new.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)
}
}