(factoryProducer)
diff --git a/lifecycle-viewcontroller/src/main/java/ru/touchin/lifecycle_viewcontroller/viewmodel/LifecycleViewModelProviders.kt b/lifecycle-viewcontroller/src/main/java/ru/touchin/lifecycle_viewcontroller/viewmodel/LifecycleViewModelProviders.kt
new file mode 100644
index 0000000..f10e7c9
--- /dev/null
+++ b/lifecycle-viewcontroller/src/main/java/ru/touchin/lifecycle_viewcontroller/viewmodel/LifecycleViewModelProviders.kt
@@ -0,0 +1,45 @@
+package ru.touchin.lifecycle_viewcontroller.viewmodel
+
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.ViewModelProviders
+import ru.touchin.lifecycle.viewmodel.BaseLifecycleViewModelProviders
+import ru.touchin.roboswag.navigation_viewcontroller.viewcontrollers.ViewController
+
+object LifecycleViewModelProviders : BaseLifecycleViewModelProviders() {
+
+ /**
+ * Creates a {@link ViewModelProvider}, which retains ViewModels while a scope of given
+ * {@code lifecycleOwner} is alive. More detailed explanation is in {@link ViewModel}.
+ *
+ * It uses the given {@link Factory} to instantiate new ViewModels.
+ *
+ * @param lifecycleOwner a lifecycle owner, in whose scope ViewModels should be retained (ViewController, Fragment, Activity)
+ * @param factory a {@code Factory} to instantiate new ViewModels
+ * @return a ViewModelProvider instance
+ */
+ override fun of(
+ lifecycleOwner: LifecycleOwner,
+ factory: ViewModelProvider.Factory
+ ): ViewModelProvider =
+ when (lifecycleOwner) {
+ is ViewController<*, *> -> ViewModelProviders.of(lifecycleOwner.fragment, factory)
+ else -> super.of(lifecycleOwner, factory)
+ }
+
+ /**
+ * Returns ViewModelProvider.Factory instance from current lifecycleOwner.
+ * Search #ViewModelFactoryProvider are produced according to priorities:
+ * 1. View controller;
+ * 2. Fragment;
+ * 3. Parent fragment recursively;
+ * 4. Activity;
+ * 5. Application.
+ */
+ override fun getViewModelFactory(provider: Any): ViewModelProvider.Factory =
+ when (provider) {
+ is ViewController<*, *> -> getViewModelFactory(provider.fragment)
+ else -> super.getViewModelFactory(provider)
+ }
+
+}
diff --git a/lifecycle/build.gradle b/lifecycle/build.gradle
index 41a0ebb..ce6fc3e 100644
--- a/lifecycle/build.gradle
+++ b/lifecycle/build.gradle
@@ -1,30 +1,38 @@
-apply plugin: 'com.android.library'
-apply plugin: 'kotlin-android'
-
-android {
- compileSdkVersion versions.compileSdk
-
- defaultConfig {
- minSdkVersion 16
- }
-
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- }
-}
+apply from: "../android-configs/lib-config.gradle"
dependencies {
- api project(":navigation")
-
compileOnly "javax.inject:javax.inject:1"
- implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+ implementation "androidx.appcompat:appcompat"
- implementation "androidx.appcompat:appcompat:$versions.appcompat"
+ implementation "androidx.fragment:fragment"
+ implementation "androidx.fragment:fragment-ktx"
- implementation "androidx.fragment:fragment:$versions.fragment"
- implementation "androidx.fragment:fragment-ktx:$versions.fragment"
+ implementation "androidx.lifecycle:lifecycle-extensions"
- implementation "androidx.lifecycle:lifecycle-extensions:$versions.lifecycle"
+ constraints {
+ implementation("androidx.appcompat:appcompat") {
+ version {
+ require '1.0.0'
+ }
+ }
+
+ implementation("androidx.lifecycle:lifecycle-extensions") {
+ version {
+ require '2.1.0'
+ }
+ }
+
+ implementation("androidx.fragment:fragment") {
+ version {
+ require '1.0.0'
+ }
+ }
+
+ implementation("androidx.fragment:fragment-ktx") {
+ version {
+ require '1.1.0'
+ }
+ }
+ }
}
diff --git a/lifecycle/src/main/java/ru/touchin/lifecycle/extensions/ImmutableExt.kt b/lifecycle/src/main/java/ru/touchin/lifecycle/extensions/ImmutableExt.kt
new file mode 100644
index 0000000..a4c1873
--- /dev/null
+++ b/lifecycle/src/main/java/ru/touchin/lifecycle/extensions/ImmutableExt.kt
@@ -0,0 +1,6 @@
+package ru.touchin.lifecycle.extensions
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+
+fun MutableLiveData.toImmutable() = this as LiveData
diff --git a/lifecycle/src/main/java/ru/touchin/lifecycle/viewmodel/BaseLifecycleViewModelProviders.kt b/lifecycle/src/main/java/ru/touchin/lifecycle/viewmodel/BaseLifecycleViewModelProviders.kt
new file mode 100644
index 0000000..7026ddd
--- /dev/null
+++ b/lifecycle/src/main/java/ru/touchin/lifecycle/viewmodel/BaseLifecycleViewModelProviders.kt
@@ -0,0 +1,47 @@
+package ru.touchin.lifecycle.viewmodel
+
+import android.app.Activity
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentActivity
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.ViewModelProvider
+
+abstract class BaseLifecycleViewModelProviders {
+
+ /**
+ * Creates a {@link ViewModelProvider}, which retains ViewModels while a scope of given
+ * {@code lifecycleOwner} is alive. More detailed explanation is in {@link ViewModel}.
+ *
+ * It uses the given {@link Factory} to instantiate new ViewModels.
+ *
+ * @param lifecycleOwner a lifecycle owner, in whose scope ViewModels should be retained (ViewController, Fragment, Activity)
+ * @param factory a {@code Factory} to instantiate new ViewModels
+ * @return a ViewModelProvider instance
+ */
+ open fun of(
+ lifecycleOwner: LifecycleOwner,
+ factory: ViewModelProvider.Factory = getViewModelFactory(lifecycleOwner)
+ ): ViewModelProvider =
+ when (lifecycleOwner) {
+ is Fragment -> ViewModelProvider(lifecycleOwner, factory)
+ is FragmentActivity -> ViewModelProvider(lifecycleOwner, factory)
+ else -> throw IllegalArgumentException("Not supported LifecycleOwner.")
+ }
+
+ /**
+ * Returns ViewModelProvider.Factory instance from current lifecycleOwner.
+ * Search #ViewModelFactoryProvider are produced according to priorities:
+ * 1. Fragment;
+ * 2. Parent fragment recursively;
+ * 3. Activity;
+ * 4. Application.
+ */
+ open fun getViewModelFactory(provider: Any): ViewModelProvider.Factory =
+ when (provider) {
+ is ViewModelFactoryProvider -> provider.viewModelFactory
+ is Fragment -> getViewModelFactory(provider.parentFragment ?: provider.requireActivity())
+ is Activity -> getViewModelFactory(provider.application)
+ else -> throw IllegalArgumentException("View model factory not found.")
+ }
+
+}
diff --git a/lifecycle/src/main/java/ru/touchin/lifecycle/viewmodel/LifecycleViewModelProviders.kt b/lifecycle/src/main/java/ru/touchin/lifecycle/viewmodel/LifecycleViewModelProviders.kt
index ae3c610..438cdc4 100644
--- a/lifecycle/src/main/java/ru/touchin/lifecycle/viewmodel/LifecycleViewModelProviders.kt
+++ b/lifecycle/src/main/java/ru/touchin/lifecycle/viewmodel/LifecycleViewModelProviders.kt
@@ -1,49 +1,3 @@
package ru.touchin.lifecycle.viewmodel
-import android.app.Activity
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.ViewModelProvider
-import androidx.lifecycle.ViewModelProviders
-import androidx.fragment.app.Fragment
-import androidx.fragment.app.FragmentActivity
-import ru.touchin.roboswag.components.navigation.viewcontrollers.ViewController
-
-object LifecycleViewModelProviders {
-
- /**
- * Creates a {@link ViewModelProvider}, which retains ViewModels while a scope of given
- * {@code lifecycleOwner} is alive. More detailed explanation is in {@link ViewModel}.
- *
- * It uses the given {@link Factory} to instantiate new ViewModels.
- *
- * @param lifecycleOwner a lifecycle owner, in whose scope ViewModels should be retained (ViewController, Fragment, Activity)
- * @param factory a {@code Factory} to instantiate new ViewModels
- * @return a ViewModelProvider instance
- */
- fun of(lifecycleOwner: LifecycleOwner, factory: ViewModelProvider.Factory = getViewModelFactory(lifecycleOwner)): ViewModelProvider =
- when (lifecycleOwner) {
- is ViewController<*, *> -> ViewModelProviders.of(lifecycleOwner.fragment, factory)
- is Fragment -> ViewModelProviders.of(lifecycleOwner, factory)
- is FragmentActivity -> ViewModelProviders.of(lifecycleOwner, factory)
- else -> throw IllegalArgumentException("Not supported LifecycleOwner.")
- }
-
- /**
- * Returns ViewModelProvider.Factory instance from current lifecycleOwner.
- * Search #ViewModelFactoryProvider are produced according to priorities:
- * 1. View controller;
- * 2. Fragment;
- * 3. Parent fragment recursively;
- * 4. Activity;
- * 5. Application.
- */
- fun getViewModelFactory(provider: Any): ViewModelProvider.Factory =
- when (provider) {
- is ViewModelFactoryProvider -> provider.viewModelFactory
- is ViewController<*, *> -> getViewModelFactory(provider.fragment)
- is Fragment -> getViewModelFactory(provider.parentFragment ?: provider.requireActivity())
- is Activity -> getViewModelFactory(provider.application)
- else -> throw IllegalArgumentException("View model factory not found.")
- }
-
-}
+object LifecycleViewModelProviders : BaseLifecycleViewModelProviders()
diff --git a/livedata-location/build.gradle b/livedata-location/build.gradle
index 297284c..15afa97 100644
--- a/livedata-location/build.gradle
+++ b/livedata-location/build.gradle
@@ -1,19 +1,15 @@
-apply plugin: 'com.android.library'
-apply plugin: 'kotlin-android'
-
-android {
- compileSdkVersion versions.compileSdk
-
- defaultConfig {
- minSdkVersion 16
- }
-
-}
+apply from: "../android-configs/lib-config.gradle"
dependencies {
api project(":lifecycle")
- implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+ implementation "com.google.android.gms:play-services-location"
- implementation "com.google.android.gms:play-services-location:$versions.location"
+ constraints {
+ implementation("com.google.android.gms:play-services-location") {
+ version {
+ require '17.0.0'
+ }
+ }
+ }
}
diff --git a/logging/build.gradle b/logging/build.gradle
index 21ed245..57d4392 100644
--- a/logging/build.gradle
+++ b/logging/build.gradle
@@ -1,18 +1,21 @@
-apply plugin: 'com.android.library'
-
-android {
- compileSdkVersion versions.compileSdk
-
- defaultConfig {
- minSdkVersion 16
- }
-
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- }
-}
+apply from: "../android-configs/lib-config.gradle"
dependencies {
- implementation "androidx.annotation:annotation:$versions.androidx"
+ implementation "androidx.annotation:annotation"
+
+ implementation "com.google.firebase:firebase-crashlytics"
+
+ constraints {
+ implementation("androidx.annotation:annotation") {
+ version {
+ require '1.0.0'
+ }
+ }
+
+ implementation("com.google.firebase:firebase-crashlytics") {
+ version {
+ require '17.1.0'
+ }
+ }
+ }
}
diff --git a/logging/src/main/java/ru/touchin/roboswag/core/log/LcGroup.java b/logging/src/main/java/ru/touchin/roboswag/core/log/LcGroup.java
index f1ea1b0..6994622 100644
--- a/logging/src/main/java/ru/touchin/roboswag/core/log/LcGroup.java
+++ b/logging/src/main/java/ru/touchin/roboswag/core/log/LcGroup.java
@@ -19,12 +19,11 @@
package ru.touchin.roboswag.core.log;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
import java.text.SimpleDateFormat;
import java.util.Locale;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import ru.touchin.roboswag.core.utils.ShouldNotHappenException;
import ru.touchin.roboswag.core.utils.ThreadLocalValue;
@@ -45,6 +44,10 @@ public class LcGroup {
* Logging group to log UI lifecycle (onCreate, onStart, onResume etc.).
*/
public static final LcGroup UI_LIFECYCLE = new LcGroup("UI_LIFECYCLE");
+ /**
+ * Logging group to log api validation errors.
+ */
+ public static final LcGroup API_VALIDATION = new LcGroup("API_VALIDATION");
private static final ThreadLocalValue DATE_TIME_FORMATTER
= new ThreadLocalValue<>(() -> new SimpleDateFormat("HH:mm:ss.SSS", Locale.getDefault()));
diff --git a/logging/src/main/java/ru/touchin/roboswag/core/utils/CrashlyticsLogProcessor.java b/logging/src/main/java/ru/touchin/roboswag/core/utils/CrashlyticsLogProcessor.java
new file mode 100644
index 0000000..5dd9970
--- /dev/null
+++ b/logging/src/main/java/ru/touchin/roboswag/core/utils/CrashlyticsLogProcessor.java
@@ -0,0 +1,70 @@
+package ru.touchin.roboswag.core.utils;
+
+import android.util.Log;
+
+import com.google.firebase.crashlytics.FirebaseCrashlytics;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+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;
+
+public class CrashlyticsLogProcessor extends LogProcessor {
+
+ @NonNull
+ private final FirebaseCrashlytics crashlytics;
+
+ public CrashlyticsLogProcessor(@NonNull final FirebaseCrashlytics crashlytics) {
+ super(LcLevel.INFO);
+ this.crashlytics = crashlytics;
+ }
+
+ private String getLogMessage(final int priorityLevel, final String tag, final String message) {
+ return "Priority:" + priorityLevel + ' ' + tag + ':' + message;
+ }
+
+ @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.log(getLogMessage(level.getPriority(), tag, message));
+ } else if (!level.lessThan(LcLevel.ASSERT)
+ || (group == LcGroup.API_VALIDATION && level == LcLevel.ERROR)) {
+ Log.e(tag, message);
+ if (throwable != null) {
+ crashlytics.log(getLogMessage(level.getPriority(), tag, message));
+ crashlytics.recordException(throwable);
+ } else {
+ final ShouldNotHappenException exceptionToLog = new ShouldNotHappenException(tag + ':' + message);
+ reduceStackTrace(exceptionToLog);
+ crashlytics.recordException(exceptionToLog);
+ }
+ }
+ }
+
+ private void reduceStackTrace(@NonNull final Throwable throwable) {
+ final StackTraceElement[] stackTrace = throwable.getStackTrace();
+ final List 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);
+ }
+
+}
diff --git a/mvi-arch/.gitignore b/mvi-arch/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/mvi-arch/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/mvi-arch/README.md b/mvi-arch/README.md
new file mode 100644
index 0000000..6cd5bb4
--- /dev/null
+++ b/mvi-arch/README.md
@@ -0,0 +1,4 @@
+mvi_arch
+====
+
+TODO: rewrite dependencies
diff --git a/mvi-arch/build.gradle b/mvi-arch/build.gradle
new file mode 100644
index 0000000..6059357
--- /dev/null
+++ b/mvi-arch/build.gradle
@@ -0,0 +1,97 @@
+apply from: "../android-configs/lib-config.gradle"
+apply plugin: 'kotlin-kapt'
+
+dependencies {
+ implementation project(":navigation-base")
+ implementation project(":lifecycle")
+ implementation project(":kotlin-extensions")
+ implementation project(":logging")
+
+ implementation("androidx.core:core-ktx")
+ implementation("androidx.appcompat:appcompat")
+
+ implementation("androidx.fragment:fragment")
+ implementation("androidx.fragment:fragment-ktx")
+
+ implementation("androidx.lifecycle:lifecycle-extensions")
+ implementation("androidx.lifecycle:lifecycle-viewmodel-ktx")
+ implementation("androidx.lifecycle:lifecycle-livedata-ktx")
+
+ implementation("com.google.dagger:dagger")
+ kapt("com.google.dagger:dagger-compiler")
+ implementation("com.github.valeryponomarenko.componentsmanager:androidx")
+
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core")
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android")
+
+ implementation("com.tylerthrailkill.helpers:pretty-print:2.0.2")
+
+ def fragmentVersion = "1.2.1"
+ def lifecycleVersion = "2.2.0"
+ def coroutinesVersion = "1.3.7"
+ def daggerVersion = "2.27"
+
+ constraints {
+ implementation("androidx.core:core-ktx") {
+ version {
+ require("1.2.0")
+ }
+ }
+ implementation("androidx.appcompat:appcompat") {
+ version {
+ require("1.0.2")
+ }
+ }
+ implementation("androidx.fragment:fragment") {
+ version {
+ require(fragmentVersion)
+ }
+ }
+ implementation("androidx.fragment:fragment-ktx") {
+ version {
+ require(fragmentVersion)
+ }
+ }
+ implementation("androidx.lifecycle:lifecycle-extensions") {
+ version {
+ require(lifecycleVersion)
+ }
+ }
+ implementation("androidx.lifecycle:lifecycle-viewmodel-ktx") {
+ version {
+ require(lifecycleVersion)
+ }
+ }
+ implementation("androidx.lifecycle:lifecycle-livedata-ktx") {
+ version {
+ require(lifecycleVersion)
+ }
+ }
+ implementation("com.google.dagger:dagger") {
+ version {
+ require(daggerVersion)
+ }
+ }
+ kapt("com.google.dagger:dagger-compiler") {
+ version {
+ require(daggerVersion)
+ }
+ }
+ implementation("com.github.valeryponomarenko.componentsmanager:androidx") {
+ version {
+ require("2.1.0")
+ }
+ }
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core") {
+ version {
+ require(coroutinesVersion)
+ }
+ }
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android") {
+ version {
+ require(coroutinesVersion)
+ }
+ }
+ }
+
+}
diff --git a/mvi-arch/src/main/AndroidManifest.xml b/mvi-arch/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..25831d9
--- /dev/null
+++ b/mvi-arch/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+
diff --git a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/MviFragment.kt b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/MviFragment.kt
new file mode 100644
index 0000000..80e7758
--- /dev/null
+++ b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/MviFragment.kt
@@ -0,0 +1,163 @@
+package ru.touchin.roboswag.mvi_arch.core
+
+import android.os.Bundle
+import android.os.Parcelable
+import android.view.View
+import androidx.activity.OnBackPressedCallback
+import androidx.annotation.CallSuper
+import androidx.annotation.LayoutRes
+import androidx.annotation.MainThread
+import androidx.core.os.bundleOf
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentActivity
+import androidx.lifecycle.Observer
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import ru.touchin.extensions.setOnRippleClickListener
+import ru.touchin.roboswag.mvi_arch.di.ViewModelAssistedFactory
+import ru.touchin.roboswag.mvi_arch.di.ViewModelFactory
+import ru.touchin.roboswag.mvi_arch.marker.ViewAction
+import ru.touchin.roboswag.mvi_arch.marker.ViewState
+import ru.touchin.roboswag.navigation_base.fragments.BaseFragment
+import ru.touchin.roboswag.navigation_base.fragments.EmptyState
+import javax.inject.Inject
+
+/**
+ * Base [Fragment] to use in MVI architecture.
+ *
+ * @param NavArgs Type of arguments class of this screen.
+ * It must implement [NavArgs] interface provided by navigation library that is a part of Google Jetpack.
+ * An instance of this class is generated by [SafeArgs](https://developer.android.com/guide/navigation/navigation-pass-data#Safe-args)
+ * plugin according to related configuration file in navigation resource folder of your project.
+ *
+ * @param State Type of view state class of this screen.
+ * It must implement [ViewState] interface. Usually it's a data class that presents full state of current screen's view.
+ * @see [ViewState] for more information.
+ *
+ * @param Action Type of view actions class of this screen.
+ * It must implement [Action] interface. Usually it's a sealed class that contains classes and objects representing
+ * view actions of this view, e.g. button clicks, text changes, etc.
+ * @see [Action] for more information.
+ *
+ * @param VM Type of view model class of this screen.
+ * It must extends [MviViewModel] class with the same params.
+ * @see [MviViewModel] for more information.
+ *
+ * @author Created by Max Bachinsky and Ivan Vlasov at Touch Instinct.
+ */
+abstract class MviFragment(
+ @LayoutRes layout: Int,
+ navArgs: NavArgs = EmptyState as NavArgs
+) : BaseFragment(layout)
+ where NavArgs : Parcelable,
+ State : ViewState,
+ Action : ViewAction,
+ VM : MviViewModel {
+
+ companion object {
+ const val INIT_ARGS_KEY = "INIT_ARGS"
+ }
+
+ /**
+ * Use [viewModel] extension to get an instance of your view model class.
+ */
+ protected abstract val viewModel: VM
+
+ /**
+ * Used for smooth view model injection to this class.
+ */
+ @Inject
+ lateinit var viewModelMap: MutableMap, ViewModelAssistedFactory>
+
+ init {
+ arguments?.putParcelable(INIT_ARGS_KEY, navArgs) ?: let {
+ arguments = bundleOf(INIT_ARGS_KEY to navArgs)
+ }
+ }
+
+ @CallSuper
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ viewModel.state.observe(viewLifecycleOwner, Observer(this::renderState))
+ }
+
+ /**
+ * Use this method to subscribe on view state changes.
+ *
+ * You should render view state here.
+ *
+ * Must not be called before [onAttach] and after [onDetach].
+ */
+ protected open fun renderState(viewState: State) {}
+
+ /**
+ * Use this method to dispatch view actions to view model.
+ */
+ protected fun dispatchAction(actionProvider: () -> Action) {
+ viewModel.dispatchAction(actionProvider.invoke())
+ }
+
+ /**
+ * Use this method to dispatch view actions to view model.
+ */
+ protected fun dispatchAction(action: Action) {
+ viewModel.dispatchAction(action)
+ }
+
+ protected fun addOnBackPressedCallback(actionProvider: () -> Action) {
+ addOnBackPressedCallback(actionProvider.invoke())
+ }
+
+ protected fun addOnBackPressedCallback(action: Action) {
+ requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, object : OnBackPressedCallback(true) {
+ override fun handleOnBackPressed() {
+ dispatchAction(action)
+ }
+ })
+ }
+
+ /**
+ * Lazily provides view model of this screen with transmitted arguments if exist.
+ *
+ * Value of this lazily providing must not be accessed before [onAttach] and after [onDetach].
+ */
+ @MainThread
+ protected inline fun viewModel(): Lazy =
+ lazy {
+ val fragmentArguments = arguments ?: bundleOf()
+
+ ViewModelProvider(
+ viewModelStore,
+ ViewModelFactory(viewModelMap, this, fragmentArguments)
+ ).get(ViewModel::class.java)
+ }
+
+ /**
+ * Simple extension for dispatching view events to view model with on click.
+ */
+ protected fun View.dispatchActionOnClick(actionProvider: () -> Action) {
+ setOnClickListener { dispatchAction(actionProvider) }
+ }
+
+ /**
+ * Simple extension for dispatching view events to view model with on click.
+ */
+ protected fun View.dispatchActionOnClick(action: Action) {
+ setOnClickListener { dispatchAction(action) }
+ }
+
+ /**
+ * Simple extension for dispatching view events to view model with on ripple click.
+ */
+ protected fun View.dispatchActionOnRippleClick(actionProvider: () -> Action) {
+ setOnRippleClickListener { dispatchAction(actionProvider) }
+ }
+
+ /**
+ * Simple extension for dispatching view events to view model with on ripple click.
+ */
+ protected fun View.dispatchActionOnRippleClick(action: Action) {
+ setOnRippleClickListener { dispatchAction(action) }
+ }
+
+}
diff --git a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/MviStoreViewModel.kt b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/MviStoreViewModel.kt
new file mode 100644
index 0000000..811a8cd
--- /dev/null
+++ b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/MviStoreViewModel.kt
@@ -0,0 +1,82 @@
+package ru.touchin.roboswag.mvi_arch.core
+
+import android.os.Parcelable
+import androidx.annotation.CallSuper
+import androidx.lifecycle.SavedStateHandle
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import ru.touchin.roboswag.mvi_arch.marker.SideEffect
+import ru.touchin.roboswag.mvi_arch.marker.StateChange
+import ru.touchin.roboswag.mvi_arch.marker.ViewAction
+import ru.touchin.roboswag.mvi_arch.marker.ViewState
+
+/**
+ * Base [ViewModel] to use in MVI architecture.
+ *
+ * @param NavArgs Type of arguments class of this screen.
+ * It must implement [NavArgs] interface provided by navigation library that is a part of Google Jetpack.
+ * An instance of this class is generated by [SafeArgs](https://developer.android.com/guide/navigation/navigation-pass-data#Safe-args)
+ * plugin according to related configuration file in navigation resource folder of your project.
+ *
+ * @param State Type of view state class of this screen.
+ * It must implement [ViewState] interface. Usually it's a data class that presents full state of current screen's view.
+ * @see [ViewState] for more information.
+ *
+ * @param Action Type of view actions class of this screen.
+ * It must implement [Action] interface. Usually it's a sealed class that contains classes and objects representing
+ * view actions of this view, e.g. button clicks, text changes, etc.
+ * @see [Action] for more information.
+ *
+ * @author Created by Max Bachinsky and Ivan Vlasov at Touch Instinct.
+ */
+
+abstract class MviStoreViewModel(
+ initialState: State,
+ handle: SavedStateHandle
+) : MviViewModel(initialState, handle) {
+
+ private lateinit var store: ChildStore<*, *, *>
+
+ protected fun connectStore(
+ store: Store,
+ mapAction: (Action) -> IChange?,
+ mapState: (IState) -> State
+ ) {
+ this.store = ChildStore(store, mapAction)
+
+ store
+ .observeState()
+ .map { mapState(it) }
+ .onEach { this._state.postValue(it) }
+ .launchIn(viewModelScope)
+
+ }
+
+ @CallSuper
+ override fun dispatchAction(action: Action) {
+ store.dispatchAction(action)
+ }
+
+ @CallSuper
+ override fun onCleared() {
+ super.onCleared()
+ store.onCleared()
+ }
+
+ private inner class ChildStore(
+ val store: Store,
+ val changeMapper: (Action) -> IChange?
+ ) {
+ fun onCleared() {
+ store.onCleared()
+ }
+
+ fun dispatchAction(action: Action) {
+ changeMapper(action)?.let(store::changeState)
+ }
+ }
+
+}
diff --git a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/MviViewModel.kt b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/MviViewModel.kt
new file mode 100644
index 0000000..0654c0e
--- /dev/null
+++ b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/MviViewModel.kt
@@ -0,0 +1,76 @@
+package ru.touchin.roboswag.mvi_arch.core
+
+import android.os.Parcelable
+import androidx.annotation.CallSuper
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.Observer
+import androidx.lifecycle.SavedStateHandle
+import androidx.lifecycle.Transformations
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import kotlinx.coroutines.launch
+import ru.touchin.mvi_arch.BuildConfig
+import ru.touchin.roboswag.mvi_arch.marker.ViewAction
+import ru.touchin.roboswag.mvi_arch.marker.ViewState
+import ru.touchin.roboswag.mvi_arch.mediator.LoggingMediator
+import ru.touchin.roboswag.mvi_arch.mediator.MediatorStore
+
+/**
+ * Base [ViewModel] to use in MVI architecture.
+ *
+ * @param NavArgs Type of arguments class of this screen.
+ * It must implement [NavArgs] interface provided by navigation library that is a part of Google Jetpack.
+ * An instance of this class is generated by [SafeArgs](https://developer.android.com/guide/navigation/navigation-pass-data#Safe-args)
+ * plugin according to related configuration file in navigation resource folder of your project.
+ *
+ * @param State Type of view state class of this screen.
+ * It must implement [ViewState] interface. Usually it's a data class that presents full state of current screen's view.
+ * @see [ViewState] for more information.
+ *
+ * @param Action Type of view actions class of this screen.
+ * It must implement [Action] interface. Usually it's a sealed class that contains classes and objects representing
+ * view actions of this view, e.g. button clicks, text changes, etc.
+ * @see [Action] for more information.
+ *
+ * @author Created by Max Bachinsky and Ivan Vlasov at Touch Instinct.
+ */
+
+abstract class MviViewModel(
+ private val initialState: State,
+ protected val handle: SavedStateHandle
+) : ViewModel() {
+
+ private val mediatorStore = MediatorStore(
+ listOfNotNull(
+ LoggingMediator(this::class.simpleName!!).takeIf { BuildConfig.DEBUG }
+ )
+ )
+
+ protected val navArgs: NavArgs = handle.get(MviFragment.INIT_ARGS_KEY) ?: throw IllegalStateException("Nav args mustn't be null")
+
+ protected val _state = MutableLiveData(initialState)
+ internal val state = Transformations.distinctUntilChanged(_state)
+
+ protected val currentState: State
+ get() = _state.value ?: initialState
+
+ private val stateMediatorObserver = Observer(mediatorStore::onNewState)
+
+ init {
+ viewModelScope.launch {
+ state.observeForever(stateMediatorObserver)
+ }
+ }
+
+ @CallSuper
+ open fun dispatchAction(action: Action) {
+ mediatorStore.onAction(action)
+ }
+
+ @CallSuper
+ override fun onCleared() {
+ super.onCleared()
+ state.removeObserver(stateMediatorObserver)
+ }
+
+}
diff --git a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/Store.kt b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/Store.kt
new file mode 100644
index 0000000..5655d64
--- /dev/null
+++ b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/Store.kt
@@ -0,0 +1,119 @@
+package ru.touchin.roboswag.mvi_arch.core
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.consumeAsFlow
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
+import ru.touchin.mvi_arch.BuildConfig
+import ru.touchin.roboswag.mvi_arch.marker.SideEffect
+import ru.touchin.roboswag.mvi_arch.marker.StateChange
+import ru.touchin.roboswag.mvi_arch.marker.ViewState
+import ru.touchin.roboswag.mvi_arch.mediator.LoggingMediator
+import ru.touchin.roboswag.mvi_arch.mediator.MediatorStore
+
+abstract class Store(
+ initialState: State
+) {
+
+ protected val currentState: State
+ get() = state.value
+
+ private val storeScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
+
+ private val effects = Channel(Channel.UNLIMITED)
+ private val state = MutableStateFlow(initialState)
+
+ private val childStores: MutableList> = mutableListOf()
+
+ private val mediatorStore = MediatorStore(
+ listOfNotNull(
+ LoggingMediator(this::class.simpleName!!).takeIf { BuildConfig.DEBUG }
+ )
+ )
+
+ init {
+ storeScope.launch {
+ effects
+ .consumeAsFlow()
+ .filterNotNull()
+ .handleSideEffect()
+ .collect { newChange -> changeState(newChange) }
+ }
+ }
+
+ fun changeState(change: Change) {
+ mediatorStore.onStateChange(change)
+
+ val (newState, newEffect) = reduce(currentState, change)
+
+ if (currentState != newState) {
+ state.value = newState
+ mediatorStore.onNewState(newState)
+ }
+
+ childStores.forEach { childStore ->
+ childStore.change(change)
+ }
+
+ newEffect?.let {
+ effects.offer(it)
+ mediatorStore.onEffect(it)
+ }
+
+ }
+
+ fun observeState(): Flow = state
+
+ fun onCleared() {
+ storeScope.coroutineContext.cancel()
+ childStores.forEach(Store.ChildStore<*, *, *>::onCleared)
+ }
+
+ fun State.only(): Pair = this to null
+
+ fun Effect.only(): Pair = currentState to this
+
+ fun same(): Pair = currentState.only()
+
+ protected fun addChildStore(
+ store: Store,
+ changeMapper: (Change) -> ChildChange?,
+ stateMapper: (ChildState) -> State
+ ) {
+ childStores.add(ChildStore(store, changeMapper))
+
+ store
+ .observeState()
+ .onEach { state.value = stateMapper(it) }
+ .launchIn(storeScope)
+
+ }
+
+ protected open fun Flow.handleSideEffect(): Flow = emptyFlow()
+
+ protected abstract fun reduce(currentState: State, change: Change): Pair
+
+ private inner class ChildStore(
+ val store: Store,
+ val changeMapper: (Change) -> ChildChange?
+ ) {
+ fun onCleared() {
+ store.onCleared()
+ }
+
+ fun change(change: Change) {
+ changeMapper(change)?.let(store::changeState)
+ }
+ }
+
+}
diff --git a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/di/ViewModelAssistedFactory.kt b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/di/ViewModelAssistedFactory.kt
new file mode 100644
index 0000000..d958ce0
--- /dev/null
+++ b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/di/ViewModelAssistedFactory.kt
@@ -0,0 +1,8 @@
+package ru.touchin.roboswag.mvi_arch.di
+
+import androidx.lifecycle.SavedStateHandle
+import androidx.lifecycle.ViewModel
+
+interface ViewModelAssistedFactory {
+ fun create(handle: SavedStateHandle): VM
+}
diff --git a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/di/ViewModelFactory.kt b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/di/ViewModelFactory.kt
new file mode 100644
index 0000000..5ae4d27
--- /dev/null
+++ b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/di/ViewModelFactory.kt
@@ -0,0 +1,19 @@
+package ru.touchin.roboswag.mvi_arch.di
+
+import android.os.Bundle
+import androidx.lifecycle.AbstractSavedStateViewModelFactory
+import androidx.lifecycle.SavedStateHandle
+import androidx.lifecycle.ViewModel
+import androidx.savedstate.SavedStateRegistryOwner
+
+class ViewModelFactory(
+ private val viewModelMap: MutableMap, ViewModelAssistedFactory>,
+ owner: SavedStateRegistryOwner,
+ arguments: Bundle
+) : AbstractSavedStateViewModelFactory(owner, arguments) {
+
+ @Suppress("UNCHECKED_CAST")
+ override fun create(key: String, modelClass: Class, handle: SavedStateHandle): T {
+ return viewModelMap[modelClass]?.create(handle) as? T ?: throw IllegalStateException("Unknown ViewModel class")
+ }
+}
diff --git a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/di/ViewModelKey.kt b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/di/ViewModelKey.kt
new file mode 100644
index 0000000..672c4d0
--- /dev/null
+++ b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/di/ViewModelKey.kt
@@ -0,0 +1,9 @@
+package ru.touchin.roboswag.mvi_arch.di
+
+import androidx.lifecycle.ViewModel
+import dagger.MapKey
+import kotlin.reflect.KClass
+
+@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
+@MapKey
+annotation class ViewModelKey(val value: KClass)
diff --git a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/marker/SideEffect.kt b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/marker/SideEffect.kt
new file mode 100644
index 0000000..7fea61c
--- /dev/null
+++ b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/marker/SideEffect.kt
@@ -0,0 +1,3 @@
+package ru.touchin.roboswag.mvi_arch.marker
+
+interface SideEffect
diff --git a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/marker/StateChange.kt b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/marker/StateChange.kt
new file mode 100644
index 0000000..1d5da27
--- /dev/null
+++ b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/marker/StateChange.kt
@@ -0,0 +1,3 @@
+package ru.touchin.roboswag.mvi_arch.marker
+
+interface StateChange
diff --git a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/marker/ViewAction.kt b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/marker/ViewAction.kt
new file mode 100644
index 0000000..6e6d7cd
--- /dev/null
+++ b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/marker/ViewAction.kt
@@ -0,0 +1,22 @@
+package ru.touchin.roboswag.mvi_arch.marker
+
+/**
+ * This interface should be implemented to create your own view actions and use it with [MviFragment] and [MviViewModel].
+ *
+ * Usually it's sealed class with nested classes and objects representing view actions.
+ *
+ * Quite common cases:
+ * 1. View contains simple button:
+ * object OnButtonClicked : YourViewAction()
+ *
+ * 2. View contains button with parameter:
+ * data class OnButtonWithParamClicked(val param: Param): YourViewAction()
+ *
+ * 3. View contains text input field:
+ * data class OnInputChanged(val input: String): YourViewAction()
+ *
+ * Exemplars of this classes used to generate new [ViewState] in [MviViewModel].
+ *
+ * @author Created by Max Bachinsky and Ivan Vlasov at Touch Instinct.
+ */
+interface ViewAction
diff --git a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/marker/ViewState.kt b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/marker/ViewState.kt
new file mode 100644
index 0000000..3d065e6
--- /dev/null
+++ b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/marker/ViewState.kt
@@ -0,0 +1,12 @@
+package ru.touchin.roboswag.mvi_arch.marker
+
+/**
+ * This interface should be implemented to create your own view state and use it with [MviFragment] and [MviViewModel].
+ *
+ * Usually it's a data class that presents full state of view.
+ *
+ * You should not use mutable values here. All values should be immutable.
+ *
+ * @author Created by Max Bachinsky and Ivan Vlasov at Touch Instinct.
+ */
+interface ViewState
diff --git a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/mediator/LoggingMediator.kt b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/mediator/LoggingMediator.kt
new file mode 100644
index 0000000..99ffdaf
--- /dev/null
+++ b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/mediator/LoggingMediator.kt
@@ -0,0 +1,49 @@
+package ru.touchin.roboswag.mvi_arch.mediator
+
+import com.tylerthrailkill.helpers.prettyprint.pp
+import ru.touchin.roboswag.core.log.Lc
+import ru.touchin.roboswag.mvi_arch.marker.SideEffect
+import ru.touchin.roboswag.mvi_arch.marker.StateChange
+import ru.touchin.roboswag.mvi_arch.marker.ViewAction
+import ru.touchin.roboswag.mvi_arch.marker.ViewState
+
+class LoggingMediator(private val objectName: String) : Mediator {
+ override fun onEffect(effect: SideEffect) {
+ logObject(
+ prefix = "New Effect:\n",
+ obj = effect
+ )
+ }
+
+ override fun onAction(action: ViewAction) {
+ logObject(
+ prefix = "New Action:\n",
+ obj = action
+ )
+ }
+
+ override fun onNewState(state: ViewState) {
+ logObject(
+ prefix = "New State:\n",
+ obj = state
+ )
+ }
+
+ override fun onStateChange(change: StateChange) {
+ logObject(
+ prefix = "New State change:\n",
+ obj = change
+ )
+ }
+
+ private fun logObject(
+ prefix: String,
+ obj: T
+ ) {
+ val builder = StringBuilder()
+ pp(obj = obj, writeTo = builder)
+
+ val prettyOutput = builder.toString()
+ Lc.d("$objectName: $prefix$prettyOutput\n")
+ }
+}
diff --git a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/mediator/Mediator.kt b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/mediator/Mediator.kt
new file mode 100644
index 0000000..699b3ba
--- /dev/null
+++ b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/mediator/Mediator.kt
@@ -0,0 +1,18 @@
+package ru.touchin.roboswag.mvi_arch.mediator
+
+import ru.touchin.roboswag.mvi_arch.marker.SideEffect
+import ru.touchin.roboswag.mvi_arch.marker.StateChange
+import ru.touchin.roboswag.mvi_arch.marker.ViewAction
+import ru.touchin.roboswag.mvi_arch.marker.ViewState
+
+interface Mediator {
+
+ fun onEffect(effect: SideEffect)
+
+ fun onAction(action: ViewAction)
+
+ fun onNewState(state: ViewState)
+
+ fun onStateChange(change: StateChange)
+
+}
diff --git a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/mediator/MediatorStore.kt b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/mediator/MediatorStore.kt
new file mode 100644
index 0000000..70ede8d
--- /dev/null
+++ b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/mediator/MediatorStore.kt
@@ -0,0 +1,25 @@
+package ru.touchin.roboswag.mvi_arch.mediator
+
+import ru.touchin.roboswag.mvi_arch.marker.SideEffect
+import ru.touchin.roboswag.mvi_arch.marker.StateChange
+import ru.touchin.roboswag.mvi_arch.marker.ViewAction
+import ru.touchin.roboswag.mvi_arch.marker.ViewState
+
+class MediatorStore(private val mediators: List) : Mediator {
+
+ override fun onAction(action: ViewAction) {
+ mediators.forEach { it.onAction(action) }
+ }
+
+ override fun onEffect(effect: SideEffect) {
+ mediators.forEach { it.onEffect(effect) }
+ }
+
+ override fun onNewState(state: ViewState) {
+ mediators.forEach { it.onNewState(state) }
+ }
+
+ override fun onStateChange(change: StateChange) {
+ mediators.forEach { it.onStateChange(change) }
+ }
+}
diff --git a/mvi-arch/src/main/res/values/strings.xml b/mvi-arch/src/main/res/values/strings.xml
new file mode 100644
index 0000000..3c24c32
--- /dev/null
+++ b/mvi-arch/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ mvi-arch
+
diff --git a/navigation-base/.gitignore b/navigation-base/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/navigation-base/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/navigation-new/README.md b/navigation-base/README.md
similarity index 100%
rename from navigation-new/README.md
rename to navigation-base/README.md
diff --git a/navigation-base/build.gradle b/navigation-base/build.gradle
new file mode 100644
index 0000000..ef03f06
--- /dev/null
+++ b/navigation-base/build.gradle
@@ -0,0 +1,77 @@
+apply from: "../android-configs/lib-config.gradle"
+
+apply plugin: 'kotlin-kapt'
+
+android {
+ buildFeatures.viewBinding = true
+}
+
+dependencies {
+ implementation project(":utils")
+ implementation project(":logging")
+
+ implementation "com.google.dagger:dagger"
+
+ implementation 'net.danlew:android.joda'
+
+ implementation "androidx.appcompat:appcompat"
+
+ implementation "androidx.fragment:fragment"
+ implementation "androidx.fragment:fragment-ktx"
+
+ implementation "androidx.lifecycle:lifecycle-common-java8"
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx"
+
+ implementation "com.google.firebase:firebase-crashlytics"
+
+ constraints {
+
+ implementation("com.google.dagger:dagger") {
+ version {
+ require '2.10'
+ }
+ }
+
+ implementation("net.danlew:android.joda") {
+ version {
+ require '2.10.0'
+ }
+ }
+
+ implementation("androidx.appcompat:appcompat") {
+ version {
+ require '1.0.0'
+ }
+ }
+
+ implementation("androidx.fragment:fragment") {
+ version {
+ require '1.1.0'
+ }
+ }
+
+ implementation("androidx.fragment:fragment-ktx") {
+ version {
+ require '1.1.0'
+ }
+ }
+
+ implementation("com.google.firebase:firebase-crashlytics") {
+ version {
+ require '17.1.0'
+ }
+ }
+
+ implementation("androidx.lifecycle:lifecycle-common-java8") {
+ version {
+ require '2.2.0'
+ }
+ }
+
+ implementation("androidx.lifecycle:lifecycle-livedata-ktx") {
+ version {
+ require '2.2.0'
+ }
+ }
+ }
+}
diff --git a/navigation-base/src/main/AndroidManifest.xml b/navigation-base/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..b05e20d
--- /dev/null
+++ b/navigation-base/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+
diff --git a/navigation-new/src/main/java/ru/touchin/roboswag/components/navigation_new/FragmentNavigation.kt b/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/FragmentNavigation.kt
similarity index 89%
rename from navigation-new/src/main/java/ru/touchin/roboswag/components/navigation_new/FragmentNavigation.kt
rename to navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/FragmentNavigation.kt
index 8ef56cf..5a1df6c 100644
--- a/navigation-new/src/main/java/ru/touchin/roboswag/components/navigation_new/FragmentNavigation.kt
+++ b/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/FragmentNavigation.kt
@@ -17,7 +17,7 @@
*
*/
-package ru.touchin.roboswag.components.navigation_new
+package ru.touchin.roboswag.navigation_base
import android.content.Context
import android.os.Bundle
@@ -28,8 +28,7 @@ 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_new.fragments.BaseFragment
-import ru.touchin.roboswag.components.navigation.viewcontrollers.EmptyState
+import ru.touchin.roboswag.navigation_base.fragments.StatefulFragment
import kotlin.reflect.KClass
/**
@@ -96,6 +95,7 @@ open class FragmentNavigation(
addToStack: Boolean,
args: Bundle?,
backStackName: String?,
+ tag: String? = null,
transactionSetup: ((FragmentTransaction) -> Unit)?
) {
if (fragmentManager.isDestroyed) {
@@ -108,7 +108,7 @@ open class FragmentNavigation(
val fragmentTransaction = fragmentManager.beginTransaction()
transactionSetup?.invoke(fragmentTransaction)
- fragmentTransaction.replace(containerViewId, fragment, null)
+ fragmentTransaction.replace(containerViewId, fragment, tag)
if (addToStack) {
fragmentTransaction
.addToBackStack(backStackName)
@@ -155,9 +155,10 @@ open class FragmentNavigation(
args: Bundle? = null,
addToStack: Boolean = true,
backStackName: String? = null,
+ tag: String? = null,
transactionSetup: ((FragmentTransaction) -> Unit)? = null
) {
- addToStack(fragmentClass, null, 0, addToStack, args, backStackName, transactionSetup)
+ addToStack(fragmentClass, null, 0, addToStack, args, backStackName, tag, transactionSetup)
}
/**
@@ -167,14 +168,15 @@ open class FragmentNavigation(
* @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 push(
- fragmentClass: KClass>,
- state: T? = null,
+ fun push(
+ fragmentClass: KClass>,
+ state: T,
addToStack: Boolean = true,
backStackName: String? = null,
+ tag: String? = null,
transactionSetup: ((FragmentTransaction) -> Unit)? = null
) {
- push(fragmentClass.java, BaseFragment.args(state ?: EmptyState), addToStack, backStackName, transactionSetup)
+ push(fragmentClass.java, StatefulFragment.args(state), addToStack, backStackName, tag, transactionSetup)
}
/**
@@ -190,6 +192,7 @@ open class FragmentNavigation(
targetFragment: Fragment,
targetRequestCode: Int,
args: Bundle? = null,
+ tag: String? = null,
transactionSetup: ((FragmentTransaction) -> Unit)? = null
) {
addToStack(
@@ -199,6 +202,7 @@ open class FragmentNavigation(
true,
args,
null,
+ tag,
transactionSetup
)
}
@@ -211,14 +215,15 @@ open class FragmentNavigation(
* @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 pushForResult(
- fragmentClass: KClass>,
+ fun pushForResult(
+ fragmentClass: KClass>,
targetFragment: Fragment,
targetRequestCode: Int,
- state: T? = null,
+ state: T,
+ tag: String? = null,
transactionSetup: ((FragmentTransaction) -> Unit)? = null
) {
- pushForResult(fragmentClass.java, targetFragment, targetRequestCode, BaseFragment.args(state ?: EmptyState), transactionSetup)
+ pushForResult(fragmentClass.java, targetFragment, targetRequestCode, StatefulFragment.args(state), tag, transactionSetup)
}
/**
@@ -233,9 +238,10 @@ open class FragmentNavigation(
fragmentClass: Class,
args: Bundle? = null,
addToStack: Boolean = true,
+ tag: String? = null,
transactionSetup: ((FragmentTransaction) -> Unit)? = null
) {
- addToStack(fragmentClass, null, 0, addToStack, args, TOP_FRAGMENT_TAG_MARK, transactionSetup)
+ addToStack(fragmentClass, null, 0, addToStack, args, TOP_FRAGMENT_TAG_MARK, tag, transactionSetup)
}
/**
@@ -249,10 +255,11 @@ open class FragmentNavigation(
fun setInitial(
fragmentClass: Class,
args: Bundle? = null,
+ tag: String? = null,
transactionSetup: ((FragmentTransaction) -> Unit)? = null
) {
beforeSetInitialActions()
- setAsTop(fragmentClass, args, false, transactionSetup)
+ setAsTop(fragmentClass, args, false, tag, transactionSetup)
}
/**
@@ -262,13 +269,14 @@ open class FragmentNavigation(
* @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 setInitial(
- fragmentClass: KClass>,
- state: T? = null,
+ fun setInitial(
+ fragmentClass: KClass>,
+ state: T,
+ tag: String? = null,
transactionSetup: ((FragmentTransaction) -> Unit)? = null
) {
beforeSetInitialActions()
- setAsTop(fragmentClass.java, BaseFragment.args(state ?: EmptyState), false, transactionSetup)
+ setAsTop(fragmentClass.java, StatefulFragment.args(state), false, tag, transactionSetup)
}
/**
diff --git a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/SimpleActionBarDrawerToggle.kt b/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/SimpleActionBarDrawerToggle.kt
similarity index 95%
rename from navigation/src/main/java/ru/touchin/roboswag/components/navigation/SimpleActionBarDrawerToggle.kt
rename to navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/SimpleActionBarDrawerToggle.kt
index 79d417c..cf2e2b3 100644
--- a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/SimpleActionBarDrawerToggle.kt
+++ b/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/SimpleActionBarDrawerToggle.kt
@@ -17,7 +17,7 @@
*
*/
-package ru.touchin.roboswag.components.navigation
+package ru.touchin.roboswag.navigation_base
import android.animation.ValueAnimator
import android.view.MenuItem
@@ -25,9 +25,9 @@ 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
+import ru.touchin.roboswag.components.utils.hideSoftInput
+import ru.touchin.roboswag.navigation_base.activities.BaseActivity
+import ru.touchin.roboswag.navigation_base.activities.OnBackPressedListener
/**
* Created by Gavriil Sitnikov on 11/03/16.
@@ -174,7 +174,7 @@ class SimpleActionBarDrawerToggle(
}
override fun onDrawerOpened(drawerView: View) {
- UiUtils.OfViews.hideSoftInput(activity)
+ activity.hideSoftInput()
if (isInvalidateOptionsMenuSupported) {
activity.invalidateOptionsMenu()
}
diff --git a/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/TouchinApp.java b/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/TouchinApp.java
new file mode 100644
index 0000000..60cabfd
--- /dev/null
+++ b/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/TouchinApp.java
@@ -0,0 +1,76 @@
+/*
+ * 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.navigation_base;
+
+import android.app.Application;
+import android.os.StrictMode;
+
+import com.google.firebase.crashlytics.FirebaseCrashlytics;
+
+import net.danlew.android.joda.JodaTimeAndroid;
+
+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.utils.CrashlyticsLogProcessor;
+
+/**
+ * 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
+ 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 FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
+ crashlytics.setCrashlyticsCollectionEnabled(true);
+ 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"
+ + "com.google.firebase:firebase-crashlytics\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());
+ }
+
+}
diff --git a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.kt b/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/activities/BaseActivity.kt
similarity index 76%
rename from navigation/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.kt
rename to navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/activities/BaseActivity.kt
index 8fced4c..be2244e 100644
--- a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.kt
+++ b/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/activities/BaseActivity.kt
@@ -17,16 +17,19 @@
*
*/
-package ru.touchin.roboswag.components.navigation.activities
+package ru.touchin.roboswag.navigation_base.activities
+import android.content.Context
import android.content.Intent
+import android.content.res.Configuration
import android.os.Bundle
import android.os.PersistableBundle
+import android.view.WindowManager
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
+import ru.touchin.roboswag.navigation_base.fragments.LifecycleLoggingObserver
+import ru.touchin.roboswag.navigation_base.keyboard_resizeable.KeyboardBehaviorDetector
/**
* Created by Gavriil Sitnikov on 08/03/2016.
@@ -36,7 +39,9 @@ abstract class BaseActivity : AppCompatActivity() {
private val onBackPressedListeners = ArrayList()
- open val keyboardBehaviorDetector: KeyboardBehaviorDetector? = null
+ var keyboardBehaviorDetector: KeyboardBehaviorDetector? = null
+
+ open val freezeFontScaleFactor: Boolean = true
init {
lifecycle.addObserver(LifecycleLoggingObserver(this))
@@ -44,6 +49,7 @@ abstract class BaseActivity : AppCompatActivity() {
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.
@@ -51,6 +57,10 @@ abstract class BaseActivity : AppCompatActivity() {
Lc.e("Finishing activity as it is launcher but not root")
finish()
}
+
+ if (freezeFontScaleFactor) {
+ adjustFontScale(resources.configuration)
+ }
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
@@ -89,4 +99,12 @@ abstract class BaseActivity : AppCompatActivity() {
super.onBackPressed()
}
+ private fun adjustFontScale(configuration: Configuration) {
+ configuration.fontScale = 1f
+ val metrics = resources.displayMetrics
+ (getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay.getMetrics(metrics)
+ metrics.scaledDensity = configuration.fontScale * metrics.density
+ baseContext.resources.updateConfiguration(configuration, metrics)
+ }
+
}
diff --git a/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/activities/NavigationActivity.kt b/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/activities/NavigationActivity.kt
new file mode 100644
index 0000000..ed45972
--- /dev/null
+++ b/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/activities/NavigationActivity.kt
@@ -0,0 +1,14 @@
+package ru.touchin.roboswag.navigation_base.activities
+
+import androidx.fragment.app.FragmentTransaction
+import ru.touchin.roboswag.navigation_base.FragmentNavigation
+
+abstract class NavigationActivity : BaseActivity() {
+
+ protected abstract val fragmentContainerViewId: Int
+
+ protected open val transition = FragmentTransaction.TRANSIT_NONE
+
+ abstract val navigation: TNavigation
+
+}
diff --git a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/activities/OnBackPressedListener.java b/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/activities/OnBackPressedListener.java
similarity index 54%
rename from navigation/src/main/java/ru/touchin/roboswag/components/navigation/activities/OnBackPressedListener.java
rename to navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/activities/OnBackPressedListener.java
index de5d318..f1f05c6 100644
--- a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/activities/OnBackPressedListener.java
+++ b/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/activities/OnBackPressedListener.java
@@ -1,4 +1,4 @@
-package ru.touchin.roboswag.components.navigation.activities;
+package ru.touchin.roboswag.navigation_base.activities;
public interface OnBackPressedListener {
diff --git a/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/extensions/Parcelable.kt b/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/extensions/Parcelable.kt
new file mode 100644
index 0000000..2ec8844
--- /dev/null
+++ b/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/extensions/Parcelable.kt
@@ -0,0 +1,50 @@
+package ru.touchin.roboswag.navigation_base.extensions
+
+import android.annotation.SuppressLint
+import android.os.Parcel
+import android.os.Parcelable
+import ru.touchin.roboswag.navigation_base.fragments.EmptyState
+
+// This method used to check unique state of each fragment.
+// If two fragments share same class for state, you should not pass state instance of current fragment to the one you transition to
+@SuppressLint("Recycle")
+fun Parcelable.reserialize(): T {
+ var parcel = Parcel.obtain()
+
+ parcel.writeParcelable(this, 0)
+
+ val serializableBytes = parcel.marshall()
+
+ parcel.recycle()
+
+ parcel = Parcel.obtain().apply {
+ unmarshall(serializableBytes, 0, serializableBytes.size)
+ setDataPosition(0)
+ }
+
+ val result = parcel.readParcelable(Thread.currentThread().contextClassLoader)
+ ?: throw IllegalStateException("It must not be null")
+
+ parcel.recycle()
+
+ return result
+}
+
+@SuppressLint("Recycle")
+fun Parcelable.copy(): Parcelable =
+ if (this is EmptyState) {
+ EmptyState
+ } else {
+ val parcel = Parcel.obtain()
+
+ parcel.writeParcelable(this, 0)
+ parcel.setDataPosition(0)
+
+ val result = parcel.readParcelable(
+ javaClass.classLoader ?: Thread.currentThread().contextClassLoader
+ ) ?: throw IllegalStateException("Failed to copy tab state")
+
+ parcel.recycle()
+
+ result
+ }
diff --git a/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/fragments/BaseFragment.kt b/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/fragments/BaseFragment.kt
new file mode 100644
index 0000000..b38027f
--- /dev/null
+++ b/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/fragments/BaseFragment.kt
@@ -0,0 +1,53 @@
+package ru.touchin.roboswag.navigation_base.fragments
+
+import android.content.Context
+import android.content.res.ColorStateList
+import android.graphics.drawable.Drawable
+import android.os.Bundle
+import android.view.View
+import androidx.annotation.ColorInt
+import androidx.annotation.ColorRes
+import androidx.annotation.DrawableRes
+import androidx.annotation.IdRes
+import androidx.annotation.LayoutRes
+import androidx.core.content.ContextCompat
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentActivity
+
+open class BaseFragment : Fragment {
+
+ constructor() : super()
+
+ constructor(@LayoutRes layoutRes: Int) : super(layoutRes)
+
+ protected val view: View
+ @JvmName("requireViewKtx") get() = requireView()
+
+ protected val activity: TActivity
+ @JvmName("requireActivityKtx") get() = requireActivity() as TActivity
+
+ protected val context: Context
+ @JvmName("requireContextKtx") get() = requireContext()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ setHasOptionsMenu(true)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ lifecycle.addObserver(LifecycleLoggingObserver(this))
+ }
+
+ fun findViewById(@IdRes id: Int): T = view.findViewById(id)
+
+ @ColorInt
+ fun getColor(@ColorRes resId: Int): Int = ContextCompat.getColor(requireContext(), resId)
+
+ fun getColorStateList(@ColorRes resId: Int): ColorStateList? = ContextCompat.getColorStateList(context, resId)
+
+ fun getDrawable(@DrawableRes resId: Int): Drawable? = ContextCompat.getDrawable(context, resId)
+
+}
diff --git a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/EmptyState.kt b/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/fragments/EmptyState.kt
similarity index 86%
rename from navigation/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/EmptyState.kt
rename to navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/fragments/EmptyState.kt
index 12424bc..0afa2dc 100644
--- a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/EmptyState.kt
+++ b/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/fragments/EmptyState.kt
@@ -1,4 +1,4 @@
-package ru.touchin.roboswag.components.navigation.viewcontrollers
+package ru.touchin.roboswag.navigation_base.fragments
import android.os.Parcel
import android.os.Parcelable
diff --git a/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/fragments/FragmentViewBindingDelegate.kt b/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/fragments/FragmentViewBindingDelegate.kt
new file mode 100644
index 0000000..6d7bee4
--- /dev/null
+++ b/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/fragments/FragmentViewBindingDelegate.kt
@@ -0,0 +1,49 @@
+package ru.touchin.roboswag.navigation_base.fragments
+
+import android.view.View
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.DefaultLifecycleObserver
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.observe
+import androidx.viewbinding.ViewBinding
+import kotlin.properties.ReadOnlyProperty
+import kotlin.reflect.KProperty
+
+class FragmentViewBindingDelegate(
+ val fragment: Fragment,
+ val viewBindingFactory: (View) -> T
+) : ReadOnlyProperty {
+ private var binding: T? = null
+
+ init {
+ fragment.lifecycle.addObserver(object : DefaultLifecycleObserver {
+ override fun onCreate(owner: LifecycleOwner) {
+ fragment.viewLifecycleOwnerLiveData.observe(fragment) { viewLifecycleOwner ->
+ viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {
+ override fun onDestroy(owner: LifecycleOwner) {
+ binding = null
+ }
+ })
+ }
+ }
+ })
+ }
+
+ override fun getValue(thisRef: Fragment, property: KProperty<*>): T {
+ val binding = binding
+ if (binding != null) {
+ return binding
+ }
+
+ val lifecycle = fragment.viewLifecycleOwner.lifecycle
+ if (!lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) {
+ throw IllegalStateException("Should not attempt to get bindings when Fragment views are destroyed.")
+ }
+
+ return viewBindingFactory(thisRef.requireView()).also { this.binding = it }
+ }
+}
+
+fun Fragment.viewBinding(viewBindingFactory: (View) -> T) =
+ FragmentViewBindingDelegate(this, viewBindingFactory)
diff --git a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/LifecycleLoggingObserver.kt b/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/fragments/LifecycleLoggingObserver.kt
similarity index 95%
rename from navigation/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/LifecycleLoggingObserver.kt
rename to navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/fragments/LifecycleLoggingObserver.kt
index 43ef959..a85255b 100644
--- a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/viewcontrollers/LifecycleLoggingObserver.kt
+++ b/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/fragments/LifecycleLoggingObserver.kt
@@ -1,4 +1,4 @@
-package ru.touchin.roboswag.components.navigation.viewcontrollers
+package ru.touchin.roboswag.navigation_base.fragments
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
diff --git a/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/fragments/StatefulFragment.kt b/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/fragments/StatefulFragment.kt
new file mode 100644
index 0000000..4e46f3b
--- /dev/null
+++ b/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/fragments/StatefulFragment.kt
@@ -0,0 +1,41 @@
+package ru.touchin.roboswag.navigation_base.fragments
+
+import android.os.Bundle
+import android.os.Parcelable
+import androidx.annotation.LayoutRes
+import androidx.fragment.app.FragmentActivity
+import ru.touchin.roboswag.navigation_base.BuildConfig
+import ru.touchin.roboswag.navigation_base.extensions.reserialize
+
+open class StatefulFragment(
+ @LayoutRes layoutRes: Int
+) : BaseFragment(layoutRes) {
+
+ companion object {
+ private const val BASE_FRAGMENT_STATE_EXTRA = "BASE_FRAGMENT_STATE_EXTRA"
+
+ fun args(state: Parcelable?): Bundle = Bundle().also { it.putParcelable(BASE_FRAGMENT_STATE_EXTRA, state) }
+
+ }
+
+ protected lateinit var state: TState
+ private set
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ state = savedInstanceState?.getParcelable(BASE_FRAGMENT_STATE_EXTRA)
+ ?: arguments?.getParcelable(BASE_FRAGMENT_STATE_EXTRA)
+ ?: throw IllegalStateException("Fragment state can't be null")
+
+ if (BuildConfig.DEBUG) {
+ state = state.reserialize()
+ }
+ }
+
+ override fun onSaveInstanceState(outState: Bundle) {
+ super.onSaveInstanceState(outState)
+ outState.putParcelable(BASE_FRAGMENT_STATE_EXTRA, state)
+ }
+
+}
diff --git a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/keyboard_resizeable/KeyboardBehaviorDetector.kt b/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/keyboard_resizeable/KeyboardBehaviorDetector.kt
similarity index 90%
rename from navigation/src/main/java/ru/touchin/roboswag/components/navigation/keyboard_resizeable/KeyboardBehaviorDetector.kt
rename to navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/keyboard_resizeable/KeyboardBehaviorDetector.kt
index cc6880d..30774a2 100644
--- a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/keyboard_resizeable/KeyboardBehaviorDetector.kt
+++ b/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/keyboard_resizeable/KeyboardBehaviorDetector.kt
@@ -1,11 +1,11 @@
-package ru.touchin.roboswag.components.navigation.keyboard_resizeable
+package ru.touchin.roboswag.navigation_base.keyboard_resizeable
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
-import ru.touchin.roboswag.components.navigation.activities.BaseActivity
+import ru.touchin.roboswag.navigation_base.activities.BaseActivity
/**
* This detector NOT detect landscape fullscreen keyboard
@@ -43,7 +43,7 @@ class KeyboardBehaviorDetector(
if (startNavigationBarHeight == -1) startNavigationBarHeight = bottomInset
- windowInsets
+ ViewCompat.onApplyWindowInsets(view, windowInsets)
}
ViewCompat.requestApplyInsets(view)
}
diff --git a/navigation-new/src/main/java/ru/touchin/roboswag/components/navigation_new/keyboard_resizeable/KeyboardResizeableFragment.kt b/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/keyboard_resizeable/KeyboardResizeableFragment.kt
similarity index 83%
rename from navigation-new/src/main/java/ru/touchin/roboswag/components/navigation_new/keyboard_resizeable/KeyboardResizeableFragment.kt
rename to navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/keyboard_resizeable/KeyboardResizeableFragment.kt
index 7b03ce2..e43d405 100644
--- a/navigation-new/src/main/java/ru/touchin/roboswag/components/navigation_new/keyboard_resizeable/KeyboardResizeableFragment.kt
+++ b/navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/keyboard_resizeable/KeyboardResizeableFragment.kt
@@ -1,4 +1,4 @@
-package ru.touchin.roboswag.components.navigation_new.keyboard_resizeable
+package ru.touchin.roboswag.navigation_base.keyboard_resizeable
import android.os.Build
import android.os.Bundle
@@ -6,14 +6,14 @@ import android.os.Parcelable
import android.view.View
import androidx.annotation.LayoutRes
import androidx.lifecycle.LifecycleObserver
-import ru.touchin.roboswag.components.navigation.activities.BaseActivity
-import ru.touchin.roboswag.components.navigation.activities.OnBackPressedListener
-import ru.touchin.roboswag.components.navigation_new.fragments.BaseFragment
-import ru.touchin.roboswag.components.utils.UiUtils
+import ru.touchin.roboswag.components.utils.hideSoftInput
+import ru.touchin.roboswag.navigation_base.activities.BaseActivity
+import ru.touchin.roboswag.navigation_base.activities.OnBackPressedListener
+import ru.touchin.roboswag.navigation_base.fragments.StatefulFragment
abstract class KeyboardResizeableFragment(
@LayoutRes layoutRes: Int
-) : BaseFragment(
+) : StatefulFragment(
layoutRes
) {
@@ -21,7 +21,7 @@ abstract class KeyboardResizeableFragment
diff --git a/navigation-cicerone/src/main/java/ru/touchin/roboswag/navigation_cicerone/CiceroneTuner.kt b/navigation-cicerone/src/main/java/ru/touchin/roboswag/navigation_cicerone/CiceroneTuner.kt
new file mode 100644
index 0000000..ba525d3
--- /dev/null
+++ b/navigation-cicerone/src/main/java/ru/touchin/roboswag/navigation_cicerone/CiceroneTuner.kt
@@ -0,0 +1,24 @@
+package ru.touchin.roboswag.navigation_cicerone
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleObserver
+import androidx.lifecycle.OnLifecycleEvent
+import ru.terrakok.cicerone.Navigator
+import ru.terrakok.cicerone.NavigatorHolder
+
+class CiceroneTuner(
+ private val navigatorHolder: NavigatorHolder,
+ private val navigator: Navigator
+) : LifecycleObserver {
+
+ @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
+ fun addNavigator() {
+ navigatorHolder.setNavigator(navigator)
+ }
+
+ @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
+ fun removeNavigator() {
+ navigatorHolder.removeNavigator()
+ }
+
+}
diff --git a/navigation-cicerone/src/main/java/ru/touchin/roboswag/navigation_cicerone/flow/FlowFragment.kt b/navigation-cicerone/src/main/java/ru/touchin/roboswag/navigation_cicerone/flow/FlowFragment.kt
new file mode 100644
index 0000000..08903be
--- /dev/null
+++ b/navigation-cicerone/src/main/java/ru/touchin/roboswag/navigation_cicerone/flow/FlowFragment.kt
@@ -0,0 +1,62 @@
+package ru.touchin.roboswag.navigation_cicerone.flow
+
+import android.os.Bundle
+import android.view.View
+import androidx.activity.OnBackPressedCallback
+import androidx.annotation.IdRes
+import androidx.fragment.app.Fragment
+import ru.terrakok.cicerone.Navigator
+import ru.terrakok.cicerone.NavigatorHolder
+import ru.terrakok.cicerone.Router
+import ru.terrakok.cicerone.android.support.SupportAppNavigator
+import ru.terrakok.cicerone.android.support.SupportAppScreen
+import ru.touchin.mvi_arch.core_nav.R
+import ru.touchin.roboswag.navigation_cicerone.CiceroneTuner
+import javax.inject.Inject
+
+abstract class FlowFragment : Fragment(R.layout.fragment_flow) {
+
+ @Inject
+ @FlowNavigation
+ lateinit var navigatorHolder: NavigatorHolder
+
+ @Inject
+ @FlowNavigation
+ lateinit var router: Router
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ injectComponent()
+ if (childFragmentManager.fragments.isEmpty()) {
+ router.newRootScreen(getLaunchScreen())
+ }
+ }
+
+ abstract fun injectComponent()
+
+ private val exitRouterOnBackPressed = object : OnBackPressedCallback(true) {
+ override fun handleOnBackPressed() {
+ router.exit()
+ }
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ viewLifecycleOwner.lifecycle.addObserver(
+ CiceroneTuner(navigatorHolder = navigatorHolder, navigator = createNavigator())
+ )
+
+ requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, exitRouterOnBackPressed)
+ }
+
+ open fun createNavigator(): Navigator = SupportAppNavigator(
+ requireActivity(),
+ childFragmentManager,
+ getFragmentContainerId()
+ )
+
+ @IdRes
+ protected fun getFragmentContainerId(): Int = R.id.flow_parent
+
+ abstract fun getLaunchScreen(): SupportAppScreen
+}
diff --git a/navigation-cicerone/src/main/java/ru/touchin/roboswag/navigation_cicerone/flow/FlowNavigation.kt b/navigation-cicerone/src/main/java/ru/touchin/roboswag/navigation_cicerone/flow/FlowNavigation.kt
new file mode 100644
index 0000000..fa67fc7
--- /dev/null
+++ b/navigation-cicerone/src/main/java/ru/touchin/roboswag/navigation_cicerone/flow/FlowNavigation.kt
@@ -0,0 +1,6 @@
+package ru.touchin.roboswag.navigation_cicerone.flow
+
+import javax.inject.Qualifier
+
+@Qualifier
+annotation class FlowNavigation
diff --git a/navigation-cicerone/src/main/java/ru/touchin/roboswag/navigation_cicerone/flow/FlowNavigationModule.kt b/navigation-cicerone/src/main/java/ru/touchin/roboswag/navigation_cicerone/flow/FlowNavigationModule.kt
new file mode 100644
index 0000000..2592563
--- /dev/null
+++ b/navigation-cicerone/src/main/java/ru/touchin/roboswag/navigation_cicerone/flow/FlowNavigationModule.kt
@@ -0,0 +1,26 @@
+package ru.touchin.roboswag.navigation_cicerone.flow
+
+import dagger.Module
+import dagger.Provides
+import ru.terrakok.cicerone.Cicerone
+import ru.terrakok.cicerone.NavigatorHolder
+import ru.terrakok.cicerone.Router
+import ru.touchin.roboswag.navigation_base.scopes.FeatureScope
+
+@Module
+class FlowNavigationModule {
+
+ @Provides
+ @FlowNavigation
+ @FeatureScope
+ fun provideCicerone(): Cicerone = Cicerone.create()
+
+ @Provides
+ @FlowNavigation
+ fun provideNavigatorHolder(@FlowNavigation cicerone: Cicerone): NavigatorHolder = cicerone.navigatorHolder
+
+ @Provides
+ @FlowNavigation
+ fun provideRouter(@FlowNavigation cicerone: Cicerone): Router = cicerone.router
+
+}
diff --git a/navigation-cicerone/src/main/res/layout/fragment_flow.xml b/navigation-cicerone/src/main/res/layout/fragment_flow.xml
new file mode 100644
index 0000000..b17fb3e
--- /dev/null
+++ b/navigation-cicerone/src/main/res/layout/fragment_flow.xml
@@ -0,0 +1,5 @@
+
+
diff --git a/navigation-cicerone/src/main/res/values/strings.xml b/navigation-cicerone/src/main/res/values/strings.xml
new file mode 100644
index 0000000..3c24c32
--- /dev/null
+++ b/navigation-cicerone/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ mvi-arch
+
diff --git a/navigation-new/build.gradle b/navigation-new/build.gradle
deleted file mode 100644
index c53d4a0..0000000
--- a/navigation-new/build.gradle
+++ /dev/null
@@ -1,41 +0,0 @@
-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(":navigation")
- 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
- }
-}
diff --git a/navigation-new/src/main/AndroidManifest.xml b/navigation-new/src/main/AndroidManifest.xml
deleted file mode 100644
index 93721cf..0000000
--- a/navigation-new/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/navigation-new/src/main/java/ru/touchin/roboswag/components/navigation_new/activities/NavigationActivity.kt b/navigation-new/src/main/java/ru/touchin/roboswag/components/navigation_new/activities/NavigationActivity.kt
deleted file mode 100644
index 39f6d35..0000000
--- a/navigation-new/src/main/java/ru/touchin/roboswag/components/navigation_new/activities/NavigationActivity.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-package ru.touchin.roboswag.components.navigation_new.activities
-
-import androidx.fragment.app.FragmentTransaction
-import ru.touchin.roboswag.components.navigation.activities.BaseActivity
-import ru.touchin.roboswag.components.navigation_new.FragmentNavigation
-
-/**
- * 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 {
- FragmentNavigation(
- this,
- supportFragmentManager,
- fragmentContainerViewId,
- transition
- )
- }
-
-}
diff --git a/navigation-new/src/main/java/ru/touchin/roboswag/components/navigation_new/fragments/BaseFragment.kt b/navigation-new/src/main/java/ru/touchin/roboswag/components/navigation_new/fragments/BaseFragment.kt
deleted file mode 100644
index 830952a..0000000
--- a/navigation-new/src/main/java/ru/touchin/roboswag/components/navigation_new/fragments/BaseFragment.kt
+++ /dev/null
@@ -1,100 +0,0 @@
-package ru.touchin.roboswag.components.navigation_new.fragments
-
-import android.content.Context
-import android.content.res.ColorStateList
-import android.graphics.drawable.Drawable
-import android.os.Bundle
-import android.os.Parcel
-import android.os.Parcelable
-import android.view.View
-import androidx.annotation.ColorInt
-import androidx.annotation.ColorRes
-import androidx.annotation.DrawableRes
-import androidx.annotation.IdRes
-import androidx.annotation.LayoutRes
-import androidx.core.content.ContextCompat
-import androidx.fragment.app.Fragment
-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.viewcontrollers.LifecycleLoggingObserver
-
-open class BaseFragment(@LayoutRes layoutRes: Int) : Fragment(layoutRes) {
-
- companion object {
- private const val BASE_FRAGMENT_STATE_EXTRA = "BASE_FRAGMENT_STATE_EXTRA"
-
- fun args(state: Parcelable?): Bundle = Bundle().also { it.putParcelable(BASE_FRAGMENT_STATE_EXTRA, state) }
-
- // This method used to check unique state of each fragment.
- // If two fragments share same class for state, you should not pass state instance of current fragment to the one you transition to
- private fun reserialize(parcelable: T): 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(Thread.currentThread().contextClassLoader) ?: throw IllegalStateException("It must not be null")
- parcel.recycle()
- return result
- }
- }
-
- protected val view: View
- @JvmName("requireViewKtx") get() = requireView()
-
- protected val activity: TActivity
- @JvmName("requireActivityKtx") get() = requireActivity() as TActivity
-
- protected val context: Context
- @JvmName("requireContextKtx") get() = requireContext()
-
- protected lateinit var state: TState
- private set
-
- private lateinit var butterKnifeUnbinder: Unbinder
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- setHasOptionsMenu(true)
-
- state = savedInstanceState?.getParcelable(BASE_FRAGMENT_STATE_EXTRA)
- ?: arguments?.getParcelable(BASE_FRAGMENT_STATE_EXTRA)
- ?: throw IllegalStateException("Fragment state can't be null")
-
- if (BuildConfig.DEBUG) {
- state = reserialize(state)
- }
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
-
- lifecycle.addObserver(LifecycleLoggingObserver(this))
- butterKnifeUnbinder = ButterKnife.bind(this, view)
- }
-
- override fun onDestroyView() {
- butterKnifeUnbinder.unbind()
- super.onDestroyView()
- }
-
- override fun onSaveInstanceState(outState: Bundle) {
- super.onSaveInstanceState(outState)
- outState.putParcelable(BASE_FRAGMENT_STATE_EXTRA, state)
- }
-
- fun findViewById(@IdRes id: Int): T = view.findViewById(id)
-
- @ColorInt
- fun getColor(@ColorRes resId: Int): Int = ContextCompat.getColor(requireContext(), resId)
-
- fun getColorStateList(@ColorRes resId: Int): ColorStateList? = ContextCompat.getColorStateList(context, resId)
-
- fun getDrawable(@DrawableRes resId: Int): Drawable? = ContextCompat.getDrawable(context, resId)
-
-}
diff --git a/navigation-viewcontroller/.gitignore b/navigation-viewcontroller/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/navigation-viewcontroller/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/navigation/README.md b/navigation-viewcontroller/README.md
similarity index 100%
rename from navigation/README.md
rename to navigation-viewcontroller/README.md
diff --git a/navigation-viewcontroller/build.gradle b/navigation-viewcontroller/build.gradle
new file mode 100644
index 0000000..95448fe
--- /dev/null
+++ b/navigation-viewcontroller/build.gradle
@@ -0,0 +1,43 @@
+apply from: "../android-configs/lib-config.gradle"
+
+apply plugin: 'kotlin-kapt'
+
+dependencies {
+ implementation project(":utils")
+ implementation project(":logging")
+ implementation project(":navigation-base")
+
+ implementation 'androidx.multidex:multidex'
+
+ implementation 'net.danlew:android.joda'
+
+ implementation "androidx.appcompat:appcompat"
+
+ implementation("com.crashlytics.sdk.android:crashlytics")
+
+ constraints {
+ implementation("androidx.multidex:multidex") {
+ version {
+ require '2.0.1'
+ }
+ }
+
+ implementation("net.danlew:android.joda") {
+ version {
+ require '2.10.2'
+ }
+ }
+
+ implementation("androidx.appcompat:appcompat") {
+ version {
+ require '1.0.2'
+ }
+ }
+
+ implementation("com.crashlytics.sdk.android:crashlytics") {
+ version {
+ require '2.10.0'
+ }
+ }
+ }
+}
diff --git a/navigation-viewcontroller/src/main/AndroidManifest.xml b/navigation-viewcontroller/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..79d1d6e
--- /dev/null
+++ b/navigation-viewcontroller/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+
diff --git a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.kt b/navigation-viewcontroller/src/main/java/ru/touchin/roboswag/navigation_viewcontroller/fragments/ViewControllerFragment.kt
similarity index 97%
rename from navigation/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.kt
rename to navigation-viewcontroller/src/main/java/ru/touchin/roboswag/navigation_viewcontroller/fragments/ViewControllerFragment.kt
index 21a2f33..053f9a9 100644
--- a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.kt
+++ b/navigation-viewcontroller/src/main/java/ru/touchin/roboswag/navigation_viewcontroller/fragments/ViewControllerFragment.kt
@@ -17,7 +17,7 @@
*
*/
-package ru.touchin.roboswag.components.navigation.fragments
+package ru.touchin.roboswag.navigation_viewcontroller.fragments
import android.animation.Animator
import android.annotation.SuppressLint
@@ -35,8 +35,8 @@ 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
+import ru.touchin.roboswag.navigation_viewcontroller.BuildConfig
+import ru.touchin.roboswag.navigation_viewcontroller.viewcontrollers.ViewController
/**
* Created by Gavriil Sitnikov on 21/10/2015.
diff --git a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/keyboard_resizeable/KeyboardResizeableViewController.kt b/navigation-viewcontroller/src/main/java/ru/touchin/roboswag/navigation_viewcontroller/keyboard_resizeable/KeyboardResizeableViewController.kt
similarity index 86%
rename from navigation/src/main/java/ru/touchin/roboswag/components/navigation/keyboard_resizeable/KeyboardResizeableViewController.kt
rename to navigation-viewcontroller/src/main/java/ru/touchin/roboswag/navigation_viewcontroller/keyboard_resizeable/KeyboardResizeableViewController.kt
index 85a4e78..bd7f7c8 100644
--- a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/keyboard_resizeable/KeyboardResizeableViewController.kt
+++ b/navigation-viewcontroller/src/main/java/ru/touchin/roboswag/navigation_viewcontroller/keyboard_resizeable/KeyboardResizeableViewController.kt
@@ -1,13 +1,14 @@
-package ru.touchin.roboswag.components.navigation.keyboard_resizeable
+package ru.touchin.roboswag.navigation_viewcontroller.keyboard_resizeable
import android.os.Build
import android.os.Parcelable
+import androidx.annotation.CallSuper
import androidx.annotation.LayoutRes
import androidx.lifecycle.LifecycleObserver
-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
+import ru.touchin.roboswag.navigation_base.activities.BaseActivity
+import ru.touchin.roboswag.navigation_base.activities.OnBackPressedListener
+import ru.touchin.roboswag.navigation_viewcontroller.viewcontrollers.ViewController
abstract class KeyboardResizeableViewController(
@LayoutRes layoutRes: Int,
@@ -16,6 +17,7 @@ abstract class KeyboardResizeableViewController= Build.VERSION_CODES.KITKAT_WATCH) {
creationContext.container?.requestApplyInsets()
@@ -55,6 +57,7 @@ abstract class KeyboardResizeableViewController(
transactionSetup: ((FragmentTransaction) -> Unit)? = null
) {
addToStack(
- ViewControllerFragment::class.java,
- null,
- 0,
- addToStack,
- ViewControllerFragment.args(viewControllerClass, state),
- backStackName,
- tag,
- transactionSetup
+ fragmentClass = ViewControllerFragment::class.java,
+ targetFragment = null,
+ targetRequestCode = 0,
+ addToStack = addToStack,
+ args = ViewControllerFragment.args(viewControllerClass, state),
+ backStackName = backStackName,
+ tag = tag,
+ transactionSetup = transactionSetup
)
}
@@ -98,14 +97,14 @@ open class ViewControllerNavigation(
transactionSetup: ((FragmentTransaction) -> Unit)? = null
) {
addToStack(
- ViewControllerFragment::class.java,
- targetFragment,
- targetRequestCode,
- true,
- ViewControllerFragment.args(viewControllerClass, state),
- backStackName,
- tag,
- transactionSetup
+ fragmentClass = ViewControllerFragment::class.java,
+ targetFragment = targetFragment,
+ targetRequestCode = targetRequestCode,
+ addToStack = true,
+ args = ViewControllerFragment.args(viewControllerClass, state),
+ backStackName = backStackName,
+ tag = tag,
+ transactionSetup = transactionSetup
)
}
@@ -127,14 +126,14 @@ open class ViewControllerNavigation(
transactionSetup: ((FragmentTransaction) -> Unit)? = null
) {
addToStack(
- ViewControllerFragment::class.java,
- null,
- 0,
- addToStack,
- ViewControllerFragment.args(viewControllerClass, state),
- TOP_FRAGMENT_TAG_MARK,
- tag,
- transactionSetup
+ fragmentClass = ViewControllerFragment::class.java,
+ targetFragment = null,
+ targetRequestCode = 0,
+ addToStack = addToStack,
+ args = ViewControllerFragment.args(viewControllerClass, state),
+ backStackName = TOP_FRAGMENT_TAG_MARK,
+ tag = tag,
+ transactionSetup = transactionSetup
)
}
diff --git a/navigation/build.gradle b/navigation/build.gradle
deleted file mode 100644
index c4fa596..0000000
--- a/navigation/build.gradle
+++ /dev/null
@@ -1,34 +0,0 @@
-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("com.crashlytics.sdk.android:crashlytics:$versions.crashlytics@aar") {
- transitive = true
- }
-}
diff --git a/navigation/src/main/AndroidManifest.xml b/navigation/src/main/AndroidManifest.xml
deleted file mode 100644
index bd2d3ee..0000000
--- a/navigation/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.kt b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.kt
deleted file mode 100644
index 2a0aa6a..0000000
--- a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.kt
+++ /dev/null
@@ -1,244 +0,0 @@
-/*
- * Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
- *
- * This file is part of RoboSwag library.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package ru.touchin.roboswag.components.navigation
-
-import android.content.Context
-import android.os.Bundle
-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
-
-/**
- * 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 tag Optional tag name for the [Fragment];
- * @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info.
- */
- fun addToStack(
- fragmentClass: Class,
- targetFragment: Fragment?,
- targetRequestCode: Int,
- addToStack: Boolean,
- args: Bundle?,
- backStackName: String?,
- tag: 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, tag)
- 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 tag Optional tag name for the [Fragment];
- * @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info.
- */
- fun push(
- fragmentClass: Class,
- args: Bundle? = null,
- addToStack: Boolean = true,
- backStackName: String? = null,
- tag: String? = null,
- transactionSetup: ((FragmentTransaction) -> Unit)? = null
- ) {
- addToStack(fragmentClass, null, 0, addToStack, args, backStackName, tag, 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 tag Optional tag name for the [Fragment];
- * @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info.
- */
- fun pushForResult(
- fragmentClass: Class,
- targetFragment: Fragment,
- targetRequestCode: Int,
- args: Bundle? = null,
- tag: String? = null,
- transactionSetup: ((FragmentTransaction) -> Unit)? = null
- ) {
- addToStack(
- fragmentClass,
- targetFragment,
- targetRequestCode,
- true,
- args,
- null,
- tag,
- 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 tag Optional tag name for the [Fragment];
- * @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info.
- */
- fun setAsTop(
- fragmentClass: Class,
- args: Bundle? = null,
- addToStack: Boolean = true,
- tag: String? = null,
- transactionSetup: ((FragmentTransaction) -> Unit)? = null
- ) {
- addToStack(fragmentClass, null, 0, addToStack, args, TOP_FRAGMENT_TAG_MARK, tag, 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 tag Optional tag name for the [Fragment];
- * @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info.
- */
- @JvmOverloads
- fun setInitial(
- fragmentClass: Class,
- args: Bundle? = null,
- tag: String? = null,
- transactionSetup: ((FragmentTransaction) -> Unit)? = null
- ) {
- beforeSetInitialActions()
- setAsTop(fragmentClass, args, false, tag, 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)
- }
- }
-
-}
diff --git a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/TouchinApp.java b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/TouchinApp.java
deleted file mode 100644
index 49f4047..0000000
--- a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/TouchinApp.java
+++ /dev/null
@@ -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;
-
-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 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);
- }
-
- }
-
-}
diff --git a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/activities/NavigationActivity.kt b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/activities/NavigationActivity.kt
deleted file mode 100644
index 51e7cfc..0000000
--- a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/activities/NavigationActivity.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-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(
- this,
- supportFragmentManager,
- fragmentContainerViewId,
- transition
- )
- }
-
-}
diff --git a/pagination/README.md b/pagination/README.md
new file mode 100644
index 0000000..7ff06b0
--- /dev/null
+++ b/pagination/README.md
@@ -0,0 +1,4 @@
+pagination
+====
+
+TODO: rewrite dependencies
diff --git a/pagination/build.gradle b/pagination/build.gradle
new file mode 100644
index 0000000..4458f16
--- /dev/null
+++ b/pagination/build.gradle
@@ -0,0 +1,46 @@
+apply from: "../android-configs/lib-config.gradle"
+
+dependencies {
+ implementation project(":mvi-arch")
+ implementation project(":recyclerview-adapters")
+ implementation project(":utils")
+ implementation project(":views")
+ implementation project(":kotlin-extensions")
+
+ implementation("com.google.android.material:material")
+ implementation("androidx.swiperefreshlayout:swiperefreshlayout")
+ implementation("androidx.recyclerview:recyclerview")
+
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core")
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android")
+
+ def coroutinesVersion = "1.3.7"
+
+ constraints {
+ implementation("com.google.android.material:material") {
+ version {
+ require("1.2.0")
+ }
+ }
+ implementation("androidx.swiperefreshlayout:swiperefreshlayout") {
+ version {
+ require("1.0.0")
+ }
+ }
+ implementation("androidx.recyclerview:recyclerview") {
+ version {
+ require("1.1.0")
+ }
+ }
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core") {
+ version {
+ require(coroutinesVersion)
+ }
+ }
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android") {
+ version {
+ require(coroutinesVersion)
+ }
+ }
+ }
+}
diff --git a/pagination/src/main/AndroidManifest.xml b/pagination/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..49414e3
--- /dev/null
+++ b/pagination/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+
diff --git a/pagination/src/main/java/ru/touchin/roboswag/pagination/ErrorItem.kt b/pagination/src/main/java/ru/touchin/roboswag/pagination/ErrorItem.kt
new file mode 100644
index 0000000..cb5db29
--- /dev/null
+++ b/pagination/src/main/java/ru/touchin/roboswag/pagination/ErrorItem.kt
@@ -0,0 +1,3 @@
+package ru.touchin.roboswag.pagination
+
+object ErrorItem
diff --git a/pagination/src/main/java/ru/touchin/roboswag/pagination/PaginationAdapter.kt b/pagination/src/main/java/ru/touchin/roboswag/pagination/PaginationAdapter.kt
new file mode 100644
index 0000000..a2f456e
--- /dev/null
+++ b/pagination/src/main/java/ru/touchin/roboswag/pagination/PaginationAdapter.kt
@@ -0,0 +1,48 @@
+package ru.touchin.roboswag.pagination
+
+import android.annotation.SuppressLint
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.RecyclerView
+import ru.touchin.roboswag.recyclerview_adapters.adapters.AdapterDelegate
+import ru.touchin.roboswag.recyclerview_adapters.adapters.DelegationListAdapter
+
+class PaginationAdapter(
+ private val nextPageCallback: () -> Unit,
+ private val itemIdDiff: (old: Any, new: Any) -> Boolean,
+ vararg delegate: AdapterDelegate
+) : DelegationListAdapter(
+ object : DiffUtil.ItemCallback() {
+ override fun areItemsTheSame(oldItem: Any, newItem: Any): Boolean = itemIdDiff(oldItem, newItem)
+
+ @SuppressLint("DiffUtilEquals")
+ override fun areContentsTheSame(oldItem: Any, newItem: Any): Boolean = oldItem == newItem
+ }
+) {
+
+ internal var fullData = false
+
+ init {
+ addDelegate(ProgressAdapterDelegate())
+ delegate.forEach(this::addDelegate)
+ }
+
+ fun update(data: List, updateState: UpdateState) {
+ submitList(data + listOfNotNull(when (updateState) {
+ is UpdateState.Common -> null
+ is UpdateState.Progress -> ProgressItem
+ is UpdateState.Error -> ErrorItem
+ }))
+ }
+
+ override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int, payloads: List) {
+ super.onBindViewHolder(holder, position, payloads)
+ if (!fullData && position >= itemCount - 10) nextPageCallback.invoke()
+ }
+
+ sealed class UpdateState {
+ object Common : UpdateState()
+ object Progress : UpdateState()
+ object Error : UpdateState()
+ }
+
+}
diff --git a/pagination/src/main/java/ru/touchin/roboswag/pagination/PaginationView.kt b/pagination/src/main/java/ru/touchin/roboswag/pagination/PaginationView.kt
new file mode 100644
index 0000000..d6d686a
--- /dev/null
+++ b/pagination/src/main/java/ru/touchin/roboswag/pagination/PaginationView.kt
@@ -0,0 +1,69 @@
+package ru.touchin.roboswag.pagination
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.widget.FrameLayout
+import androidx.recyclerview.widget.StaggeredGridLayoutManager
+import ru.touchin.extensions.setOnRippleClickListener
+import ru.touchin.mvi_arch.core_pagination.databinding.ViewPaginationBinding
+
+// TODO: add an errorview with empty state and error text
+class PaginationView @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null
+) : FrameLayout(context, attrs) {
+
+ private lateinit var refreshCallback: (() -> Unit)
+ private lateinit var adapter: PaginationAdapter
+
+ private val binding = ViewPaginationBinding.inflate(LayoutInflater.from(context), this, true)
+
+ init {
+ with(binding) {
+ swipeToRefresh.setOnRefreshListener { refreshCallback() }
+ elementsRecycler.layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
+ emptyText.setOnRippleClickListener { refreshCallback() }
+ }
+ }
+
+ fun init(refreshCallback: () -> Unit, adapter: PaginationAdapter) {
+ this.refreshCallback = refreshCallback
+ this.adapter = adapter
+ binding.elementsRecycler.adapter = adapter
+ }
+
+ fun render(state: Paginator.State) {
+ with(binding) {
+ swipeToRefresh.isRefreshing = state is Paginator.State.Refresh<*>
+ swipeToRefresh.isEnabled = state !is Paginator.State.EmptyProgress
+ switcher.showChild(
+ when (state) {
+ Paginator.State.Empty, is Paginator.State.EmptyError -> emptyText.id
+ Paginator.State.EmptyProgress -> progressBar.id
+ else -> elementsRecycler.id
+ }
+ )
+ adapter.fullData = state === Paginator.State.Empty || state is Paginator.State.FullData<*>
+
+ when (state) {
+ is Paginator.State.EmptyError, Paginator.State.Empty, Paginator.State.EmptyProgress -> {
+ adapter.update(emptyList(), PaginationAdapter.UpdateState.Common)
+ }
+ is Paginator.State.Data<*> -> {
+ adapter.update(state.data as List, PaginationAdapter.UpdateState.Common)
+ }
+ is Paginator.State.Refresh<*> -> {
+ adapter.update(state.data as List, PaginationAdapter.UpdateState.Common)
+ }
+ is Paginator.State.NewPageProgress<*> -> {
+ adapter.update(state.data as List, PaginationAdapter.UpdateState.Progress)
+ }
+ is Paginator.State.FullData<*> -> {
+ adapter.update(state.data as List, PaginationAdapter.UpdateState.Common)
+ }
+ }
+ }
+ }
+
+}
diff --git a/pagination/src/main/java/ru/touchin/roboswag/pagination/Paginator.kt b/pagination/src/main/java/ru/touchin/roboswag/pagination/Paginator.kt
new file mode 100644
index 0000000..1393444
--- /dev/null
+++ b/pagination/src/main/java/ru/touchin/roboswag/pagination/Paginator.kt
@@ -0,0 +1,156 @@
+package ru.touchin.roboswag.pagination
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flow
+import ru.touchin.roboswag.mvi_arch.core.Store
+import ru.touchin.roboswag.mvi_arch.marker.SideEffect
+import ru.touchin.roboswag.mvi_arch.marker.StateChange
+import ru.touchin.roboswag.mvi_arch.marker.ViewState
+
+class Paginator- (
+ private val errorHandleMod: ErrorHandleMod,
+ private val loadPage: suspend (Int) -> List
- ,
+ private val pageSize: Int
+) : Store(State.Empty) {
+
+ sealed class Change : StateChange {
+ object Refresh : Change()
+ object Restart : Change()
+ object LoadMore : Change()
+ object Reset : Change()
+ data class NewPageLoaded(val pageNumber: Int, val items: List) : Change()
+ data class PageLoadError(val error: Throwable) : Change()
+ }
+
+ sealed class Effect : SideEffect {
+ data class LoadPage(val page: Int = 0) : Effect()
+ }
+
+ sealed class State : ViewState {
+ object Empty : State()
+ object EmptyProgress : State()
+ data class EmptyError(val error: Throwable) : State()
+ data class Data(val pageCount: Int = 0, val data: List, val error: Throwable? = null) : State()
+ data class Refresh(val pageCount: Int, val data: List) : State()
+ data class NewPageProgress(val pageCount: Int, val data: List) : State()
+ data class FullData(val pageCount: Int, val data: List) : State()
+ }
+
+ sealed class Error {
+ object NewPageFailed : Error()
+ object RefreshFailed : Error()
+ }
+
+ sealed class ErrorHandleMod {
+ data class Alert(val showError: (Error) -> Unit) : ErrorHandleMod()
+ object ErrorItem : ErrorHandleMod()
+ }
+
+ override fun reduce(currentState: State, change: Change): Pair = when (change) {
+ Change.Refresh -> {
+ when (currentState) {
+ State.Empty -> State.EmptyProgress
+ is State.EmptyError -> State.EmptyProgress
+ is State.Data<*> -> State.Refresh(currentState.pageCount, currentState.data)
+ is State.NewPageProgress<*> -> State.Refresh(currentState.pageCount, currentState.data)
+ is State.FullData<*> -> State.Refresh(currentState.pageCount, currentState.data)
+ else -> currentState
+ } to Effect.LoadPage()
+ }
+ Change.Restart -> {
+ State.EmptyProgress to Effect.LoadPage()
+ }
+ Change.LoadMore -> {
+ when (currentState) {
+ is State.Data<*> -> {
+ State.NewPageProgress(currentState.pageCount, currentState.data) to Effect.LoadPage(currentState.pageCount + 1)
+ }
+ else -> currentState.only()
+ }
+ }
+ Change.Reset -> {
+ State.Empty.only()
+ }
+ is Change.NewPageLoaded<*> -> {
+ val items = change.items
+ when (currentState) {
+ is State.EmptyProgress -> {
+ when {
+ items.isEmpty() -> State.Empty
+ items.size < pageSize -> State.FullData(0, items)
+ else -> State.Data(0, items)
+ }
+ }
+ is State.Refresh<*> -> {
+ when {
+ items.isEmpty() -> State.Empty
+ items.size < pageSize -> State.FullData(0, items)
+ else -> State.Data(0, items)
+ }
+ }
+ is State.NewPageProgress<*> -> {
+ if (items.size < pageSize) {
+ State.FullData(currentState.pageCount, currentState.data + items)
+ } else {
+ State.Data(currentState.pageCount + 1, currentState.data + items)
+ }
+ }
+ else -> currentState
+ }.only()
+ }
+ is Change.PageLoadError -> {
+ when (currentState) {
+ is State.EmptyProgress -> State.EmptyError(change.error)
+ is State.Refresh<*> -> {
+ when (errorHandleMod) {
+ is ErrorHandleMod.Alert -> {
+ errorHandleMod.showError(Error.RefreshFailed)
+ State.Data(currentState.pageCount, currentState.data)
+ }
+ is ErrorHandleMod.ErrorItem -> {
+ State.Data(
+ pageCount = currentState.pageCount,
+ data = currentState.data,
+ error = change.error
+ )
+ }
+ }
+ }
+ is State.NewPageProgress<*> -> {
+ when (errorHandleMod) {
+ is ErrorHandleMod.Alert -> {
+ errorHandleMod.showError(Error.NewPageFailed)
+ State.Data(currentState.pageCount, currentState.data)
+ }
+ is ErrorHandleMod.ErrorItem -> {
+ State.Data(
+ pageCount = currentState.pageCount,
+ data = currentState.data,
+ error = change.error
+ )
+ }
+ }
+ }
+ else -> currentState
+ }.only()
+ }
+ }
+
+ override fun Flow.handleSideEffect(): Flow = flatMapLatest { effect ->
+ flow {
+ when (effect) {
+ is Effect.LoadPage -> {
+ try {
+ val items = loadPage(effect.page)
+ emit(Change.NewPageLoaded(effect.page, items))
+ } catch (e: Exception) {
+ emit(Change.PageLoadError(e))
+ }
+
+ }
+ }
+ }
+ }
+
+}
diff --git a/pagination/src/main/java/ru/touchin/roboswag/pagination/ProgressAdapterDelegate.kt b/pagination/src/main/java/ru/touchin/roboswag/pagination/ProgressAdapterDelegate.kt
new file mode 100644
index 0000000..f9b148c
--- /dev/null
+++ b/pagination/src/main/java/ru/touchin/roboswag/pagination/ProgressAdapterDelegate.kt
@@ -0,0 +1,21 @@
+package ru.touchin.roboswag.pagination
+
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import ru.touchin.roboswag.recyclerview_adapters.adapters.ItemAdapterDelegate
+import ru.touchin.mvi_arch.core_pagination.R
+import ru.touchin.roboswag.components.utils.UiUtils
+
+class ProgressAdapterDelegate : ItemAdapterDelegate() {
+
+ override fun onCreateViewHolder(parent: ViewGroup): RecyclerView.ViewHolder =
+ object : RecyclerView.ViewHolder(UiUtils.inflate(R.layout.item_progress, parent)) {}
+
+ override fun onBindViewHolder(
+ holder: RecyclerView.ViewHolder,
+ item: ProgressItem,
+ adapterPosition: Int,
+ collectionPosition: Int,
+ payloads: MutableList
+ ) = Unit
+}
diff --git a/pagination/src/main/java/ru/touchin/roboswag/pagination/ProgressItem.kt b/pagination/src/main/java/ru/touchin/roboswag/pagination/ProgressItem.kt
new file mode 100644
index 0000000..ffcc5fb
--- /dev/null
+++ b/pagination/src/main/java/ru/touchin/roboswag/pagination/ProgressItem.kt
@@ -0,0 +1,3 @@
+package ru.touchin.roboswag.pagination
+
+object ProgressItem
diff --git a/pagination/src/main/res/layout/item_progress.xml b/pagination/src/main/res/layout/item_progress.xml
new file mode 100644
index 0000000..726a55b
--- /dev/null
+++ b/pagination/src/main/res/layout/item_progress.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
diff --git a/pagination/src/main/res/layout/view_pagination.xml b/pagination/src/main/res/layout/view_pagination.xml
new file mode 100644
index 0000000..a930418
--- /dev/null
+++ b/pagination/src/main/res/layout/view_pagination.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pagination/src/main/res/values/strings.xml b/pagination/src/main/res/values/strings.xml
new file mode 100644
index 0000000..16f2c81
--- /dev/null
+++ b/pagination/src/main/res/values/strings.xml
@@ -0,0 +1,5 @@
+
+
+ New page load error. Try again.
+ Refresh screen error. Try again.
+
diff --git a/recyclerview-adapters/build.gradle b/recyclerview-adapters/build.gradle
index f4730f9..8b8971b 100644
--- a/recyclerview-adapters/build.gradle
+++ b/recyclerview-adapters/build.gradle
@@ -1,18 +1,22 @@
-apply plugin: 'com.android.library'
-apply plugin: 'kotlin-android'
-
-android {
- compileSdkVersion versions.compileSdk
-
- defaultConfig {
- minSdkVersion 16
- }
-}
+apply from: "../android-configs/lib-config.gradle"
dependencies {
- api project(':kotlin-extensions')
+ implementation project(':kotlin-extensions')
- implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+ implementation "androidx.recyclerview:recyclerview"
- implementation "androidx.recyclerview:recyclerview:$versions.androidx"
+ implementation "androidx.core:core-ktx"
+
+ constraints {
+ implementation("androidx.recyclerview:recyclerview") {
+ version {
+ require '1.0.0'
+ }
+ }
+ implementation("androidx.core:core-ktx") {
+ version {
+ require '1.0.0'
+ }
+ }
+ }
}
diff --git a/recyclerview-adapters/src/main/AndroidManifest.xml b/recyclerview-adapters/src/main/AndroidManifest.xml
index b9c7cac..e662989 100644
--- a/recyclerview-adapters/src/main/AndroidManifest.xml
+++ b/recyclerview-adapters/src/main/AndroidManifest.xml
@@ -1,2 +1,2 @@
+ package="ru.touchin.roboswag.recyclerview_adapters"/>
diff --git a/recyclerview-adapters/src/main/java/ru/touchin/adapters/AdapterDelegate.java b/recyclerview-adapters/src/main/java/ru/touchin/roboswag/recyclerview_adapters/adapters/AdapterDelegate.java
similarity index 98%
rename from recyclerview-adapters/src/main/java/ru/touchin/adapters/AdapterDelegate.java
rename to recyclerview-adapters/src/main/java/ru/touchin/roboswag/recyclerview_adapters/adapters/AdapterDelegate.java
index f9802cf..884e899 100644
--- a/recyclerview-adapters/src/main/java/ru/touchin/adapters/AdapterDelegate.java
+++ b/recyclerview-adapters/src/main/java/ru/touchin/roboswag/recyclerview_adapters/adapters/AdapterDelegate.java
@@ -17,7 +17,7 @@
*
*/
-package ru.touchin.adapters;
+package ru.touchin.roboswag.recyclerview_adapters.adapters;
import androidx.annotation.NonNull;
import androidx.core.view.ViewCompat;
diff --git a/recyclerview-adapters/src/main/java/ru/touchin/adapters/DelegatesManager.kt b/recyclerview-adapters/src/main/java/ru/touchin/roboswag/recyclerview_adapters/adapters/DelegatesManager.kt
similarity index 97%
rename from recyclerview-adapters/src/main/java/ru/touchin/adapters/DelegatesManager.kt
rename to recyclerview-adapters/src/main/java/ru/touchin/roboswag/recyclerview_adapters/adapters/DelegatesManager.kt
index ed73229..834d7ae 100644
--- a/recyclerview-adapters/src/main/java/ru/touchin/adapters/DelegatesManager.kt
+++ b/recyclerview-adapters/src/main/java/ru/touchin/roboswag/recyclerview_adapters/adapters/DelegatesManager.kt
@@ -1,4 +1,4 @@
-package ru.touchin.adapters
+package ru.touchin.roboswag.recyclerview_adapters.adapters
import androidx.recyclerview.widget.RecyclerView
import android.util.SparseArray
diff --git a/recyclerview-adapters/src/main/java/ru/touchin/adapters/DelegationListAdapter.kt b/recyclerview-adapters/src/main/java/ru/touchin/roboswag/recyclerview_adapters/adapters/DelegationListAdapter.kt
similarity index 94%
rename from recyclerview-adapters/src/main/java/ru/touchin/adapters/DelegationListAdapter.kt
rename to recyclerview-adapters/src/main/java/ru/touchin/roboswag/recyclerview_adapters/adapters/DelegationListAdapter.kt
index 8328211..dcc3bc4 100644
--- a/recyclerview-adapters/src/main/java/ru/touchin/adapters/DelegationListAdapter.kt
+++ b/recyclerview-adapters/src/main/java/ru/touchin/roboswag/recyclerview_adapters/adapters/DelegationListAdapter.kt
@@ -1,10 +1,10 @@
-package ru.touchin.adapters
+package ru.touchin.roboswag.recyclerview_adapters.adapters
+import android.view.ViewGroup
import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.AsyncListDiffer
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
-import android.view.ViewGroup
import ru.touchin.extensions.setOnRippleClickListener
/**
@@ -36,7 +36,7 @@ open class DelegationListAdapter(config: AsyncDifferConfig) : Recy
if (collectionPosition in 0 until getList().size) {
if (itemClickListener != null) {
holder.itemView.setOnRippleClickListener {
- itemClickListener?.invoke(getList()[getCollectionPosition(holder.adapterPosition)], holder)
+ getList().getOrNull(getCollectionPosition(holder.adapterPosition))?.let { item -> itemClickListener?.invoke(item, holder) }
}
} else {
holder.itemView.setOnClickListener(null)
diff --git a/recyclerview-adapters/src/main/java/ru/touchin/adapters/ItemAdapterDelegate.java b/recyclerview-adapters/src/main/java/ru/touchin/roboswag/recyclerview_adapters/adapters/ItemAdapterDelegate.java
similarity index 98%
rename from recyclerview-adapters/src/main/java/ru/touchin/adapters/ItemAdapterDelegate.java
rename to recyclerview-adapters/src/main/java/ru/touchin/roboswag/recyclerview_adapters/adapters/ItemAdapterDelegate.java
index 25ec210..a290cb1 100644
--- a/recyclerview-adapters/src/main/java/ru/touchin/adapters/ItemAdapterDelegate.java
+++ b/recyclerview-adapters/src/main/java/ru/touchin/roboswag/recyclerview_adapters/adapters/ItemAdapterDelegate.java
@@ -1,4 +1,4 @@
-package ru.touchin.adapters;
+package ru.touchin.roboswag.recyclerview_adapters.adapters;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
diff --git a/recyclerview-adapters/src/main/java/ru/touchin/adapters/OffsetAdapterUpdateCallback.kt b/recyclerview-adapters/src/main/java/ru/touchin/roboswag/recyclerview_adapters/adapters/OffsetAdapterUpdateCallback.kt
similarity index 93%
rename from recyclerview-adapters/src/main/java/ru/touchin/adapters/OffsetAdapterUpdateCallback.kt
rename to recyclerview-adapters/src/main/java/ru/touchin/roboswag/recyclerview_adapters/adapters/OffsetAdapterUpdateCallback.kt
index cb480f0..a632734 100644
--- a/recyclerview-adapters/src/main/java/ru/touchin/adapters/OffsetAdapterUpdateCallback.kt
+++ b/recyclerview-adapters/src/main/java/ru/touchin/roboswag/recyclerview_adapters/adapters/OffsetAdapterUpdateCallback.kt
@@ -1,4 +1,4 @@
-package ru.touchin.adapters
+package ru.touchin.roboswag.recyclerview_adapters.adapters
import androidx.recyclerview.widget.ListUpdateCallback
import androidx.recyclerview.widget.RecyclerView
diff --git a/recyclerview-adapters/src/main/java/ru/touchin/adapters/PositionAdapterDelegate.java b/recyclerview-adapters/src/main/java/ru/touchin/roboswag/recyclerview_adapters/adapters/PositionAdapterDelegate.java
similarity index 97%
rename from recyclerview-adapters/src/main/java/ru/touchin/adapters/PositionAdapterDelegate.java
rename to recyclerview-adapters/src/main/java/ru/touchin/roboswag/recyclerview_adapters/adapters/PositionAdapterDelegate.java
index 7da5e5c..1c5fe74 100644
--- a/recyclerview-adapters/src/main/java/ru/touchin/adapters/PositionAdapterDelegate.java
+++ b/recyclerview-adapters/src/main/java/ru/touchin/roboswag/recyclerview_adapters/adapters/PositionAdapterDelegate.java
@@ -1,4 +1,4 @@
-package ru.touchin.adapters;
+package ru.touchin.roboswag.recyclerview_adapters.adapters;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
diff --git a/recyclerview-adapters/src/main/java/ru/touchin/adapters/SimpleDataObserver.kt b/recyclerview-adapters/src/main/java/ru/touchin/roboswag/recyclerview_adapters/adapters/SimpleDataObserver.kt
similarity index 92%
rename from recyclerview-adapters/src/main/java/ru/touchin/adapters/SimpleDataObserver.kt
rename to recyclerview-adapters/src/main/java/ru/touchin/roboswag/recyclerview_adapters/adapters/SimpleDataObserver.kt
index 97fc0c6..ddcd2e2 100644
--- a/recyclerview-adapters/src/main/java/ru/touchin/adapters/SimpleDataObserver.kt
+++ b/recyclerview-adapters/src/main/java/ru/touchin/roboswag/recyclerview_adapters/adapters/SimpleDataObserver.kt
@@ -1,4 +1,4 @@
-package ru.touchin.adapters
+package ru.touchin.roboswag.recyclerview_adapters.adapters
import androidx.recyclerview.widget.RecyclerView
diff --git a/recyclerview-calendar/build.gradle b/recyclerview-calendar/build.gradle
index 09ee166..811a091 100644
--- a/recyclerview-calendar/build.gradle
+++ b/recyclerview-calendar/build.gradle
@@ -1,16 +1,22 @@
-apply plugin: 'com.android.library'
-
-android {
- compileSdkVersion versions.compileSdk
-
- defaultConfig {
- minSdkVersion 16
- }
-}
+apply from: "../android-configs/lib-config.gradle"
dependencies {
- api project(":logging")
- api 'net.danlew:android.joda:2.9.9.4'
+ implementation project(":logging")
+ implementation 'net.danlew:android.joda'
- implementation "androidx.recyclerview:recyclerview:$versions.androidx"
+ implementation "androidx.recyclerview:recyclerview"
+
+ constraints {
+ implementation("androidx.recyclerview:recyclerview") {
+ version {
+ require '1.0.0'
+ }
+ }
+
+ implementation("net.danlew:android.joda") {
+ version {
+ require '2.9.9.4'
+ }
+ }
+ }
}
diff --git a/recyclerview-decorators/.gitignore b/recyclerview-decorators/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/recyclerview-decorators/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/recyclerview-decorators/build.gradle b/recyclerview-decorators/build.gradle
new file mode 100644
index 0000000..6b6633f
--- /dev/null
+++ b/recyclerview-decorators/build.gradle
@@ -0,0 +1,32 @@
+apply from: "../android-configs/lib-config.gradle"
+apply plugin: 'kotlin-android'
+
+dependencies {
+ implementation project(":utils")
+ implementation project(":kotlin-extensions")
+
+ implementation "com.google.android.material:material"
+ implementation "androidx.core:core-ktx"
+
+ constraints {
+ implementation("com.google.android.material:material") {
+ version {
+ require '1.0.0'
+ }
+ }
+ implementation("androidx.core:core-ktx") {
+ version {
+ require '1.3.1'
+ }
+ }
+ implementation("org.jetbrains.kotlin:kotlin-stdlib") {
+ version {
+ require '1.3.0'
+ }
+ }
+ }
+}
+
+repositories {
+ mavenCentral()
+}
diff --git a/recyclerview-decorators/src/main/AndroidManifest.xml b/recyclerview-decorators/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..11a6418
--- /dev/null
+++ b/recyclerview-decorators/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+
diff --git a/recyclerview-decorators/src/main/java/ru/touchin/roboswag/recyclerview_decorators/decorators/BottomDividerItemDecoration.kt b/recyclerview-decorators/src/main/java/ru/touchin/roboswag/recyclerview_decorators/decorators/BottomDividerItemDecoration.kt
new file mode 100644
index 0000000..e3599d1
--- /dev/null
+++ b/recyclerview-decorators/src/main/java/ru/touchin/roboswag/recyclerview_decorators/decorators/BottomDividerItemDecoration.kt
@@ -0,0 +1,30 @@
+package ru.touchin.roboswag.recyclerview_decorators.decorators
+
+import android.content.Context
+import android.graphics.Rect
+import android.view.View
+import androidx.annotation.DrawableRes
+import androidx.recyclerview.widget.RecyclerView
+
+open class BottomDividerItemDecoration(
+ context: Context,
+ @DrawableRes drawableId: Int? = null,
+ override val predicate: ((position: Int) -> Boolean) = { true },
+ override val startMargin: Int = 0,
+ override val endMargin: Int = 0,
+ override val offset: Boolean = true,
+ override val showOnLastItem: Boolean = false
+) : DividerItemDecoration(context, drawableId, predicate, startMargin, endMargin, offset, showOnLastItem) {
+
+ override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
+ val position = parent.getChildAdapterPosition(view)
+ if (offset && predicate(position) && (position != state.itemCount - 1 || showOnLastItem)) {
+ outRect.set(0, 0, 0, divider.intrinsicHeight)
+ }
+ }
+
+ override fun getDividerTop(child: View): Int = getDividerBottom(child) - divider.intrinsicHeight
+
+ override fun getDividerBottom(child: View): Int = bounds.bottom + child.translationY.toInt()
+
+}
diff --git a/recyclerview-decorators/src/main/java/ru/touchin/roboswag/recyclerview_decorators/decorators/DividerItemDecoration.kt b/recyclerview-decorators/src/main/java/ru/touchin/roboswag/recyclerview_decorators/decorators/DividerItemDecoration.kt
new file mode 100644
index 0000000..386dd77
--- /dev/null
+++ b/recyclerview-decorators/src/main/java/ru/touchin/roboswag/recyclerview_decorators/decorators/DividerItemDecoration.kt
@@ -0,0 +1,74 @@
+package ru.touchin.roboswag.recyclerview_decorators.decorators
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Rect
+import android.graphics.drawable.Drawable
+import android.view.View
+import androidx.annotation.DrawableRes
+import androidx.core.content.res.getDrawableOrThrow
+import androidx.core.view.children
+import androidx.recyclerview.widget.RecyclerView
+import ru.touchin.roboswag.components.utils.px
+
+abstract class DividerItemDecoration(
+ context: Context,
+ @DrawableRes drawableId: Int? = null,
+ protected open val predicate: ((position: Int) -> Boolean) = { true },
+ protected open val startMargin: Int = 0,
+ protected open val endMargin: Int = 0,
+ protected open val offset: Boolean = true,
+ protected open val showOnLastItem: Boolean = false
+) : RecyclerView.ItemDecoration() {
+
+ protected val bounds = Rect()
+ protected val divider: Drawable
+
+ init {
+ if (drawableId == null) {
+ context.obtainStyledAttributes(intArrayOf(android.R.attr.listDivider)).apply {
+ divider = getDrawableOrThrow(0)
+ recycle()
+ }
+ } else {
+ divider = context.getDrawable(drawableId)!!
+ }
+ }
+
+ override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) {
+ if (offset) {
+ drawDivider(canvas, parent, state)
+ }
+ }
+
+ override fun onDrawOver(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) {
+ if (!offset) {
+ drawDivider(canvas, parent, state)
+ }
+ }
+
+ private fun drawDivider(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) {
+ canvas.save()
+ parent.children.forEach { child ->
+ val position = parent.getChildAdapterPosition(child)
+ if (predicate(position) && (position != state.itemCount - 1 || showOnLastItem)) {
+ parent.getDecoratedBoundsWithMargins(child, bounds)
+ val top = getDividerTop(child)
+ val bottom = getDividerBottom(child)
+ divider.setBounds(
+ bounds.left + startMargin,
+ top,
+ bounds.right - (endMargin.toFloat().px).toInt(),
+ bottom
+ )
+ divider.draw(canvas)
+ }
+ }
+ canvas.restore()
+ }
+
+ abstract fun getDividerTop(child: View): Int
+
+ abstract fun getDividerBottom(child: View): Int
+
+}
diff --git a/recyclerview-decorators/src/main/java/ru/touchin/roboswag/recyclerview_decorators/decorators/GroupItemDecoration.kt b/recyclerview-decorators/src/main/java/ru/touchin/roboswag/recyclerview_decorators/decorators/GroupItemDecoration.kt
new file mode 100644
index 0000000..6ff4d29
--- /dev/null
+++ b/recyclerview-decorators/src/main/java/ru/touchin/roboswag/recyclerview_decorators/decorators/GroupItemDecoration.kt
@@ -0,0 +1,108 @@
+package ru.touchin.roboswag.recyclerview_decorators.decorators
+
+import android.graphics.Canvas
+import android.graphics.Rect
+import android.util.SparseArray
+import android.view.View
+import android.view.ViewGroup
+import androidx.annotation.IdRes
+import androidx.core.util.set
+import androidx.core.view.children
+import androidx.recyclerview.widget.RecyclerView
+
+class GroupItemDecoration(
+ @RecyclerView.Orientation
+ private val orientation: Int = RecyclerView.VERTICAL,
+ private val predicate: (adapterPosition: Int) -> Boolean,
+ private val onCreateViewHolder: (parent: ViewGroup) -> TViewHolder,
+ private val onBindViewHolder: (adapterPosition: Int, TViewHolder) -> Unit
+) : RecyclerView.ItemDecoration() {
+
+ private val viewHoldersPool = SparseArray()
+ private val bounds = Rect()
+
+ override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
+ val adapterPosition = parent.getChildAdapterPosition(view)
+ if (predicate(adapterPosition)) {
+ calculateOutRectPosition(adapterPosition, parent, outRect)
+ } else {
+ viewHoldersPool.remove(adapterPosition)
+ }
+ }
+
+ @Suppress("detekt.NestedBlockDepth")
+ override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) = parent.children.forEach { child ->
+ val adapterPosition = parent.getChildAdapterPosition(child)
+ val groupView = viewHoldersPool[adapterPosition]?.view
+ if (predicate(adapterPosition)) {
+ if (groupView != null) {
+ onDrawGroupView(groupView, parent, child, canvas)
+ } else {
+ parent.invalidateItemDecorations()
+ return
+ }
+ } else if (groupView != null) {
+ parent.invalidateItemDecorations()
+ return
+ }
+ }
+
+ private fun calculateOutRectPosition(adapterPosition: Int, parent: RecyclerView, outRect: Rect) {
+ val groupViewHolder = obtainViewHolder(adapterPosition, parent)
+ onBindViewHolder(adapterPosition, groupViewHolder)
+ val groupView = groupViewHolder.view
+ val layoutParams = groupView.layoutParams as ViewGroup.MarginLayoutParams
+ val widthSpec = getWidthChildMeasureSpec(parent, layoutParams, groupView)
+ val heightSpec = getHeightChildMeasureSpec(parent, layoutParams, groupView)
+ groupView.measure(widthSpec, heightSpec)
+ groupView.layout(0, 0, groupView.measuredWidth, groupView.measuredHeight)
+ when (orientation) {
+ RecyclerView.VERTICAL -> outRect.top = groupView.measuredHeight
+ RecyclerView.HORIZONTAL -> outRect.left = groupView.measuredWidth
+ }
+ }
+
+ private fun onDrawGroupView(groupView: View, parent: RecyclerView, child: View, canvas: Canvas) {
+ val layoutParams = groupView.layoutParams as ViewGroup.MarginLayoutParams
+ parent.getDecoratedBoundsWithMargins(child, bounds)
+ canvas.save()
+ translateCanvasByOrientation(canvas, parent, layoutParams)
+ groupView.draw(canvas)
+ canvas.restore()
+ }
+
+ private fun translateCanvasByOrientation(canvas: Canvas, parent: RecyclerView, layoutParams: ViewGroup.MarginLayoutParams) {
+ when (orientation) {
+ RecyclerView.VERTICAL -> canvas.translate(parent.paddingLeft.toFloat() + layoutParams.leftMargin, bounds.top.toFloat())
+ RecyclerView.HORIZONTAL -> canvas.translate(bounds.left.toFloat(), parent.paddingTop.toFloat() + layoutParams.topMargin)
+ }
+ }
+
+ private fun obtainViewHolder(adapterPosition: Int, parent: RecyclerView): TViewHolder = viewHoldersPool[adapterPosition]
+ ?: onCreateViewHolder(parent).also { viewHoldersPool[adapterPosition] = it }
+
+ private fun getHeightChildMeasureSpec(
+ parent: RecyclerView,
+ layoutParams: ViewGroup.MarginLayoutParams,
+ groupView: View
+ ): Int = ViewGroup.getChildMeasureSpec(
+ View.MeasureSpec.makeMeasureSpec(parent.measuredHeight, View.MeasureSpec.EXACTLY),
+ parent.paddingTop + parent.paddingBottom + layoutParams.topMargin + layoutParams.bottomMargin,
+ groupView.layoutParams.height
+ )
+
+ private fun getWidthChildMeasureSpec(
+ parent: RecyclerView,
+ layoutParams: ViewGroup.MarginLayoutParams,
+ groupView: View
+ ): Int = ViewGroup.getChildMeasureSpec(
+ View.MeasureSpec.makeMeasureSpec(parent.measuredWidth, View.MeasureSpec.EXACTLY),
+ parent.paddingLeft + parent.paddingRight + layoutParams.leftMargin + layoutParams.rightMargin,
+ groupView.layoutParams.width
+ )
+
+ open class ViewHolder(val view: View) {
+ fun findViewById(@IdRes resId: Int): T = view.findViewById(resId)
+ }
+
+}
diff --git a/rx-extensions/.gitignore b/rx-extensions/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/rx-extensions/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/rx-extensions/build.gradle b/rx-extensions/build.gradle
new file mode 100644
index 0000000..3e4f58f
--- /dev/null
+++ b/rx-extensions/build.gradle
@@ -0,0 +1,16 @@
+apply from: "../android-configs/lib-config.gradle"
+
+dependencies {
+ implementation project(":utils")
+ implementation project(":logging")
+
+ implementation "io.reactivex.rxjava2:rxjava"
+
+ constraints {
+ implementation("io.reactivex.rxjava2:rxjava") {
+ version {
+ require '2.2.9'
+ }
+ }
+ }
+}
diff --git a/rx-extensions/src/main/AndroidManifest.xml b/rx-extensions/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..afb773f
--- /dev/null
+++ b/rx-extensions/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
diff --git a/rx-extensions/src/main/java/ru/touchin/extensions/rx/Flowable.kt b/rx-extensions/src/main/java/ru/touchin/extensions/rx/Flowable.kt
new file mode 100644
index 0000000..9f2f959
--- /dev/null
+++ b/rx-extensions/src/main/java/ru/touchin/extensions/rx/Flowable.kt
@@ -0,0 +1,23 @@
+package ru.touchin.extensions.rx
+
+import io.reactivex.Completable
+import io.reactivex.Flowable
+import ru.touchin.extensions.rx.utils.StringConstants
+import ru.touchin.roboswag.core.utils.Optional
+import ru.touchin.roboswag.core.utils.ShouldNotHappenException
+
+fun Flowable.emitAfter(other: Completable): Flowable = this.flatMap { value ->
+ other.andThen(Flowable.just(value))
+}
+
+fun Flowable>.unwrapOrError(
+ errorMessage: String = StringConstants.OPTIONAL_UNWRAPPING_ERROR_MESSAGE
+): Flowable = this.flatMap { wrapper ->
+ wrapper.get()
+ ?.let { Flowable.just(it) }
+ ?: Flowable.error(ShouldNotHappenException(errorMessage))
+}
+
+fun Flowable>.unwrapOrFilter(): Flowable = this
+ .filter { it.get() != null }
+ .map { it.get() }
diff --git a/rx-extensions/src/main/java/ru/touchin/extensions/rx/Maybe.kt b/rx-extensions/src/main/java/ru/touchin/extensions/rx/Maybe.kt
new file mode 100644
index 0000000..4fdd873
--- /dev/null
+++ b/rx-extensions/src/main/java/ru/touchin/extensions/rx/Maybe.kt
@@ -0,0 +1,19 @@
+package ru.touchin.extensions.rx
+
+import io.reactivex.Completable
+import io.reactivex.Maybe
+import ru.touchin.extensions.rx.utils.StringConstants
+import ru.touchin.roboswag.core.utils.Optional
+import ru.touchin.roboswag.core.utils.ShouldNotHappenException
+
+fun Maybe.emitAfter(other: Completable): Maybe = this.flatMap { value ->
+ other.andThen(Maybe.just(value))
+}
+
+fun Maybe>.unwrapOrError(
+ errorMessage: String = StringConstants.OPTIONAL_UNWRAPPING_ERROR_MESSAGE
+): Maybe = this.flatMap { wrapper ->
+ wrapper.get()
+ ?.let { Maybe.just(it) }
+ ?: Maybe.error(ShouldNotHappenException(errorMessage))
+}
diff --git a/rx-extensions/src/main/java/ru/touchin/extensions/rx/Observable.kt b/rx-extensions/src/main/java/ru/touchin/extensions/rx/Observable.kt
new file mode 100644
index 0000000..eb643dd
--- /dev/null
+++ b/rx-extensions/src/main/java/ru/touchin/extensions/rx/Observable.kt
@@ -0,0 +1,23 @@
+package ru.touchin.extensions.rx
+
+import io.reactivex.Completable
+import io.reactivex.Observable
+import ru.touchin.extensions.rx.utils.StringConstants
+import ru.touchin.roboswag.core.utils.Optional
+import ru.touchin.roboswag.core.utils.ShouldNotHappenException
+
+fun Observable.emitAfter(other: Completable): Observable = this.flatMap { value ->
+ other.andThen(Observable.just(value))
+}
+
+fun Observable>.unwrapOrError(
+ errorMessage: String = StringConstants.OPTIONAL_UNWRAPPING_ERROR_MESSAGE
+): Observable = this.flatMap { wrapper ->
+ wrapper.get()
+ ?.let { Observable.just(it) }
+ ?: Observable.error(ShouldNotHappenException(errorMessage))
+}
+
+fun Observable>.unwrapOrFilter(): Observable = this
+ .filter { it.get() != null }
+ .map { it.get() }
diff --git a/rx-extensions/src/main/java/ru/touchin/extensions/rx/Single.kt b/rx-extensions/src/main/java/ru/touchin/extensions/rx/Single.kt
new file mode 100644
index 0000000..392de40
--- /dev/null
+++ b/rx-extensions/src/main/java/ru/touchin/extensions/rx/Single.kt
@@ -0,0 +1,19 @@
+package ru.touchin.extensions.rx
+
+import io.reactivex.Completable
+import io.reactivex.Single
+import ru.touchin.extensions.rx.utils.StringConstants
+import ru.touchin.roboswag.core.utils.Optional
+import ru.touchin.roboswag.core.utils.ShouldNotHappenException
+
+fun Single.emitAfter(other: Completable): Single = this.flatMap { value ->
+ other.andThen(Single.just(value))
+}
+
+fun Single>.unwrapOrError(
+ errorMessage: String = StringConstants.OPTIONAL_UNWRAPPING_ERROR_MESSAGE
+): Single = this.flatMap { wrapper ->
+ wrapper.get()
+ ?.let { Single.just(it) }
+ ?: Single.error(ShouldNotHappenException(errorMessage))
+}
diff --git a/rx-extensions/src/main/java/ru/touchin/extensions/rx/utils/StringConstants.kt b/rx-extensions/src/main/java/ru/touchin/extensions/rx/utils/StringConstants.kt
new file mode 100644
index 0000000..ad51ae2
--- /dev/null
+++ b/rx-extensions/src/main/java/ru/touchin/extensions/rx/utils/StringConstants.kt
@@ -0,0 +1,5 @@
+package ru.touchin.extensions.rx.utils
+
+object StringConstants {
+ const val OPTIONAL_UNWRAPPING_ERROR_MESSAGE = "Wrapped object must not be null"
+}
diff --git a/storable/build.gradle b/storable/build.gradle
index 340a921..58f4456 100644
--- a/storable/build.gradle
+++ b/storable/build.gradle
@@ -1,25 +1,38 @@
-apply plugin: 'com.android.library'
-
-android {
- compileSdkVersion versions.compileSdk
-
- defaultConfig {
- minSdkVersion 16
- }
-
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- }
-}
+apply from: "../android-configs/lib-config.gradle"
dependencies {
- api project(":utils")
- api project(":logging")
+ implementation project(":utils")
+ implementation project(":logging")
- implementation "androidx.core:core:$versions.androidx"
- implementation "androidx.annotation:annotation:$versions.androidx"
+ implementation "androidx.core:core"
+ implementation "androidx.annotation:annotation"
- implementation "io.reactivex.rxjava2:rxjava:$versions.rxJava"
- implementation "io.reactivex.rxjava2:rxandroid:$versions.rxAndroid"
+ implementation "io.reactivex.rxjava2:rxjava"
+ implementation "io.reactivex.rxjava2:rxandroid"
+
+ constraints {
+ implementation("androidx.core:core") {
+ version {
+ require '1.0.0'
+ }
+ }
+
+ implementation("androidx.annotation:annotation") {
+ version {
+ require '1.0.0'
+ }
+ }
+
+ implementation("io.reactivex.rxjava2:rxjava") {
+ version {
+ require '2.2.6'
+ }
+ }
+
+ implementation("io.reactivex.rxjava2:rxandroid") {
+ version {
+ require '2.1.1'
+ }
+ }
+ }
}
diff --git a/storable/src/main/java/ru/touchin/roboswag/components/utils/storables/PreferenceUtils.java b/storable/src/main/java/ru/touchin/roboswag/components/utils/storables/PreferenceUtils.java
index 9b87a51..e9f85ba 100644
--- a/storable/src/main/java/ru/touchin/roboswag/components/utils/storables/PreferenceUtils.java
+++ b/storable/src/main/java/ru/touchin/roboswag/components/utils/storables/PreferenceUtils.java
@@ -20,6 +20,7 @@
package ru.touchin.roboswag.components.utils.storables;
import android.content.SharedPreferences;
+
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -37,7 +38,7 @@ import ru.touchin.roboswag.core.observables.storable.NonNullStorable;
public final class PreferenceUtils {
/**
- * Creates {@link Storable} that stores string into {@link SharedPreferences}.
+ * Creates {@link Storable} that stores string into {@link SharedPreferences}. Default value is null
*
* @param name Name of preference;
* @param preferences Preferences to store value;
diff --git a/tabbar-navigation-new/build.gradle b/tabbar-navigation-new/build.gradle
deleted file mode 100644
index a7e4bf3..0000000
--- a/tabbar-navigation-new/build.gradle
+++ /dev/null
@@ -1,25 +0,0 @@
-apply plugin: 'com.android.library'
-apply plugin: 'kotlin-android'
-
-android {
- compileSdkVersion versions.compileSdk
-
- defaultConfig {
- minSdkVersion 16
- }
-
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- }
-}
-
-dependencies {
- api project(":navigation-new")
-
- implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
-
- implementation "androidx.core:core-ktx:$versions.coreKtx"
-
- implementation "androidx.appcompat:appcompat:$versions.appcompat"
-}
diff --git a/tabbar-navigation-new/src/main/AndroidManifest.xml b/tabbar-navigation-new/src/main/AndroidManifest.xml
deleted file mode 100644
index 07f130a..0000000
--- a/tabbar-navigation-new/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,2 +0,0 @@
-
diff --git a/tabbar-navigation-new/src/main/java/ru/touchin/roboswag/components/tabbarnavigation_new/BottomNavigationActivity.kt b/tabbar-navigation-new/src/main/java/ru/touchin/roboswag/components/tabbarnavigation_new/BottomNavigationActivity.kt
deleted file mode 100644
index faf4c84..0000000
--- a/tabbar-navigation-new/src/main/java/ru/touchin/roboswag/components/tabbarnavigation_new/BottomNavigationActivity.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-package ru.touchin.roboswag.components.tabbarnavigation_new
-
-import android.os.Parcelable
-import androidx.annotation.IdRes
-import androidx.fragment.app.FragmentManager
-import ru.touchin.roboswag.components.navigation_new.activities.NavigationActivity
-import ru.touchin.roboswag.components.navigation_new.FragmentNavigation
-
-/**
- * Created by Daniil Borisovskii on 15/08/2019.
- * Activity to manage tab container navigation.
- */
-abstract class BottomNavigationActivity : NavigationActivity() {
-
- val innerNavigation: FragmentNavigation
- get() = getNavigationContainer(supportFragmentManager)?.navigation ?: navigation
-
- /**
- * Navigates to the given navigation tab.
- * Can be called from any node of navigation graph so all back stack will be cleared.
- *
- * @param navigationTabId Id of navigation tab.
- * @param state State of the given tab. If not null tab's fragment will be recreated, otherwise only in case it has not been created before.
- */
- fun navigateTo(@IdRes navigationTabId: Int, state: Parcelable? = null) {
- supportFragmentManager.run {
- // Clear all navigation stack unto the main bottom navigation (tagged as top)
- popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
-
- (primaryNavigationFragment as? BottomNavigationFragment)?.navigateTo(navigationTabId, state)
- }
- }
-
- private fun getNavigationContainer(fragmentManager: FragmentManager?): NavigationContainerFragment? =
- fragmentManager
- ?.primaryNavigationFragment
- ?.let { navigationFragment ->
- navigationFragment as? NavigationContainerFragment
- ?: getNavigationContainer(navigationFragment.childFragmentManager)
- }
-
-}
diff --git a/tabbar-navigation-new/src/main/java/ru/touchin/roboswag/components/tabbarnavigation_new/BottomNavigationController.kt b/tabbar-navigation-new/src/main/java/ru/touchin/roboswag/components/tabbarnavigation_new/BottomNavigationController.kt
deleted file mode 100644
index 67c0f95..0000000
--- a/tabbar-navigation-new/src/main/java/ru/touchin/roboswag/components/tabbarnavigation_new/BottomNavigationController.kt
+++ /dev/null
@@ -1,125 +0,0 @@
-package ru.touchin.roboswag.components.tabbarnavigation_new
-
-import android.content.Context
-import android.os.Bundle
-import android.os.Parcelable
-import android.util.SparseArray
-import android.view.View
-import android.view.ViewGroup
-import androidx.annotation.IdRes
-import androidx.annotation.LayoutRes
-import androidx.core.util.forEach
-import androidx.core.view.children
-import androidx.fragment.app.Fragment
-import androidx.fragment.app.FragmentManager
-import ru.touchin.roboswag.components.navigation_new.fragments.BaseFragment
-import ru.touchin.roboswag.core.utils.ShouldNotHappenException
-
-class BottomNavigationController(
- private val context: Context,
- private val fragmentManager: FragmentManager,
- private val fragments: SparseArray>, Parcelable>>,
- @IdRes private val contentContainerViewId: Int,
- @LayoutRes private val contentContainerLayoutId: Int,
- private val wrapWithNavigationContainer: Boolean = false,
- @IdRes private val topLevelFragmentId: Int = 0, // If it zero back press with empty fragment back stack would close the app
- private val onReselectListener: (() -> Unit)? = null
-) {
-
- private var callback: FragmentManager.FragmentLifecycleCallbacks? = null
-
- private var currentFragmentId = -1
-
- fun attach(navigationTabsContainer: ViewGroup) {
- detach()
-
- //This is provides to set pressed tab status to isActivated providing an opportunity to specify custom style
- callback = object : FragmentManager.FragmentLifecycleCallbacks() {
- override fun onFragmentViewCreated(fragmentManager: FragmentManager, fragment: Fragment, view: View, savedInstanceState: Bundle?) {
- fragments.forEach { itemId, (fragmentClass, _) ->
- if (isFragment(fragment, fragmentClass)) {
- navigationTabsContainer.children.forEach { itemView -> itemView.isActivated = itemView.id == itemId }
- }
- }
- }
- }
- fragmentManager.registerFragmentLifecycleCallbacks(callback!!, false)
-
- navigationTabsContainer.children.forEach { itemView ->
- fragments[itemView.id]?.let { (fragmentClass, _) ->
- itemView.setOnClickListener {
- if (!isFragment(fragmentManager.primaryNavigationFragment, fragmentClass)) {
- navigateTo(itemView.id)
- } else {
- onReselectListener?.invoke()
- }
- }
- }
- }
- }
-
- fun detach() = callback?.let(fragmentManager::unregisterFragmentLifecycleCallbacks)
-
- fun navigateTo(@IdRes itemId: Int, state: Parcelable? = null) {
- // Find fragment class that needs to open
- val (fragmentClass, defaultFragmentState) = fragments[itemId] ?: return
- if (state != null && state::class != defaultFragmentState::class) {
- throw ShouldNotHappenException(
- "Incorrect state type for navigation tab root Fragment. Should be ${defaultFragmentState::class}"
- )
- }
- val fragmentState = state ?: defaultFragmentState
- val transaction = fragmentManager.beginTransaction()
- // Detach current primary fragment
- fragmentManager.primaryNavigationFragment?.let(transaction::detach)
- val fragmentName = fragmentClass.canonicalName
- var fragment = fragmentManager.findFragmentByTag(fragmentName)
-
- if (state == null && fragment != null) {
- transaction.attach(fragment)
- } else {
- // If fragment already exist remove it first
- if (fragment != null) transaction.remove(fragment)
-
- fragment = if (wrapWithNavigationContainer) {
- Fragment.instantiate(
- context,
- fragmentClass.name,
- NavigationContainerFragment.args(fragmentClass, fragmentState, contentContainerViewId, contentContainerLayoutId)
- )
- } else {
- Fragment.instantiate(
- context,
- fragmentClass.name,
- BaseFragment.args(fragmentState)
- )
- }
- transaction.add(contentContainerViewId, fragment, fragmentName)
- }
-
- transaction
- .setPrimaryNavigationFragment(fragment)
- .setReorderingAllowed(true)
- .commit()
-
- currentFragmentId = itemId
- }
-
- // When you are in any tab instead of main you firstly navigate to main tab before exit application
- fun onBackPressed() =
- if (fragmentManager.primaryNavigationFragment?.childFragmentManager?.backStackEntryCount == 0
- && topLevelFragmentId != 0
- && currentFragmentId != topLevelFragmentId) {
- navigateTo(topLevelFragmentId)
- true
- } else {
- false
- }
-
- private fun isFragment(fragment: Fragment?, fragmentClass: Class>) =
- if (wrapWithNavigationContainer) {
- (fragment as NavigationContainerFragment).getFragmentClass()
- } else {
- (fragment as BaseFragment<*, *>).javaClass
- } === fragmentClass
-}
diff --git a/tabbar-navigation-new/src/main/java/ru/touchin/roboswag/components/tabbarnavigation_new/BottomNavigationFragment.kt b/tabbar-navigation-new/src/main/java/ru/touchin/roboswag/components/tabbarnavigation_new/BottomNavigationFragment.kt
deleted file mode 100644
index cb204f3..0000000
--- a/tabbar-navigation-new/src/main/java/ru/touchin/roboswag/components/tabbarnavigation_new/BottomNavigationFragment.kt
+++ /dev/null
@@ -1,75 +0,0 @@
-package ru.touchin.roboswag.components.tabbarnavigation_new
-
-import android.os.Bundle
-import android.os.Parcelable
-import android.util.SparseArray
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.annotation.IdRes
-import androidx.fragment.app.Fragment
-import ru.touchin.roboswag.components.navigation.activities.OnBackPressedListener
-import ru.touchin.roboswag.components.navigation_new.fragments.BaseFragment
-
-abstract class BottomNavigationFragment : Fragment() {
-
- private lateinit var bottomNavigationController: BottomNavigationController
-
- private val backPressedListener = OnBackPressedListener { bottomNavigationController.onBackPressed() }
-
- protected abstract val rootLayoutId: Int
-
- protected abstract val navigationContainerViewId: Int
-
- protected abstract val contentContainerViewId: Int
-
- protected abstract val contentContainerLayoutId: Int
-
- protected abstract val topLevelFragmentId: Int
-
- protected abstract val wrapWithNavigationContainer: Boolean
-
- protected abstract val navigationFragments: SparseArray>, Parcelable>>
-
- protected open val reselectListener: (() -> Unit) = { getNavigationActivity().innerNavigation.up(inclusive = true) }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- bottomNavigationController = BottomNavigationController(
- context = requireContext(),
- fragmentManager = childFragmentManager,
- fragments = navigationFragments,
- contentContainerViewId = contentContainerViewId,
- contentContainerLayoutId = contentContainerLayoutId,
- topLevelFragmentId = topLevelFragmentId,
- wrapWithNavigationContainer = wrapWithNavigationContainer,
- onReselectListener = reselectListener
- )
- if (savedInstanceState == null) {
- bottomNavigationController.navigateTo(topLevelFragmentId)
- }
- }
-
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
- val fragmentView = inflater.inflate(rootLayoutId, container, false)
-
- bottomNavigationController.attach(fragmentView.findViewById(navigationContainerViewId))
-
- (activity as BottomNavigationActivity).addOnBackPressedListener(backPressedListener)
-
- return fragmentView
- }
-
- override fun onDestroyView() {
- super.onDestroyView()
- (activity as BottomNavigationActivity).removeOnBackPressedListener(backPressedListener)
- bottomNavigationController.detach()
- }
-
- fun navigateTo(@IdRes navigationTabId: Int, state: Parcelable? = null) {
- bottomNavigationController.navigateTo(navigationTabId, state)
- }
-
- private fun getNavigationActivity() = requireActivity() as BottomNavigationActivity
-
-}
diff --git a/tabbar-navigation-new/src/main/java/ru/touchin/roboswag/components/tabbarnavigation_new/NavigationContainerFragment.kt b/tabbar-navigation-new/src/main/java/ru/touchin/roboswag/components/tabbarnavigation_new/NavigationContainerFragment.kt
deleted file mode 100644
index 160f883..0000000
--- a/tabbar-navigation-new/src/main/java/ru/touchin/roboswag/components/tabbarnavigation_new/NavigationContainerFragment.kt
+++ /dev/null
@@ -1,77 +0,0 @@
-package ru.touchin.roboswag.components.tabbarnavigation_new
-
-import android.os.Bundle
-import android.os.Parcelable
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.annotation.IdRes
-import androidx.annotation.LayoutRes
-import androidx.fragment.app.Fragment
-import androidx.fragment.app.FragmentTransaction
-import ru.touchin.roboswag.components.navigation_new.FragmentNavigation
-import ru.touchin.roboswag.components.navigation_new.fragments.BaseFragment
-import ru.touchin.roboswag.core.utils.ShouldNotHappenException
-
-class NavigationContainerFragment : Fragment() {
-
- companion object {
- private const val FRAGMENT_CLASS_ARG = "FRAGMENT_CLASS_ARG"
- private const val FRAGMENT_STATE_ARG = "FRAGMENT_STATE_ARG"
- private const val CONTAINER_VIEW_ID_ARG = "CONTAINER_VIEW_ID_ARG"
- private const val CONTAINER_LAYOUT_ID_ARG = "CONTAINER_LAYOUT_ID_ARG"
- private const val TRANSITION_ARG = "TRANSITION_ARG"
-
- fun args(
- cls: Class>,
- state: Parcelable,
- @IdRes containerViewId: Int,
- @LayoutRes containerLayoutId: Int,
- transition: Int = FragmentTransaction.TRANSIT_NONE
- ) = Bundle().apply {
- putSerializable(FRAGMENT_CLASS_ARG, cls)
- putParcelable(FRAGMENT_STATE_ARG, state)
- putInt(CONTAINER_VIEW_ID_ARG, containerViewId)
- putInt(CONTAINER_LAYOUT_ID_ARG, containerLayoutId)
- putInt(TRANSITION_ARG, transition)
- }
- }
-
- val navigation by lazy {
- FragmentNavigation(
- requireContext(),
- childFragmentManager,
- containerViewId,
- transition
- )
- }
-
- @IdRes
- private var containerViewId = 0
-
- @LayoutRes
- private var containerLayoutId = 0
-
- private var transition = 0
-
- @Suppress("UNCHECKED_CAST")
- fun getFragmentClass(): Class> =
- arguments?.getSerializable(FRAGMENT_CLASS_ARG) as Class>
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- if (savedInstanceState == null) {
- val args = arguments ?: throw ShouldNotHappenException("Fragment is not instantiable without arguments")
- with(args) {
- containerViewId = getInt(CONTAINER_VIEW_ID_ARG)
- containerLayoutId = getInt(CONTAINER_LAYOUT_ID_ARG)
- transition = getInt(TRANSITION_ARG)
- }
- navigation.setInitial(getFragmentClass(), args.getParcelable(FRAGMENT_STATE_ARG))
- }
- }
-
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
- inflater.inflate(containerLayoutId, container, false)
-
-}
diff --git a/tabbar-navigation/build.gradle b/tabbar-navigation/build.gradle
deleted file mode 100644
index fb98a51..0000000
--- a/tabbar-navigation/build.gradle
+++ /dev/null
@@ -1,25 +0,0 @@
-apply plugin: 'com.android.library'
-apply plugin: 'kotlin-android'
-
-android {
- compileSdkVersion versions.compileSdk
-
- defaultConfig {
- minSdkVersion 16
- }
-
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- }
-}
-
-dependencies {
- api project(":navigation")
-
- implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
-
- implementation "androidx.core:core-ktx:$versions.coreKtx"
-
- implementation "androidx.appcompat:appcompat:$versions.appcompat"
-}
diff --git a/tabbar-navigation/src/main/AndroidManifest.xml b/tabbar-navigation/src/main/AndroidManifest.xml
deleted file mode 100644
index 9437bbd..0000000
--- a/tabbar-navigation/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,2 +0,0 @@
-
diff --git a/tabbar-navigation/src/main/java/ru/touchin/roboswag/components/tabbarnavigation/BottomNavigationController.kt b/tabbar-navigation/src/main/java/ru/touchin/roboswag/components/tabbarnavigation/BottomNavigationController.kt
deleted file mode 100644
index b8a1bc6..0000000
--- a/tabbar-navigation/src/main/java/ru/touchin/roboswag/components/tabbarnavigation/BottomNavigationController.kt
+++ /dev/null
@@ -1,126 +0,0 @@
-package ru.touchin.roboswag.components.tabbarnavigation
-
-import android.content.Context
-import android.os.Bundle
-import android.os.Parcelable
-import android.util.SparseArray
-import android.view.View
-import android.view.ViewGroup
-import androidx.annotation.IdRes
-import androidx.annotation.LayoutRes
-import androidx.core.util.forEach
-import androidx.core.view.children
-import androidx.fragment.app.Fragment
-import androidx.fragment.app.FragmentManager
-import ru.touchin.roboswag.components.navigation.fragments.ViewControllerFragment
-import ru.touchin.roboswag.components.navigation.viewcontrollers.ViewController
-import ru.touchin.roboswag.core.utils.ShouldNotHappenException
-
-class BottomNavigationController(
- private val context: Context,
- private val fragmentManager: FragmentManager,
- private val viewControllers: SparseArray,
- @IdRes private val contentContainerViewId: Int,
- @LayoutRes private val contentContainerLayoutId: Int,
- private val wrapWithNavigationContainer: Boolean = false,
- @IdRes private val topLevelViewControllerId: Int = 0, // If it zero back press with empty fragment back stack would close the app
- private val onReselectListener: (() -> Unit)? = null
-) {
-
- private var callback: FragmentManager.FragmentLifecycleCallbacks? = null
-
- private var currentViewControllerId = -1
-
- fun attach(navigationTabsContainer: ViewGroup) {
- detach()
-
- //This is provides to set pressed tab status to isActivated providing an opportunity to specify custom style
- callback = object : FragmentManager.FragmentLifecycleCallbacks() {
- override fun onFragmentViewCreated(fragmentManager: FragmentManager, fragment: Fragment, view: View, savedInstanceState: Bundle?) {
- viewControllers.forEach { itemId, (viewControllerClass, _) ->
- if (isViewControllerFragment(fragment, viewControllerClass)) {
- navigationTabsContainer.children.forEach { itemView -> itemView.isActivated = itemView.id == itemId }
- }
- }
- }
- }
- fragmentManager.registerFragmentLifecycleCallbacks(callback!!, false)
-
- navigationTabsContainer.children.forEach { itemView ->
- viewControllers[itemView.id]?.let { (viewControllerClass, _) ->
- itemView.setOnClickListener {
- if (!isViewControllerFragment(fragmentManager.primaryNavigationFragment, viewControllerClass)) {
- navigateTo(itemView.id)
- } else {
- onReselectListener?.invoke()
- }
- }
- }
- }
- }
-
- fun detach() = callback?.let(fragmentManager::unregisterFragmentLifecycleCallbacks)
-
- fun navigateTo(@IdRes itemId: Int, state: Parcelable? = null) {
- // Find view controller class that needs to open
- val (viewControllerClass, defaultViewControllerState, saveStateOnSwitching) = viewControllers[itemId] ?: return
- if (state != null && state::class != defaultViewControllerState::class) {
- throw ShouldNotHappenException(
- "Incorrect state type for navigation tab root ViewController. Should be ${defaultViewControllerState::class}"
- )
- }
- val viewControllerState = state ?: defaultViewControllerState
- val transaction = fragmentManager.beginTransaction()
- // Detach current primary fragment
- fragmentManager.primaryNavigationFragment?.let(transaction::detach)
- val viewControllerName = viewControllerClass.canonicalName
- var fragment = fragmentManager.findFragmentByTag(viewControllerName)
-
- if (saveStateOnSwitching && state == null && fragment != null) {
- transaction.attach(fragment)
- } else {
- // If fragment already exist remove it first
- if (fragment != null) transaction.remove(fragment)
-
- fragment = if (wrapWithNavigationContainer) {
- Fragment.instantiate(
- context,
- NavigationContainerFragment::class.java.name,
- NavigationContainerFragment.args(viewControllerClass, viewControllerState, contentContainerViewId, contentContainerLayoutId)
- )
- } else {
- Fragment.instantiate(
- context,
- ViewControllerFragment::class.java.name,
- ViewControllerFragment.args(viewControllerClass, viewControllerState)
- )
- }
- transaction.add(contentContainerViewId, fragment, viewControllerName)
- }
-
- transaction
- .setPrimaryNavigationFragment(fragment)
- .setReorderingAllowed(true)
- .commit()
-
- currentViewControllerId = itemId
- }
-
- // When you are in any tab instead of main you firstly navigate to main tab before exit application
- fun onBackPressed() =
- if (fragmentManager.primaryNavigationFragment?.childFragmentManager?.backStackEntryCount == 0
- && topLevelViewControllerId != 0
- && currentViewControllerId != topLevelViewControllerId) {
- navigateTo(topLevelViewControllerId)
- true
- } else {
- false
- }
-
- private fun isViewControllerFragment(fragment: Fragment?, viewControllerClass: Class>) =
- if (wrapWithNavigationContainer) {
- (fragment as NavigationContainerFragment).getViewControllerClass()
- } else {
- (fragment as ViewControllerFragment<*, *>).viewControllerClass
- } === viewControllerClass
-}
diff --git a/tabbar-navigation/src/main/java/ru/touchin/roboswag/components/tabbarnavigation/BottomNavigationFragment.kt b/tabbar-navigation/src/main/java/ru/touchin/roboswag/components/tabbarnavigation/BottomNavigationFragment.kt
deleted file mode 100644
index c1364fc..0000000
--- a/tabbar-navigation/src/main/java/ru/touchin/roboswag/components/tabbarnavigation/BottomNavigationFragment.kt
+++ /dev/null
@@ -1,118 +0,0 @@
-package ru.touchin.roboswag.components.tabbarnavigation
-
-import android.os.Bundle
-import android.os.Parcel
-import android.os.Parcelable
-import android.util.SparseArray
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.annotation.IdRes
-import androidx.fragment.app.Fragment
-import ru.touchin.roboswag.components.navigation.activities.OnBackPressedListener
-import ru.touchin.roboswag.components.navigation.viewcontrollers.EmptyState
-import ru.touchin.roboswag.components.navigation.viewcontrollers.ViewController
-
-abstract class BottomNavigationFragment : Fragment() {
-
- private lateinit var bottomNavigationController: BottomNavigationController
-
- private val backPressedListener = OnBackPressedListener { bottomNavigationController.onBackPressed() }
-
- protected abstract val rootLayoutId: Int
-
- protected abstract val navigationContainerViewId: Int
-
- protected abstract val contentContainerViewId: Int
-
- protected abstract val contentContainerLayoutId: Int
-
- protected abstract val topLevelViewControllerId: Int
-
- protected abstract val wrapWithNavigationContainer: Boolean
-
- protected abstract val navigationViewControllers: SparseArray
-
- protected open val reselectListener: (() -> Unit) = { getNavigationActivity().innerNavigation.up(inclusive = true) }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- bottomNavigationController = BottomNavigationController(
- context = requireContext(),
- fragmentManager = childFragmentManager,
- viewControllers = navigationViewControllers,
- contentContainerViewId = contentContainerViewId,
- contentContainerLayoutId = contentContainerLayoutId,
- topLevelViewControllerId = topLevelViewControllerId,
- wrapWithNavigationContainer = wrapWithNavigationContainer,
- onReselectListener = reselectListener
- )
- if (savedInstanceState == null) {
- bottomNavigationController.navigateTo(topLevelViewControllerId)
- }
- }
-
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
- val fragmentView = inflater.inflate(rootLayoutId, container, false)
-
- bottomNavigationController.attach(fragmentView.findViewById(navigationContainerViewId))
-
- (activity as BottomNavigationActivity).addOnBackPressedListener(backPressedListener)
-
- return fragmentView
- }
-
- override fun onDestroyView() {
- super.onDestroyView()
- (activity as BottomNavigationActivity).removeOnBackPressedListener(backPressedListener)
- bottomNavigationController.detach()
- }
-
- fun navigateTo(@IdRes navigationTabId: Int, state: Parcelable? = null) {
- bottomNavigationController.navigateTo(navigationTabId, state)
- }
-
- private fun getNavigationActivity() = requireActivity() as BottomNavigationActivity
-
- class TabData(
- val viewControllerClass: Class>,
- viewControllerState: Parcelable,
- /**
- * It can be useful in some cases when it is necessary to create ViewController
- * with initial state every time when tab opens.
- */
- val saveStateOnSwitching: Boolean = true
- ) {
-
- /**
- * It is value as class body property instead of value as constructor parameter to specify
- * custom getter of this field which returns copy of Parcelable every time it be called.
- * This is necessary to avoid modifying this value if it would be a value as constructor parameter
- * and every getting of this value would return the same instance.
- */
- val viewControllerState = viewControllerState
- get() = field.copy()
-
- operator fun component1() = viewControllerClass
-
- operator fun component2() = viewControllerState
-
- operator fun component3() = saveStateOnSwitching
-
- private fun Parcelable.copy(): Parcelable =
- if (this is EmptyState) {
- EmptyState
- } else {
- val parcel = Parcel.obtain()
- parcel.writeParcelable(this, 0)
- parcel.setDataPosition(0)
- val result = parcel.readParcelable(
- javaClass.classLoader ?: Thread.currentThread().contextClassLoader
- ) ?: throw IllegalStateException("Failed to copy tab state")
- parcel.recycle()
- result
- }
-
- }
-
-}
diff --git a/tabbar-navigation/src/main/java/ru/touchin/roboswag/components/tabbarnavigation/NavigationContainerFragment.kt b/tabbar-navigation/src/main/java/ru/touchin/roboswag/components/tabbarnavigation/NavigationContainerFragment.kt
deleted file mode 100644
index 7948b9e..0000000
--- a/tabbar-navigation/src/main/java/ru/touchin/roboswag/components/tabbarnavigation/NavigationContainerFragment.kt
+++ /dev/null
@@ -1,79 +0,0 @@
-package ru.touchin.roboswag.components.tabbarnavigation
-
-import android.os.Bundle
-import android.os.Parcelable
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.annotation.IdRes
-import androidx.annotation.LayoutRes
-import androidx.fragment.app.Fragment
-import androidx.fragment.app.FragmentTransaction
-import ru.touchin.roboswag.components.navigation.viewcontrollers.ViewController
-import ru.touchin.roboswag.components.navigation.viewcontrollers.ViewControllerNavigation
-import ru.touchin.roboswag.core.utils.ShouldNotHappenException
-
-class NavigationContainerFragment : Fragment() {
-
- companion object {
- private const val VIEW_CONTROLLER_CLASS_ARG = "VIEW_CONTROLLER_CLASS_ARG"
- private const val VIEW_CONTROLLER_STATE_ARG = "VIEW_CONTROLLER_STATE_ARG"
- private const val CONTAINER_VIEW_ID_ARG = "CONTAINER_VIEW_ID_ARG"
- private const val CONTAINER_LAYOUT_ID_ARG = "CONTAINER_LAYOUT_ID_ARG"
- private const val TRANSITION_ARG = "TRANSITION_ARG"
-
- fun args(
- cls: Class>,
- state: Parcelable,
- @IdRes containerViewId: Int,
- @LayoutRes containerLayoutId: Int,
- transition: Int = FragmentTransaction.TRANSIT_NONE
- ) = Bundle().apply {
- putSerializable(VIEW_CONTROLLER_CLASS_ARG, cls)
- putParcelable(VIEW_CONTROLLER_STATE_ARG, state)
- putInt(CONTAINER_VIEW_ID_ARG, containerViewId)
- putInt(CONTAINER_LAYOUT_ID_ARG, containerLayoutId)
- putInt(TRANSITION_ARG, transition)
- }
- }
-
- val navigation by lazy {
- ViewControllerNavigation(
- requireContext(),
- childFragmentManager,
- containerViewId,
- transition
- )
- }
-
- @IdRes
- private var containerViewId = 0
-
- @LayoutRes
- private var containerLayoutId = 0
-
- private var transition = 0
-
- @Suppress("UNCHECKED_CAST")
- fun getViewControllerClass(): Class> =
- arguments?.getSerializable(VIEW_CONTROLLER_CLASS_ARG) as Class>
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- arguments?.let { args ->
- transition = args.getInt(TRANSITION_ARG)
- containerViewId = args.getInt(CONTAINER_VIEW_ID_ARG)
- containerLayoutId = args.getInt(CONTAINER_LAYOUT_ID_ARG)
-
- if (savedInstanceState == null) {
- navigation.setInitialViewController(getViewControllerClass(), args.getParcelable(VIEW_CONTROLLER_STATE_ARG)
- ?: throw ShouldNotHappenException("Fragment state must not be null"))
- }
- } ?: throw ShouldNotHappenException("Fragment is not instantiable without arguments")
- }
-
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
- inflater.inflate(containerLayoutId, container, false)
-
-}
diff --git a/utils/build.gradle b/utils/build.gradle
index ac3a82f..2a77fe4 100644
--- a/utils/build.gradle
+++ b/utils/build.gradle
@@ -1,21 +1,20 @@
-apply plugin: 'com.android.library'
-apply plugin: 'kotlin-android'
-
-android {
- compileSdkVersion versions.compileSdk
-
- defaultConfig {
- minSdkVersion 16
- }
-
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- }
-}
+apply from: "../android-configs/lib-config.gradle"
dependencies {
- implementation "androidx.core:core:$versions.androidx"
- implementation "androidx.annotation:annotation:$versions.androidx"
- implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+ implementation "androidx.core:core"
+ implementation "androidx.annotation:annotation"
+
+ constraints {
+ implementation("androidx.core:core") {
+ version {
+ require '1.0.0'
+ }
+ }
+
+ implementation("androidx.annotation:annotation") {
+ version {
+ require '1.1.0'
+ }
+ }
+ }
}
diff --git a/utils/src/main/java/ru/touchin/defaults/DefaultActivityLifecycleCallbacks.kt b/utils/src/main/java/ru/touchin/defaults/DefaultActivityLifecycleCallbacks.kt
new file mode 100644
index 0000000..d464502
--- /dev/null
+++ b/utils/src/main/java/ru/touchin/defaults/DefaultActivityLifecycleCallbacks.kt
@@ -0,0 +1,23 @@
+package ru.touchin.defaults
+
+import android.app.Activity
+import android.app.Application
+import android.os.Bundle
+
+open class DefaultActivityLifecycleCallbacks : Application.ActivityLifecycleCallbacks {
+
+ override fun onActivityPaused(activity: Activity) = Unit
+
+ override fun onActivityResumed(activity: Activity) = Unit
+
+ override fun onActivityStarted(activity: Activity) = Unit
+
+ override fun onActivityDestroyed(activity: Activity) = Unit
+
+ override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle?) = Unit
+
+ override fun onActivityStopped(activity: Activity) = Unit
+
+ override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) = Unit
+
+}
diff --git a/utils/src/main/java/ru/touchin/hardware/Extensions.kt b/utils/src/main/java/ru/touchin/hardware/Extensions.kt
new file mode 100644
index 0000000..7e76320
--- /dev/null
+++ b/utils/src/main/java/ru/touchin/hardware/Extensions.kt
@@ -0,0 +1,42 @@
+package ru.touchin.hardware
+
+import android.Manifest
+import android.content.Context
+import android.os.Build
+import android.os.VibrationEffect
+import android.os.VibrationEffect.DEFAULT_AMPLITUDE
+import android.os.Vibrator
+import androidx.annotation.RequiresApi
+import androidx.annotation.RequiresPermission
+
+@RequiresPermission(Manifest.permission.VIBRATE)
+@RequiresApi(Build.VERSION_CODES.O)
+fun Context.startVibrate(vibrationEffect: VibrationEffect) {
+ (this.getSystemService(Context.VIBRATOR_SERVICE) as? Vibrator)?.vibrate(vibrationEffect)
+
+}
+
+@RequiresPermission(Manifest.permission.VIBRATE)
+fun Context.startVibrate(duration: Long = 500, pattern: LongArray = LongArray(0)) {
+ (this.getSystemService(Context.VIBRATOR_SERVICE) as? Vibrator)?.let { vibrationService ->
+ if (pattern.isEmpty()) {
+ vibrationService.vibrate(duration)
+ } else {
+ vibrationService.vibrate(pattern, duration.toInt())
+ }
+ }
+}
+
+@RequiresPermission(Manifest.permission.VIBRATE)
+fun Context.startSimpleVibration(duration: Long = 200) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ startVibrate(VibrationEffect.createOneShot(duration, DEFAULT_AMPLITUDE))
+ } else {
+ startVibrate(duration)
+ }
+}
+
+@RequiresPermission(Manifest.permission.VIBRATE)
+fun Context.cancelVibrate() {
+ (this.getSystemService(Context.VIBRATOR_SERVICE) as? Vibrator)?.cancel()
+}
diff --git a/utils/src/main/java/ru/touchin/roboswag/components/utils/ContextExtensions.kt b/utils/src/main/java/ru/touchin/roboswag/components/utils/ContextExtensions.kt
new file mode 100644
index 0000000..b8522ad
--- /dev/null
+++ b/utils/src/main/java/ru/touchin/roboswag/components/utils/ContextExtensions.kt
@@ -0,0 +1,11 @@
+package ru.touchin.roboswag.components.utils
+
+import android.content.Context
+import android.graphics.drawable.Drawable
+import androidx.annotation.ColorRes
+import androidx.annotation.DrawableRes
+import androidx.core.content.ContextCompat
+
+fun Context.getColorSimple(@ColorRes id: Int): Int = ContextCompat.getColor(this, id)
+
+fun Context.getDrawableSimple(@DrawableRes id: Int): Drawable? = ContextCompat.getDrawable(this, id)
diff --git a/utils/src/main/java/ru/touchin/roboswag/components/utils/MetricExtensions.kt b/utils/src/main/java/ru/touchin/roboswag/components/utils/MetricExtensions.kt
new file mode 100644
index 0000000..ff91a66
--- /dev/null
+++ b/utils/src/main/java/ru/touchin/roboswag/components/utils/MetricExtensions.kt
@@ -0,0 +1,56 @@
+package ru.touchin.roboswag.components.utils
+
+import android.app.Application
+import android.content.Context
+import android.content.res.Resources
+import android.util.DisplayMetrics
+
+private const val MAX_METRICS_TRIES_COUNT = 5
+
+/**
+ * Returns right metrics with non-zero height/width.
+ * It is common bug when metrics are calling at [Application.onCreate] method and it returns metrics with zero height/width.
+ *
+ * @param context [Context] of metrics;
+ * @return [DisplayMetrics].
+ */
+fun Context.getDisplayMetrics(): DisplayMetrics {
+ var result = resources.displayMetrics
+ // it is needed to avoid bug with invalid metrics when user restore application from other application
+ var metricsTryNumber = 0
+ while (metricsTryNumber < MAX_METRICS_TRIES_COUNT && (result.heightPixels <= 0 || result.widthPixels <= 0)) {
+ try {
+ Thread.sleep(500)
+ } catch (ignored: InterruptedException) {
+ return result
+ }
+
+ result = resources.displayMetrics
+ metricsTryNumber++
+ }
+ return result
+}
+
+/**
+ * Simply converts Dp to pixels.
+ *
+ * @return Size in pixels.
+ */
+val Int.px: Int
+ get() = (this * Resources.getSystem().displayMetrics.density).toInt()
+
+/**
+ * Simply converts Dp to pixels.
+ *
+ * @return Size in pixels.
+ */
+val Float.px: Float
+ get() = this * Resources.getSystem().displayMetrics.density
+
+/**
+ * Simply converts pixels to Dp.
+ *
+ * @return Size in dp.
+ */
+val Int.dp: Int
+ get() = (this / Resources.getSystem().displayMetrics.density).toInt()
diff --git a/utils/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.kt b/utils/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.kt
index 642520c..4ea8a15 100644
--- a/utils/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.kt
+++ b/utils/src/main/java/ru/touchin/roboswag/components/utils/UiUtils.kt
@@ -23,17 +23,14 @@ import android.app.Activity
import android.app.Application
import android.content.Context
import android.content.Intent
-import android.content.res.Resources
import android.os.Build
import android.util.DisplayMetrics
-import android.util.TypedValue
import android.view.KeyCharacterMap
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewConfiguration
import android.view.ViewGroup
-import android.view.inputmethod.InputMethodManager
import androidx.annotation.LayoutRes
import ru.touchin.roboswag.components.utils.spans.getSpannedTextWithUrls
@@ -78,8 +75,6 @@ object UiUtils {
*/
object OfMetrics {
- private const val MAX_METRICS_TRIES_COUNT = 5
-
/**
* Returns right metrics with non-zero height/width.
* It is common bug when metrics are calling at [Application.onCreate] method and it returns metrics with zero height/width.
@@ -87,22 +82,11 @@ object UiUtils {
* @param context [Context] of metrics;
* @return [DisplayMetrics].
*/
- fun getDisplayMetrics(context: Context): DisplayMetrics {
- var result = context.resources.displayMetrics
- // it is needed to avoid bug with invalid metrics when user restore application from other application
- var metricsTryNumber = 0
- while (metricsTryNumber < MAX_METRICS_TRIES_COUNT && (result.heightPixels <= 0 || result.widthPixels <= 0)) {
- try {
- Thread.sleep(500)
- } catch (ignored: InterruptedException) {
- return result
- }
-
- result = context.resources.displayMetrics
- metricsTryNumber++
- }
- return result
- }
+ @Deprecated(
+ message = "Use extension instead",
+ replaceWith = ReplaceWith("context.getDisplayMetrics()")
+ )
+ fun getDisplayMetrics(context: Context): DisplayMetrics = context.getDisplayMetrics()
/**
* Simply converts DP to pixels.
@@ -111,10 +95,19 @@ object UiUtils {
* @param sizeInDp Size in DP;
* @return Size in pixels.
*/
- fun dpToPixels(context: Context, sizeInDp: Float): Float =
- TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, sizeInDp, getDisplayMetrics(context))
+ @Deprecated(
+ message = "Use extension instead",
+ replaceWith = ReplaceWith("sizeInDp.px")
+ )
+ @Suppress("detekt.UnusedPrivateMember")
+ fun dpToPixels(context: Context, sizeInDp: Float): Float = sizeInDp.px
- fun pixelsToDp(context: Context, pixels: Int): Int = (pixels * getDisplayMetrics(context).density + 0.5f).toInt()
+ @Deprecated(
+ message = "Use extension instead",
+ replaceWith = ReplaceWith("pixels.dp")
+ )
+ @Suppress("detekt.UnusedPrivateMember")
+ fun pixelsToDp(context: Context, pixels: Int): Int = pixels.dp
}
@@ -129,7 +122,7 @@ object UiUtils {
* @param activity Activity of action bar;
* @return Height of action bar.
*/
- fun getActionBarHeight(activity: Activity): Int = OfMetrics.dpToPixels(activity, 56f).toInt()
+ fun getActionBarHeight(): Int = 56.px
/**
* Returns status bar (on top where system info is) common height.
@@ -205,27 +198,30 @@ object UiUtils {
* @param view [View] to get ID from;
* @return Readable ID.
*/
- fun getViewIdString(view: View): String = try {
- view.resources.getResourceName(view.id)
- } catch (exception: Resources.NotFoundException) {
- view.id.toString()
- }
+ @Deprecated(
+ message = "Use extension instead",
+ replaceWith = ReplaceWith("view.getViewIdString()")
+ )
+ fun getViewIdString(view: View): String = view.getViewIdString()
/**
* Hides device keyboard for target activity.
*/
- fun hideSoftInput(activity: Activity) {
- activity.currentFocus?.let(this::hideSoftInput)
- }
+ @Deprecated(
+ message = "Use extension instead",
+ replaceWith = ReplaceWith("activity.hideSoftInput()")
+ )
+ fun hideSoftInput(activity: Activity) = activity.hideSoftInput()
+
/**
* Hides device keyboard for target view.
*/
- fun hideSoftInput(view: View) {
- view.clearFocus()
- val inputManager = view.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
- inputManager.hideSoftInputFromWindow(view.windowToken, 0)
- }
+ @Deprecated(
+ message = "Use extension instead",
+ replaceWith = ReplaceWith("view.hideSoftInput()")
+ )
+ fun hideSoftInput(view: View) = view.hideSoftInput()
/**
* Shows device keyboard over [Activity] and focuses [View].
@@ -234,11 +230,11 @@ object UiUtils {
*
* @param view View to get focus for input from keyboard.
*/
- fun showSoftInput(view: View) {
- view.requestFocus()
- val inputManager = view.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
- inputManager.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT)
- }
+ @Deprecated(
+ message = "Use extension instead",
+ replaceWith = ReplaceWith("view.showSoftInput()")
+ )
+ fun showSoftInput(view: View) = view.showSoftInput()
}
diff --git a/utils/src/main/java/ru/touchin/roboswag/components/utils/ViewExtensions.kt b/utils/src/main/java/ru/touchin/roboswag/components/utils/ViewExtensions.kt
new file mode 100644
index 0000000..9c65ac7
--- /dev/null
+++ b/utils/src/main/java/ru/touchin/roboswag/components/utils/ViewExtensions.kt
@@ -0,0 +1,48 @@
+package ru.touchin.roboswag.components.utils
+
+import android.app.Activity
+import android.content.Context
+import android.content.res.Resources
+import android.view.View
+import android.view.inputmethod.InputMethodManager
+
+/**
+ * Returns string representation of [View]'s ID.
+ *
+ * @param view [View] to get ID from;
+ * @return Readable ID.
+ */
+fun View.getViewIdString(): String = try {
+ resources.getResourceName(id)
+} catch (exception: Resources.NotFoundException) {
+ id.toString()
+}
+
+/**
+ * Hides device keyboard for target activity.
+ */
+fun Activity.hideSoftInput() {
+ currentFocus?.hideSoftInput()
+}
+
+/**
+ * Hides device keyboard for target view.
+ */
+fun View.hideSoftInput() {
+ clearFocus()
+ val inputManager = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
+ inputManager.hideSoftInputFromWindow(windowToken, 0)
+}
+
+/**
+ * Shows device keyboard over [Activity] and focuses [View].
+ * Do NOT use it if keyboard is over [android.app.Dialog] - it won't work as they have different [Activity.getWindow].
+ * Do NOT use it if you are not sure that view is already added on screen.
+ *
+ * @param view View to get focus for input from keyboard.
+ */
+fun View.showSoftInput() {
+ requestFocus()
+ val inputManager = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
+ inputManager.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT)
+}
diff --git a/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/SpanUtils.kt b/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/SpanUtils.kt
index 5ad792f..25b6fec 100644
--- a/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/SpanUtils.kt
+++ b/utils/src/main/java/ru/touchin/roboswag/components/utils/spans/SpanUtils.kt
@@ -13,8 +13,9 @@ fun String.getSpannedTextWithUrls(
removeUnderline: Boolean = true,
flags: Int = HtmlCompat.FROM_HTML_MODE_COMPACT
): Spanned {
-
- val spannableText = SpannableString(HtmlCompat.fromHtml(this, flags))
+ // HtmlCompat.fromHtml doesn't respect line breaks
+ val text = this.replace(lineBreakRegex, "
")
+ val spannableText = SpannableString(HtmlCompat.fromHtml(text, flags))
// Linkify removes all previous URLSpan's, we need to save all created spans for reapply after Linkify
val spans = spannableText.getUrlSpans()
@@ -38,6 +39,10 @@ fun String.getSpannedTextWithUrls(
return spannableText
}
+private val lineBreakRegex by lazy(LazyThreadSafetyMode.NONE) {
+ "\r?\n".toRegex()
+}
+
private fun SpannableString.getUrlSpans() = getSpans(0, length, URLSpan::class.java)
.map { UrlSpanWithBorders(it, this.getSpanStart(it), this.getSpanEnd(it)) }
diff --git a/utils/src/main/java/ru/touchin/roboswag/core/utils/StringUtils.java b/utils/src/main/java/ru/touchin/roboswag/core/utils/StringUtils.java
index 9768287..d773f3e 100644
--- a/utils/src/main/java/ru/touchin/roboswag/core/utils/StringUtils.java
+++ b/utils/src/main/java/ru/touchin/roboswag/core/utils/StringUtils.java
@@ -19,12 +19,13 @@
package ru.touchin.roboswag.core.utils;
-import androidx.annotation.NonNull;
-
import java.io.UnsupportedEncodingException;
+import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
+import androidx.annotation.NonNull;
+
/**
* Created by Gavriil Sitnikov on 29/08/2016.
* Utility class to providing some string-related helper methods.
@@ -40,7 +41,7 @@ public final class StringUtils {
@NonNull
public static String md5(@NonNull final String string) throws NoSuchAlgorithmException, UnsupportedEncodingException {
final MessageDigest digest = MessageDigest.getInstance("MD5");
- digest.update(string.getBytes("UTF-8"));
+ digest.update(string.getBytes(StandardCharsets.UTF_8));
final byte[] messageDigestArray = digest.digest();
final StringBuilder hexString = new StringBuilder();
diff --git a/utils/src/main/java/ru/touchin/templates/DeviceUtils.java b/utils/src/main/java/ru/touchin/templates/DeviceUtils.java
deleted file mode 100644
index 4a48f10..0000000
--- a/utils/src/main/java/ru/touchin/templates/DeviceUtils.java
+++ /dev/null
@@ -1,154 +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.templates;
-
-import android.Manifest;
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.os.Process;
-import androidx.annotation.NonNull;
-import android.telephony.TelephonyManager;
-
-/**
- * Utility class that is providing common methods related to android device.
- */
-public final class DeviceUtils {
-
- /**
- * Detects active network type.
- *
- * @param context Application context
- * @return Active network type {@link NetworkType}
- */
- @NonNull
- public static NetworkType getNetworkType(@NonNull final Context context) {
- if (context.checkPermission(Manifest.permission.ACCESS_NETWORK_STATE, Process.myPid(), Process.myUid())
- != PackageManager.PERMISSION_GRANTED) {
- return NetworkType.UNKNOWN;
- }
- final ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
- if (cm == null) {
- return NetworkType.UNKNOWN;
- }
- @SuppressLint("MissingPermission") final NetworkInfo info = cm.getActiveNetworkInfo();
- if (info == null || !info.isConnected()) {
- return NetworkType.NONE;
- }
- switch (info.getType()) {
- case ConnectivityManager.TYPE_WIFI:
- return NetworkType.WI_FI;
- case ConnectivityManager.TYPE_MOBILE:
- return getMobileNetworkType(info);
- default:
- return NetworkType.UNKNOWN;
- }
- }
-
- /**
- * Detects if some network connected.
- *
- * @param context Application context
- * @return true if network connected, false otherwise.
- */
- public static boolean isNetworkConnected(@NonNull final Context context) {
- return getNetworkType(context) != NetworkType.NONE;
- }
-
- @NonNull
- private static NetworkType getMobileNetworkType(@NonNull final NetworkInfo info) {
- switch (info.getSubtype()) {
- case TelephonyManager.NETWORK_TYPE_GPRS:
- case TelephonyManager.NETWORK_TYPE_EDGE:
- case TelephonyManager.NETWORK_TYPE_CDMA:
- case TelephonyManager.NETWORK_TYPE_1xRTT:
- case TelephonyManager.NETWORK_TYPE_IDEN:
- return NetworkType.MOBILE_2G;
- case TelephonyManager.NETWORK_TYPE_UMTS:
- case TelephonyManager.NETWORK_TYPE_EVDO_0:
- case TelephonyManager.NETWORK_TYPE_EVDO_A:
- case TelephonyManager.NETWORK_TYPE_HSDPA:
- case TelephonyManager.NETWORK_TYPE_HSUPA:
- case TelephonyManager.NETWORK_TYPE_HSPA:
- case TelephonyManager.NETWORK_TYPE_EVDO_B:
- case TelephonyManager.NETWORK_TYPE_EHRPD:
- case TelephonyManager.NETWORK_TYPE_HSPAP:
- return NetworkType.MOBILE_3G;
- case TelephonyManager.NETWORK_TYPE_LTE:
- case 19: // NETWORK_TYPE_LTE_CA is hide
- return NetworkType.MOBILE_LTE;
- case TelephonyManager.NETWORK_TYPE_UNKNOWN:
- default:
- return NetworkType.UNKNOWN;
- }
- }
-
- private DeviceUtils() {
- }
-
- /**
- * Available network types.
- */
- public enum NetworkType {
- /**
- * Mobile 2G network.
- */
- MOBILE_2G("2g"),
- /**
- * Mobile 3G network.
- */
- MOBILE_3G("3g"),
- /**
- * Mobile LTE network.
- */
- MOBILE_LTE("lte"),
- /**
- * Wi-Fi network.
- */
- WI_FI("Wi-Fi"),
- /**
- * Unknown network type.
- */
- UNKNOWN("unknown"),
- /**
- * No network.
- */
- NONE("none");
-
- @NonNull
- private final String name;
-
- NetworkType(@NonNull final String name) {
- this.name = name;
- }
-
- /**
- * @return Network type readable name.
- */
- @NonNull
- public String getName() {
- return name;
- }
-
- }
-
-}
diff --git a/utils/src/main/java/ru/touchin/templates/DeviceUtils.kt b/utils/src/main/java/ru/touchin/templates/DeviceUtils.kt
new file mode 100644
index 0000000..db3b7d0
--- /dev/null
+++ b/utils/src/main/java/ru/touchin/templates/DeviceUtils.kt
@@ -0,0 +1,115 @@
+/*
+ * 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.templates
+
+import android.content.Context
+import android.net.NetworkInfo
+import android.telephony.TelephonyManager
+
+/**
+ * Utility class that is providing common methods related to android device.
+ */
+object DeviceUtils {
+ /**
+ * Detects active network type.
+ *
+ * @param context Application context
+ * @return Active network type [NetworkType]
+ */
+ @Deprecated(
+ "Use extension instead",
+ ReplaceWith("context.getNetworkType()")
+ )
+ fun getNetworkType(context: Context) = context.getNetworkType()
+
+ /**
+ * Detects if some network connected.
+ *
+ * @param context Application context
+ * @return true if network connected, false otherwise.
+ */
+ @Deprecated(
+ "Use extension instead",
+ ReplaceWith("context.isNetworkConnected()")
+ )
+ fun isNetworkConnected(context: Context) = context.isNetworkConnected()
+
+
+ fun getMobileNetworkType(info: NetworkInfo): NetworkType =
+ when (info.subtype) {
+ TelephonyManager.NETWORK_TYPE_GPRS,
+ TelephonyManager.NETWORK_TYPE_EDGE,
+ TelephonyManager.NETWORK_TYPE_CDMA,
+ TelephonyManager.NETWORK_TYPE_1xRTT,
+ TelephonyManager.NETWORK_TYPE_IDEN -> NetworkType.MOBILE_2G
+ TelephonyManager.NETWORK_TYPE_UMTS,
+ TelephonyManager.NETWORK_TYPE_EVDO_0,
+ TelephonyManager.NETWORK_TYPE_EVDO_A,
+ TelephonyManager.NETWORK_TYPE_HSDPA,
+ TelephonyManager.NETWORK_TYPE_HSUPA,
+ TelephonyManager.NETWORK_TYPE_HSPA,
+ TelephonyManager.NETWORK_TYPE_EVDO_B,
+ TelephonyManager.NETWORK_TYPE_EHRPD,
+ TelephonyManager.NETWORK_TYPE_HSPAP -> NetworkType.MOBILE_3G
+ TelephonyManager.NETWORK_TYPE_LTE,
+ 19 -> NetworkType.MOBILE_LTE
+ TelephonyManager.NETWORK_TYPE_UNKNOWN -> NetworkType.UNKNOWN
+ else -> NetworkType.UNKNOWN
+ }
+
+ /**
+ * Available network types.
+ */
+ enum class NetworkType(val networkName: String) {
+ /**
+ * Mobile 2G network.
+ */
+ MOBILE_2G("2g"),
+
+ /**
+ * Mobile 3G network.
+ */
+ MOBILE_3G("3g"),
+
+ /**
+ * Mobile LTE network.
+ */
+ MOBILE_LTE("lte"),
+
+ /**
+ * Wi-Fi network.
+ */
+ WI_FI("Wi-Fi"),
+
+ /**
+ * Unknown network type.
+ */
+ UNKNOWN("unknown"),
+
+ /**
+ * No network.
+ */
+ NONE("none");
+
+ /**
+ * @return Network type readable name.
+ */
+
+ }
+}
diff --git a/utils/src/main/java/ru/touchin/templates/DeviceUtilsExtensions.kt b/utils/src/main/java/ru/touchin/templates/DeviceUtilsExtensions.kt
new file mode 100644
index 0000000..ef213a9
--- /dev/null
+++ b/utils/src/main/java/ru/touchin/templates/DeviceUtilsExtensions.kt
@@ -0,0 +1,46 @@
+package ru.touchin.templates
+
+import android.Manifest
+import android.annotation.SuppressLint
+import android.content.Context
+import android.content.pm.PackageManager
+import android.hardware.biometrics.BiometricManager
+import android.net.ConnectivityManager
+import android.os.Build
+import android.os.Process
+import androidx.annotation.RequiresApi
+import androidx.annotation.RequiresPermission
+import androidx.core.hardware.fingerprint.FingerprintManagerCompat
+import ru.touchin.templates.DeviceUtils.NetworkType
+import ru.touchin.templates.DeviceUtils.getMobileNetworkType
+
+fun Context.isNetworkConnected(): Boolean = getNetworkType() != NetworkType.NONE
+
+fun Context.getNetworkType(): NetworkType {
+ if (checkPermission(Manifest.permission.ACCESS_NETWORK_STATE, Process.myPid(), Process.myUid())
+ != PackageManager.PERMISSION_GRANTED) {
+ return NetworkType.UNKNOWN
+ }
+ val cm = getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager?
+ ?: return NetworkType.UNKNOWN
+ @SuppressLint("MissingPermission") val info = cm.activeNetworkInfo
+ return if (info == null || !info.isConnected) {
+ NetworkType.NONE
+ } else when (info.type) {
+ ConnectivityManager.TYPE_WIFI -> NetworkType.WI_FI
+ ConnectivityManager.TYPE_MOBILE -> getMobileNetworkType(info)
+ else -> NetworkType.UNKNOWN
+ }
+}
+
+@RequiresApi(Build.VERSION_CODES.M)
+@Suppress("InlinedApi")
+@RequiresPermission(anyOf = [Manifest.permission.USE_FINGERPRINT, Manifest.permission.USE_BIOMETRIC])
+fun Context.canAuthenticateWithBiometrics(): Boolean = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
+ val fingerprintManagerCompat = FingerprintManagerCompat.from(this)
+ fingerprintManagerCompat.hasEnrolledFingerprints() && fingerprintManagerCompat.isHardwareDetected
+} else {
+ getSystemService(BiometricManager::class.java)?.let { biometricManager ->
+ biometricManager.canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS
+ } ?: false
+}
diff --git a/views/build.gradle b/views/build.gradle
index 6b34f5b..e76deb5 100644
--- a/views/build.gradle
+++ b/views/build.gradle
@@ -1,24 +1,39 @@
-apply plugin: 'com.android.library'
+apply from: "../android-configs/lib-config.gradle"
apply plugin: 'kotlin-android'
android {
- compileSdkVersion versions.compileSdk
-
- defaultConfig {
- minSdkVersion 16
- }
-
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
+ buildFeatures {
+ viewBinding true
}
}
dependencies {
- api project(":utils")
- api project(":logging")
+ implementation project(":utils")
+ implementation project(":kotlin-extensions")
+ implementation project(":logging")
- implementation "com.google.android.material:material:$versions.material"
- implementation "androidx.core:core-ktx:$versions.coreKtx"
- implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+ implementation "com.google.android.material:material"
+ implementation "androidx.core:core-ktx"
+
+ constraints {
+ implementation("com.google.android.material:material") {
+ version {
+ require '1.0.0'
+ }
+ }
+ implementation("androidx.core:core-ktx") {
+ version {
+ require '1.3.1'
+ }
+ }
+ implementation("org.jetbrains.kotlin:kotlin-stdlib") {
+ version {
+ require '1.3.0'
+ }
+ }
+ }
+}
+
+repositories {
+ mavenCentral()
}
diff --git a/views/src/main/AndroidManifest.xml b/views/src/main/AndroidManifest.xml
index 354617c..6f72453 100644
--- a/views/src/main/AndroidManifest.xml
+++ b/views/src/main/AndroidManifest.xml
@@ -1,2 +1,2 @@
+ package="ru.touchin.roboswag.views"/>
diff --git a/views/src/main/java/ru/touchin/roboswag/components/views/MaterialLoadingBar.java b/views/src/main/java/ru/touchin/roboswag/views/MaterialLoadingBar.java
similarity index 98%
rename from views/src/main/java/ru/touchin/roboswag/components/views/MaterialLoadingBar.java
rename to views/src/main/java/ru/touchin/roboswag/views/MaterialLoadingBar.java
index 2592c8a..1b495b2 100644
--- a/views/src/main/java/ru/touchin/roboswag/components/views/MaterialLoadingBar.java
+++ b/views/src/main/java/ru/touchin/roboswag/views/MaterialLoadingBar.java
@@ -17,7 +17,7 @@
*
*/
-package ru.touchin.roboswag.components.views;
+package ru.touchin.roboswag.views;
import android.content.Context;
import android.content.res.TypedArray;
diff --git a/views/src/main/java/ru/touchin/roboswag/components/views/MaterialProgressDrawable.java b/views/src/main/java/ru/touchin/roboswag/views/MaterialProgressDrawable.java
similarity index 99%
rename from views/src/main/java/ru/touchin/roboswag/components/views/MaterialProgressDrawable.java
rename to views/src/main/java/ru/touchin/roboswag/views/MaterialProgressDrawable.java
index 667a757..b102bb3 100644
--- a/views/src/main/java/ru/touchin/roboswag/components/views/MaterialProgressDrawable.java
+++ b/views/src/main/java/ru/touchin/roboswag/views/MaterialProgressDrawable.java
@@ -17,7 +17,7 @@
*
*/
-package ru.touchin.roboswag.components.views;
+package ru.touchin.roboswag.views;
import android.content.Context;
import android.graphics.Canvas;
diff --git a/views/src/main/java/ru/touchin/roboswag/components/views/SkeletonView.kt b/views/src/main/java/ru/touchin/roboswag/views/SkeletonView.kt
similarity index 99%
rename from views/src/main/java/ru/touchin/roboswag/components/views/SkeletonView.kt
rename to views/src/main/java/ru/touchin/roboswag/views/SkeletonView.kt
index 5ea869a..327b7fd 100644
--- a/views/src/main/java/ru/touchin/roboswag/components/views/SkeletonView.kt
+++ b/views/src/main/java/ru/touchin/roboswag/views/SkeletonView.kt
@@ -1,4 +1,4 @@
-package ru.touchin.roboswag.components.views
+package ru.touchin.roboswag.views
import android.animation.ValueAnimator
import android.content.Context
diff --git a/views/src/main/java/ru/touchin/roboswag/components/views/TypefacedEditText.java b/views/src/main/java/ru/touchin/roboswag/views/TypefacedEditText.java
similarity index 98%
rename from views/src/main/java/ru/touchin/roboswag/components/views/TypefacedEditText.java
rename to views/src/main/java/ru/touchin/roboswag/views/TypefacedEditText.java
index 6730704..3d38246 100644
--- a/views/src/main/java/ru/touchin/roboswag/components/views/TypefacedEditText.java
+++ b/views/src/main/java/ru/touchin/roboswag/views/TypefacedEditText.java
@@ -17,7 +17,7 @@
*
*/
-package ru.touchin.roboswag.components.views;
+package ru.touchin.roboswag.views;
import android.content.Context;
import android.content.res.TypedArray;
@@ -40,7 +40,9 @@ import java.util.ArrayList;
import java.util.List;
import ru.touchin.defaults.DefaultTextWatcher;
-import ru.touchin.roboswag.components.views.internal.AttributesUtils;
+import ru.touchin.roboswag.views.BuildConfig;
+import ru.touchin.roboswag.views.R;
+import ru.touchin.roboswag.views.internal.AttributesUtils;
import ru.touchin.roboswag.core.log.Lc;
/**
diff --git a/views/src/main/java/ru/touchin/roboswag/components/views/TypefacedTextView.java b/views/src/main/java/ru/touchin/roboswag/views/TypefacedTextView.java
similarity index 99%
rename from views/src/main/java/ru/touchin/roboswag/components/views/TypefacedTextView.java
rename to views/src/main/java/ru/touchin/roboswag/views/TypefacedTextView.java
index 8bce61a..ea849ad 100644
--- a/views/src/main/java/ru/touchin/roboswag/components/views/TypefacedTextView.java
+++ b/views/src/main/java/ru/touchin/roboswag/views/TypefacedTextView.java
@@ -17,7 +17,7 @@
*
*/
-package ru.touchin.roboswag.components.views;
+package ru.touchin.roboswag.views;
import android.annotation.SuppressLint;
import android.content.Context;
@@ -34,7 +34,9 @@ import java.util.ArrayList;
import java.util.List;
import ru.touchin.roboswag.components.utils.UiUtils;
-import ru.touchin.roboswag.components.views.internal.AttributesUtils;
+import ru.touchin.roboswag.views.BuildConfig;
+import ru.touchin.roboswag.views.R;
+import ru.touchin.roboswag.views.internal.AttributesUtils;
import ru.touchin.roboswag.core.log.Lc;
/**
diff --git a/views/src/main/java/ru/touchin/roboswag/components/views/internal/AttributesUtils.java b/views/src/main/java/ru/touchin/roboswag/views/internal/AttributesUtils.java
similarity index 99%
rename from views/src/main/java/ru/touchin/roboswag/components/views/internal/AttributesUtils.java
rename to views/src/main/java/ru/touchin/roboswag/views/internal/AttributesUtils.java
index abe7169..16f09cd 100644
--- a/views/src/main/java/ru/touchin/roboswag/components/views/internal/AttributesUtils.java
+++ b/views/src/main/java/ru/touchin/roboswag/views/internal/AttributesUtils.java
@@ -17,7 +17,7 @@
*
*/
-package ru.touchin.roboswag.components.views.internal;
+package ru.touchin.roboswag.views.internal;
import android.content.Context;
import android.content.res.TypedArray;
diff --git a/views/src/main/java/ru/touchin/roboswag/views/widget/AmountWithDecimalDecorator.kt b/views/src/main/java/ru/touchin/roboswag/views/widget/AmountWithDecimalDecorator.kt
new file mode 100644
index 0000000..526f5a1
--- /dev/null
+++ b/views/src/main/java/ru/touchin/roboswag/views/widget/AmountWithDecimalDecorator.kt
@@ -0,0 +1,221 @@
+package ru.touchin.roboswag.views.widget
+
+import android.widget.EditText
+import androidx.core.widget.doOnTextChanged
+import java.text.DecimalFormat
+import java.text.DecimalFormatSymbols
+import kotlin.math.abs
+import kotlin.math.max
+import kotlin.math.min
+import kotlin.math.pow
+import kotlin.math.roundToLong
+
+class AmountWithDecimalDecorator(
+ val editText: EditText,
+ val decimalSeparator: String = DEFAULT_DECIMAL_SEPARATOR,
+ val decimalPartLength: Int = DEFAULT_DECIMAL_PART_LENGTH,
+ val integerPartLength: Int = DEFAULT_INTEGER_PART_LENGTH,
+ val isSeparatorCutInvalidDecimalLength: Boolean = false
+) {
+
+ companion object {
+
+ private const val COMMON_MONEY_MASK = "###,##0"
+ private const val DOT_SYMBOL = "."
+ private const val DEFAULT_DECIMAL_SEPARATOR = DOT_SYMBOL
+ private const val GROUPING_SEPARATOR = ' '
+ private const val DEFAULT_DECIMAL_PART_LENGTH = 2
+ private const val DEFAULT_INTEGER_PART_LENGTH = 13
+ private val hardcodedSymbols = listOf(GROUPING_SEPARATOR)
+ private val possibleDecimalSeparators = listOf(",", ".")
+
+ }
+
+ var onTextChanged: (text: String) -> Unit = {}
+
+ private var previousInputtedText = ""
+ private var isTextWasArtificiallyChanged = true
+
+ init {
+ if (!possibleDecimalSeparators.contains(decimalSeparator)) {
+ throw IllegalArgumentException("Not allowed decimal separator. Supports only: $possibleDecimalSeparators")
+ }
+
+ editText.doOnTextChanged { text, _, _, _ -> doOnTextChanged(text.toString()) }
+ }
+
+ fun getTextWithoutFormatting(decimalSeparatorToReplace: String = decimalSeparator): String =
+ previousInputtedText.withoutFormatting(decimalSeparatorToReplace)
+
+ @Suppress("detekt.TooGenericExceptionCaught")
+ private fun doOnTextChanged(text: String) {
+ if (isTextWasArtificiallyChanged) {
+ isTextWasArtificiallyChanged = false
+ val cursorPosition = editText.selectionStart
+ try {
+ var currentText = text
+ possibleDecimalSeparators.forEach { currentText = currentText.replace(it, decimalSeparator) }
+ val currentIntegerPartLength = currentText.withoutFormatting().split(decimalSeparator)[0].length
+
+ if (isIntegerPartToLong(currentIntegerPartLength)) {
+ currentText = trimIntegerPart(currentText)
+ }
+
+ if (isTextFormatIncorrect(currentText)) {
+ setTextWhenNewInputIncorrect(currentText, cursorPosition)
+ return
+ }
+
+ if (isTextHasHeadZero(currentText)) {
+ setTextWithHeadZero(currentText, cursorPosition)
+ return
+ }
+
+ val currentDecimalPartLength = currentText.split(decimalSeparator).getOrNull(1)?.length
+ if (isDecimalPartTooLong(currentDecimalPartLength)) {
+ setTextWhenNewInputIncorrect(currentText, cursorPosition)
+ return
+ }
+
+ val formattedText = if (currentText.isNotEmpty()) {
+ currentText.withoutFormatting().formatMoney(currentDecimalPartLength)
+ } else ""
+
+ if (isTextErased(previousInputtedText, formattedText)) {
+ setTextAfterUserErase(formattedText, cursorPosition)
+ } else {
+ setTextAfterUserInput(formattedText, cursorPosition)
+ }
+ } catch (e: Throwable) {
+ editText.setText(previousInputtedText)
+ editText.setSelection(previousInputtedText.length)
+ }
+ } else {
+ previousInputtedText = text
+ isTextWasArtificiallyChanged = true
+ onTextChanged(text)
+ }
+ }
+
+ private fun isIntegerPartToLong(currentIntegerPartLength: Int) = currentIntegerPartLength > integerPartLength
+
+ private fun trimIntegerPart(currentText: String): String {
+ val splittedText = currentText.withoutFormatting().split(decimalSeparator)
+ val integerPart = splittedText[0]
+ val decimalPart = if (splittedText.size > 1) decimalSeparator + splittedText[1] else ""
+ return integerPart.take(integerPartLength) + decimalPart
+ }
+
+ private fun isTextFormatIncorrect(currentText: String) =
+ currentText == decimalSeparator
+ || currentText.count { it == decimalSeparator[0] } > 1
+ || currentText.take(2) == "00"
+
+ private fun isTextHasHeadZero(currentText: String) =
+ currentText.length >= 2 && currentText[0] == '0' && currentText[1] != decimalSeparator[0]
+
+ private fun setTextWithHeadZero(text: String, cursorPosition: Int) {
+ if (abs(previousInputtedText.length - text.length) > 1) {
+ setTextWhichWasInserted(text)
+ } else {
+ editText.setText(text.substring(1, text.length))
+ editText.setSelection(max(cursorPosition - 1, 0))
+ }
+ }
+
+ private fun setTextWhenNewInputIncorrect(text: String, cursorPosition: Int) {
+ if (abs(previousInputtedText.length - text.length) > 1) {
+ setTextWhichWasInserted(text)
+ } else {
+ editText.setText(previousInputtedText)
+ editText.setSelection(max(cursorPosition - 1, 0))
+ }
+ }
+
+ private fun setTextAfterUserInput(formattedText: String, cursorPosition: Int) {
+ val diff = formattedText.length - previousInputtedText.length - 1
+ editText.setText(formattedText)
+ editText.setSelection(min(cursorPosition + diff, formattedText.length))
+ }
+
+ private fun setTextAfterUserErase(formattedText: String, cursorPosition: Int) {
+ if (!previousInputtedText.contains(decimalSeparator)
+ && formattedText.contains(decimalSeparator)
+ ) {
+ editText.setText(formattedText)
+ editText.setSelection(min(formattedText.length, formattedText.indexOf(decimalSeparator) + 1))
+ return
+ }
+ val diff = previousInputtedText.length - formattedText.length
+ if (diff == 0) {
+ editText.setText(formattedText)
+ editText.setSelection(min(cursorPosition, formattedText.length))
+ } else {
+ editText.setText(formattedText)
+ editText.setSelection(max(cursorPosition - diff + 1, 0))
+ }
+ }
+
+ private fun isDecimalPartTooLong(currentDecimalPartLength: Int?) =
+ !isSeparatorCutInvalidDecimalLength
+ && currentDecimalPartLength != null
+ && currentDecimalPartLength > decimalPartLength
+
+ private fun setTextWhichWasInserted(text: String) {
+ var result = ""
+ var decimalLength = -1
+ var index = 0
+ while (decimalLength < decimalPartLength && index < text.length) {
+ if (text[index] == decimalSeparator[0]) {
+ if (decimalLength == -1 && decimalPartLength != 0) {
+ decimalLength = 0
+ result += text[index]
+ } else {
+ break
+ }
+ } else {
+ result += text[index]
+ }
+ index++
+ }
+ result = result.formatMoney(decimalPartLength)
+ editText.setText(result)
+ editText.setSelection(result.length)
+ }
+
+ private fun String.withoutFormatting(decimalSeparatorToReplace: String = decimalSeparator): String {
+ var result = this
+ hardcodedSymbols.forEach { result = this.replace(it.toString(), "") }
+ result = result.replace(decimalSeparator, decimalSeparatorToReplace)
+ return result
+ }
+
+ private fun String.replaceSeparatorsToDot(): String {
+ var result = this
+ possibleDecimalSeparators.forEach {
+ result = result.replace(it, DOT_SYMBOL)
+ }
+ return result.withoutFormatting()
+ }
+
+ private fun isTextErased(textBefore: String, formattedText: String) =
+ formattedText.length <= textBefore.length
+
+ private fun String.formatMoney(currentDecimalPartLength: Int?): String {
+ var mask = COMMON_MONEY_MASK
+ if (currentDecimalPartLength != null && decimalPartLength != 0) {
+ mask += DOT_SYMBOL + "0".repeat(min(currentDecimalPartLength, decimalPartLength))
+ }
+
+ val formatter = DecimalFormat(mask)
+ formatter.decimalFormatSymbols = DecimalFormatSymbols().also {
+ it.decimalSeparator = decimalSeparator[0]
+ it.groupingSeparator = GROUPING_SEPARATOR
+ }
+ return formatter.format(this.replaceSeparatorsToDot().toDouble().floor())
+ }
+
+ // TODO make it simple
+ private fun Double.floor() = (this * 10.0.pow(decimalPartLength)).roundToLong() / 10.0.pow(decimalPartLength)
+
+}
diff --git a/views/src/main/java/ru/touchin/roboswag/views/widget/LoadingContentView.kt b/views/src/main/java/ru/touchin/roboswag/views/widget/LoadingContentView.kt
new file mode 100644
index 0000000..72c5f71
--- /dev/null
+++ b/views/src/main/java/ru/touchin/roboswag/views/widget/LoadingContentView.kt
@@ -0,0 +1,69 @@
+package ru.touchin.roboswag.views.widget
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import androidx.core.content.withStyledAttributes
+import ru.touchin.extensions.observable
+import ru.touchin.extensions.setOnRippleClickListener
+import ru.touchin.roboswag.views.databinding.ProgressViewBinding
+import ru.touchin.roboswag.views.R
+import kotlin.properties.Delegates
+
+//TODO make customizable views list and views style
+class LoadingContentView @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0
+) : Switcher(context, attrs) {
+
+ private val binding = ProgressViewBinding.inflate(LayoutInflater.from(context), this)
+
+ var state by Delegates.observable(State.Loading, this::updateView)
+
+ init {
+ if (attrs != null) {
+ context.withStyledAttributes(attrs, R.styleable.LoadingContentView, defStyleAttr, 0) {
+ if (hasValue(R.styleable.LoadingContentView_stubText)) {
+ setStubText(getString(R.styleable.LoadingContentView_stubText))
+ }
+ }
+ }
+ }
+
+ private fun setStubText(text: String?) {
+ binding.textStub.text = text
+ }
+
+ private fun updateView(state: State) {
+ if (state == State.ShowContent) {
+ getChildAt(childCount - 1)?.let { showChild(it.id) }
+ } else {
+ when (state) {
+ is State.Stub -> {
+ setStubText(state.stubText)
+ showChild(R.id.text_stub)
+ }
+ is State.Loading -> {
+ showChild(R.id.progress_bar)
+ }
+ is State.Error -> {
+ binding.apply {
+ errorText.text = state.errorText
+ errorRepeatButton.setOnRippleClickListener { state.action.invoke() }
+ errorRepeatButton.text = state.repeatButtonText
+ showChild(R.id.error_with_repeat)
+ }
+ }
+ }
+ }
+ }
+
+ sealed class State {
+ object ShowContent : State()
+ data class Stub(val stubText: String) : State()
+ object Loading : State()
+ data class Error(val action: () -> Unit, val errorText: String, val repeatButtonText: String) : State()
+ }
+
+}
diff --git a/views/src/main/java/ru/touchin/widget/Switcher.java b/views/src/main/java/ru/touchin/roboswag/views/widget/Switcher.java
similarity index 97%
rename from views/src/main/java/ru/touchin/widget/Switcher.java
rename to views/src/main/java/ru/touchin/roboswag/views/widget/Switcher.java
index 25be341..949f993 100644
--- a/views/src/main/java/ru/touchin/widget/Switcher.java
+++ b/views/src/main/java/ru/touchin/roboswag/views/widget/Switcher.java
@@ -1,4 +1,4 @@
-package ru.touchin.widget;
+package ru.touchin.roboswag.views.widget;
import android.content.Context;
import android.content.res.TypedArray;
@@ -15,7 +15,7 @@ import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat;
-import ru.touchin.roboswag.components.views.R;
+import ru.touchin.roboswag.views.R;
public class Switcher extends FrameLayout {
diff --git a/views/src/main/res/drawable/light_button_background.xml b/views/src/main/res/drawable/light_button_background.xml
new file mode 100644
index 0000000..5719816
--- /dev/null
+++ b/views/src/main/res/drawable/light_button_background.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/views/src/main/res/font/roboto.ttf b/views/src/main/res/font/roboto.ttf
new file mode 100644
index 0000000..2c97eea
Binary files /dev/null and b/views/src/main/res/font/roboto.ttf differ
diff --git a/views/src/main/res/font/roboto_medium.ttf b/views/src/main/res/font/roboto_medium.ttf
new file mode 100644
index 0000000..1a7f3b0
Binary files /dev/null and b/views/src/main/res/font/roboto_medium.ttf differ
diff --git a/views/src/main/res/layout/progress_view.xml b/views/src/main/res/layout/progress_view.xml
new file mode 100644
index 0000000..6e82f03
--- /dev/null
+++ b/views/src/main/res/layout/progress_view.xml
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/views/src/main/res/values/attrs.xml b/views/src/main/res/values/attrs.xml
index c445ff7..7c3f8ca 100644
--- a/views/src/main/res/values/attrs.xml
+++ b/views/src/main/res/values/attrs.xml
@@ -47,4 +47,8 @@
+
+
+
+
diff --git a/views/src/main/res/values/colors.xml b/views/src/main/res/values/colors.xml
index c447899..e01d549 100644
--- a/views/src/main/res/values/colors.xml
+++ b/views/src/main/res/values/colors.xml
@@ -4,4 +4,8 @@
#33E5E5EA
#D9DFE2
-
+
+ #8D8EA6
+ #999BBF
+ #141233
+
\ No newline at end of file
diff --git a/views/src/main/res/values/styles.xml b/views/src/main/res/values/styles.xml
index 0dbe7b6..06eee69 100644
--- a/views/src/main/res/values/styles.xml
+++ b/views/src/main/res/values/styles.xml
@@ -4,4 +4,17 @@
- @android:anim/fade_in
- @android:anim/fade_out
+
+
+
+
+
+
+
diff --git a/yandex-map/build.gradle b/yandex-map/build.gradle
index e34fbaf..0f4b147 100644
--- a/yandex-map/build.gradle
+++ b/yandex-map/build.gradle
@@ -1,18 +1,15 @@
-apply plugin: 'com.android.library'
-apply plugin: 'kotlin-android'
-
-android {
- compileSdkVersion versions.compileSdk
-
- defaultConfig {
- minSdkVersion 17
- }
-}
+apply from: "../android-configs/lib-config.gradle"
dependencies {
- api project(":base-map")
+ implementation project(":base-map")
- implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+ implementation "com.yandex.android:mapkit"
- implementation "com.yandex.android:mapkit:$versions.yandex_mapkit"
+ constraints {
+ implementation("com.yandex.android:mapkit") {
+ version {
+ require '3.4.0'
+ }
+ }
+ }
}