├── .gitmodules ├── alerts ├── .gitignore ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ ├── res │ │ └── values │ │ │ └── strings.xml │ │ └── java │ │ └── ru │ │ └── touchin │ │ └── roboswag │ │ └── viewable_dialog │ │ └── AlertDialogUtils.kt └── build.gradle ├── recaptcha ├── .gitignore ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── ru │ │ └── touchin │ │ └── recaptcha │ │ ├── GoogleCaptchaClient.kt │ │ ├── CaptchaClient.kt │ │ ├── HuaweiCaptchaClient.kt │ │ └── CaptchaManager.kt ├── README.md └── build.gradle ├── utils ├── .gitignore ├── src │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── ru │ │ │ └── touchin │ │ │ ├── roboswag │ │ │ ├── components │ │ │ │ └── utils │ │ │ │ │ ├── spans │ │ │ │ │ ├── URLSpanWithoutUnderline.kt │ │ │ │ │ ├── PhoneSpan.kt │ │ │ │ │ ├── ColoredUrlSpan.kt │ │ │ │ │ └── TypefaceSpan.kt │ │ │ │ │ ├── ContextExtensions.kt │ │ │ │ │ ├── SimpleTextWatcher.kt │ │ │ │ │ └── MetricExtensions.kt │ │ │ └── core │ │ │ │ └── utils │ │ │ │ └── DateFormatUtils.kt │ │ │ ├── defaults │ │ │ ├── DefaultTextWatcher.java │ │ │ └── DefaultActivityLifecycleCallbacks.kt │ │ │ └── hardware │ │ │ └── Extensions.kt │ └── test │ │ └── java │ │ └── DateFormatUtilsTest.kt └── build.gradle ├── views ├── .gitignore ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ ├── res │ │ ├── font │ │ │ ├── roboto.ttf │ │ │ ├── roboto_bold.ttf │ │ │ ├── roboto_medium.ttf │ │ │ └── roboto_regular.ttf │ │ ├── anim │ │ │ ├── fade_out_animation.xml │ │ │ └── fade_in_animation.xml │ │ ├── values │ │ │ ├── colors.xml │ │ │ └── styles.xml │ │ └── drawable │ │ │ └── light_button_background.xml │ │ └── java │ │ └── ru │ │ └── touchin │ │ └── roboswag │ │ └── views │ │ ├── CrossfadeView.kt │ │ ├── text_view │ │ ├── EllipsizeSpannableTextView.kt │ │ └── StyleableSubTextView.kt │ │ └── ActionTextView.kt └── build.gradle ├── base-map ├── .gitignore ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── res │ └── drawable │ │ └── marker_default_icon.xml │ └── java │ └── ru │ └── touchin │ └── basemap │ ├── BaseIconGenerator.kt │ ├── BaseMapItemRenderer.kt │ └── MapExtension.kt ├── client-services ├── .gitignore ├── src │ └── main │ │ ├── java │ │ └── ru │ │ │ └── touchin │ │ │ └── client_services │ │ │ ├── MobileService.kt │ │ │ └── ServicesUtils.kt │ │ └── AndroidManifest.xml └── build.gradle ├── google-map ├── .gitignore ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ ├── res │ │ ├── drawable │ │ │ └── default_cluster_background.xml │ │ └── layout │ │ │ └── view_google_map_cluster_item.xml │ │ └── java │ │ └── ru │ │ └── touchin │ │ └── googlemap │ │ └── GoogleMapItemRenderer.kt └── build.gradle ├── lifecycle ├── .gitignore ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ ├── res │ │ └── values │ │ │ └── strings.xml │ │ └── java │ │ └── ru │ │ └── touchin │ │ └── lifecycle │ │ ├── viewmodel │ │ ├── LifecycleViewModelProviders.kt │ │ ├── ViewModelFactoryProvider.kt │ │ └── ViewModelFactory.kt │ │ ├── extensions │ │ ├── ImmutableExt.kt │ │ └── LifecycleOwner.kt │ │ ├── event │ │ ├── Event.kt │ │ └── ContentEvent.kt │ │ ├── livedata │ │ ├── SingleLiveEvent.kt │ │ └── EmptySingleLiveEvent.kt │ │ ├── scope │ │ └── ViewCoroutineScope.kt │ │ └── OnLifecycle.kt ├── build.gradle └── README.md ├── logging ├── .gitignore ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── ru │ │ └── touchin │ │ └── roboswag │ │ └── core │ │ ├── utils │ │ ├── ThreadLocalValue.java │ │ └── ShouldNotHappenException.java │ │ └── log │ │ ├── LcLevel.java │ │ └── ConsoleLogProcessor.java └── build.gradle ├── mvi-arch ├── .gitignore ├── README.md └── src │ └── main │ ├── AndroidManifest.xml │ ├── res │ └── values │ │ └── strings.xml │ └── java │ └── ru │ └── touchin │ └── roboswag │ └── mvi_arch │ ├── marker │ ├── SideEffect.kt │ ├── StateChange.kt │ ├── ViewState.kt │ └── ViewAction.kt │ ├── di │ ├── ViewModelAssistedFactory.kt │ ├── ViewModelKey.kt │ └── ViewModelFactory.kt │ ├── mediator │ ├── Mediator.kt │ ├── MediatorStore.kt │ └── LoggingMediator.kt │ └── core │ ├── Extensions.kt │ └── FullscreenBottomSheetDialog.kt ├── storable ├── .gitignore ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── ru │ │ └── touchin │ │ └── roboswag │ │ └── core │ │ └── observables │ │ └── storable │ │ └── SameTypesConverter.java └── build.gradle ├── webview ├── .gitignore ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── ru │ │ │ └── touchin │ │ │ └── roboswag │ │ │ └── webview │ │ │ └── web_view │ │ │ ├── WebViewState.kt │ │ │ ├── WebViewCallback.kt │ │ │ ├── redirection │ │ │ └── IgnoredErrorsHolder.kt │ │ │ ├── CustomWebView.kt │ │ │ └── BaseChromeWebViewClient.kt │ │ └── res │ │ └── values │ │ └── attrs.xml └── build.gradle ├── yandex-map ├── .gitignore ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── ru │ │ │ └── touchin │ │ │ └── yandexmap │ │ │ ├── PointModel.kt │ │ │ ├── YandexMapItemRenderer.kt │ │ │ ├── BitmapHelper.kt │ │ │ ├── DefaultIconGenerator.kt │ │ │ └── YandexIconGenerator.kt │ │ └── res │ │ └── layout │ │ ├── default_cluster_item_view.xml │ │ └── default_cluster_view.xml └── build.gradle ├── api-logansquare ├── .gitignore ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ ├── com │ │ └── bluelinelabs │ │ │ └── logansquare │ │ │ └── ConverterUtils.java │ │ └── ru │ │ └── touchin │ │ └── templates │ │ └── logansquare │ │ ├── LoganSquareEnum.java │ │ └── LoganSquareJsonModel.java └── build.gradle ├── code-confirm ├── .gitignore ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── ru │ │ └── touchin │ │ └── code_confirm │ │ ├── BaseCodeResponse.kt │ │ ├── BaseCodeConfirmState.kt │ │ ├── BaseCodeConfirmAction.kt │ │ └── LifeTimer.kt └── build.gradle ├── lifecycle-rx ├── .gitignore ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── ru │ │ └── touchin │ │ └── lifecycle │ │ └── viewmodel │ │ ├── RxViewModel.kt │ │ └── LiveDataDispatcher.kt └── build.gradle ├── logging_reader ├── .gitignore ├── src │ └── main │ │ ├── res │ │ ├── xml │ │ │ └── provider_paths.xml │ │ ├── values │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── layout │ │ │ └── log_item.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── ru │ │ └── touchin │ │ └── roboswag │ │ └── loggging_reader │ │ ├── LogItemAdapter.kt │ │ └── LogFileManager.kt ├── README.md └── build.gradle ├── navigation-base ├── .gitignore ├── README.md └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── ru │ └── touchin │ └── roboswag │ └── navigation_base │ ├── scopes │ ├── FeatureScope.kt │ ├── FragmentScope.kt │ └── UserLoggedScope.kt │ ├── activities │ ├── OnBackPressedListener.java │ └── NavigationActivity.kt │ ├── fragments │ ├── EmptyState.kt │ ├── LifecycleLoggingObserver.kt │ ├── StatefulFragment.kt │ ├── BaseFragment.kt │ └── FragmentViewBindingDelegate.kt │ ├── keyboard_resizeable │ ├── Fragment.kt │ └── FragmentKeyboardListenerObserver.kt │ └── extensions │ └── Parcelable.kt ├── rx-extensions ├── .gitignore ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── ru │ │ └── touchin │ │ └── extensions │ │ └── rx │ │ ├── utils │ │ └── StringConstants.kt │ │ ├── Maybe.kt │ │ ├── Single.kt │ │ ├── Flowable.kt │ │ └── Observable.kt └── build.gradle ├── text-processing ├── .gitignore ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── ru │ │ └── touchin │ │ └── roboswag │ │ └── textprocessing │ │ ├── generators │ │ ├── Matrix.kt │ │ ├── regexgenerator │ │ │ ├── PCREGeneratorItem.kt │ │ │ └── RegexReplaceGenerator.kt │ │ ├── DecoroMaskGenerator.kt │ │ └── PlaceholderGenerator.kt │ │ ├── validators │ │ └── CustomValidator.kt │ │ └── TextFormatter.kt ├── build.gradle └── README.md ├── kotlin-extensions ├── .gitignore ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── ru │ │ └── touchin │ │ ├── extensions │ │ ├── CharSequence.kt │ │ ├── TypedArray.kt │ │ ├── Coroutines.kt │ │ ├── Delegates.kt │ │ ├── Fragment.kt │ │ ├── TextView.kt │ │ ├── Context.kt │ │ ├── ViewHolder.kt │ │ ├── View.kt │ │ └── Activity.kt │ │ └── utils │ │ ├── BundleExtractorDelegate.kt │ │ └── ActionThrottler.kt └── build.gradle ├── livedata-location ├── .gitignore ├── src │ └── main │ │ └── AndroidManifest.xml ├── build.gradle └── README.md ├── recyclerview-adapters ├── .gitignore ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── ru │ │ └── touchin │ │ └── roboswag │ │ └── recyclerview_adapters │ │ └── adapters │ │ ├── SimpleDataObserver.kt │ │ ├── OffsetAdapterUpdateCallback.kt │ │ └── SubmittablePagingAdapter.kt └── build.gradle ├── recyclerview-calendar ├── .gitignore ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── ru │ │ └── touchin │ │ └── calendar │ │ ├── ComparingToToday.java │ │ ├── CalendarEmptyItem.java │ │ ├── CalendarItem.java │ │ └── CalendarHeaderItem.java └── build.gradle ├── bottom-navigation-base ├── .gitignore ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── ru │ │ └── touchin │ │ └── roboswag │ │ └── bottom_navigation_base │ │ └── BaseNavigationTab.kt └── build.gradle ├── bottom-navigation-fragment ├── .gitignore ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── ru │ │ └── touchin │ │ └── roboswag │ │ └── bottom_navigation_fragment │ │ ├── NavigationTab.kt │ │ ├── BottomNavigationActivity.kt │ │ ├── BottomNavigationFragment.kt │ │ ├── NavigationContainerFragment.kt │ │ └── BottomNavigationController.kt └── build.gradle ├── lifecycle-viewcontroller ├── .gitignore ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── ru │ │ └── touchin │ │ └── lifecycle_viewcontroller │ │ └── viewmodel │ │ └── LifecycleViewModelProviders.kt └── build.gradle ├── navigation-viewcontroller ├── .gitignore ├── src │ └── main │ │ └── AndroidManifest.xml └── build.gradle ├── recyclerview-decorators ├── .gitignore ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── ru │ │ └── touchin │ │ └── roboswag │ │ └── recyclerview_decorators │ │ └── decorators │ │ └── BottomDividerItemDecoration.kt └── build.gradle ├── bottom-navigation-viewcontroller ├── .gitignore ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── ru │ │ └── touchin │ │ └── roboswag │ │ └── bottom_navigation_viewcontroller │ │ ├── NavigationTab.kt │ │ ├── BottomNavigationActivity.kt │ │ ├── BottomNavigationFragment.kt │ │ ├── NavigationContainerFragment.kt │ │ └── BottomNavigationController.kt └── build.gradle ├── CODEOWNERS ├── pagination ├── README.md ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── ru │ │ │ └── touchin │ │ │ └── roboswag │ │ │ └── pagination │ │ │ ├── ErrorItem.kt │ │ │ ├── ProgressItem.kt │ │ │ ├── ProgressAdapterDelegate.kt │ │ │ └── PaginationAdapter.kt │ │ └── res │ │ ├── values │ │ └── strings.xml │ │ └── layout │ │ ├── item_progress.xml │ │ └── view_pagination.xml └── build.gradle ├── navigation-cicerone ├── README.md ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ ├── res │ │ ├── values │ │ │ └── strings.xml │ │ └── layout │ │ │ └── fragment_flow.xml │ │ └── java │ │ └── ru │ │ └── touchin │ │ └── roboswag │ │ └── navigation_cicerone │ │ ├── flow │ │ ├── FlowNavigation.kt │ │ └── FlowNavigationModule.kt │ │ └── CiceroneTuner.kt └── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── android-configs ├── lib-config.gradle ├── app-config.gradle └── common-config.gradle └── gradle.properties /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /alerts/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /recaptcha/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /utils/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /views/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /base-map/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /client-services/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /google-map/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /lifecycle/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /logging/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /mvi-arch/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /storable/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /webview/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /yandex-map/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /api-logansquare/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /code-confirm/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /lifecycle-rx/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /logging_reader/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /navigation-base/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /rx-extensions/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /text-processing/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /kotlin-extensions/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /livedata-location/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /recyclerview-adapters/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /recyclerview-calendar/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /bottom-navigation-base/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /bottom-navigation-fragment/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /lifecycle-viewcontroller/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /navigation-viewcontroller/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /recyclerview-decorators/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /bottom-navigation-viewcontroller/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Ответственный за все модули 2 | * @maxbach 3 | -------------------------------------------------------------------------------- /navigation-base/README.md: -------------------------------------------------------------------------------- 1 | navigation-new 2 | ==== 3 | 4 | Новая навигация -------------------------------------------------------------------------------- /base-map/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "../android-configs/lib-config.gradle" 2 | -------------------------------------------------------------------------------- /mvi-arch/README.md: -------------------------------------------------------------------------------- 1 | mvi_arch 2 | ==== 3 | 4 | TODO: rewrite dependencies 5 | -------------------------------------------------------------------------------- /base-map/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /pagination/README.md: -------------------------------------------------------------------------------- 1 | pagination 2 | ==== 3 | 4 | TODO: rewrite dependencies 5 | -------------------------------------------------------------------------------- /google-map/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /lifecycle/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /mvi-arch/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /yandex-map/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /code-confirm/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /lifecycle-rx/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /logging/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /utils/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /alerts/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /navigation-cicerone/README.md: -------------------------------------------------------------------------------- 1 | navigation-cicerone 2 | ==== 3 | 4 | TODO: rewrite dependencies 5 | -------------------------------------------------------------------------------- /recyclerview-calendar/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /views/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /webview/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /kotlin-extensions/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /pagination/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /rx-extensions/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /storable/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /navigation-base/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /navigation-cicerone/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /text-processing/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /api-logansquare/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /livedata-location/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /lifecycle-viewcontroller/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /mvi-arch/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | mvi-arch 3 | 4 | -------------------------------------------------------------------------------- /recyclerview-decorators/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /bottom-navigation-base/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TouchInstinct/RoboSwag/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /navigation-viewcontroller/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /recyclerview-adapters/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /views/src/main/res/font/roboto.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TouchInstinct/RoboSwag/HEAD/views/src/main/res/font/roboto.ttf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /captures 7 | .externalNativeBuild 8 | **/build 9 | -------------------------------------------------------------------------------- /android-configs/lib-config.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | apply from: '../android-configs/common-config.gradle' -------------------------------------------------------------------------------- /bottom-navigation-fragment/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /navigation-cicerone/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | mvi-arch 3 | 4 | -------------------------------------------------------------------------------- /pagination/src/main/java/ru/touchin/roboswag/pagination/ErrorItem.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.pagination 2 | 3 | object ErrorItem 4 | -------------------------------------------------------------------------------- /views/src/main/res/font/roboto_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TouchInstinct/RoboSwag/HEAD/views/src/main/res/font/roboto_bold.ttf -------------------------------------------------------------------------------- /pagination/src/main/java/ru/touchin/roboswag/pagination/ProgressItem.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.pagination 2 | 3 | object ProgressItem 4 | -------------------------------------------------------------------------------- /views/src/main/res/font/roboto_medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TouchInstinct/RoboSwag/HEAD/views/src/main/res/font/roboto_medium.ttf -------------------------------------------------------------------------------- /views/src/main/res/font/roboto_regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TouchInstinct/RoboSwag/HEAD/views/src/main/res/font/roboto_regular.ttf -------------------------------------------------------------------------------- /bottom-navigation-viewcontroller/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/marker/SideEffect.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.mvi_arch.marker 2 | 3 | interface SideEffect 4 | -------------------------------------------------------------------------------- /mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/marker/StateChange.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.mvi_arch.marker 2 | 3 | interface StateChange 4 | -------------------------------------------------------------------------------- /lifecycle/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | view_coroutine_scope 4 | -------------------------------------------------------------------------------- /text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/Matrix.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.textprocessing.generators 2 | 3 | typealias Matrix = List> 4 | -------------------------------------------------------------------------------- /client-services/src/main/java/ru/touchin/client_services/MobileService.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.client_services 2 | 3 | enum class MobileService { 4 | HUAWEI_SERVICE, GOOGLE_SERVICE 5 | } 6 | -------------------------------------------------------------------------------- /logging_reader/src/main/res/xml/provider_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /lifecycle/src/main/java/ru/touchin/lifecycle/viewmodel/LifecycleViewModelProviders.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.lifecycle.viewmodel 2 | 3 | object LifecycleViewModelProviders : BaseLifecycleViewModelProviders() 4 | -------------------------------------------------------------------------------- /webview/src/main/java/ru/touchin/roboswag/webview/web_view/WebViewState.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.webview.web_view 2 | 3 | enum class WebViewState { 4 | LOADING, 5 | SUCCESS, 6 | ERROR 7 | } 8 | -------------------------------------------------------------------------------- /yandex-map/src/main/java/ru/touchin/yandexmap/PointModel.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.yandexmap 2 | 3 | import com.yandex.mapkit.geometry.Point 4 | 5 | open class PointModel( 6 | open val point: Point 7 | ) 8 | -------------------------------------------------------------------------------- /alerts/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | OK 4 | Cancel 5 | 6 | -------------------------------------------------------------------------------- /recaptcha/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /views/src/main/res/anim/fade_out_animation.xml: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/scopes/FeatureScope.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.navigation_base.scopes 2 | 3 | import javax.inject.Scope 4 | 5 | @Scope 6 | annotation class FeatureScope 7 | -------------------------------------------------------------------------------- /client-services/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /kotlin-extensions/src/main/java/ru/touchin/extensions/CharSequence.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.extensions 2 | 3 | fun CharSequence.indexesOf(substring: String) = Regex(substring).find(this)?.let { it.range.first to it.range.last + 1 } 4 | -------------------------------------------------------------------------------- /navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/scopes/FragmentScope.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.navigation_base.scopes 2 | 3 | import javax.inject.Scope 4 | 5 | @Scope 6 | annotation class FragmentScope 7 | -------------------------------------------------------------------------------- /code-confirm/src/main/java/ru/touchin/code_confirm/BaseCodeResponse.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.code_confirm 2 | 3 | abstract class BaseCodeResponse( 4 | open val codeLifetime: Int, 5 | open val codeId: String? = null 6 | ) 7 | -------------------------------------------------------------------------------- /lifecycle/src/main/java/ru/touchin/lifecycle/viewmodel/ViewModelFactoryProvider.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.lifecycle.viewmodel 2 | 3 | interface ViewModelFactoryProvider { 4 | 5 | val viewModelFactory: ViewModelFactory 6 | 7 | } 8 | -------------------------------------------------------------------------------- /navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/scopes/UserLoggedScope.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.navigation_base.scopes 2 | 3 | import javax.inject.Scope 4 | 5 | @Scope 6 | annotation class UserLoggedScope 7 | -------------------------------------------------------------------------------- /navigation-cicerone/src/main/java/ru/touchin/roboswag/navigation_cicerone/flow/FlowNavigation.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.navigation_cicerone.flow 2 | 3 | import javax.inject.Qualifier 4 | 5 | @Qualifier 6 | annotation class FlowNavigation 7 | -------------------------------------------------------------------------------- /rx-extensions/src/main/java/ru/touchin/extensions/rx/utils/StringConstants.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.extensions.rx.utils 2 | 3 | object StringConstants { 4 | const val OPTIONAL_UNWRAPPING_ERROR_MESSAGE = "Wrapped object must not be null" 5 | } 6 | -------------------------------------------------------------------------------- /android-configs/app-config.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | apply from: '../RoboSwag/android-configs/common-config.gradle' 4 | 5 | apply plugin: 'kotlin-android' 6 | apply plugin: 'kotlin-parcelize' 7 | apply plugin: 'kotlin-kapt' 8 | -------------------------------------------------------------------------------- /navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/activities/OnBackPressedListener.java: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.navigation_base.activities; 2 | 3 | public interface OnBackPressedListener { 4 | 5 | boolean onBackPressed(); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /views/src/main/res/anim/fade_in_animation.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /google-map/src/main/res/drawable/default_cluster_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /lifecycle/src/main/java/ru/touchin/lifecycle/extensions/ImmutableExt.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.lifecycle.extensions 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.MutableLiveData 5 | 6 | fun MutableLiveData.toImmutable() = this as LiveData 7 | -------------------------------------------------------------------------------- /logging_reader/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Update 4 | Share 5 | 6 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Sep 27 13:20:10 MSK 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip 7 | -------------------------------------------------------------------------------- /lifecycle/src/main/java/ru/touchin/lifecycle/event/Event.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.lifecycle.event 2 | 3 | sealed class Event { 4 | 5 | object Loading : Event() 6 | 7 | object Complete : Event() 8 | 9 | data class Error(val throwable: Throwable) : Event() 10 | 11 | } 12 | -------------------------------------------------------------------------------- /pagination/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | New page load error. Try again. 4 | Refresh screen error. Try again. 5 | 6 | -------------------------------------------------------------------------------- /text-processing/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "../android-configs/lib-config.gradle" 2 | 3 | dependencies { 4 | implementation 'org.antlr:antlr4:4.9.2' 5 | implementation 'org.antlr:antlr4-runtime:4.9.2' 6 | implementation 'ru.tinkoff.decoro:decoro:1.5.1' 7 | testImplementation 'junit:junit:4.13.2' 8 | } 9 | -------------------------------------------------------------------------------- /mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/di/ViewModelAssistedFactory.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.mvi_arch.di 2 | 3 | import androidx.lifecycle.SavedStateHandle 4 | import androidx.lifecycle.ViewModel 5 | 6 | interface ViewModelAssistedFactory { 7 | fun create(handle: SavedStateHandle): VM 8 | } 9 | -------------------------------------------------------------------------------- /navigation-cicerone/src/main/res/layout/fragment_flow.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | -------------------------------------------------------------------------------- /yandex-map/src/main/res/layout/default_cluster_item_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | -------------------------------------------------------------------------------- /logging_reader/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/regexgenerator/PCREGeneratorItem.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.textprocessing.generators.regexgenerator 2 | 3 | import ru.touchin.roboswag.textprocessing.generators.Matrix 4 | 5 | class PCREGeneratorItem( 6 | val regexReplaceString: String, 7 | val matrixOfSymbols: Matrix 8 | ) 9 | -------------------------------------------------------------------------------- /base-map/src/main/res/drawable/marker_default_icon.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/di/ViewModelKey.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.mvi_arch.di 2 | 3 | import androidx.lifecycle.ViewModel 4 | import dagger.MapKey 5 | import kotlin.reflect.KClass 6 | 7 | @Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER) 8 | @MapKey 9 | annotation class ViewModelKey(val value: KClass) 10 | -------------------------------------------------------------------------------- /yandex-map/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "../android-configs/lib-config.gradle" 2 | 3 | dependencies { 4 | implementation project(":base-map") 5 | 6 | implementation "com.yandex.android:maps.mobile" 7 | 8 | constraints { 9 | implementation("com.yandex.android:maps.mobile") { 10 | version { 11 | require '4.2.2-lite' 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /views/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #33E5E5EA 5 | #D9DFE2 6 | 7 | #8D8EA6 8 | #999BBF 9 | #141233 10 | -------------------------------------------------------------------------------- /base-map/src/main/java/ru/touchin/basemap/BaseIconGenerator.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.basemap 2 | 3 | interface BaseIconGenerator { 4 | 5 | fun getClusterIcon(cluster: TCluster): TViewIcon? 6 | 7 | fun getClusterItemIcon(clusterItem: TPoint): TViewIcon? 8 | 9 | fun getClusterItemView(clusterItem: TPoint): TViewIcon? 10 | 11 | fun getClusterView(cluster: TCluster): TViewIcon? 12 | } 13 | -------------------------------------------------------------------------------- /views/src/main/res/drawable/light_button_background.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /livedata-location/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "../android-configs/lib-config.gradle" 2 | 3 | dependencies { 4 | api project(":lifecycle") 5 | 6 | implementation "com.google.android.gms:play-services-location" 7 | 8 | constraints { 9 | implementation("com.google.android.gms:play-services-location") { 10 | version { 11 | require '17.0.0' 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /logging_reader/README.md: -------------------------------------------------------------------------------- 1 | logging_reader 2 | ===== 3 | 4 | ### Общее описание 5 | 6 | Модуль служит для получания на устройстве файла с логами разного приоритета, их анализа и возможности отправить файл через стандартный шеринг. 7 | 8 | 9 | ### Пример 10 | 11 | Для вызова диалогового окна, позволяющего сохранять, читать и отправлять логи, достаточно вызвать: 12 | ```kotlin 13 | DebugLogsDialogFragment().show(parentFragmentManager, null) 14 | ``` -------------------------------------------------------------------------------- /rx-extensions/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "../android-configs/lib-config.gradle" 2 | 3 | dependencies { 4 | implementation project(":utils") 5 | implementation project(":logging") 6 | 7 | implementation "io.reactivex.rxjava2:rxjava" 8 | 9 | constraints { 10 | implementation("io.reactivex.rxjava2:rxjava") { 11 | version { 12 | require '2.2.9' 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /utils/src/main/java/ru/touchin/roboswag/components/utils/spans/URLSpanWithoutUnderline.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.components.utils.spans 2 | 3 | import android.text.TextPaint 4 | import android.text.style.URLSpan 5 | 6 | open class URLSpanWithoutUnderline(url: String) : URLSpan(url) { 7 | 8 | override fun updateDrawState(ds: TextPaint) { 9 | super.updateDrawState(ds) 10 | ds.isUnderlineText = false 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /base-map/src/main/java/ru/touchin/basemap/BaseMapItemRenderer.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.basemap 2 | 3 | interface BaseMapItemRenderer { 4 | 5 | var iconGenerator: BaseIconGenerator 6 | 7 | fun getClusterItemIcon(item: TPoint): TViewIcon? = iconGenerator.getClusterItemView(item) 8 | 9 | fun getClusterIcon(cluster: TCluster): TViewIcon? = iconGenerator.getClusterView(cluster) 10 | } 11 | -------------------------------------------------------------------------------- /yandex-map/src/main/java/ru/touchin/yandexmap/YandexMapItemRenderer.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.yandexmap 2 | 3 | import com.yandex.runtime.ui_view.ViewProvider 4 | import ru.touchin.basemap.BaseIconGenerator 5 | import ru.touchin.basemap.BaseMapItemRenderer 6 | 7 | class YandexMapItemRenderer( 8 | override var iconGenerator: BaseIconGenerator, ViewProvider> 9 | ) : BaseMapItemRenderer, ViewProvider> 10 | -------------------------------------------------------------------------------- /pagination/src/main/res/layout/item_progress.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /yandex-map/src/main/res/layout/default_cluster_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | -------------------------------------------------------------------------------- /google-map/src/main/res/layout/view_google_map_cluster_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | -------------------------------------------------------------------------------- /mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/marker/ViewState.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.mvi_arch.marker 2 | 3 | /** 4 | * This interface should be implemented to create your own view state and use it with [MviFragment] and [MviViewModel]. 5 | * 6 | * Usually it's a data class that presents full state of view. 7 | * 8 | * You should not use mutable values here. All values should be immutable. 9 | * 10 | * @author Created by Max Bachinsky and Ivan Vlasov at Touch Instinct. 11 | */ 12 | interface ViewState 13 | -------------------------------------------------------------------------------- /base-map/src/main/java/ru/touchin/basemap/MapExtension.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.basemap 2 | 3 | import android.util.SparseArray 4 | 5 | inline fun MutableMap.getOrPutIfNotNull(key: K, defaultValue: () -> V?): V? = 6 | get(key) ?: defaultValue()?.also { value -> 7 | put(key, value) 8 | } 9 | 10 | inline fun SparseArray.getOrPutIfNotNull(key: Int, defaultValue: () -> V?): V? = 11 | get(key) ?: defaultValue()?.also { value -> 12 | put(key, value) 13 | } 14 | -------------------------------------------------------------------------------- /kotlin-extensions/src/main/java/ru/touchin/extensions/TypedArray.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.extensions 2 | 3 | import android.content.res.TypedArray 4 | import androidx.annotation.StyleableRes 5 | 6 | private const val NOT_FOUND_VALUE = -1 7 | 8 | fun TypedArray.getResourceIdOrNull(@StyleableRes index: Int) = getResourceId(index, NOT_FOUND_VALUE) 9 | .takeIf { it != NOT_FOUND_VALUE } 10 | 11 | fun TypedArray.getColorOrNull(@StyleableRes index: Int) = getColor(index, NOT_FOUND_VALUE) 12 | .takeIf { it != NOT_FOUND_VALUE } 13 | -------------------------------------------------------------------------------- /utils/src/main/java/ru/touchin/roboswag/components/utils/ContextExtensions.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.components.utils 2 | 3 | import android.content.Context 4 | import android.graphics.drawable.Drawable 5 | import androidx.annotation.ColorRes 6 | import androidx.annotation.DrawableRes 7 | import androidx.core.content.ContextCompat 8 | 9 | fun Context.getColorSimple(@ColorRes id: Int): Int = ContextCompat.getColor(this, id) 10 | 11 | fun Context.getDrawableSimple(@DrawableRes id: Int): Drawable? = ContextCompat.getDrawable(this, id) 12 | -------------------------------------------------------------------------------- /navigation-viewcontroller/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "../android-configs/lib-config.gradle" 2 | 3 | apply plugin: 'kotlin-kapt' 4 | 5 | dependencies { 6 | implementation project(":utils") 7 | implementation project(":logging") 8 | implementation project(":navigation-base") 9 | 10 | implementation "androidx.appcompat:appcompat" 11 | 12 | constraints { 13 | implementation("androidx.appcompat:appcompat") { 14 | version { 15 | require '1.0.2' 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lifecycle/src/main/java/ru/touchin/lifecycle/event/ContentEvent.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.lifecycle.event 2 | 3 | sealed class ContentEvent(open val data: T?) { 4 | 5 | data class Loading(override val data: T? = null) : ContentEvent(data) 6 | 7 | data class Success(override val data: T) : ContentEvent(data) 8 | 9 | data class Error(val throwable: Throwable, override val data: T? = null) : ContentEvent(data) 10 | 11 | data class Complete(override val data: T? = null) : ContentEvent(data) 12 | 13 | } 14 | -------------------------------------------------------------------------------- /bottom-navigation-fragment/src/main/java/ru/touchin/roboswag/bottom_navigation_fragment/NavigationTab.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.bottom_navigation_fragment 2 | 3 | import android.os.Parcelable 4 | import ru.touchin.roboswag.bottom_navigation_base.BaseNavigationTab 5 | import ru.touchin.roboswag.navigation_base.fragments.StatefulFragment 6 | 7 | class NavigationTab( 8 | override val cls: Class>, 9 | state: Parcelable, 10 | saveStateOnSwitching: Boolean = true 11 | ) : BaseNavigationTab(cls, state, saveStateOnSwitching) 12 | -------------------------------------------------------------------------------- /navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/activities/NavigationActivity.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.navigation_base.activities 2 | 3 | import androidx.fragment.app.FragmentTransaction 4 | import ru.touchin.roboswag.navigation_base.FragmentNavigation 5 | 6 | abstract class NavigationActivity : BaseActivity() { 7 | 8 | protected abstract val fragmentContainerViewId: Int 9 | 10 | protected open val transition = FragmentTransaction.TRANSIT_NONE 11 | 12 | abstract val navigation: TNavigation 13 | 14 | } 15 | -------------------------------------------------------------------------------- /text-processing/src/main/java/ru/touchin/roboswag/textprocessing/validators/CustomValidator.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.textprocessing.validators 2 | 3 | import ru.tinkoff.decoro.slots.Slot 4 | 5 | class CustomValidator private constructor( 6 | private val slotSymbols: List 7 | ) : Slot.SlotValidator { 8 | 9 | companion object { 10 | fun customSlot(slotSymbols: List) = Slot(null, CustomValidator(slotSymbols)) 11 | } 12 | 13 | override fun validate(value: Char): Boolean { 14 | return slotSymbols.contains(value) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/mediator/Mediator.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.mvi_arch.mediator 2 | 3 | import ru.touchin.roboswag.mvi_arch.marker.SideEffect 4 | import ru.touchin.roboswag.mvi_arch.marker.StateChange 5 | import ru.touchin.roboswag.mvi_arch.marker.ViewAction 6 | import ru.touchin.roboswag.mvi_arch.marker.ViewState 7 | 8 | interface Mediator { 9 | 10 | fun onEffect(effect: SideEffect) 11 | 12 | fun onAction(action: ViewAction) 13 | 14 | fun onNewState(state: ViewState) 15 | 16 | fun onStateChange(change: StateChange) 17 | 18 | } 19 | -------------------------------------------------------------------------------- /logging/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "../android-configs/lib-config.gradle" 2 | 3 | dependencies { 4 | implementation "androidx.annotation:annotation" 5 | 6 | implementation "com.google.firebase:firebase-crashlytics" 7 | 8 | constraints { 9 | implementation("androidx.annotation:annotation") { 10 | version { 11 | require '1.0.0' 12 | } 13 | } 14 | 15 | implementation("com.google.firebase:firebase-crashlytics") { 16 | version { 17 | require '17.1.0' 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /recyclerview-calendar/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "../android-configs/lib-config.gradle" 2 | 3 | dependencies { 4 | implementation project(":logging") 5 | implementation 'net.danlew:android.joda' 6 | 7 | implementation "androidx.recyclerview:recyclerview" 8 | 9 | constraints { 10 | implementation("androidx.recyclerview:recyclerview") { 11 | version { 12 | require '1.0.0' 13 | } 14 | } 15 | 16 | implementation("net.danlew:android.joda") { 17 | version { 18 | require '2.9.9.4' 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /kotlin-extensions/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "../android-configs/lib-config.gradle" 2 | 3 | dependencies { 4 | implementation "androidx.recyclerview:recyclerview" 5 | implementation "androidx.fragment:fragment-ktx" 6 | implementation project(path: ':logging') 7 | 8 | constraints { 9 | implementation("androidx.recyclerview:recyclerview") { 10 | version { 11 | require '1.0.0' 12 | } 13 | } 14 | 15 | implementation("androidx.fragment:fragment-ktx") { 16 | version { 17 | require '1.2.1' 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/Extensions.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.mvi_arch.core 2 | 3 | import android.os.Parcelable 4 | import ru.touchin.roboswag.mvi_arch.marker.ViewAction 5 | import ru.touchin.roboswag.mvi_arch.marker.ViewState 6 | 7 | /** 8 | * Used for setting arguments and initial state into Fragments 9 | */ 10 | fun , 14 | TFragment : MviFragment> 15 | TFragment.withArgs(navArgs: NavArgs) = apply { initArgs(navArgs) } 16 | -------------------------------------------------------------------------------- /navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/fragments/EmptyState.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.navigation_base.fragments 2 | 3 | import android.os.Parcel 4 | import android.os.Parcelable 5 | 6 | object EmptyState : Parcelable { 7 | 8 | override fun writeToParcel(parcel: Parcel, flags: Int) = Unit 9 | 10 | override fun describeContents() = 0 11 | 12 | @JvmField 13 | val CREATOR = object : Parcelable.Creator { 14 | override fun createFromParcel(parcel: Parcel) = EmptyState 15 | 16 | override fun newArray(size: Int): Array = arrayOfNulls(size) 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /logging_reader/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /bottom-navigation-fragment/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "../android-configs/lib-config.gradle" 2 | 3 | dependencies { 4 | implementation project(":navigation-base") 5 | implementation project(":bottom-navigation-base") 6 | 7 | implementation "androidx.core:core-ktx" 8 | 9 | implementation "androidx.appcompat:appcompat" 10 | 11 | constraints { 12 | implementation("androidx.core:core-ktx") { 13 | version { 14 | require '1.0.0' 15 | } 16 | } 17 | 18 | implementation("androidx.appcompat:appcompat") { 19 | version { 20 | require '1.0.0' 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /kotlin-extensions/src/main/java/ru/touchin/extensions/Coroutines.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.extensions 2 | 3 | import kotlinx.coroutines.CoroutineScope 4 | import kotlinx.coroutines.CoroutineStart 5 | import kotlinx.coroutines.Job 6 | import kotlinx.coroutines.launch 7 | import kotlin.coroutines.CoroutineContext 8 | import kotlin.coroutines.EmptyCoroutineContext 9 | 10 | fun CoroutineScope.safetyLaunch( 11 | context: CoroutineContext = EmptyCoroutineContext, 12 | start: CoroutineStart = CoroutineStart.DEFAULT, 13 | block: suspend () -> Unit 14 | ): Job = launch(context, start) { 15 | try { 16 | block.invoke() 17 | } catch (_: Throwable) { 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/keyboard_resizeable/Fragment.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.navigation_base.keyboard_resizeable 2 | 3 | import androidx.fragment.app.Fragment 4 | 5 | /** 6 | * Use in [Fragment.onViewCreated] to access [Fragment.getViewLifecycleOwner] 7 | */ 8 | fun Fragment.addKeyboardListener( 9 | onShow: OnShowListener? = null, 10 | onHide: OnHideListener? = null 11 | ) { 12 | viewLifecycleOwner.lifecycle.addObserver( 13 | FragmentKeyboardListenerObserver( 14 | fragment = this, 15 | onShow = onShow, 16 | onHide = onHide 17 | ) 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /lifecycle/src/main/java/ru/touchin/lifecycle/viewmodel/ViewModelFactory.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.lifecycle.viewmodel 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.ViewModelProvider 5 | 6 | import javax.inject.Inject 7 | import javax.inject.Provider 8 | 9 | class ViewModelFactory @Inject constructor( 10 | private val creators: Map, @JvmSuppressWildcards Provider> 11 | ) : ViewModelProvider.Factory { 12 | 13 | @Suppress("UNCHECKED_CAST") 14 | override fun create(modelClass: Class): T 15 | = creators[modelClass]?.get() as? T ?: throw IllegalArgumentException("Unknown model class $modelClass") 16 | 17 | } 18 | -------------------------------------------------------------------------------- /code-confirm/src/main/java/ru/touchin/code_confirm/BaseCodeConfirmState.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.code_confirm 2 | 3 | import ru.touchin.roboswag.mvi_arch.marker.ViewState 4 | 5 | abstract class BaseCodeConfirmState( 6 | open var codeLifetime: String, 7 | open var isLoadingState: Boolean, 8 | open var isWrongCode: Boolean, 9 | open var isExpired: Boolean, 10 | open var isRefreshCodeLoading: Boolean = false, 11 | open var needSendCode: Boolean = true 12 | ) : ViewState { 13 | 14 | val canRequestNewCode: Boolean 15 | get() = isExpired && !isRefreshCodeLoading 16 | 17 | abstract fun copyWith(updateBlock: T.() -> Unit): T 18 | } 19 | -------------------------------------------------------------------------------- /alerts/src/main/java/ru/touchin/roboswag/viewable_dialog/AlertDialogUtils.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.viewable_dialog 2 | 3 | import android.widget.TextView 4 | import androidx.appcompat.app.AlertDialog 5 | import androidx.core.view.isVisible 6 | import ru.touchin.extensions.setOnRippleClickListener 7 | 8 | fun setupButton(alertDialog: AlertDialog, buttonView: TextView, text: String?, onButtonClick: (() -> Unit)?) { 9 | buttonView.setTextOrGone(text) 10 | buttonView.setOnRippleClickListener { 11 | onButtonClick?.invoke() 12 | alertDialog.dismiss() 13 | } 14 | } 15 | 16 | fun TextView.setTextOrGone(text: CharSequence?) { 17 | isVisible = !text.isNullOrEmpty() 18 | setText(text) 19 | } 20 | -------------------------------------------------------------------------------- /code-confirm/src/main/java/ru/touchin/code_confirm/BaseCodeConfirmAction.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.code_confirm 2 | 3 | /** 4 | * [CodeConfirmAction] is interface for the action that will call 5 | * the confirmation request with entered code 6 | */ 7 | interface CodeConfirmAction 8 | 9 | /** 10 | * [UpdatedCodeInputAction] is interface for the action, that should be called 11 | * after each update of codeInput 12 | * @param code Updated string with code from codeInput 13 | */ 14 | interface UpdatedCodeInputAction { 15 | val code: String? 16 | } 17 | 18 | /** 19 | * [GetRefreshCodeAction] is interface for the action that will call 20 | * the request of a repeat code after it's expired 21 | */ 22 | interface GetRefreshCodeAction 23 | -------------------------------------------------------------------------------- /bottom-navigation-base/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "../android-configs/lib-config.gradle" 2 | 3 | dependencies { 4 | implementation project(":logging") 5 | implementation project(":navigation-base") 6 | 7 | implementation("org.jetbrains.kotlin:kotlin-stdlib") 8 | 9 | implementation("androidx.core:core-ktx") 10 | 11 | implementation("androidx.appcompat:appcompat") 12 | 13 | constraints { 14 | implementation("androidx.appcompat:appcompat") { 15 | version { 16 | require '1.0.0' 17 | } 18 | } 19 | 20 | implementation("androidx.core:core-ktx") { 21 | version { 22 | require '1.0.0' 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lifecycle-rx/src/main/java/ru/touchin/lifecycle/viewmodel/RxViewModel.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.lifecycle.viewmodel 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.annotation.CallSuper 5 | 6 | /** 7 | * Base class of ViewModel with [io.reactivex.disposables.Disposable] handling. 8 | */ 9 | open class RxViewModel( 10 | private val destroyable: BaseDestroyable = BaseDestroyable(), 11 | private val liveDataDispatcher: LiveDataDispatcher = BaseLiveDataDispatcher(destroyable) 12 | ) : ViewModel(), Destroyable by destroyable, LiveDataDispatcher by liveDataDispatcher { 13 | 14 | @CallSuper 15 | override fun onCleared() { 16 | super.onCleared() 17 | destroyable.onDestroy() 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /bottom-navigation-fragment/src/main/java/ru/touchin/roboswag/bottom_navigation_fragment/BottomNavigationActivity.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.bottom_navigation_fragment 2 | 3 | import ru.touchin.roboswag.bottom_navigation_base.BaseBottomNavigationActivity 4 | import ru.touchin.roboswag.navigation_base.FragmentNavigation 5 | 6 | abstract class BottomNavigationActivity : 7 | BaseBottomNavigationActivity() { 8 | 9 | override val navigation by lazy { 10 | FragmentNavigation( 11 | this, 12 | supportFragmentManager, 13 | fragmentContainerViewId, 14 | transition 15 | ) 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /bottom-navigation-viewcontroller/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "../android-configs/lib-config.gradle" 2 | 3 | dependencies { 4 | implementation project(":navigation-base") 5 | implementation project(":navigation-viewcontroller") 6 | implementation project(":bottom-navigation-base") 7 | 8 | implementation "androidx.core:core-ktx" 9 | 10 | implementation "androidx.appcompat:appcompat" 11 | 12 | constraints { 13 | implementation("androidx.core:core-ktx") { 14 | version { 15 | require '1.0.0' 16 | } 17 | } 18 | 19 | implementation("androidx.appcompat:appcompat") { 20 | version { 21 | require '1.0.0' 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /code-confirm/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "../android-configs/lib-config.gradle" 2 | 3 | dependencies { 4 | implementation project(":mvi-arch") 5 | implementation project(":lifecycle") 6 | 7 | implementation "androidx.lifecycle:lifecycle-extensions" 8 | implementation("androidx.lifecycle:lifecycle-viewmodel-ktx") 9 | 10 | def lifecycleVersion = "2.2.0" 11 | 12 | constraints { 13 | implementation("androidx.lifecycle:lifecycle-extensions") { 14 | version { 15 | require '2.1.0' 16 | } 17 | } 18 | implementation("androidx.lifecycle:lifecycle-viewmodel-ktx") { 19 | version { 20 | require(lifecycleVersion) 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /webview/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /bottom-navigation-viewcontroller/src/main/java/ru/touchin/roboswag/bottom_navigation_viewcontroller/NavigationTab.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.bottom_navigation_viewcontroller 2 | 3 | import android.os.Parcelable 4 | import ru.touchin.roboswag.bottom_navigation_base.BaseNavigationTab 5 | import ru.touchin.roboswag.navigation_viewcontroller.viewcontrollers.ViewController 6 | 7 | class NavigationTab( 8 | override val cls: Class>, 9 | state: Parcelable, 10 | /** 11 | * It can be useful in some cases when it is necessary to create ViewController 12 | * with initial state every time when tab opens. 13 | */ 14 | saveStateOnSwitching: Boolean = true 15 | ) : BaseNavigationTab(cls, state, saveStateOnSwitching) 16 | -------------------------------------------------------------------------------- /kotlin-extensions/src/main/java/ru/touchin/utils/BundleExtractorDelegate.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.utils 2 | 3 | import kotlin.properties.ReadWriteProperty 4 | import kotlin.reflect.KProperty 5 | 6 | class BundleExtractorDelegate(private val initializer: (R, KProperty<*>) -> T) : ReadWriteProperty { 7 | 8 | private object EMPTY 9 | 10 | private var value: Any? = EMPTY 11 | 12 | override fun setValue(thisRef: R, property: KProperty<*>, value: T) { 13 | this.value = value 14 | } 15 | 16 | override fun getValue(thisRef: R, property: KProperty<*>): T { 17 | if (value == EMPTY) { 18 | value = initializer(thisRef, property) 19 | } 20 | 21 | @Suppress("UNCHECKED_CAST") 22 | return value as T 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /utils/src/main/java/ru/touchin/defaults/DefaultTextWatcher.java: -------------------------------------------------------------------------------- 1 | package ru.touchin.defaults; 2 | 3 | import androidx.annotation.NonNull; 4 | import android.text.Editable; 5 | import android.text.TextWatcher; 6 | 7 | public class DefaultTextWatcher implements TextWatcher { 8 | 9 | @Override 10 | public void beforeTextChanged(@NonNull final CharSequence oldText, final int start, final int count, final int after) { 11 | // Do nothing 12 | } 13 | 14 | @Override 15 | public void onTextChanged(@NonNull final CharSequence inputText, final int start, final int before, final int count) { 16 | // Do nothing 17 | } 18 | 19 | @Override 20 | public void afterTextChanged(@NonNull final Editable editable) { 21 | // Do nothing 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /rx-extensions/src/main/java/ru/touchin/extensions/rx/Maybe.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.extensions.rx 2 | 3 | import io.reactivex.Completable 4 | import io.reactivex.Maybe 5 | import ru.touchin.extensions.rx.utils.StringConstants 6 | import ru.touchin.roboswag.core.utils.Optional 7 | import ru.touchin.roboswag.core.utils.ShouldNotHappenException 8 | 9 | fun Maybe.emitAfter(other: Completable): Maybe = this.flatMap { value -> 10 | other.andThen(Maybe.just(value)) 11 | } 12 | 13 | fun Maybe>.unwrapOrError( 14 | errorMessage: String = StringConstants.OPTIONAL_UNWRAPPING_ERROR_MESSAGE 15 | ): Maybe = this.flatMap { wrapper -> 16 | wrapper.get() 17 | ?.let { Maybe.just(it) } 18 | ?: Maybe.error(ShouldNotHappenException(errorMessage)) 19 | } 20 | -------------------------------------------------------------------------------- /rx-extensions/src/main/java/ru/touchin/extensions/rx/Single.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.extensions.rx 2 | 3 | import io.reactivex.Completable 4 | import io.reactivex.Single 5 | import ru.touchin.extensions.rx.utils.StringConstants 6 | import ru.touchin.roboswag.core.utils.Optional 7 | import ru.touchin.roboswag.core.utils.ShouldNotHappenException 8 | 9 | fun Single.emitAfter(other: Completable): Single = this.flatMap { value -> 10 | other.andThen(Single.just(value)) 11 | } 12 | 13 | fun Single>.unwrapOrError( 14 | errorMessage: String = StringConstants.OPTIONAL_UNWRAPPING_ERROR_MESSAGE 15 | ): Single = this.flatMap { wrapper -> 16 | wrapper.get() 17 | ?.let { Single.just(it) } 18 | ?: Single.error(ShouldNotHappenException(errorMessage)) 19 | } 20 | -------------------------------------------------------------------------------- /android-configs/common-config.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'kotlin-android' 2 | 3 | rootProject.ext { 4 | compileSdk = 30 5 | 6 | minSdk = 21 7 | targetSdk = 30 8 | } 9 | 10 | android { 11 | compileSdkVersion rootProject.ext.compileSdk 12 | 13 | defaultConfig { 14 | minSdkVersion rootProject.ext.minSdk 15 | targetSdkVersion rootProject.ext.targetSdk 16 | } 17 | 18 | compileOptions { 19 | sourceCompatibility = JavaVersion.VERSION_1_8 20 | targetCompatibility = JavaVersion.VERSION_1_8 21 | } 22 | 23 | kotlinOptions { 24 | jvmTarget = JavaVersion.VERSION_1_8.toString() 25 | } 26 | 27 | buildFeatures { 28 | viewBinding true 29 | } 30 | } 31 | 32 | dependencies { 33 | implementation "org.jetbrains.kotlin:kotlin-stdlib" 34 | } 35 | -------------------------------------------------------------------------------- /recaptcha/src/main/java/ru/touchin/recaptcha/GoogleCaptchaClient.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.recaptcha 2 | 3 | import android.app.Activity 4 | import com.google.android.gms.safetynet.SafetyNet 5 | 6 | class GoogleCaptchaClient( 7 | onNewTokenReceived: (String) -> Unit, 8 | private val processThrowable: (Throwable) -> Unit 9 | ) : CaptchaClient(onNewTokenReceived, processThrowable) { 10 | 11 | override fun showCaptcha(activity: Activity, captchaKey: String) { 12 | SafetyNet.getClient(activity) 13 | .verifyWithRecaptcha(captchaKey) 14 | .addOnSuccessListener(activity) { response -> 15 | onServiceTokenResponse(response?.tokenResult) 16 | } 17 | .addOnFailureListener(activity, processThrowable) 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /api-logansquare/src/main/java/com/bluelinelabs/logansquare/ConverterUtils.java: -------------------------------------------------------------------------------- 1 | package com.bluelinelabs.logansquare; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import java.lang.reflect.Type; 6 | 7 | /** 8 | * Utility class for the {@link ru.touchin.templates.logansquare.LoganSquareJsonFactory}. This resides in LoganSquare's 9 | * main package in order to take advantage of the package-visible ConcreteParameterizedType class, which is essential 10 | * to the support of generic classes in the Retrofit converter. 11 | */ 12 | public final class ConverterUtils { 13 | 14 | @NonNull 15 | public static ParameterizedType parameterizedTypeOf(@NonNull final Type type) { 16 | return new ParameterizedType.ConcreteParameterizedType(type); 17 | } 18 | 19 | private ConverterUtils() { 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /navigation-cicerone/src/main/java/ru/touchin/roboswag/navigation_cicerone/CiceroneTuner.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.navigation_cicerone 2 | 3 | import androidx.lifecycle.Lifecycle 4 | import androidx.lifecycle.LifecycleObserver 5 | import androidx.lifecycle.OnLifecycleEvent 6 | import ru.terrakok.cicerone.Navigator 7 | import ru.terrakok.cicerone.NavigatorHolder 8 | 9 | class CiceroneTuner( 10 | private val navigatorHolder: NavigatorHolder, 11 | private val navigator: Navigator 12 | ) : LifecycleObserver { 13 | 14 | @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) 15 | fun addNavigator() { 16 | navigatorHolder.setNavigator(navigator) 17 | } 18 | 19 | @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) 20 | fun removeNavigator() { 21 | navigatorHolder.removeNavigator() 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /lifecycle/src/main/java/ru/touchin/lifecycle/livedata/SingleLiveEvent.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.lifecycle.livedata 2 | 3 | import androidx.lifecycle.LifecycleOwner 4 | import androidx.lifecycle.MutableLiveData 5 | import androidx.lifecycle.Observer 6 | import java.util.concurrent.atomic.AtomicBoolean 7 | 8 | open class SingleLiveEvent : MutableLiveData() { 9 | 10 | private val pending = AtomicBoolean(false) 11 | 12 | override fun observe(owner: LifecycleOwner, observer: Observer) { 13 | super.observe(owner, Observer { value -> 14 | if (pending.compareAndSet(true, false)) { 15 | observer.onChanged(value) 16 | } 17 | }) 18 | } 19 | 20 | override fun setValue(value: T) { 21 | pending.set(true) 22 | super.setValue(value) 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /recaptcha/src/main/java/ru/touchin/recaptcha/CaptchaClient.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.recaptcha 2 | 3 | import android.app.Activity 4 | 5 | abstract class CaptchaClient( 6 | private val onNewTokenReceived: (String) -> Unit = {}, 7 | private val processThrowable: (Throwable) -> Unit = {} 8 | ) { 9 | 10 | abstract fun showCaptcha(activity: Activity, captchaKey: String) 11 | 12 | protected fun onServiceTokenResponse(newToken: String?) { 13 | if (!newToken.isNullOrBlank()) { 14 | onNewTokenReceived.invoke(newToken) 15 | } else { 16 | processThrowable.invoke(EmptyCaptchaTokenException()) 17 | } 18 | } 19 | 20 | } 21 | 22 | class EmptyCaptchaKeyException : Throwable("Captcha key is empty") 23 | 24 | class EmptyCaptchaTokenException : Throwable("Captcha token is empty") 25 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | android.enableJetifier=true 10 | android.useAndroidX=true 11 | 12 | org.gradle.jvmargs=-Xmx4096m 13 | # When configured, Gradle will run in incubating parallel mode. 14 | # This option should only be used with decoupled projects. More details, visit 15 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 16 | org.gradle.parallel=true 17 | -------------------------------------------------------------------------------- /yandex-map/src/main/java/ru/touchin/yandexmap/BitmapHelper.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.yandexmap 2 | 3 | import android.content.Context 4 | import android.graphics.Bitmap 5 | import android.graphics.Canvas 6 | import androidx.core.content.ContextCompat 7 | 8 | object BitmapHelper { 9 | 10 | fun getBitmapFromVectorDrawable(context: Context, drawableId: Int): Bitmap? { 11 | val drawable = ContextCompat.getDrawable(context, drawableId) ?: return null 12 | 13 | val bitmap = Bitmap.createBitmap( 14 | drawable.intrinsicWidth, 15 | drawable.intrinsicHeight, 16 | Bitmap.Config.ARGB_8888) ?: return null 17 | val canvas = Canvas(bitmap) 18 | drawable.setBounds(0, 0, canvas.width, canvas.height) 19 | drawable.draw(canvas) 20 | 21 | return bitmap 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /utils/src/main/java/ru/touchin/roboswag/components/utils/SimpleTextWatcher.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.components.utils 2 | 3 | import android.text.Editable 4 | import android.text.TextWatcher 5 | 6 | class SimpleTextWatcher(private val onTextChanged: (Editable) -> Unit) : TextWatcher { 7 | 8 | private var ignore = false 9 | 10 | override fun afterTextChanged(s: Editable) { 11 | doPreventListenerLoop { onTextChanged(s) } 12 | } 13 | 14 | override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) = Unit 15 | 16 | override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) = Unit 17 | 18 | private fun doPreventListenerLoop(callback: () -> Unit) { 19 | if (ignore) return 20 | ignore = true 21 | callback() 22 | ignore = false 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /storable/src/main/java/ru/touchin/roboswag/core/observables/storable/SameTypesConverter.java: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.core.observables.storable; 2 | 3 | import androidx.annotation.NonNull; 4 | import androidx.annotation.Nullable; 5 | 6 | import java.lang.reflect.Type; 7 | 8 | /** 9 | * Simple safe converter that is doing nothing on conversion. 10 | * 11 | * @param Same type. 12 | */ 13 | public class SameTypesConverter implements Converter { 14 | 15 | @Nullable 16 | @Override 17 | public T toStoreObject(@NonNull final Type type1, @NonNull final Type type2, @Nullable final T object) { 18 | return object; 19 | } 20 | 21 | @Nullable 22 | @Override 23 | public T toObject(@NonNull final Type type1, @NonNull final Type type2, @Nullable final T object) { 24 | return object; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /utils/src/main/java/ru/touchin/roboswag/components/utils/spans/PhoneSpan.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.components.utils.spans 2 | 3 | import android.content.ActivityNotFoundException 4 | import android.content.Intent 5 | import android.net.Uri 6 | import android.view.View 7 | 8 | /** 9 | * Created by Gavriil Sitnikov on 14/11/2015. 10 | * Span that is opening phone call intent. 11 | */ 12 | class PhoneSpan(phoneNumber: String) : URLSpanWithoutUnderline(phoneNumber) { 13 | 14 | override fun onClick(widget: View) { 15 | super.onClick(widget) 16 | try { 17 | val intent = Intent(Intent.ACTION_DIAL) 18 | intent.data = Uri.parse(url) 19 | widget.context.startActivity(intent) 20 | } catch (exception: ActivityNotFoundException) { 21 | // Do nothing 22 | } 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /recyclerview-adapters/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "../android-configs/lib-config.gradle" 2 | 3 | dependencies { 4 | implementation project(':kotlin-extensions') 5 | 6 | implementation "androidx.recyclerview:recyclerview" 7 | 8 | implementation "androidx.core:core-ktx" 9 | 10 | implementation "androidx.paging:paging-runtime" 11 | 12 | constraints { 13 | implementation("androidx.recyclerview:recyclerview") { 14 | version { 15 | require '1.0.0' 16 | } 17 | } 18 | implementation("androidx.core:core-ktx") { 19 | version { 20 | require '1.0.0' 21 | } 22 | } 23 | implementation("androidx.paging:paging-runtime") { 24 | version { 25 | require '3.0.0-alpha06' 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /bottom-navigation-viewcontroller/src/main/java/ru/touchin/roboswag/bottom_navigation_viewcontroller/BottomNavigationActivity.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.bottom_navigation_viewcontroller 2 | 3 | import ru.touchin.roboswag.bottom_navigation_base.BaseBottomNavigationActivity 4 | import ru.touchin.roboswag.navigation_viewcontroller.viewcontrollers.ViewControllerNavigation 5 | 6 | abstract class BottomNavigationActivity : 7 | BaseBottomNavigationActivity, BottomNavigationFragment, NavigationContainerFragment>() { 8 | 9 | final override val navigation by lazy { 10 | ViewControllerNavigation( 11 | this, 12 | supportFragmentManager, 13 | fragmentContainerViewId, 14 | transition 15 | ) 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /utils/src/main/java/ru/touchin/defaults/DefaultActivityLifecycleCallbacks.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.defaults 2 | 3 | import android.app.Activity 4 | import android.app.Application 5 | import android.os.Bundle 6 | 7 | open class DefaultActivityLifecycleCallbacks : Application.ActivityLifecycleCallbacks { 8 | 9 | override fun onActivityPaused(activity: Activity) = Unit 10 | 11 | override fun onActivityResumed(activity: Activity) = Unit 12 | 13 | override fun onActivityStarted(activity: Activity) = Unit 14 | 15 | override fun onActivityDestroyed(activity: Activity) = Unit 16 | 17 | override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) = Unit 18 | 19 | override fun onActivityStopped(activity: Activity) = Unit 20 | 21 | override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) = Unit 22 | 23 | } 24 | -------------------------------------------------------------------------------- /logging_reader/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "../android-configs/lib-config.gradle" 2 | 3 | dependencies { 4 | implementation project(":navigation-base") 5 | 6 | implementation "androidx.recyclerview:recyclerview" 7 | implementation "androidx.constraintlayout:constraintlayout" 8 | implementation "androidx.fragment:fragment-ktx" 9 | 10 | constraints { 11 | implementation("androidx.recyclerview:recyclerview") { 12 | version { 13 | require '1.1.0' 14 | } 15 | } 16 | 17 | implementation("androidx.constraintlayout:constraintlayout"){ 18 | version { 19 | require '2.2.0-alpha03' 20 | } 21 | } 22 | 23 | implementation("androidx.fragment:fragment-ktx") { 24 | version { 25 | require '1.2.1' 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /bottom-navigation-fragment/src/main/java/ru/touchin/roboswag/bottom_navigation_fragment/BottomNavigationFragment.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.bottom_navigation_fragment 2 | 3 | import ru.touchin.roboswag.bottom_navigation_base.BaseBottomNavigationFragment 4 | 5 | abstract class BottomNavigationFragment : BaseBottomNavigationFragment() { 6 | 7 | override fun createNavigationController() = BottomNavigationController( 8 | context = requireContext(), 9 | fragments = tabs, 10 | fragmentManager = childFragmentManager, 11 | defaultTabId = defaultTabId, 12 | contentContainerViewId = contentContainerViewId, 13 | contentContainerLayoutId = contentContainerLayoutId, 14 | wrapWithNavigationContainer = wrapWithNavigationContainer, 15 | onReselectListener = reselectListener 16 | ) 17 | 18 | } 19 | -------------------------------------------------------------------------------- /webview/src/main/java/ru/touchin/roboswag/webview/web_view/WebViewCallback.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.webview.web_view 2 | 3 | import android.webkit.ConsoleMessage 4 | import android.webkit.WebView 5 | 6 | interface WebViewCallback { 7 | 8 | fun onStateChanged(newState: WebViewState) 9 | 10 | fun onOverrideUrlLoading(url: String?): Boolean 11 | 12 | fun onRepeatButtonClicked() 13 | 14 | fun onPageCookiesLoaded(cookies: Map?) = Unit 15 | 16 | fun onRedirectInsideWebView(webView: WebView, url: String?) = Unit 17 | 18 | fun onWebViewScrolled(scrollX: Int, scrollY: Int) = Unit 19 | 20 | fun onJsConfirm(message: String?) = Unit 21 | 22 | fun onJsAlert(message: String?) = Unit 23 | 24 | fun onJsPrompt(defaultValue: String?) = Unit 25 | 26 | fun onJsError(error: ConsoleMessage) = Unit 27 | 28 | fun onProgressChanged(progress: Int) = Unit 29 | } 30 | -------------------------------------------------------------------------------- /mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/di/ViewModelFactory.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.mvi_arch.di 2 | 3 | import android.os.Bundle 4 | import androidx.lifecycle.AbstractSavedStateViewModelFactory 5 | import androidx.lifecycle.SavedStateHandle 6 | import androidx.lifecycle.ViewModel 7 | import androidx.savedstate.SavedStateRegistryOwner 8 | 9 | class ViewModelFactory( 10 | private val viewModelMap: MutableMap, ViewModelAssistedFactory>, 11 | owner: SavedStateRegistryOwner, 12 | arguments: Bundle 13 | ) : AbstractSavedStateViewModelFactory(owner, arguments) { 14 | 15 | @Suppress("UNCHECKED_CAST") 16 | override fun create(key: String, modelClass: Class, handle: SavedStateHandle): T = 17 | viewModelMap[modelClass]?.create(handle) as? T ?: throw IllegalStateException("Unknown ViewModel class") 18 | 19 | } 20 | -------------------------------------------------------------------------------- /navigation-cicerone/src/main/java/ru/touchin/roboswag/navigation_cicerone/flow/FlowNavigationModule.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.navigation_cicerone.flow 2 | 3 | import dagger.Module 4 | import dagger.Provides 5 | import ru.terrakok.cicerone.Cicerone 6 | import ru.terrakok.cicerone.NavigatorHolder 7 | import ru.terrakok.cicerone.Router 8 | import ru.touchin.roboswag.navigation_base.scopes.FeatureScope 9 | 10 | @Module 11 | class FlowNavigationModule { 12 | 13 | @Provides 14 | @FlowNavigation 15 | @FeatureScope 16 | fun provideCicerone(): Cicerone = Cicerone.create() 17 | 18 | @Provides 19 | @FlowNavigation 20 | fun provideNavigatorHolder(@FlowNavigation cicerone: Cicerone): NavigatorHolder = cicerone.navigatorHolder 21 | 22 | @Provides 23 | @FlowNavigation 24 | fun provideRouter(@FlowNavigation cicerone: Cicerone): Router = cicerone.router 25 | 26 | } 27 | -------------------------------------------------------------------------------- /bottom-navigation-viewcontroller/src/main/java/ru/touchin/roboswag/bottom_navigation_viewcontroller/BottomNavigationFragment.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.bottom_navigation_viewcontroller 2 | 3 | import ru.touchin.roboswag.bottom_navigation_base.BaseBottomNavigationFragment 4 | 5 | abstract class BottomNavigationFragment : BaseBottomNavigationFragment() { 6 | 7 | override fun createNavigationController() = BottomNavigationController( 8 | context = requireContext(), 9 | fragmentManager = childFragmentManager, 10 | viewControllers = tabs, 11 | defaultTabId = defaultTabId, 12 | contentContainerViewId = contentContainerViewId, 13 | contentContainerLayoutId = contentContainerLayoutId, 14 | wrapWithNavigationContainer = wrapWithNavigationContainer, 15 | onReselectListener = reselectListener 16 | ) 17 | 18 | } 19 | 20 | -------------------------------------------------------------------------------- /recyclerview-decorators/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "../android-configs/lib-config.gradle" 2 | apply plugin: 'kotlin-android' 3 | 4 | dependencies { 5 | implementation project(":utils") 6 | implementation project(":kotlin-extensions") 7 | 8 | implementation "com.google.android.material:material" 9 | implementation "androidx.core:core-ktx" 10 | 11 | constraints { 12 | implementation("com.google.android.material:material") { 13 | version { 14 | require '1.0.0' 15 | } 16 | } 17 | implementation("androidx.core:core-ktx") { 18 | version { 19 | require '1.3.1' 20 | } 21 | } 22 | implementation("org.jetbrains.kotlin:kotlin-stdlib") { 23 | version { 24 | require '1.3.0' 25 | } 26 | } 27 | } 28 | } 29 | 30 | repositories { 31 | mavenCentral() 32 | } 33 | -------------------------------------------------------------------------------- /mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/marker/ViewAction.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.mvi_arch.marker 2 | 3 | /** 4 | * This interface should be implemented to create your own view actions and use it with [MviFragment] and [MviViewModel]. 5 | * 6 | * Usually it's sealed class with nested classes and objects representing view actions. 7 | * 8 | * Quite common cases: 9 | * 1. View contains simple button: 10 | * object OnButtonClicked : YourViewAction() 11 | * 12 | * 2. View contains button with parameter: 13 | * data class OnButtonWithParamClicked(val param: Param): YourViewAction() 14 | * 15 | * 3. View contains text input field: 16 | * data class OnInputChanged(val input: String): YourViewAction() 17 | * 18 | * Exemplars of this classes used to generate new [ViewState] in [MviViewModel]. 19 | * 20 | * @author Created by Max Bachinsky and Ivan Vlasov at Touch Instinct. 21 | */ 22 | interface ViewAction 23 | -------------------------------------------------------------------------------- /mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/mediator/MediatorStore.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.mvi_arch.mediator 2 | 3 | import ru.touchin.roboswag.mvi_arch.marker.SideEffect 4 | import ru.touchin.roboswag.mvi_arch.marker.StateChange 5 | import ru.touchin.roboswag.mvi_arch.marker.ViewAction 6 | import ru.touchin.roboswag.mvi_arch.marker.ViewState 7 | 8 | class MediatorStore(private val mediators: List) : Mediator { 9 | 10 | override fun onAction(action: ViewAction) { 11 | mediators.forEach { it.onAction(action) } 12 | } 13 | 14 | override fun onEffect(effect: SideEffect) { 15 | mediators.forEach { it.onEffect(effect) } 16 | } 17 | 18 | override fun onNewState(state: ViewState) { 19 | mediators.forEach { it.onNewState(state) } 20 | } 21 | 22 | override fun onStateChange(change: StateChange) { 23 | mediators.forEach { it.onStateChange(change) } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lifecycle/src/main/java/ru/touchin/lifecycle/livedata/EmptySingleLiveEvent.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.lifecycle.livedata 2 | 3 | import androidx.annotation.MainThread 4 | 5 | /** 6 | * A lifecycle-aware observable that sends only new updates after subscription, used for events like 7 | * navigation and Snackbar messages. 8 | * 9 | * 10 | * This avoids a common problem with events: on configuration change (like rotation) an update 11 | * can be emitted if the observer is active. This LiveData only calls the observable if there's an 12 | * explicit call to setValue() or call(). 13 | * 14 | * 15 | * Note that only one observer is going to be notified of changes. 16 | * 17 | * This version of SingleLiveEvent supports empty events 18 | */ 19 | class EmptySingleLiveEvent : SingleLiveEvent() { 20 | @MainThread 21 | fun call() { 22 | value = null 23 | } 24 | 25 | fun postCall() { 26 | super.postValue(null) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /pagination/src/main/java/ru/touchin/roboswag/pagination/ProgressAdapterDelegate.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.pagination 2 | 3 | import android.view.ViewGroup 4 | import androidx.recyclerview.widget.RecyclerView 5 | import ru.touchin.roboswag.recyclerview_adapters.adapters.ItemAdapterDelegate 6 | import ru.touchin.mvi_arch.core_pagination.R 7 | import ru.touchin.roboswag.components.utils.UiUtils 8 | 9 | class ProgressAdapterDelegate : ItemAdapterDelegate() { 10 | 11 | override fun onCreateViewHolder(parent: ViewGroup): RecyclerView.ViewHolder = 12 | object : RecyclerView.ViewHolder(UiUtils.inflate(R.layout.item_progress, parent)) {} 13 | 14 | override fun onBindViewHolder( 15 | holder: RecyclerView.ViewHolder, 16 | item: ProgressItem, 17 | adapterPosition: Int, 18 | collectionPosition: Int, 19 | payloads: MutableList 20 | ) = Unit 21 | } 22 | -------------------------------------------------------------------------------- /yandex-map/src/main/java/ru/touchin/yandexmap/DefaultIconGenerator.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.yandexmap 2 | 3 | import android.content.Context 4 | import android.view.LayoutInflater 5 | import android.widget.TextView 6 | import com.yandex.runtime.ui_view.ViewProvider 7 | 8 | class DefaultIconGenerator(private val context: Context) : YandexIconGenerator() { 9 | 10 | override fun getClusterIcon(cluster: List): ViewProvider { 11 | val textView = LayoutInflater.from(context).inflate(R.layout.default_cluster_view, null).apply { 12 | (this as? TextView)?.text = cluster.size.toString() 13 | setBackgroundResource(ru.touchin.basemap.R.drawable.marker_default_icon) 14 | } 15 | return ViewProvider(textView) 16 | } 17 | 18 | override fun getClusterItemIcon(clusterItem: T) = ViewProvider( 19 | LayoutInflater.from(context).inflate(R.layout.default_cluster_item_view, null) 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /rx-extensions/src/main/java/ru/touchin/extensions/rx/Flowable.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.extensions.rx 2 | 3 | import io.reactivex.Completable 4 | import io.reactivex.Flowable 5 | import ru.touchin.extensions.rx.utils.StringConstants 6 | import ru.touchin.roboswag.core.utils.Optional 7 | import ru.touchin.roboswag.core.utils.ShouldNotHappenException 8 | 9 | fun Flowable.emitAfter(other: Completable): Flowable = this.flatMap { value -> 10 | other.andThen(Flowable.just(value)) 11 | } 12 | 13 | fun Flowable>.unwrapOrError( 14 | errorMessage: String = StringConstants.OPTIONAL_UNWRAPPING_ERROR_MESSAGE 15 | ): Flowable = this.flatMap { wrapper -> 16 | wrapper.get() 17 | ?.let { Flowable.just(it) } 18 | ?: Flowable.error(ShouldNotHappenException(errorMessage)) 19 | } 20 | 21 | fun Flowable>.unwrapOrFilter(): Flowable = this 22 | .filter { it.get() != null } 23 | .map { it.get() } 24 | -------------------------------------------------------------------------------- /recaptcha/README.md: -------------------------------------------------------------------------------- 1 | recaptcha 2 | ===== 3 | 4 | ### Общее описание 5 | 6 | Модуль содержит класс `CaptchaManager` - служит для проверки используемого сервиса (Huawei или Google) и показа диалога с каптчёй 7 | В конструктуре `CaptchaManager` принимает два callback: 8 | `onNewTokenReceived` - успешная проверка, возвращает токен 9 | `processThrowable` - ошибка, возвращает `Throwable` 10 | 11 | ### Требования 12 | 13 | Для использования модуля нужно добавить json файл с сервисами в корневую папку проекта: 14 | 15 | 1. Для Google - google-services.json 16 | 2. Для Huawei - agconnect-services.json 17 | 18 | ### Пример 19 | 20 | Во `Fragment` 21 | 22 | ```kotlin 23 | val manager = CaptchaManager(onNewTokenReceived = { token -> 24 | viewModel.sendRequest(token) 25 | }, processThrowable = { error -> 26 | showError(error) 27 | }) 28 | 29 | manager.showRecaptchaAlert( 30 | activity = activity, 31 | captchaKey = BuildConfig.CAPTCHA_TOKEN 32 | ) 33 | ``` 34 | -------------------------------------------------------------------------------- /utils/src/test/java/DateFormatUtilsTest.kt: -------------------------------------------------------------------------------- 1 | import org.joda.time.DateTime 2 | import org.junit.Assert 3 | import org.junit.Test 4 | import ru.touchin.roboswag.core.utils.DateFormatUtils 5 | 6 | class DateFormatUtilsTest { 7 | 8 | @Test 9 | fun `Assert Date format parsing`() { 10 | val dateTime = DateFormatUtils.fromString( 11 | value = "2015-04-29", 12 | format = DateFormatUtils.Format.DATE_FORMAT 13 | ) 14 | Assert.assertEquals(DateTime(2015, 4, 29, 0, 0, 0), dateTime) 15 | } 16 | 17 | @Test 18 | fun `Assert Date format parsing with default value`() { 19 | val currentDateTime = DateTime.now() 20 | 21 | val dateTime = DateFormatUtils.fromString( 22 | value = "2015-04-29", 23 | format = DateFormatUtils.Format.DATE_TIME_FORMAT, 24 | defaultValue = currentDateTime 25 | ) 26 | Assert.assertEquals(currentDateTime, dateTime) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /rx-extensions/src/main/java/ru/touchin/extensions/rx/Observable.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.extensions.rx 2 | 3 | import io.reactivex.Completable 4 | import io.reactivex.Observable 5 | import ru.touchin.extensions.rx.utils.StringConstants 6 | import ru.touchin.roboswag.core.utils.Optional 7 | import ru.touchin.roboswag.core.utils.ShouldNotHappenException 8 | 9 | fun Observable.emitAfter(other: Completable): Observable = this.flatMap { value -> 10 | other.andThen(Observable.just(value)) 11 | } 12 | 13 | fun Observable>.unwrapOrError( 14 | errorMessage: String = StringConstants.OPTIONAL_UNWRAPPING_ERROR_MESSAGE 15 | ): Observable = this.flatMap { wrapper -> 16 | wrapper.get() 17 | ?.let { Observable.just(it) } 18 | ?: Observable.error(ShouldNotHappenException(errorMessage)) 19 | } 20 | 21 | fun Observable>.unwrapOrFilter(): Observable = this 22 | .filter { it.get() != null } 23 | .map { it.get() } 24 | -------------------------------------------------------------------------------- /recyclerview-adapters/src/main/java/ru/touchin/roboswag/recyclerview_adapters/adapters/SimpleDataObserver.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.recyclerview_adapters.adapters 2 | 3 | import androidx.recyclerview.widget.RecyclerView 4 | 5 | abstract class SimpleDataObserver : RecyclerView.AdapterDataObserver() { 6 | 7 | abstract fun onChange() 8 | 9 | override fun onItemRangeChanged(positionStart: Int, itemCount: Int) = onChange() 10 | 11 | override fun onItemRangeInserted(positionStart: Int, itemCount: Int) = onChange() 12 | 13 | override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) = onChange() 14 | 15 | override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) = onChange() 16 | 17 | } 18 | 19 | fun RecyclerView.Adapter.onDataUpdatedAndDrawn(onChanged: () -> Unit) = registerAdapterDataObserver( 20 | object : SimpleDataObserver() { 21 | override fun onChange() = onChanged() 22 | } 23 | ) 24 | -------------------------------------------------------------------------------- /lifecycle-rx/src/main/java/ru/touchin/lifecycle/viewmodel/LiveDataDispatcher.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.lifecycle.viewmodel 2 | 3 | import androidx.lifecycle.MutableLiveData 4 | import io.reactivex.Completable 5 | import io.reactivex.Flowable 6 | import io.reactivex.Maybe 7 | import io.reactivex.Observable 8 | import io.reactivex.Single 9 | import io.reactivex.disposables.Disposable 10 | import ru.touchin.lifecycle.event.ContentEvent 11 | import ru.touchin.lifecycle.event.Event 12 | 13 | interface LiveDataDispatcher { 14 | 15 | fun Flowable.dispatchTo(liveData: MutableLiveData>): Disposable 16 | 17 | fun Observable.dispatchTo(liveData: MutableLiveData>): Disposable 18 | 19 | fun Single.dispatchTo(liveData: MutableLiveData>): Disposable 20 | 21 | fun Maybe.dispatchTo(liveData: MutableLiveData>): Disposable 22 | 23 | fun Completable.dispatchTo(liveData: MutableLiveData): Disposable 24 | 25 | } 26 | -------------------------------------------------------------------------------- /lifecycle/src/main/java/ru/touchin/lifecycle/extensions/LifecycleOwner.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.lifecycle.extensions 2 | 3 | import androidx.lifecycle.Lifecycle 4 | import androidx.lifecycle.LifecycleOwner 5 | import ru.touchin.lifecycle.OnLifecycle 6 | import kotlin.properties.ReadOnlyProperty 7 | 8 | fun R.onCreateEvent( 9 | initializer: (R) -> T 10 | ): ReadOnlyProperty = OnLifecycle(this, Lifecycle.Event.ON_CREATE, initializer) 11 | 12 | fun R.onStartEvent( 13 | initializer: (R) -> T 14 | ): ReadOnlyProperty = OnLifecycle(this, Lifecycle.Event.ON_START, initializer) 15 | 16 | fun R.onResumeEvent( 17 | initializer: (R) -> T 18 | ): ReadOnlyProperty = OnLifecycle(this, Lifecycle.Event.ON_RESUME, initializer) 19 | 20 | fun R.onLifecycle( 21 | initializeEvent: Lifecycle.Event, 22 | initializer: (R) -> T 23 | ): ReadOnlyProperty = OnLifecycle(this, initializeEvent, initializer) 24 | -------------------------------------------------------------------------------- /bottom-navigation-fragment/src/main/java/ru/touchin/roboswag/bottom_navigation_fragment/NavigationContainerFragment.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.bottom_navigation_fragment 2 | 3 | import android.os.Parcelable 4 | import ru.touchin.roboswag.bottom_navigation_base.BaseNavigationContainerFragment 5 | import ru.touchin.roboswag.navigation_base.FragmentNavigation 6 | import ru.touchin.roboswag.navigation_base.fragments.StatefulFragment 7 | 8 | class NavigationContainerFragment : 9 | BaseNavigationContainerFragment, FragmentNavigation>() { 10 | 11 | override val navigation by lazy { 12 | FragmentNavigation( 13 | requireContext(), 14 | childFragmentManager, 15 | containerViewId, 16 | transition 17 | ) 18 | } 19 | 20 | override fun onContainerCreated() { 21 | navigation.setInitial(getContainedClass().kotlin, arguments?.getParcelable(FRAGMENT_STATE_ARG)!!) 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /client-services/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "../android-configs/lib-config.gradle" 2 | apply plugin: 'com.huawei.agconnect' 3 | 4 | dependencies { 5 | implementation "androidx.core:core" 6 | implementation "androidx.annotation:annotation" 7 | implementation "com.google.android.gms:play-services-base" 8 | implementation "com.huawei.hms:base" 9 | 10 | constraints { 11 | implementation("androidx.core:core") { 12 | version { 13 | require '1.0.0' 14 | } 15 | } 16 | 17 | implementation("androidx.annotation:annotation") { 18 | version { 19 | require '1.1.0' 20 | } 21 | } 22 | 23 | implementation("com.google.android.gms:play-services-base") { 24 | version { 25 | require '18.0.1' 26 | } 27 | } 28 | 29 | implementation("com.huawei.hms:base") { 30 | version { 31 | require '6.3.0.303' 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /views/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "../android-configs/lib-config.gradle" 2 | apply plugin: 'kotlin-android' 3 | 4 | android { 5 | buildFeatures { 6 | viewBinding true 7 | } 8 | } 9 | 10 | dependencies { 11 | implementation project(":utils") 12 | implementation project(":kotlin-extensions") 13 | implementation project(":logging") 14 | 15 | implementation "com.google.android.material:material" 16 | implementation "androidx.core:core-ktx" 17 | 18 | constraints { 19 | implementation("com.google.android.material:material") { 20 | version { 21 | require '1.0.0' 22 | } 23 | } 24 | implementation("androidx.core:core-ktx") { 25 | version { 26 | require '1.3.1' 27 | } 28 | } 29 | implementation("org.jetbrains.kotlin:kotlin-stdlib") { 30 | version { 31 | require '1.3.0' 32 | } 33 | } 34 | } 35 | } 36 | 37 | repositories { 38 | mavenCentral() 39 | } 40 | -------------------------------------------------------------------------------- /yandex-map/src/main/java/ru/touchin/yandexmap/YandexIconGenerator.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.yandexmap 2 | 3 | import com.yandex.runtime.ui_view.ViewProvider 4 | import ru.touchin.basemap.BaseIconGenerator 5 | import ru.touchin.basemap.getOrPutIfNotNull 6 | 7 | abstract class YandexIconGenerator 8 | : BaseIconGenerator, ViewProvider> { 9 | 10 | private val placemarksCache = mutableMapOf() 11 | private val clustersCache = mutableMapOf, ViewProvider>() 12 | 13 | override fun getClusterItemView(clusterItem: TPoint): ViewProvider? = 14 | placemarksCache.getOrPutIfNotNull(clusterItem) { getClusterItemIcon(clusterItem) } 15 | 16 | override fun getClusterView(cluster: List): ViewProvider? = 17 | clustersCache.getOrPutIfNotNull(cluster) { getClusterIcon(cluster) } 18 | 19 | abstract override fun getClusterIcon(cluster: List): ViewProvider? 20 | 21 | abstract override fun getClusterItemIcon(clusterItem: TPoint): ViewProvider? 22 | } 23 | -------------------------------------------------------------------------------- /livedata-location/README.md: -------------------------------------------------------------------------------- 1 | livedata-location 2 | ===== 3 | 4 | Модуль позволяющий получать местоположение пользователя в виде потока данных `LiveData`. 5 | 6 | ### Основный интерфейсы и классы 7 | 8 | Класс `LocationLiveData`. В конструкторе принимает `Context` и `LocationRequest`. Посылает `Location` подписчикам через указанные в `LocationRequest` интервалы времени. Метод *observe* позволяет подписаться на эти обновления. Данный метод принимает `LifecycleOwner` и `Observer`. Стоит учесть, что для использования данного класса нужно одно из следующих разрешений `ACCESS_COARSE_LOCATION` или `ACCESS_FINE_LOCATION`. 9 | ### Примеры 10 | 11 | Во `ViewModel`. 12 | 13 | ```kotlin 14 | val locationWithInterval = LocationLiveData( 15 | context, 16 | LocationRequest 17 | .create() 18 | .setInterval(5000) 19 | .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY) 20 | ) 21 | ``` 22 | 23 | Во `ViewController`. 24 | 25 | ```kotlin 26 | viewModel.locationWithInterval.observe(this, Observer(::onLocationChanged)) 27 | ``` 28 | -------------------------------------------------------------------------------- /bottom-navigation-base/src/main/java/ru/touchin/roboswag/bottom_navigation_base/BaseNavigationTab.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.bottom_navigation_base 2 | 3 | import android.os.Parcelable 4 | import ru.touchin.roboswag.navigation_base.extensions.copy 5 | 6 | open class BaseNavigationTab( 7 | open val cls: Class<*>, 8 | state: Parcelable, 9 | /** 10 | * It can be useful in some cases when it is necessary to create ViewController 11 | * with initial state every time when tab opens. 12 | */ 13 | val saveStateOnSwitching: Boolean = true 14 | ) { 15 | 16 | /** 17 | * It is value as class body property instead of value as constructor parameter to specify 18 | * custom getter of this field which returns copy of Parcelable every time it be called. 19 | * This is necessary to avoid modifying this value if it would be a value as constructor parameter 20 | * and every getting of this value would return the same instance. 21 | */ 22 | val state = state 23 | get() = field.copy() 24 | 25 | } 26 | -------------------------------------------------------------------------------- /storable/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "../android-configs/lib-config.gradle" 2 | 3 | dependencies { 4 | implementation project(":utils") 5 | implementation project(":logging") 6 | 7 | implementation "androidx.core:core" 8 | implementation "androidx.annotation:annotation" 9 | 10 | implementation "io.reactivex.rxjava2:rxjava" 11 | implementation "io.reactivex.rxjava2:rxandroid" 12 | 13 | constraints { 14 | implementation("androidx.core:core") { 15 | version { 16 | require '1.0.0' 17 | } 18 | } 19 | 20 | implementation("androidx.annotation:annotation") { 21 | version { 22 | require '1.0.0' 23 | } 24 | } 25 | 26 | implementation("io.reactivex.rxjava2:rxjava") { 27 | version { 28 | require '2.2.6' 29 | } 30 | } 31 | 32 | implementation("io.reactivex.rxjava2:rxandroid") { 33 | version { 34 | require '2.1.1' 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/regexgenerator/RegexReplaceGenerator.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.textprocessing.generators.regexgenerator 2 | 3 | import org.antlr.v4.runtime.CharStreams 4 | import org.antlr.v4.runtime.CommonTokenStream 5 | import org.antlr.v4.runtime.tree.ParseTreeWalker 6 | import ru.touchin.roboswag.textprocessing.pcre.parser.PCRELexer 7 | import ru.touchin.roboswag.textprocessing.pcre.parser.PCREParser 8 | 9 | class RegexReplaceGenerator { 10 | 11 | fun regexToRegexReplace(regex: String): PCREGeneratorItem { 12 | val stringStream = CharStreams.fromString(regex) 13 | 14 | val lexer = PCRELexer(stringStream) 15 | 16 | val parser = PCREParser(CommonTokenStream(lexer)) 17 | 18 | val parseContext = parser.parse() 19 | 20 | val walker = ParseTreeWalker() 21 | 22 | val pcreGeneratorListener = PCREGeneratorListener() 23 | 24 | walker.walk(pcreGeneratorListener, parseContext) 25 | return pcreGeneratorListener.toPCREGeneratorItem() 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /kotlin-extensions/src/main/java/ru/touchin/extensions/Delegates.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.extensions 2 | 3 | import kotlin.properties.Delegates 4 | import kotlin.properties.ObservableProperty 5 | import kotlin.properties.ReadWriteProperty 6 | import kotlin.reflect.KProperty 7 | 8 | /** 9 | * Simple observable delegate only for notification of new value. 10 | */ 11 | inline fun Delegates.observable( 12 | initialValue: T, 13 | crossinline onChange: (newValue: T) -> Unit 14 | ): ReadWriteProperty = object : ObservableProperty(initialValue) { 15 | override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = onChange(newValue) 16 | } 17 | 18 | inline fun Delegates.distinctUntilChanged( 19 | initialValue: T, 20 | crossinline onChange: (newValue: T) -> Unit 21 | ): ReadWriteProperty = object : ObservableProperty(initialValue) { 22 | override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = 23 | if (newValue != null && oldValue != newValue) onChange(newValue) else Unit 24 | } 25 | -------------------------------------------------------------------------------- /lifecycle/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "../android-configs/lib-config.gradle" 2 | 3 | dependencies { 4 | compileOnly "javax.inject:javax.inject:1" 5 | 6 | implementation "androidx.appcompat:appcompat" 7 | 8 | implementation "androidx.fragment:fragment" 9 | implementation "androidx.fragment:fragment-ktx" 10 | 11 | implementation "androidx.lifecycle:lifecycle-extensions" 12 | 13 | constraints { 14 | implementation("androidx.appcompat:appcompat") { 15 | version { 16 | require '1.0.0' 17 | } 18 | } 19 | 20 | implementation("androidx.lifecycle:lifecycle-extensions") { 21 | version { 22 | require '2.1.0' 23 | } 24 | } 25 | 26 | implementation("androidx.fragment:fragment") { 27 | version { 28 | require '1.0.0' 29 | } 30 | } 31 | 32 | implementation("androidx.fragment:fragment-ktx") { 33 | version { 34 | require '1.1.0' 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /webview/src/main/java/ru/touchin/roboswag/webview/web_view/redirection/IgnoredErrorsHolder.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.webview.web_view.redirection 2 | 3 | import android.webkit.WebResourceError 4 | import android.webkit.WebResourceRequest 5 | 6 | /** 7 | * Define some urls here which errors should be ignored while redirection to prevent show error screen 8 | * Additionally you can add custom error ignoring condition 9 | */ 10 | class IgnoredErrorsHolder( 11 | private val ignoredUrlsList: List = listOf( 12 | "https://stats.g.doubleclick.net/" 13 | ), 14 | private val ignoreCondition: ((WebResourceRequest, WebResourceError) -> Boolean)? = null 15 | ) { 16 | 17 | constructor(vararg urls: String) : this(urls.toList()) 18 | 19 | fun shouldIgnoreError(request: WebResourceRequest, error: WebResourceError): Boolean { 20 | if (ignoreCondition != null) return ignoreCondition.invoke(request, error) 21 | 22 | val url = request.url.toString() 23 | 24 | return ignoredUrlsList.any { url in it } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /webview/src/main/java/ru/touchin/roboswag/webview/web_view/CustomWebView.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.webview.web_view 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.webkit.WebView 6 | 7 | class CustomWebView @JvmOverloads constructor( 8 | context: Context, 9 | attrs: AttributeSet?, 10 | defStyleAttr: Int = 0 11 | ) : WebView(context, attrs, defStyleAttr) { 12 | 13 | var onWebViewDisplayedContent: (() -> Unit)? = null 14 | var onScrollChanged: ((Int, Int, Int, Int) -> Unit)? = null 15 | 16 | // https://stackoverflow.com/a/14678910 17 | override fun invalidate() { 18 | super.invalidate() 19 | 20 | if (contentHeight > 0) { 21 | onWebViewDisplayedContent?.invoke() 22 | } 23 | 24 | } 25 | 26 | override fun onScrollChanged(scrollX: Int, scrollY: Int, oldScrollX: Int, oldScrollY: Int) { 27 | super.onScrollChanged(scrollX, scrollY, oldScrollX, oldScrollY) 28 | onScrollChanged?.invoke(scrollX, scrollY, oldScrollX, oldScrollY) 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /kotlin-extensions/src/main/java/ru/touchin/utils/ActionThrottler.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.utils 2 | 3 | import android.os.SystemClock 4 | import ru.touchin.extensions.RIPPLE_EFFECT_DELAY_MS 5 | 6 | object ActionThrottler { 7 | 8 | // It is necessary because in interval after ripple effect finish and before 9 | // action invoking start user may be in time to click and launch action again 10 | private const val PREVENTION_OF_CLICK_AGAIN_COEFFICIENT = 2 11 | private const val DELAY_MS = PREVENTION_OF_CLICK_AGAIN_COEFFICIENT * RIPPLE_EFFECT_DELAY_MS 12 | 13 | const val DEFAULT_THROTTLE_DELAY_MS = 500L 14 | private var lastActionTime = 0L 15 | 16 | fun throttleAction(throttleDelay: Long = DELAY_MS, action: () -> Unit): Boolean { 17 | val currentTime = SystemClock.elapsedRealtime() 18 | val diff = currentTime - lastActionTime 19 | 20 | return if (diff >= throttleDelay) { 21 | lastActionTime = currentTime 22 | action.invoke() 23 | true 24 | } else { 25 | false 26 | } 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareEnum.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Touch Instinct 3 | * 4 | * This file is part of RoboSwag library. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | package ru.touchin.templates.logansquare; 21 | 22 | import androidx.annotation.NonNull; 23 | 24 | /** 25 | * Created by Gavriil Sitnikov. 26 | * LoganSquare enum base class. 27 | */ 28 | public interface LoganSquareEnum { 29 | 30 | @NonNull 31 | String getValueName(); 32 | 33 | } 34 | -------------------------------------------------------------------------------- /recyclerview-calendar/src/main/java/ru/touchin/calendar/ComparingToToday.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) 3 | * 4 | * This file is part of RoboSwag library. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | package ru.touchin.calendar; 21 | 22 | /** 23 | * Created by Ilia Kurtov on 18/03/2016. 24 | * Show the comparison between a date and today. 25 | */ 26 | public enum ComparingToToday { 27 | 28 | BEFORE_TODAY, 29 | TODAY, 30 | AFTER_TODAY 31 | 32 | } 33 | -------------------------------------------------------------------------------- /logging_reader/src/main/res/layout/log_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 20 | 21 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /recaptcha/src/main/java/ru/touchin/recaptcha/HuaweiCaptchaClient.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.recaptcha 2 | 3 | import android.app.Activity 4 | import com.huawei.hms.support.api.safetydetect.SafetyDetect 5 | 6 | class HuaweiCaptchaClient( 7 | onNewTokenReceived: (String) -> Unit, 8 | private val processThrowable: (Throwable) -> Unit 9 | ) : CaptchaClient(onNewTokenReceived, processThrowable) { 10 | 11 | override fun showCaptcha(activity: Activity, captchaKey: String) { 12 | val huaweiSafetyDetectClient = SafetyDetect.getClient(activity) 13 | 14 | huaweiSafetyDetectClient.initUserDetect() 15 | .addOnSuccessListener { 16 | huaweiSafetyDetectClient.userDetection(captchaKey) 17 | .addOnSuccessListener { response -> 18 | onServiceTokenResponse(response?.responseToken) 19 | } 20 | .addOnFailureListener(activity, processThrowable) 21 | } 22 | .addOnFailureListener(activity, processThrowable) 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /recyclerview-adapters/src/main/java/ru/touchin/roboswag/recyclerview_adapters/adapters/OffsetAdapterUpdateCallback.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.recyclerview_adapters.adapters 2 | 3 | import androidx.recyclerview.widget.ListUpdateCallback 4 | import androidx.recyclerview.widget.RecyclerView 5 | 6 | class OffsetAdapterUpdateCallback(private val adapter: RecyclerView.Adapter<*>, private val offsetProvider: () -> Int) : ListUpdateCallback { 7 | 8 | override fun onInserted(position: Int, count: Int) { 9 | adapter.notifyItemRangeInserted(position + offsetProvider(), count) 10 | } 11 | 12 | override fun onRemoved(position: Int, count: Int) { 13 | adapter.notifyItemRangeRemoved(position + offsetProvider(), count) 14 | } 15 | 16 | override fun onMoved(fromPosition: Int, toPosition: Int) { 17 | adapter.notifyItemMoved(fromPosition + offsetProvider(), toPosition + offsetProvider()) 18 | } 19 | 20 | override fun onChanged(position: Int, count: Int, payload: Any?) { 21 | adapter.notifyItemRangeChanged(position + offsetProvider(), count, payload) 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/DecoroMaskGenerator.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.textprocessing.generators 2 | 3 | import ru.tinkoff.decoro.MaskImpl 4 | import ru.tinkoff.decoro.slots.PredefinedSlots 5 | import ru.tinkoff.decoro.slots.Slot 6 | import ru.tinkoff.decoro.watchers.MaskFormatWatcher 7 | import ru.touchin.roboswag.textprocessing.validators.CustomValidator 8 | 9 | class DecoroMaskGenerator { 10 | 11 | /** Генерация маски и слотов на основе возможных символов для placeholder, 12 | * если возможный символ всего один, то символ хардкодится в слот 13 | * **/ 14 | fun mask(placeholder: String, matrixOfSymbols: Matrix): MaskFormatWatcher { 15 | val slots = placeholder.mapIndexed { index, char -> 16 | if (matrixOfSymbols[index].size == 1) { 17 | PredefinedSlots.hardcodedSlot(char) 18 | } else { 19 | CustomValidator.customSlot(matrixOfSymbols[index]) 20 | } 21 | } 22 | return MaskFormatWatcher(MaskImpl.createTerminated(slots.toTypedArray())) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lifecycle-rx/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "../android-configs/lib-config.gradle" 2 | 3 | dependencies { 4 | api project(":utils") 5 | api project(":logging") 6 | api project(":lifecycle") 7 | 8 | implementation "androidx.appcompat:appcompat" 9 | 10 | implementation "androidx.lifecycle:lifecycle-extensions" 11 | 12 | implementation "io.reactivex.rxjava2:rxjava" 13 | implementation "io.reactivex.rxjava2:rxandroid" 14 | 15 | constraints { 16 | implementation("androidx.appcompat:appcompat") { 17 | version { 18 | require '1.0.0' 19 | } 20 | } 21 | 22 | implementation("androidx.lifecycle:lifecycle-extensions") { 23 | version { 24 | require '2.1.0' 25 | } 26 | } 27 | 28 | implementation("io.reactivex.rxjava2:rxjava") { 29 | version { 30 | require '2.2.6' 31 | } 32 | } 33 | 34 | implementation("io.reactivex.rxjava2:rxandroid") { 35 | version { 36 | require '2.0.0' 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /client-services/src/main/java/ru/touchin/client_services/ServicesUtils.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.client_services 2 | 3 | import android.content.Context 4 | import com.google.android.gms.common.ConnectionResult 5 | import com.google.android.gms.common.GoogleApiAvailability 6 | import com.huawei.hms.api.HuaweiApiAvailability 7 | 8 | /** 9 | * A class with utils for interacting with Google, Huawei services 10 | */ 11 | 12 | class ServicesUtils { 13 | 14 | fun getCurrentService(context: Context): MobileService = when { 15 | checkHuaweiServices(context) -> MobileService.HUAWEI_SERVICE 16 | checkGooglePlayServices(context) -> MobileService.GOOGLE_SERVICE 17 | else -> MobileService.GOOGLE_SERVICE 18 | } 19 | 20 | private fun checkHuaweiServices(context: Context): Boolean = 21 | HuaweiApiAvailability.getInstance() 22 | .isHuaweiMobileNoticeAvailable(context) == ConnectionResult.SUCCESS 23 | 24 | private fun checkGooglePlayServices(context: Context): Boolean = 25 | GoogleApiAvailability.getInstance() 26 | .isGooglePlayServicesAvailable(context) == ConnectionResult.SUCCESS 27 | 28 | } 29 | -------------------------------------------------------------------------------- /bottom-navigation-viewcontroller/src/main/java/ru/touchin/roboswag/bottom_navigation_viewcontroller/NavigationContainerFragment.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.bottom_navigation_viewcontroller 2 | 3 | import android.os.Parcelable 4 | import ru.touchin.roboswag.bottom_navigation_base.BaseNavigationContainerFragment 5 | import ru.touchin.roboswag.navigation_viewcontroller.viewcontrollers.ViewController 6 | import ru.touchin.roboswag.navigation_viewcontroller.viewcontrollers.ViewControllerNavigation 7 | 8 | class NavigationContainerFragment : 9 | BaseNavigationContainerFragment< 10 | ViewController, 11 | ViewControllerNavigation>() { 12 | 13 | override val navigation by lazy { 14 | ViewControllerNavigation( 15 | requireContext(), 16 | childFragmentManager, 17 | containerViewId, 18 | transition 19 | ) 20 | } 21 | 22 | override fun onContainerCreated() { 23 | navigation.setInitialViewController(getContainedClass(), arguments?.getParcelable(FRAGMENT_STATE_ARG)!!) 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /lifecycle-viewcontroller/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "../android-configs/lib-config.gradle" 2 | 3 | dependencies { 4 | implementation project(":lifecycle") 5 | implementation project(":navigation-viewcontroller") 6 | 7 | compileOnly "javax.inject:javax.inject:1" 8 | 9 | implementation "androidx.appcompat:appcompat" 10 | 11 | implementation "androidx.fragment:fragment" 12 | implementation "androidx.fragment:fragment-ktx" 13 | 14 | implementation "androidx.lifecycle:lifecycle-extensions" 15 | 16 | constraints { 17 | implementation("androidx.appcompat:appcompat") { 18 | version { 19 | require '1.0.0' 20 | } 21 | } 22 | 23 | implementation("androidx.fragment:fragment") { 24 | version { 25 | require '1.1.0' 26 | } 27 | } 28 | 29 | implementation("androidx.fragment:fragment-ktx") { 30 | version { 31 | require '1.1.0' 32 | } 33 | } 34 | 35 | implementation("androidx.lifecycle:lifecycle-extensions") { 36 | version { 37 | require '2.1.0' 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /navigation-cicerone/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "../android-configs/lib-config.gradle" 2 | apply plugin: 'kotlin-kapt' 3 | 4 | dependencies { 5 | implementation project(":navigation-base") 6 | 7 | implementation("ru.terrakok.cicerone:cicerone") 8 | implementation("androidx.fragment:fragment") 9 | implementation("com.google.dagger:dagger") 10 | kapt("com.google.dagger:dagger-compiler") 11 | 12 | implementation("com.github.valeryponomarenko.componentsmanager:androidx:2.1.0") 13 | 14 | def daggerVersion = "2.34" 15 | 16 | constraints { 17 | implementation("ru.terrakok.cicerone:cicerone") { 18 | version { 19 | require("5.1.0") 20 | } 21 | } 22 | implementation("androidx.fragment:fragment") { 23 | version { 24 | require("1.2.1") 25 | } 26 | } 27 | implementation("com.google.dagger:dagger") { 28 | version { 29 | require(daggerVersion) 30 | } 31 | } 32 | kapt("com.google.dagger:dagger-compiler") { 33 | version { 34 | require(daggerVersion) 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /views/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 11 | 12 | 15 | 16 | 19 | 20 | 25 | 26 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /code-confirm/src/main/java/ru/touchin/code_confirm/LifeTimer.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.code_confirm 2 | 3 | import android.os.CountDownTimer 4 | import java.text.SimpleDateFormat 5 | import java.util.Locale 6 | import java.util.Date 7 | 8 | /** [LifeTimer] is extends [CountDownTimer] for countdown in seconds and lifetime text formatting 9 | * @param seconds Lifetime of timer in seconds 10 | * @param tickAction Action will be called on regular interval 11 | * @param finishAction Action will be called on finish */ 12 | class LifeTimer( 13 | seconds: Int, 14 | private val tickAction: (Long) -> Unit, 15 | private val finishAction: () -> Unit 16 | ) : CountDownTimer(seconds.toLong() * 1000, 1000) { 17 | 18 | override fun onTick(millisUntilFinished: Long) { 19 | tickAction.invoke(millisUntilFinished / 1000) 20 | } 21 | 22 | override fun onFinish() { 23 | finishAction.invoke() 24 | } 25 | 26 | companion object { 27 | 28 | private val formatter = SimpleDateFormat("mm:ss", Locale.ROOT) 29 | 30 | fun getFormattedCodeLifetimeString(secondsUntilFinished: Long): String = 31 | formatter.format(Date(secondsUntilFinished * 1000L)) 32 | 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /logging_reader/src/main/java/ru/touchin/roboswag/loggging_reader/LogItemAdapter.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.loggging_reader 2 | 3 | import android.content.Context 4 | import android.view.LayoutInflater 5 | import android.view.ViewGroup 6 | import androidx.recyclerview.widget.RecyclerView 7 | import ru.touchin.roboswag.logging_reader.databinding.LogItemBinding 8 | 9 | class LogItemAdapter(private val context: Context, private val logItemList: MutableList) 10 | : RecyclerView.Adapter() { 11 | 12 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = LogItemViewHolder( 13 | binding = LogItemBinding.inflate(LayoutInflater.from(context), parent, false) 14 | ) 15 | 16 | override fun onBindViewHolder(holder: LogItemViewHolder, position: Int) { 17 | val logItem = logItemList[position] 18 | holder.bind(logItem) 19 | } 20 | 21 | override fun getItemCount(): Int { 22 | return logItemList.size 23 | } 24 | 25 | class LogItemViewHolder(private val binding: LogItemBinding) : RecyclerView.ViewHolder(binding.root) { 26 | 27 | fun bind(logItem: String) { 28 | binding.logDescription.text = logItem 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /utils/src/main/java/ru/touchin/roboswag/core/utils/DateFormatUtils.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.core.utils 2 | 3 | import org.joda.time.DateTime 4 | import org.joda.time.format.DateTimeFormat 5 | 6 | /** 7 | * Util object for handling some cases with DateTime e.g. parsing string to DateTime object 8 | */ 9 | object DateFormatUtils { 10 | 11 | enum class Format(val formatValue: String) { 12 | DATE_TIME_FORMAT("yyyy-MM-dd'T'HH:mm:ss.SSSZZ"), 13 | DATE_FORMAT("yyyy-MM-dd"), 14 | TIME_FORMAT("HH:mm:ssZ") 15 | } 16 | 17 | /** 18 | * @return the result of parsed string value 19 | * @param value is string value of date time in right format 20 | * @param format is date time format for parsing string value. 21 | * Default value is [Format.DATE_TIME_FORMAT] 22 | * @param defaultValue is value returned in case of exception 23 | */ 24 | fun fromString( 25 | value: String, 26 | format: Format = Format.DATE_TIME_FORMAT, 27 | defaultValue: DateTime? = null 28 | ): DateTime? = runCatching { value.parse(format.formatValue) }.getOrDefault(defaultValue) 29 | 30 | private fun String.parse(format: String) = DateTimeFormat.forPattern(format).parseDateTime(this) 31 | 32 | } 33 | -------------------------------------------------------------------------------- /lifecycle/src/main/java/ru/touchin/lifecycle/scope/ViewCoroutineScope.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.lifecycle.scope 2 | 3 | import android.view.View 4 | import kotlinx.coroutines.CoroutineScope 5 | import kotlinx.coroutines.Dispatchers 6 | import kotlinx.coroutines.SupervisorJob 7 | import kotlinx.coroutines.cancel 8 | import ru.touchin.lifecycle.R 9 | 10 | val View.viewScope: CoroutineScope 11 | get() { 12 | val storedScope = getTag(R.string.view_coroutine_scope) as? CoroutineScope 13 | if (storedScope != null) return storedScope 14 | 15 | val newScope = ViewCoroutineScope() 16 | if (isAttachedToWindow) { 17 | addOnAttachStateChangeListener(newScope) 18 | setTag(R.string.view_coroutine_scope, newScope) 19 | } else { 20 | newScope.cancel() 21 | } 22 | 23 | return newScope 24 | } 25 | 26 | private class ViewCoroutineScope : CoroutineScope, View.OnAttachStateChangeListener { 27 | override val coroutineContext = SupervisorJob() + Dispatchers.Main 28 | 29 | override fun onViewAttachedToWindow(view: View) = Unit 30 | 31 | override fun onViewDetachedFromWindow(view: View) { 32 | coroutineContext.cancel() 33 | view.setTag(R.string.view_coroutine_scope, null) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/FullscreenBottomSheetDialog.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.mvi_arch.core 2 | 3 | import android.app.Dialog 4 | import android.os.Parcelable 5 | import android.view.ViewGroup 6 | import android.widget.FrameLayout 7 | import androidx.annotation.LayoutRes 8 | import ru.touchin.roboswag.components.utils.setResizableListener 9 | import ru.touchin.roboswag.mvi_arch.marker.ViewAction 10 | import ru.touchin.roboswag.mvi_arch.marker.ViewState 11 | 12 | abstract class FullscreenBottomSheetDialog( 13 | @LayoutRes layoutId: Int 14 | ) : MviBottomSheet(layoutId) 15 | where NavArgs : Parcelable, 16 | Action : ViewAction, 17 | State : ViewState, 18 | VM : MviViewModel { 19 | 20 | protected var bottomSheet: FrameLayout? = null 21 | 22 | override fun onStart() { 23 | super.onStart() 24 | 25 | bottomSheet = dialog?.findViewById(com.google.android.material.R.id.design_bottom_sheet) 26 | bottomSheet?.layoutParams?.height = ViewGroup.LayoutParams.MATCH_PARENT 27 | } 28 | 29 | override fun setupDialog(dialog: Dialog, style: Int) { 30 | dialog.setResizableListener() 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/PlaceholderGenerator.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.textprocessing.generators 2 | 3 | class PlaceholderGenerator(matrixOfSymbols: Matrix) { 4 | 5 | val placeholder = generatePlaceholder(matrixOfSymbols) 6 | 7 | private fun generatePlaceholder(matrixOfSymbols: Matrix): String { 8 | val placeholderStringBuilder = StringBuilder() 9 | val indexes = hashMapOf, Int>() 10 | for (listOfSymbols in matrixOfSymbols) { 11 | indexes[listOfSymbols] = 0 12 | } 13 | for (listOfSymbols in matrixOfSymbols) { 14 | if (listOfSymbols.isEmpty()) continue 15 | /** Если элемент без повторений **/ 16 | if (listOfSymbols.size == 1) { 17 | placeholderStringBuilder.append(listOfSymbols[0]) 18 | continue 19 | } 20 | indexes[listOfSymbols]?.let { 21 | var index = it 22 | if (listOfSymbols.size <= index) index = 0 23 | placeholderStringBuilder.append(listOfSymbols[index]) 24 | index++ 25 | indexes[listOfSymbols] = index 26 | } 27 | } 28 | return placeholderStringBuilder.toString() 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /recyclerview-adapters/src/main/java/ru/touchin/roboswag/recyclerview_adapters/adapters/SubmittablePagingAdapter.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.recyclerview_adapters.adapters 2 | 3 | import androidx.paging.PagingDataAdapter 4 | import androidx.recyclerview.widget.DiffUtil 5 | import androidx.recyclerview.widget.RecyclerView 6 | 7 | abstract class SubmittablePagingAdapter(private val diffCallback: DiffUtil.ItemCallback): 8 | PagingDataAdapter(diffCallback) { 9 | /** 10 | * [items] list of updated elements 11 | * [transform] callback that keeps logic of transformation item based on newItem. 12 | * Should return updated element 13 | */ 14 | fun submitList(items: List, transform: T.(newItem: T, payload: Any?) -> T) { 15 | items.forEach { newItem -> 16 | snapshot().forEachIndexed { index, oldItem -> 17 | if (oldItem != null && diffCallback.areItemsTheSame(oldItem, newItem) && !diffCallback.areContentsTheSame(oldItem, newItem)) { 18 | val payload = diffCallback.getChangePayload(oldItem, newItem) 19 | 20 | oldItem.transform(newItem, payload) 21 | 22 | notifyItemChanged(index, payload) 23 | } 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /kotlin-extensions/src/main/java/ru/touchin/extensions/Fragment.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.extensions 2 | 3 | import android.os.Bundle 4 | import android.os.Parcelable 5 | import androidx.fragment.app.Fragment 6 | import ru.touchin.utils.BundleExtractorDelegate 7 | import kotlin.properties.ReadWriteProperty 8 | 9 | inline fun Fragment.args( 10 | key: String? = null, 11 | defaultValue: T? = null 12 | ): ReadWriteProperty { 13 | return BundleExtractorDelegate { thisRef, property -> 14 | val bundleKey = key ?: property.name 15 | extractFromBundle(thisRef.arguments, bundleKey, defaultValue) 16 | } 17 | } 18 | 19 | fun T.withArgs(receiver: Bundle.() -> Unit): T { 20 | arguments = Bundle().apply(receiver) 21 | return this 22 | } 23 | 24 | fun T.withParcelable(key: String, parcelable: Parcelable): T = withArgs { 25 | putParcelable(key, parcelable) 26 | } 27 | 28 | inline fun extractFromBundle( 29 | bundle: Bundle?, 30 | key: String? = null, 31 | defaultValue: T? = null 32 | ): T { 33 | val result = bundle?.get(key) ?: defaultValue 34 | if (result != null && result !is T) { 35 | throw ClassCastException("Property for $key has different class type") 36 | } 37 | return result as T 38 | } 39 | -------------------------------------------------------------------------------- /api-logansquare/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "../android-configs/lib-config.gradle" 2 | 3 | dependencies { 4 | implementation project(":utils") 5 | implementation project(":logging") 6 | implementation project(":storable") 7 | 8 | implementation 'net.danlew:android.joda' 9 | 10 | implementation "androidx.core:core" 11 | implementation "androidx.annotation:annotation" 12 | 13 | implementation "com.squareup.retrofit2:retrofit" 14 | 15 | implementation 'ru.touchin:logansquare' 16 | 17 | constraints { 18 | implementation("androidx.core:core") { 19 | version { 20 | require '1.0.0' 21 | } 22 | } 23 | 24 | implementation("ru.touchin:logansquare") { 25 | version { 26 | require '1.4.3' 27 | } 28 | } 29 | 30 | implementation("com.squareup.retrofit2:retrofit") { 31 | version { 32 | require '2.7.0' 33 | } 34 | } 35 | 36 | implementation("androidx.annotation:annotation") { 37 | version { 38 | require '1.0.0' 39 | } 40 | } 41 | 42 | implementation("net.danlew:android.joda") { 43 | version { 44 | require '2.9.9.4' 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /google-map/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "../android-configs/lib-config.gradle" 2 | 3 | dependencies { 4 | api project(":base-map") 5 | 6 | implementation "com.google.android.gms:play-services-maps" 7 | implementation "com.google.maps.android:android-maps-utils" 8 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core" 9 | implementation "androidx.lifecycle:lifecycle-runtime-ktx" 10 | implementation "androidx.core:core-ktx" 11 | 12 | constraints { 13 | implementation("com.google.android.gms:play-services-maps") { 14 | version { 15 | require '17.0.0' 16 | } 17 | } 18 | 19 | implementation("com.google.maps.android:android-maps-utils") { 20 | version { 21 | require '0.4' 22 | } 23 | } 24 | 25 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core") { 26 | version { 27 | require '1.4.0' 28 | } 29 | } 30 | 31 | implementation("androidx.lifecycle:lifecycle-runtime-ktx") { 32 | version { 33 | require '2.4.1' 34 | } 35 | } 36 | 37 | implementation("androidx.core:core-ktx") { 38 | version { 39 | require '1.6.0' 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /recaptcha/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "../android-configs/lib-config.gradle" 2 | apply plugin: 'com.huawei.agconnect' 3 | 4 | dependencies { 5 | implementation project(':client-services') 6 | 7 | implementation "androidx.core:core" 8 | implementation "androidx.annotation:annotation" 9 | implementation "com.google.android.gms:play-services-safetynet" 10 | implementation "com.google.android.gms:play-services-base" 11 | implementation "com.huawei.hms:safetydetect" 12 | 13 | constraints { 14 | implementation("androidx.core:core") { 15 | version { 16 | require '1.0.0' 17 | } 18 | } 19 | 20 | implementation("androidx.annotation:annotation") { 21 | version { 22 | require '1.1.0' 23 | } 24 | } 25 | 26 | implementation("com.google.android.gms:play-services-safetynet") { 27 | version { 28 | require '18.0.1' 29 | } 30 | } 31 | 32 | implementation("com.google.android.gms:play-services-base") { 33 | version { 34 | require '18.0.1' 35 | } 36 | } 37 | 38 | implementation("com.huawei.hms:safetydetect") { 39 | version { 40 | require '4.0.3.300' 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /recaptcha/src/main/java/ru/touchin/recaptcha/CaptchaManager.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.recaptcha 2 | 3 | import android.app.Activity 4 | import ru.touchin.client_services.MobileService 5 | import ru.touchin.client_services.ServicesUtils 6 | 7 | /** 8 | * A class for displaying a dialog with a captcha 9 | * with a check on the current service of the application 10 | * 11 | * @param onNewTokenReceived - callback for a successful captcha check, return token 12 | * @param processThrowable - callback for a captcha check error, return throwable 13 | */ 14 | 15 | class CaptchaManager( 16 | private val onNewTokenReceived: (String) -> Unit, 17 | private val processThrowable: (Throwable) -> Unit 18 | ) { 19 | 20 | private val clientsMap = mapOf( 21 | MobileService.GOOGLE_SERVICE to GoogleCaptchaClient(onNewTokenReceived, processThrowable), 22 | MobileService.HUAWEI_SERVICE to HuaweiCaptchaClient(onNewTokenReceived, processThrowable) 23 | ) 24 | 25 | fun showRecaptchaAlert(activity: Activity, captchaKey: String) { 26 | if (captchaKey.isBlank()) { 27 | processThrowable.invoke(EmptyCaptchaKeyException()) 28 | } else { 29 | val service = ServicesUtils().getCurrentService(activity) 30 | clientsMap[service]?.showCaptcha(activity, captchaKey) 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/mediator/LoggingMediator.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.mvi_arch.mediator 2 | 3 | import ru.touchin.roboswag.core.log.Lc 4 | import ru.touchin.roboswag.mvi_arch.marker.SideEffect 5 | import ru.touchin.roboswag.mvi_arch.marker.StateChange 6 | import ru.touchin.roboswag.mvi_arch.marker.ViewAction 7 | import ru.touchin.roboswag.mvi_arch.marker.ViewState 8 | 9 | class LoggingMediator(private val objectName: String?) : Mediator { 10 | override fun onEffect(effect: SideEffect) { 11 | logObject( 12 | prefix = "New Effect:\n", 13 | obj = effect 14 | ) 15 | } 16 | 17 | override fun onAction(action: ViewAction) { 18 | logObject( 19 | prefix = "New Action:\n", 20 | obj = action 21 | ) 22 | } 23 | 24 | override fun onNewState(state: ViewState) { 25 | logObject( 26 | prefix = "New State:\n", 27 | obj = state 28 | ) 29 | } 30 | 31 | override fun onStateChange(change: StateChange) { 32 | logObject( 33 | prefix = "New State change:\n", 34 | obj = change 35 | ) 36 | } 37 | 38 | private fun logObject( 39 | prefix: String, 40 | obj: T 41 | ) { 42 | Lc.d("$objectName: $prefix$obj\n") 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /kotlin-extensions/src/main/java/ru/touchin/extensions/TextView.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.extensions 2 | 3 | import android.graphics.drawable.Drawable 4 | import android.os.Build 5 | import android.widget.TextView 6 | 7 | var TextView.drawableStart: Drawable? 8 | get() = drawables[0] 9 | set(value) = setDrawables(value, drawableTop, drawableEnd, drawableBottom) 10 | 11 | var TextView.drawableTop: Drawable? 12 | get() = drawables[1] 13 | set(value) = setDrawables(drawableStart, value, drawableEnd, drawableBottom) 14 | 15 | var TextView.drawableEnd: Drawable? 16 | get() = drawables[2] 17 | set(value) = setDrawables(drawableStart, drawableTop, value, drawableBottom) 18 | 19 | var TextView.drawableBottom: Drawable? 20 | get() = drawables[3] 21 | set(value) = setDrawables(drawableStart, drawableTop, drawableEnd, value) 22 | 23 | private inline val TextView.drawables: Array 24 | get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) compoundDrawablesRelative else compoundDrawables 25 | 26 | private fun TextView.setDrawables(start: Drawable?, top: Drawable?, end: Drawable?, bottom: Drawable?) { 27 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { 28 | setCompoundDrawablesRelativeWithIntrinsicBounds(start, top, end, bottom) 29 | } else { 30 | setCompoundDrawablesWithIntrinsicBounds(start, top, end, bottom) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/keyboard_resizeable/FragmentKeyboardListenerObserver.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.navigation_base.keyboard_resizeable 2 | 3 | import androidx.fragment.app.Fragment 4 | import androidx.lifecycle.Lifecycle 5 | import androidx.lifecycle.LifecycleObserver 6 | import androidx.lifecycle.OnLifecycleEvent 7 | import ru.touchin.roboswag.navigation_base.activities.BaseActivity 8 | 9 | /** 10 | * Observer for adding listeners for activity's keyboardBehaviorDetector with lifecycle awareness 11 | */ 12 | class FragmentKeyboardListenerObserver( 13 | fragment: Fragment, 14 | private val onShow: OnShowListener? = null, 15 | private val onHide: OnHideListener? = null 16 | ) : LifecycleObserver { 17 | 18 | private val keyboardDetector = (fragment.requireActivity() as? BaseActivity) 19 | ?.keyboardBehaviorDetector 20 | ?: error("Fragment must be launched from BaseActivity") 21 | 22 | @OnLifecycleEvent(Lifecycle.Event.ON_START) 23 | fun addListener() { 24 | onShow?.let(keyboardDetector::addOnShowListener) 25 | onHide?.let(keyboardDetector::addOnHideListener) 26 | } 27 | 28 | @OnLifecycleEvent(Lifecycle.Event.ON_STOP) 29 | fun removeListener() { 30 | onShow?.let(keyboardDetector::removeOnShowListener) 31 | onHide?.let(keyboardDetector::removeOnHideListener) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /pagination/src/main/res/layout/view_pagination.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 18 | 19 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /text-processing/src/main/java/ru/touchin/roboswag/textprocessing/TextFormatter.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.textprocessing 2 | 3 | import android.widget.EditText 4 | import ru.touchin.roboswag.textprocessing.generators.DecoroMaskGenerator 5 | import ru.touchin.roboswag.textprocessing.generators.PlaceholderGenerator 6 | import ru.touchin.roboswag.textprocessing.generators.regexgenerator.RegexReplaceGenerator 7 | 8 | class TextFormatter(private val regex: String) { 9 | 10 | private val regexReplaceGenerator = RegexReplaceGenerator() 11 | 12 | private val decoroMaskGenerator = DecoroMaskGenerator() 13 | 14 | private val pcreGeneratorItem = regexReplaceGenerator.regexToRegexReplace(regex) 15 | 16 | private val regexReplaceString = pcreGeneratorItem.regexReplaceString 17 | 18 | private val matrixOfSymbols = pcreGeneratorItem.matrixOfSymbols 19 | 20 | private val placeholderGenerator = PlaceholderGenerator(matrixOfSymbols) 21 | 22 | fun getFormattedText(inputText: String) = inputText.replace(Regex(regex), regexReplaceString) 23 | 24 | fun getPlaceholder() = placeholderGenerator.placeholder 25 | 26 | fun getRegexReplace() = regexReplaceString 27 | 28 | fun mask(editText: EditText) { 29 | val formatWatcher = decoroMaskGenerator.mask( 30 | placeholderGenerator.placeholder, 31 | matrixOfSymbols 32 | ) 33 | formatWatcher.installOn(editText) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /recyclerview-decorators/src/main/java/ru/touchin/roboswag/recyclerview_decorators/decorators/BottomDividerItemDecoration.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.recyclerview_decorators.decorators 2 | 3 | import android.content.Context 4 | import android.graphics.Rect 5 | import android.view.View 6 | import androidx.annotation.DrawableRes 7 | import androidx.recyclerview.widget.RecyclerView 8 | 9 | open class BottomDividerItemDecoration( 10 | context: Context, 11 | @DrawableRes drawableId: Int? = null, 12 | override val predicate: ((position: Int) -> Boolean) = { true }, 13 | override val startMargin: Int = 0, 14 | override val endMargin: Int = 0, 15 | override val offset: Boolean = true, 16 | override val showOnLastItem: Boolean = false 17 | ) : DividerItemDecoration(context, drawableId, predicate, startMargin, endMargin, offset, showOnLastItem) { 18 | 19 | override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) { 20 | val position = parent.getChildAdapterPosition(view) 21 | if (offset && predicate(position) && (position != state.itemCount - 1 || showOnLastItem)) { 22 | outRect.set(0, 0, 0, divider.intrinsicHeight) 23 | } 24 | } 25 | 26 | override fun getDividerTop(child: View): Int = getDividerBottom(child) - divider.intrinsicHeight 27 | 28 | override fun getDividerBottom(child: View): Int = bounds.bottom + child.translationY.toInt() 29 | 30 | } 31 | -------------------------------------------------------------------------------- /kotlin-extensions/src/main/java/ru/touchin/extensions/Context.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.extensions 2 | 3 | import android.content.ActivityNotFoundException 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.net.Uri 7 | import android.os.Bundle 8 | import ru.touchin.roboswag.core.log.Lc 9 | import android.provider.Browser 10 | 11 | fun Context.safeStartActivity(intent: Intent, options: Bundle? = null) = 12 | try { 13 | startActivity(intent, options) 14 | } catch (e: ActivityNotFoundException) { 15 | Lc.e(e, "Couldn't find activity with this parameters") 16 | } 17 | 18 | fun Context.openBrowser(url: String) = Intent(Intent.ACTION_VIEW) 19 | .setData(Uri.parse(url)) 20 | .let { intent -> safeStartActivity(intent) } 21 | 22 | fun Context.openBrowserWithHeaders(url: String, headersMap: Map) = Intent(Intent.ACTION_VIEW) 23 | .setData(Uri.parse(url)) 24 | .let { intent -> 25 | val bundle = Bundle().apply { 26 | headersMap.forEach { (key, value) -> 27 | putString(key, value) 28 | } 29 | } 30 | intent.putExtra(Browser.EXTRA_HEADERS, bundle) 31 | safeStartActivity(intent) 32 | } 33 | 34 | fun Context.callToPhoneNumber(phoneNumber: String) = Intent(Intent.ACTION_VIEW) 35 | .setData(Uri.parse("tel:$phoneNumber")) 36 | .let { intent -> safeStartActivity(intent) } 37 | -------------------------------------------------------------------------------- /webview/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "../android-configs/lib-config.gradle" 2 | apply plugin: 'kotlin-android' 3 | 4 | android { 5 | buildFeatures { 6 | viewBinding true 7 | } 8 | } 9 | 10 | dependencies { 11 | implementation project(":views") 12 | implementation project(":kotlin-extensions") 13 | 14 | implementation "com.google.android.material:material" 15 | implementation "androidx.constraintlayout:constraintlayout" 16 | implementation "androidx.core:core-ktx" 17 | implementation "androidx.swiperefreshlayout:swiperefreshlayout" 18 | 19 | constraints { 20 | implementation("com.google.android.material:material") { 21 | version { 22 | require '1.0.0' 23 | } 24 | } 25 | implementation("androidx.constraintlayout:constraintlayout") { 26 | version { 27 | require '2.0.0-beta4' 28 | } 29 | } 30 | implementation("androidx.core:core-ktx") { 31 | version { 32 | require '1.3.1' 33 | } 34 | } 35 | implementation("org.jetbrains.kotlin:kotlin-stdlib") { 36 | version { 37 | require '1.3.0' 38 | } 39 | } 40 | implementation("androidx.swiperefreshlayout:swiperefreshlayout") { 41 | version { 42 | require '1.1.0' 43 | } 44 | } 45 | } 46 | } 47 | 48 | repositories { 49 | mavenCentral() 50 | } 51 | -------------------------------------------------------------------------------- /lifecycle/src/main/java/ru/touchin/lifecycle/OnLifecycle.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.lifecycle 2 | 3 | import androidx.lifecycle.Lifecycle 4 | import androidx.lifecycle.LifecycleEventObserver 5 | import androidx.lifecycle.LifecycleOwner 6 | import java.lang.IllegalStateException 7 | import kotlin.properties.ReadOnlyProperty 8 | import kotlin.reflect.KProperty 9 | 10 | /** 11 | * Delegate that allows to lazily initialize value on certain lifecycle event 12 | * @param initializeEvent is event when value should be initialize 13 | * @param initializer callback that handles value initialization 14 | */ 15 | 16 | class OnLifecycle( 17 | private val lifecycleOwner: R, 18 | private val initializeEvent: Lifecycle.Event, 19 | private val initializer: (R) -> T 20 | ) : ReadOnlyProperty { 21 | 22 | private var value: T? = null 23 | 24 | init { 25 | lifecycleOwner.lifecycle.addObserver(object : LifecycleEventObserver { 26 | override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { 27 | if (initializeEvent == event && value == null) { 28 | value = initializer.invoke(lifecycleOwner) 29 | } 30 | } 31 | }) 32 | } 33 | 34 | override fun getValue(thisRef: R, property: KProperty<*>) = value 35 | ?: throw IllegalStateException("Can't get access to value before $initializeEvent. Current is ${thisRef.lifecycle.currentState}") 36 | 37 | } 38 | -------------------------------------------------------------------------------- /views/src/main/java/ru/touchin/roboswag/views/CrossfadeView.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.views 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.widget.ViewAnimator 8 | import androidx.annotation.IdRes 9 | import androidx.core.content.withStyledAttributes 10 | import androidx.core.view.children 11 | 12 | class CrossfadeView @JvmOverloads constructor( 13 | context: Context, 14 | attrs: AttributeSet? = null 15 | ) : ViewAnimator(context, attrs) { 16 | 17 | @IdRes 18 | private var defaultChild: Int = 0 19 | 20 | init { 21 | setInAnimation(context, R.anim.fade_in_animation) 22 | setOutAnimation(context, R.anim.fade_out_animation) 23 | 24 | context.withStyledAttributes(attrs, R.styleable.CrossfadeView, 0) { 25 | defaultChild = getResourceId(R.styleable.CrossfadeView_crossfadeDefaultChild, 0) 26 | } 27 | } 28 | 29 | fun showChild(@IdRes childId: Int) { 30 | children.forEachIndexed { index, view -> 31 | if (view.id == childId && displayedChild != index) { 32 | displayedChild = index 33 | } 34 | } 35 | } 36 | 37 | override fun addView(child: View, index: Int, params: ViewGroup.LayoutParams?) { 38 | super.addView(child, index, params) 39 | if (child.id == defaultChild) { 40 | showChild(defaultChild) 41 | } 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /recyclerview-calendar/src/main/java/ru/touchin/calendar/CalendarEmptyItem.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) 3 | * 4 | * This file is part of RoboSwag library. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | package ru.touchin.calendar; 21 | 22 | /** 23 | * Created by Ilia Kurtov on 17/03/2016. 24 | * Calendar item for showing empty cells in calendar. 25 | */ 26 | public class CalendarEmptyItem implements CalendarItem { 27 | 28 | private final int startRange; 29 | private final int endRange; 30 | 31 | public CalendarEmptyItem(final int startRange, final int endRange) { 32 | this.startRange = startRange; 33 | this.endRange = endRange; 34 | } 35 | 36 | @Override 37 | public int getStartRange() { 38 | return startRange; 39 | } 40 | 41 | @Override 42 | public int getEndRange() { 43 | return endRange; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /bottom-navigation-fragment/src/main/java/ru/touchin/roboswag/bottom_navigation_fragment/BottomNavigationController.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.bottom_navigation_fragment 2 | 3 | import android.content.Context 4 | import android.util.SparseArray 5 | import androidx.annotation.IdRes 6 | import androidx.annotation.LayoutRes 7 | import androidx.fragment.app.FragmentManager 8 | import ru.touchin.roboswag.bottom_navigation_base.BaseBottomNavigationController 9 | 10 | class BottomNavigationController( 11 | context: Context, 12 | fragments: SparseArray, 13 | fragmentManager: FragmentManager, 14 | wrapWithNavigationContainer: Boolean = false, 15 | @LayoutRes private val contentContainerLayoutId: Int, 16 | @IdRes private val contentContainerViewId: Int, 17 | @IdRes private val defaultTabId: Int = 0, // If it zero back press with empty fragment back stack would close the app 18 | onReselectListener: (() -> Unit)? = null 19 | ) : BaseBottomNavigationController( 20 | tabs = fragments, 21 | context = context, 22 | fragmentManager = fragmentManager, 23 | defaultTabId = defaultTabId, 24 | onReselectListener = onReselectListener, 25 | contentContainerViewId = contentContainerViewId, 26 | contentContainerLayoutId = contentContainerLayoutId, 27 | wrapWithNavigationContainer = wrapWithNavigationContainer 28 | ) { 29 | 30 | override fun getNavigationContainerClass() = NavigationContainerFragment::class.java 31 | 32 | } 33 | -------------------------------------------------------------------------------- /kotlin-extensions/src/main/java/ru/touchin/extensions/ViewHolder.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.extensions 2 | 3 | import android.content.Context 4 | import android.content.res.ColorStateList 5 | import android.graphics.drawable.Drawable 6 | import androidx.annotation.ColorInt 7 | import androidx.annotation.ColorRes 8 | import androidx.annotation.DrawableRes 9 | import androidx.annotation.IdRes 10 | import androidx.annotation.StringRes 11 | import androidx.core.content.ContextCompat 12 | import androidx.recyclerview.widget.RecyclerView 13 | import android.view.View 14 | 15 | fun RecyclerView.ViewHolder.findViewById(@IdRes resId: Int): T = itemView.findViewById(resId) 16 | 17 | val RecyclerView.ViewHolder.context: Context 18 | get() = itemView.context 19 | 20 | fun RecyclerView.ViewHolder.getText(@StringRes resId: Int): CharSequence = context.getText(resId) 21 | 22 | fun RecyclerView.ViewHolder.getString(@StringRes resId: Int): String = context.getString(resId) 23 | 24 | @SuppressWarnings("SpreadOperator") // it's OK for small arrays 25 | fun RecyclerView.ViewHolder.getString(@StringRes resId: Int, vararg args: Any): String = context.getString(resId, *args) 26 | 27 | @ColorInt 28 | fun RecyclerView.ViewHolder.getColor(@ColorRes resId: Int): Int = ContextCompat.getColor(context, resId) 29 | 30 | fun RecyclerView.ViewHolder.getColorStateList(@ColorRes resId: Int): ColorStateList? = ContextCompat.getColorStateList(context, resId) 31 | 32 | fun RecyclerView.ViewHolder.getDrawable(@DrawableRes resId: Int): Drawable? = ContextCompat.getDrawable(context, resId) 33 | -------------------------------------------------------------------------------- /recyclerview-calendar/src/main/java/ru/touchin/calendar/CalendarItem.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) 3 | * 4 | * This file is part of RoboSwag library. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | package ru.touchin.calendar; 21 | 22 | /** 23 | * Created by Ilia Kurtov on 17/03/2016. 24 | * Interface for items for {@link CalendarAdapter}. Instead of storing data about all calendar cells separately, 25 | * it sores list of this items. CalendarItem represents range with the same calendar items. 26 | */ 27 | public interface CalendarItem { 28 | 29 | /** 30 | * Returns number of starting cell of this range. 31 | * 32 | * @return number of starting cell of this range. 33 | */ 34 | int getStartRange(); 35 | 36 | /** 37 | * Returns number of ending cell of this range. 38 | * 39 | * @return number of ending cell of this range. 40 | */ 41 | int getEndRange(); 42 | 43 | } 44 | -------------------------------------------------------------------------------- /utils/src/main/java/ru/touchin/roboswag/components/utils/spans/ColoredUrlSpan.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) 3 | * 4 | * This file is part of RoboSwag library. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | package ru.touchin.roboswag.components.utils.spans 21 | 22 | import android.text.TextPaint 23 | import androidx.annotation.ColorInt 24 | 25 | /** 26 | * Created by Anton Arhipov on 05/07/2017. 27 | * URLSpan that takes custom color and doesn't have the default underline. 28 | * Don't forget to use 29 | * textView.setMovementMethod(LinkMovementMethod.getInstance()); 30 | * and 31 | * textView.setText(spannableString, TextView.BufferType.SPANNABLE); 32 | */ 33 | class ColoredUrlSpan(@ColorInt private val textColor: Int, url: String) : URLSpanWithoutUnderline(url) { 34 | 35 | override fun updateDrawState(ds: TextPaint) { 36 | super.updateDrawState(ds) 37 | ds.color = textColor 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/fragments/LifecycleLoggingObserver.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.navigation_base.fragments 2 | 3 | import androidx.lifecycle.Lifecycle 4 | import androidx.lifecycle.LifecycleObserver 5 | import androidx.lifecycle.OnLifecycleEvent 6 | import ru.touchin.roboswag.core.log.Lc 7 | import ru.touchin.roboswag.core.log.LcGroup 8 | 9 | class LifecycleLoggingObserver ( 10 | private val lifecycleOwner: Any 11 | ) : LifecycleObserver { 12 | 13 | @OnLifecycleEvent(Lifecycle.Event.ON_CREATE) 14 | fun onCreateLifecycleEvent() { 15 | LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(lifecycleOwner, "onCreate")) 16 | } 17 | 18 | @OnLifecycleEvent(Lifecycle.Event.ON_START) 19 | fun onStartLifecycleEvent() { 20 | LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(lifecycleOwner, "onStop")) 21 | } 22 | 23 | @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) 24 | fun onResumeLifecycleEvent() { 25 | LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(lifecycleOwner, "onResume")) 26 | } 27 | 28 | @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) 29 | fun onPauseLifecycleEvent() { 30 | LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(lifecycleOwner, "onPause")) 31 | } 32 | 33 | @OnLifecycleEvent(Lifecycle.Event.ON_STOP) 34 | fun onStopLifecycleEvent() { 35 | LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(lifecycleOwner, "onStop")) 36 | } 37 | 38 | @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) 39 | fun onDestroyLifecycleEvent() { 40 | LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(lifecycleOwner, "onDestroy")) 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/fragments/StatefulFragment.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.navigation_base.fragments 2 | 3 | import android.os.Bundle 4 | import android.os.Parcelable 5 | import androidx.annotation.LayoutRes 6 | import androidx.fragment.app.FragmentActivity 7 | import ru.touchin.roboswag.navigation_base.BuildConfig 8 | import ru.touchin.roboswag.navigation_base.extensions.reserialize 9 | 10 | open class StatefulFragment( 11 | @LayoutRes layoutRes: Int 12 | ) : BaseFragment(layoutRes) { 13 | 14 | companion object { 15 | private const val BASE_FRAGMENT_STATE_EXTRA = "BASE_FRAGMENT_STATE_EXTRA" 16 | 17 | fun args(state: Parcelable?): Bundle = Bundle().also { it.putParcelable(BASE_FRAGMENT_STATE_EXTRA, state) } 18 | 19 | } 20 | 21 | protected lateinit var state: TState 22 | private set 23 | 24 | override fun onCreate(savedInstanceState: Bundle?) { 25 | super.onCreate(savedInstanceState) 26 | 27 | state = savedInstanceState?.getParcelable(BASE_FRAGMENT_STATE_EXTRA) 28 | ?: arguments?.getParcelable(BASE_FRAGMENT_STATE_EXTRA) 29 | ?: throw IllegalStateException("Fragment state can't be null") 30 | 31 | if (BuildConfig.DEBUG) { 32 | state = state.reserialize() 33 | } 34 | } 35 | 36 | override fun onSaveInstanceState(outState: Bundle) { 37 | super.onSaveInstanceState(outState) 38 | outState.putParcelable(BASE_FRAGMENT_STATE_EXTRA, state) 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /utils/src/main/java/ru/touchin/hardware/Extensions.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.hardware 2 | 3 | import android.Manifest 4 | import android.content.Context 5 | import android.os.Build 6 | import android.os.VibrationEffect 7 | import android.os.VibrationEffect.DEFAULT_AMPLITUDE 8 | import android.os.Vibrator 9 | import androidx.annotation.RequiresApi 10 | import androidx.annotation.RequiresPermission 11 | 12 | @RequiresPermission(Manifest.permission.VIBRATE) 13 | @RequiresApi(Build.VERSION_CODES.O) 14 | fun Context.startVibrate(vibrationEffect: VibrationEffect) { 15 | (this.getSystemService(Context.VIBRATOR_SERVICE) as? Vibrator)?.vibrate(vibrationEffect) 16 | 17 | } 18 | 19 | @RequiresPermission(Manifest.permission.VIBRATE) 20 | fun Context.startVibrate(duration: Long = 500, pattern: LongArray = LongArray(0)) { 21 | (this.getSystemService(Context.VIBRATOR_SERVICE) as? Vibrator)?.let { vibrationService -> 22 | if (pattern.isEmpty()) { 23 | vibrationService.vibrate(duration) 24 | } else { 25 | vibrationService.vibrate(pattern, duration.toInt()) 26 | } 27 | } 28 | } 29 | 30 | @RequiresPermission(Manifest.permission.VIBRATE) 31 | fun Context.startSimpleVibration(duration: Long = 200) { 32 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 33 | startVibrate(VibrationEffect.createOneShot(duration, DEFAULT_AMPLITUDE)) 34 | } else { 35 | startVibrate(duration) 36 | } 37 | } 38 | 39 | @RequiresPermission(Manifest.permission.VIBRATE) 40 | fun Context.cancelVibrate() { 41 | (this.getSystemService(Context.VIBRATOR_SERVICE) as? Vibrator)?.cancel() 42 | } 43 | -------------------------------------------------------------------------------- /alerts/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "../android-configs/lib-config.gradle" 2 | 3 | ext { 4 | composeVersion = '1.1.1' 5 | } 6 | 7 | android { 8 | buildFeatures { 9 | viewBinding true 10 | } 11 | } 12 | 13 | dependencies { 14 | implementation("androidx.core:core-ktx") 15 | implementation("androidx.constraintlayout:constraintlayout") 16 | implementation("com.google.android.material:material") 17 | implementation project(":kotlin-extensions") 18 | 19 | implementation "androidx.compose.runtime:runtime:$composeVersion" 20 | implementation "androidx.compose.ui:ui:$composeVersion" 21 | implementation "androidx.compose.foundation:foundation:$composeVersion" 22 | implementation "androidx.compose.foundation:foundation-layout:$composeVersion" 23 | implementation "androidx.compose.material:material:$composeVersion" 24 | implementation "androidx.compose.runtime:runtime-livedata:$composeVersion" 25 | implementation "androidx.compose.ui:ui-tooling:$composeVersion" 26 | implementation "com.google.android.material:compose-theme-adapter:1.1.9" 27 | 28 | constraints { 29 | implementation("androidx.core:core-ktx") { 30 | version { 31 | require '1.0.0' 32 | } 33 | } 34 | 35 | implementation("androidx.constraintlayout:constraintlayout") { 36 | version { 37 | require '2.2.0-alpha03' 38 | } 39 | } 40 | 41 | implementation("com.google.android.material:material") { 42 | version { 43 | require '1.1.0' 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lifecycle/README.md: -------------------------------------------------------------------------------- 1 | lifecycle 2 | ===== 3 | 4 | Модуль содержит обертку над `ViewModelProviders` для работы с `ViewController` и обертки для передачи событий из `ViewModel` во `ViewController`. 5 | 6 | ### Основные интерфейсы и классы 7 | 8 | `LifecycleViewModelProviders` - объект для получения `ViewModelProvider`. Содержит функцию *of*, которая принимает `LifecycleOwner` и возвращает специфичный для него `ViewModelProvider`. 9 | 10 | `SingleLiveEvent` - событие - одиночка. Посылает события только один раз. Наследуется от `MutableLiveData` и переопределяет методы `observe` и `setValue`. 11 | 12 | `ContentEvent` - событие, обертка над данными. 13 | Дочерние классы: 14 | * `Loading` - символизирует состояние загрузки, 15 | * `Success` - символизирует успешное событие, 16 | * `Error` - символизирует ошибку, 17 | * `Complete` - символизирует завершение события. 18 | 19 | `Event` - аналогичен `ContentEvent`, только не содержит никакой информации о данных. Нужен для оповещения о наступлении одного из следующих событий: `Loading`, `Complete` или `Error`. 20 | 21 | ### Примеры 22 | 23 | Получение `ViewModel` во `ViewController`. 24 | 25 | ```kotlin 26 | private val viewModel = LifecycleViewModelProviders.of(this).get(SomeViewModel::class.java) 27 | ``` 28 | 29 | Подписка на `SingleLiveEvent`. 30 | 31 | ```kotlin 32 | // во ViewModel 33 | val event = SingleLiveEvent() 34 | 35 | // во ViewController 36 | event.observe(this, Observer { event -> 37 | when (event) { 38 | is Event.Loading -> ::onEventLoading 39 | is Event.Complete -> ::onEventComplete 40 | is Event.Error -> ::onEventError 41 | } 42 | }) 43 | ``` 44 | -------------------------------------------------------------------------------- /pagination/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "../android-configs/lib-config.gradle" 2 | 3 | dependencies { 4 | implementation project(":mvi-arch") 5 | implementation project(":recyclerview-adapters") 6 | implementation project(":utils") 7 | implementation project(":views") 8 | implementation project(":kotlin-extensions") 9 | 10 | implementation("com.google.android.material:material") 11 | implementation("androidx.swiperefreshlayout:swiperefreshlayout") 12 | implementation("androidx.recyclerview:recyclerview") 13 | 14 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core") 15 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android") 16 | 17 | def coroutinesVersion = "1.3.7" 18 | 19 | constraints { 20 | implementation("com.google.android.material:material") { 21 | version { 22 | require("1.2.0") 23 | } 24 | } 25 | implementation("androidx.swiperefreshlayout:swiperefreshlayout") { 26 | version { 27 | require("1.0.0") 28 | } 29 | } 30 | implementation("androidx.recyclerview:recyclerview") { 31 | version { 32 | require("1.1.0") 33 | } 34 | } 35 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core") { 36 | version { 37 | require(coroutinesVersion) 38 | } 39 | } 40 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android") { 41 | version { 42 | require(coroutinesVersion) 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /utils/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "../android-configs/lib-config.gradle" 2 | 3 | dependencies { 4 | def coreVersion = '1.0.0' 5 | def annotationVersion = '1.1.0' 6 | def materialVersion = '1.2.0-rc01' 7 | def jodaVersion = '2.10.2' 8 | def junitVersion = '4.13.2' 9 | 10 | implementation project(':kotlin-extensions') 11 | implementation "androidx.core:core" 12 | implementation "androidx.annotation:annotation" 13 | implementation "com.google.android.material:material" 14 | implementation "net.danlew:android.joda" 15 | implementation "junit:junit" 16 | testImplementation "joda-time:joda-time" 17 | 18 | constraints { 19 | implementation("androidx.core:core") { 20 | version { 21 | require(coreVersion) 22 | } 23 | } 24 | 25 | implementation("androidx.annotation:annotation") { 26 | version { 27 | require(annotationVersion) 28 | } 29 | } 30 | 31 | implementation("com.google.android.material:material") { 32 | version { 33 | require(materialVersion) 34 | } 35 | } 36 | 37 | implementation("net.danlew:android.joda") { 38 | version { 39 | require(jodaVersion) 40 | } 41 | } 42 | 43 | testImplementation("joda-time:joda-time") { 44 | version { 45 | require(jodaVersion) 46 | } 47 | } 48 | 49 | implementation("junit:junit") { 50 | version { 51 | require(junitVersion) 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /webview/src/main/java/ru/touchin/roboswag/webview/web_view/BaseChromeWebViewClient.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.webview.web_view 2 | 3 | import android.webkit.ConsoleMessage 4 | import android.webkit.JsPromptResult 5 | import android.webkit.JsResult 6 | import android.webkit.WebChromeClient 7 | import android.webkit.WebView 8 | 9 | open class BaseChromeWebViewClient( 10 | private val callback: WebViewCallback 11 | ) : WebChromeClient() { 12 | 13 | override fun onJsConfirm(view: WebView?, url: String?, message: String?, result: JsResult?): Boolean { 14 | callback.onJsConfirm(message) 15 | result?.confirm() 16 | return true 17 | } 18 | 19 | override fun onJsAlert(view: WebView?, url: String?, message: String?, result: JsResult?): Boolean { 20 | callback.onJsAlert(message) 21 | result?.confirm() 22 | return true 23 | } 24 | 25 | override fun onConsoleMessage(consoleMessage: ConsoleMessage?): Boolean { 26 | if (consoleMessage?.messageLevel() == ConsoleMessage.MessageLevel.ERROR) { 27 | callback.onJsError(consoleMessage) 28 | } 29 | return super.onConsoleMessage(consoleMessage) 30 | } 31 | 32 | override fun onJsPrompt(view: WebView?, url: String?, message: String?, defaultValue: String?, result: JsPromptResult?): Boolean { 33 | callback.onJsPrompt(defaultValue) 34 | result?.confirm() 35 | return true 36 | } 37 | 38 | override fun onProgressChanged(view: WebView?, newProgress: Int) { 39 | super.onProgressChanged(view, newProgress) 40 | callback.onProgressChanged(newProgress) 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /utils/src/main/java/ru/touchin/roboswag/components/utils/spans/TypefaceSpan.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) 3 | * 4 | * This file is part of RoboSwag library. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | package ru.touchin.roboswag.components.utils.spans 21 | 22 | import android.graphics.Paint 23 | import android.graphics.Typeface 24 | import android.text.TextPaint 25 | import android.text.style.MetricAffectingSpan 26 | 27 | /** 28 | * Created by Ilia Kurtov on 15/02/2016. 29 | * Span for typefaces in texts. 30 | * http://stackoverflow.com/a/15181195 31 | */ 32 | class TypefaceSpan(private val typeface: Typeface) : MetricAffectingSpan() { 33 | 34 | override fun updateMeasureState(textPaint: TextPaint) { 35 | textPaint.typeface = typeface 36 | textPaint.flags = textPaint.flags or Paint.SUBPIXEL_TEXT_FLAG 37 | } 38 | 39 | override fun updateDrawState(textPaint: TextPaint) { 40 | textPaint.typeface = typeface 41 | textPaint.flags = textPaint.flags or Paint.SUBPIXEL_TEXT_FLAG 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /text-processing/README.md: -------------------------------------------------------------------------------- 1 | text-processing 2 | ===== 3 | 4 | ### Общее описание 5 | 6 | Модуль содержит функционал: 7 | - Генерация "Replace" шаблона из регулярного выражения и возможность подстановки его в строку, 8 | - Генерация Placeholder из регулярного выражения, 9 | - Генерация маски для `EditText` из регулярного выражения и возможность его подстановки. 10 | 11 | Модуль содержит класс `TextFormatter`, который в качестве аргумента принимает `String` в виде регулярного выражения. 12 | * Функция `getFormattedText` - принимает входящий параметр в виде строки для форматирования, возвращает отформатированную строку, 13 | * Функция `getPlaceholder` - возвращает Placeholder соответствующий регулярному выражению, 14 | * Функция `getRegexReplace` - возвращает "Replace" шаблон регулярного выражения, 15 | * Функция `mask` - принимает входящим параметром `EditText` и применяет к нему маску сгенерированую по регулярному выражению. 16 | 17 | ### Пример применения `textFormatter` 18 | 19 | ```kotlin 20 | class MainActivity : Activity() { 21 | 22 | /** 23 | * replace шаблон - $1/$2 24 | * placeholder - 12/34 25 | * **/ 26 | private val textFormatter = TextFormatter("(\\d{2})\\/?(\\d{2})") 27 | private lateinit var editText: EditText 28 | 29 | override fun onCreate(savedInstanceState: Bundle?) { 30 | super.onCreate(savedInstanceState) 31 | setContentView(R.layout.activity_main) 32 | editText = findViewById(R.id.editText) 33 | editText.hint = textFormatter.getPlaceholder() //В результате placeholder будет 12/34 34 | textFormatter.mask(editText) //Применение маски соответствующей регулярному выражению из textFormatter 35 | } 36 | } 37 | ``` -------------------------------------------------------------------------------- /views/src/main/java/ru/touchin/roboswag/views/text_view/EllipsizeSpannableTextView.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.views.text_view 2 | 3 | import android.content.Context 4 | import android.text.SpannableStringBuilder 5 | import android.util.AttributeSet 6 | import androidx.appcompat.widget.AppCompatTextView 7 | 8 | /** 9 | * A [android.widget.TextView] which support text as SpannableString with ellipsize implementation 10 | * @author Rinat Nurmukhametov 11 | */ 12 | open class EllipsizeSpannableTextView @JvmOverloads constructor( 13 | context: Context, 14 | attrs: AttributeSet? = null, 15 | defStyleAttr: Int = 0 16 | ) : AppCompatTextView(context, attrs, defStyleAttr) { 17 | 18 | companion object { 19 | private const val THREE_DOTS = "..." 20 | private const val THREE_DOTS_LENGTH = THREE_DOTS.length 21 | } 22 | 23 | private var spannableStringBuilder = SpannableStringBuilder() 24 | 25 | override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { 26 | if (layout.lineCount >= maxLines) { 27 | val charSequence = text 28 | val lastCharDown: Int = layout.getLineVisibleEnd(maxLines - 1) 29 | 30 | if (lastCharDown >= THREE_DOTS_LENGTH && charSequence.length > lastCharDown) { 31 | spannableStringBuilder.clear() 32 | 33 | spannableStringBuilder 34 | .append(charSequence.subSequence(0, lastCharDown - THREE_DOTS_LENGTH)) 35 | .append(THREE_DOTS) 36 | 37 | text = spannableStringBuilder 38 | } 39 | } 40 | 41 | super.onLayout(changed, left, top, right, bottom) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /views/src/main/java/ru/touchin/roboswag/views/text_view/StyleableSubTextView.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.views.text_view 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import androidx.core.content.withStyledAttributes 6 | import ru.touchin.extensions.getResourceIdOrNull 7 | import ru.touchin.roboswag.components.utils.spans.toStyleableSubstringText 8 | import ru.touchin.roboswag.views.R 9 | 10 | /** 11 | * A [android.widget.TextView] which support styling substrings of text 12 | */ 13 | open class StyleableSubTextView @JvmOverloads constructor( 14 | context: Context, 15 | attrs: AttributeSet? = null, 16 | defStyleAttr: Int = 0 17 | ) : EllipsizeSpannableTextView(context, attrs, defStyleAttr) { 18 | 19 | protected var substringStyleId = typeface.style 20 | 21 | var substring: String? = null 22 | set(value) { 23 | field = value 24 | 25 | text = text // call setText after setting substring to reset substring 26 | } 27 | 28 | init { 29 | context.withStyledAttributes(attrs, R.styleable.StyleableSubTextView, defStyleAttr, 0) { 30 | getResourceIdOrNull(R.styleable.StyleableSubTextView_subtextStyle)?.let { substringStyleId = it } 31 | substring = getString(R.styleable.StyleableSubTextView_subtext) 32 | } 33 | } 34 | 35 | override fun setText(text: CharSequence?, type: BufferType?) { 36 | val spannableText = when (substring == null) { 37 | true -> text 38 | false -> text?.toStyleableSubstringText(substring.orEmpty(), substringStyleId, context) 39 | } 40 | 41 | super.setText(spannableText, type) 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /kotlin-extensions/src/main/java/ru/touchin/extensions/View.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.extensions 2 | 3 | import android.os.Build 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import android.widget.FrameLayout 7 | import androidx.coordinatorlayout.widget.CoordinatorLayout 8 | import ru.touchin.utils.ActionThrottler 9 | 10 | const val RIPPLE_EFFECT_DELAY_MS = 150L 11 | 12 | /** 13 | * Sets click listener to view. On click it will call something after delay. 14 | * 15 | * @param listener Click listener. 16 | */ 17 | fun View.setOnRippleClickListener(listener: () -> Unit) { 18 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 19 | setOnClickListener { 20 | ActionThrottler.throttleAction { 21 | postDelayed({ if (hasWindowFocus()) listener() }, RIPPLE_EFFECT_DELAY_MS) 22 | } 23 | } 24 | } else { 25 | setOnClickListener { listener() } 26 | } 27 | } 28 | 29 | 30 | /** 31 | * Used for finding suitable parent view for snackbar 32 | * Method was taken from com.google.android.material.snackbar.Snackbar.findSuitableParent 33 | */ 34 | fun View?.findSuitableParent(): ViewGroup? { 35 | var view = this 36 | var fallback: ViewGroup? = null 37 | do { 38 | if (view is CoordinatorLayout) { 39 | return view 40 | } else if (view is FrameLayout) { 41 | if (view.id == android.R.id.content) { 42 | return view 43 | } else { 44 | fallback = view 45 | } 46 | } 47 | 48 | if (view != null) { 49 | val parent = view.parent 50 | view = if (parent is View) parent else null 51 | } 52 | } while (view != null) 53 | 54 | return fallback 55 | } 56 | -------------------------------------------------------------------------------- /navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/extensions/Parcelable.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.navigation_base.extensions 2 | 3 | import android.annotation.SuppressLint 4 | import android.os.Parcel 5 | import android.os.Parcelable 6 | import ru.touchin.roboswag.navigation_base.fragments.EmptyState 7 | 8 | // This method used to check unique state of each fragment. 9 | // If two fragments share same class for state, you should not pass state instance of current fragment to the one you transition to 10 | @SuppressLint("Recycle") 11 | fun Parcelable.reserialize(): T { 12 | var parcel = Parcel.obtain() 13 | 14 | parcel.writeParcelable(this, 0) 15 | 16 | val serializableBytes = parcel.marshall() 17 | 18 | parcel.recycle() 19 | 20 | parcel = Parcel.obtain().apply { 21 | unmarshall(serializableBytes, 0, serializableBytes.size) 22 | setDataPosition(0) 23 | } 24 | 25 | val result = parcel.readParcelable(Thread.currentThread().contextClassLoader) 26 | ?: throw IllegalStateException("It must not be null") 27 | 28 | parcel.recycle() 29 | 30 | return result 31 | } 32 | 33 | @SuppressLint("Recycle") 34 | fun Parcelable.copy(): Parcelable = 35 | if (this is EmptyState) { 36 | EmptyState 37 | } else { 38 | val parcel = Parcel.obtain() 39 | 40 | parcel.writeParcelable(this, 0) 41 | parcel.setDataPosition(0) 42 | 43 | val result = parcel.readParcelable( 44 | javaClass.classLoader ?: Thread.currentThread().contextClassLoader 45 | ) ?: throw IllegalStateException("Failed to copy tab state") 46 | 47 | parcel.recycle() 48 | 49 | result 50 | } 51 | -------------------------------------------------------------------------------- /logging/src/main/java/ru/touchin/roboswag/core/utils/ThreadLocalValue.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) 3 | * 4 | * This file is part of RoboSwag library. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | package ru.touchin.roboswag.core.utils; 21 | 22 | import androidx.annotation.NonNull; 23 | 24 | /** 25 | * Created by Gavriil Sitnikov on 13/11/2015. 26 | * Thread local value with specified creator of value per thread. 27 | */ 28 | public class ThreadLocalValue extends ThreadLocal { 29 | 30 | @NonNull 31 | private final Fabric fabric; 32 | 33 | public ThreadLocalValue(@NonNull final Fabric fabric) { 34 | super(); 35 | this.fabric = fabric; 36 | } 37 | 38 | @NonNull 39 | @Override 40 | protected T initialValue() { 41 | return fabric.create(); 42 | } 43 | 44 | /** 45 | * Fabric of thread-local objects. 46 | * 47 | * @param Type of objects. 48 | */ 49 | public interface Fabric { 50 | 51 | /** 52 | * Creates object. 53 | * 54 | * @return new instance of object. 55 | */ 56 | @NonNull 57 | T create(); 58 | 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /utils/src/main/java/ru/touchin/roboswag/components/utils/MetricExtensions.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.components.utils 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import android.content.res.Resources 6 | import android.util.DisplayMetrics 7 | 8 | private const val MAX_METRICS_TRIES_COUNT = 5 9 | 10 | /** 11 | * Returns right metrics with non-zero height/width. 12 | * It is common bug when metrics are calling at [Application.onCreate] method and it returns metrics with zero height/width. 13 | * 14 | * @param context [Context] of metrics; 15 | * @return [DisplayMetrics]. 16 | */ 17 | fun Context.getDisplayMetrics(): DisplayMetrics { 18 | var result = resources.displayMetrics 19 | // it is needed to avoid bug with invalid metrics when user restore application from other application 20 | var metricsTryNumber = 0 21 | while (metricsTryNumber < MAX_METRICS_TRIES_COUNT && (result.heightPixels <= 0 || result.widthPixels <= 0)) { 22 | try { 23 | Thread.sleep(500) 24 | } catch (ignored: InterruptedException) { 25 | return result 26 | } 27 | 28 | result = resources.displayMetrics 29 | metricsTryNumber++ 30 | } 31 | return result 32 | } 33 | 34 | /** 35 | * Simply converts Dp to pixels. 36 | * 37 | * @return Size in pixels. 38 | */ 39 | val Int.px: Int 40 | get() = (this * Resources.getSystem().displayMetrics.density).toInt() 41 | 42 | /** 43 | * Simply converts Dp to pixels. 44 | * 45 | * @return Size in pixels. 46 | */ 47 | val Float.px: Float 48 | get() = this * Resources.getSystem().displayMetrics.density 49 | 50 | /** 51 | * Simply converts pixels to Dp. 52 | * 53 | * @return Size in dp. 54 | */ 55 | val Int.dp: Int 56 | get() = (this / Resources.getSystem().displayMetrics.density).toInt() 57 | -------------------------------------------------------------------------------- /logging/src/main/java/ru/touchin/roboswag/core/utils/ShouldNotHappenException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) 3 | * 4 | * This file is part of RoboSwag library. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | package ru.touchin.roboswag.core.utils; 21 | 22 | import androidx.annotation.NonNull; 23 | 24 | /** 25 | * Created by Gavriil Sitnikov on 13/11/2015. 26 | * Exception that should be threw when some unexpected code reached. 27 | * E.g. if some value null but it is not legal or in default case in switch if all specific cases should be processed. 28 | */ 29 | public class ShouldNotHappenException extends RuntimeException { 30 | 31 | private static final long serialVersionUID = 0; 32 | 33 | public ShouldNotHappenException() { 34 | super(); 35 | } 36 | 37 | public ShouldNotHappenException(@NonNull final String detailMessage) { 38 | super(detailMessage); 39 | } 40 | 41 | public ShouldNotHappenException(@NonNull final String detailMessage, @NonNull final Throwable throwable) { 42 | super(detailMessage, throwable); 43 | } 44 | 45 | public ShouldNotHappenException(@NonNull final Throwable throwable) { 46 | super(throwable); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /kotlin-extensions/src/main/java/ru/touchin/extensions/Activity.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.extensions 2 | 3 | import android.app.Activity 4 | import android.app.ActivityManager 5 | import android.content.Intent 6 | import android.net.Uri 7 | import android.os.Build 8 | import android.os.Bundle 9 | import androidx.annotation.ColorRes 10 | import androidx.annotation.DrawableRes 11 | import androidx.core.content.ContextCompat 12 | 13 | fun Activity.safeStartActivityForResult(intent: Intent, requestCode: Int, options: Bundle? = null, resolveFlags: Int = 0): Boolean = 14 | packageManager.resolveActivity(intent, resolveFlags)?.let { startActivityForResult(intent, requestCode, options) } != null 15 | 16 | /** 17 | * Setup task description of application for Android 5.0 and later. It is showing when user opens task bar. 18 | * 19 | * @param label Name of application to show in task bar; 20 | * @param iconRes Icon of application to show in task bar; 21 | * @param primaryColorRes Color of application to show in task bar. 22 | */ 23 | fun Activity.setupTaskDescriptor(label: String, @DrawableRes iconRes: Int, @ColorRes primaryColorRes: Int) { 24 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 25 | val taskDescription = ActivityManager.TaskDescription( 26 | label, 27 | iconRes, 28 | ContextCompat.getColor(this, primaryColorRes) 29 | ) 30 | setTaskDescription(taskDescription) 31 | } 32 | } 33 | 34 | fun Activity.openGooglePlay(packageName: String) { 35 | try { //try to open play market 36 | startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=$packageName"))) 37 | } catch (e: android.content.ActivityNotFoundException) { 38 | openBrowser("https://play.google.com/store/apps/details?id=$packageName") 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareJsonModel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Touch Instinct 3 | * 4 | * This file is part of RoboSwag library. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | package ru.touchin.templates.logansquare; 21 | 22 | import androidx.annotation.Nullable; 23 | 24 | import ru.touchin.roboswag.core.log.Lc; 25 | import ru.touchin.roboswag.core.log.LcGroup; 26 | import ru.touchin.templates.ApiModel; 27 | 28 | /** 29 | * Created by Gavriil Sitnikov. 30 | * Just model from getting from API via LoganSquare. 31 | */ 32 | public abstract class LoganSquareJsonModel extends ApiModel { 33 | 34 | /** 35 | * Throws exception if object is missed or null. 36 | * 37 | * @param object Value of field to check; 38 | * @throws ValidationException Exception of validation. 39 | */ 40 | protected static void validateNotNull(@Nullable final Object object) throws ValidationException { 41 | if (object == null) { 42 | ValidationException exception = new ValidationException("Not nullable object is null or missed at " + Lc.getCodePoint(null, 1)); 43 | LcGroup.API_VALIDATION.e(exception, "Invalid item"); 44 | throw exception; 45 | } 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /views/src/main/java/ru/touchin/roboswag/views/ActionTextView.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.views 2 | 3 | import android.content.Context 4 | import android.graphics.Color 5 | import android.util.AttributeSet 6 | import androidx.appcompat.widget.AppCompatTextView 7 | import androidx.core.content.withStyledAttributes 8 | import ru.touchin.extensions.getResourceIdOrNull 9 | import ru.touchin.roboswag.components.utils.movementmethods.ClickableMovementMethod 10 | import ru.touchin.roboswag.components.utils.spans.toClickableSubstringText 11 | 12 | class ActionTextView @JvmOverloads constructor( 13 | context: Context, 14 | attrs: AttributeSet? = null, 15 | defStyleAttr: Int = 0 16 | ) : AppCompatTextView(context, attrs, defStyleAttr) { 17 | 18 | private var onClickAction: () -> Unit = { } 19 | 20 | init { 21 | isClickable = false 22 | isLongClickable = false 23 | highlightColor = Color.TRANSPARENT 24 | 25 | movementMethod = ClickableMovementMethod 26 | 27 | context.withStyledAttributes(attrs, R.styleable.ActionTextView, defStyleAttr, 0) { 28 | val actionText = getString(R.styleable.ActionTextView_actionText).orEmpty() 29 | val actionTextStyle = getResourceIdOrNull(R.styleable.ActionTextView_actionTextStyle) 30 | val isUnderlineText = getBoolean(R.styleable.ActionTextView_isUnderlineText, false) 31 | 32 | text = text.toClickableSubstringText( 33 | substring = actionText, 34 | clickAction = { onClickAction.invoke() }, 35 | styleId = actionTextStyle, 36 | isUnderlineText = isUnderlineText 37 | ) 38 | } 39 | } 40 | 41 | fun setOnActionClickListener(onClickAction: () -> Unit) { 42 | this.onClickAction = onClickAction 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /google-map/src/main/java/ru/touchin/googlemap/GoogleMapItemRenderer.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.googlemap 2 | 3 | import android.content.Context 4 | import com.google.android.gms.maps.GoogleMap 5 | import com.google.android.gms.maps.model.BitmapDescriptor 6 | import com.google.android.gms.maps.model.MarkerOptions 7 | import com.google.maps.android.clustering.Cluster 8 | import com.google.maps.android.clustering.ClusterItem 9 | import com.google.maps.android.clustering.ClusterManager 10 | import com.google.maps.android.clustering.view.DefaultClusterRenderer 11 | import ru.touchin.basemap.BaseIconGenerator 12 | 13 | open class GoogleMapItemRenderer( 14 | val context: Context, 15 | googleMap: GoogleMap, 16 | clusterManager: ClusterManager, 17 | private val minClusterItemSize: Int = 1 18 | ) : DefaultClusterRenderer(context, googleMap, clusterManager) { 19 | 20 | var iconGenerator: BaseIconGenerator, BitmapDescriptor> = 21 | GoogleIconGenerator(context).apply { setDefaultViewAndBackground() } 22 | 23 | override fun shouldRenderAsCluster(cluster: Cluster): Boolean = 24 | cluster.size > minClusterItemSize 25 | 26 | override fun onBeforeClusterItemRendered(item: TClusterItem, markerOptions: MarkerOptions) { 27 | markerOptions.icon(getMarkerIcon(item)) 28 | } 29 | 30 | override fun onBeforeClusterRendered(cluster: Cluster, markerOptions: MarkerOptions) { 31 | markerOptions.icon(getClusterIcon(cluster = cluster)) 32 | } 33 | 34 | private fun getMarkerIcon(item: TClusterItem): BitmapDescriptor? = 35 | iconGenerator.getClusterItemView(item) 36 | 37 | private fun getClusterIcon(cluster: Cluster): BitmapDescriptor? = 38 | iconGenerator.getClusterView(cluster) 39 | 40 | } 41 | -------------------------------------------------------------------------------- /pagination/src/main/java/ru/touchin/roboswag/pagination/PaginationAdapter.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.pagination 2 | 3 | import android.annotation.SuppressLint 4 | import androidx.recyclerview.widget.DiffUtil 5 | import androidx.recyclerview.widget.RecyclerView 6 | import ru.touchin.roboswag.recyclerview_adapters.adapters.AdapterDelegate 7 | import ru.touchin.roboswag.recyclerview_adapters.adapters.DelegationListAdapter 8 | 9 | class PaginationAdapter( 10 | private val nextPageCallback: () -> Unit, 11 | private val itemIdDiff: (old: Any, new: Any) -> Boolean, 12 | vararg delegate: AdapterDelegate 13 | ) : DelegationListAdapter( 14 | object : DiffUtil.ItemCallback() { 15 | override fun areItemsTheSame(oldItem: Any, newItem: Any): Boolean = itemIdDiff(oldItem, newItem) 16 | 17 | @SuppressLint("DiffUtilEquals") 18 | override fun areContentsTheSame(oldItem: Any, newItem: Any): Boolean = oldItem == newItem 19 | } 20 | ) { 21 | 22 | internal var fullData = false 23 | 24 | init { 25 | addDelegate(ProgressAdapterDelegate()) 26 | delegate.forEach(this::addDelegate) 27 | } 28 | 29 | fun update(data: List, updateState: UpdateState) { 30 | submitList(data + listOfNotNull(when (updateState) { 31 | is UpdateState.Common -> null 32 | is UpdateState.Progress -> ProgressItem 33 | is UpdateState.Error -> ErrorItem 34 | })) 35 | } 36 | 37 | override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int, payloads: List) { 38 | super.onBindViewHolder(holder, position, payloads) 39 | if (!fullData && position >= itemCount - 10) nextPageCallback.invoke() 40 | } 41 | 42 | sealed class UpdateState { 43 | object Common : UpdateState() 44 | object Progress : UpdateState() 45 | object Error : UpdateState() 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /logging/src/main/java/ru/touchin/roboswag/core/log/LcLevel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) 3 | * 4 | * This file is part of RoboSwag library. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | package ru.touchin.roboswag.core.log; 21 | 22 | import androidx.annotation.NonNull; 23 | import android.util.Log; 24 | 25 | /** 26 | * Created by Gavriil Sitnikov on 14/05/2016. 27 | * Level of log message. 28 | */ 29 | public enum LcLevel { 30 | 31 | VERBOSE(Log.VERBOSE), 32 | DEBUG(Log.DEBUG), 33 | INFO(Log.INFO), 34 | WARN(Log.WARN), 35 | ERROR(Log.ERROR), 36 | ASSERT(Log.ASSERT); 37 | 38 | private final int priority; 39 | 40 | LcLevel(final int priority) { 41 | this.priority = priority; 42 | } 43 | 44 | /** 45 | * Standard {@link Log} integer value of level represents priority of message. 46 | * 47 | * @return Integer level. 48 | */ 49 | public int getPriority() { 50 | return priority; 51 | } 52 | 53 | /** 54 | * Compares priorities of LcLevels and returns if current is less than another. 55 | * 56 | * @param logLevel {@link LcLevel} to compare priority with; 57 | * @return True if current level priority less than level passed as parameter. 58 | */ 59 | public boolean lessThan(@NonNull final LcLevel logLevel) { 60 | return this.priority < logLevel.priority; 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/fragments/BaseFragment.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.navigation_base.fragments 2 | 3 | import android.content.Context 4 | import android.content.res.ColorStateList 5 | import android.graphics.drawable.Drawable 6 | import android.os.Bundle 7 | import android.view.View 8 | import androidx.annotation.ColorInt 9 | import androidx.annotation.ColorRes 10 | import androidx.annotation.DrawableRes 11 | import androidx.annotation.IdRes 12 | import androidx.annotation.LayoutRes 13 | import androidx.core.content.ContextCompat 14 | import androidx.fragment.app.Fragment 15 | import androidx.fragment.app.FragmentActivity 16 | 17 | open class BaseFragment : Fragment { 18 | 19 | constructor() : super() 20 | 21 | constructor(@LayoutRes layoutRes: Int) : super(layoutRes) 22 | 23 | protected val view: View 24 | @JvmName("requireViewKtx") get() = requireView() 25 | 26 | protected val activity: TActivity 27 | @JvmName("requireActivityKtx") get() = requireActivity() as TActivity 28 | 29 | protected val context: Context 30 | @JvmName("requireContextKtx") get() = requireContext() 31 | 32 | override fun onCreate(savedInstanceState: Bundle?) { 33 | super.onCreate(savedInstanceState) 34 | 35 | setHasOptionsMenu(true) 36 | } 37 | 38 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 39 | super.onViewCreated(view, savedInstanceState) 40 | 41 | lifecycle.addObserver(LifecycleLoggingObserver(this)) 42 | } 43 | 44 | fun findViewById(@IdRes id: Int): T = view.findViewById(id) 45 | 46 | @ColorInt 47 | fun getColor(@ColorRes resId: Int): Int = ContextCompat.getColor(requireContext(), resId) 48 | 49 | fun getColorStateList(@ColorRes resId: Int): ColorStateList? = ContextCompat.getColorStateList(context, resId) 50 | 51 | fun getDrawable(@DrawableRes resId: Int): Drawable? = ContextCompat.getDrawable(context, resId) 52 | 53 | } 54 | -------------------------------------------------------------------------------- /logging_reader/src/main/java/ru/touchin/roboswag/loggging_reader/LogFileManager.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.loggging_reader 2 | 3 | import android.content.Context 4 | import java.io.File 5 | import java.io.IOException 6 | import java.text.SimpleDateFormat 7 | import java.util.Date 8 | import java.util.Locale 9 | 10 | class LogFileManager(private val context: Context) { 11 | 12 | enum class Priority(val title: String, val tag: String) { 13 | VERBOSE("VERBOSE", "*:V"), 14 | DEBUG("DEBUG", "*:D"), 15 | INFO("INFO", "*:I"), 16 | WARNING("WARNING", "*:W"), 17 | ERROR("ERROR", "*:E"), 18 | ASSERT("ASSERT", "*:A") 19 | } 20 | 21 | companion object { 22 | private const val logDirecroryName = "log" 23 | const val fileProviderName = ".fileprovider" 24 | } 25 | 26 | fun getLogDirectory(): File { 27 | val appDirectory = context.getExternalFilesDir(null) 28 | return File(appDirectory.toString() + "/$logDirecroryName") 29 | } 30 | 31 | fun saveLogcatToFile(priorityTag: String) { 32 | val logDirectory = initLogDirectory(context) 33 | 34 | val sdf = SimpleDateFormat("yyyy-MM-dd'T'HH_mm_ss_SSS", Locale.getDefault()) 35 | val logFile = File(logDirectory, "logcat_${sdf.format(Date())}.txt") 36 | 37 | try { 38 | Runtime.getRuntime().exec("logcat $priorityTag -f $logFile") 39 | } catch (e: IOException) { 40 | e.printStackTrace() 41 | } 42 | } 43 | 44 | private fun initLogDirectory(context: Context): File { 45 | val appDirectory = context.getExternalFilesDir(null) 46 | if (appDirectory != null && !appDirectory.exists()) { 47 | appDirectory.mkdir() 48 | } 49 | 50 | val logDirectory = File(appDirectory.toString() + "/$logDirecroryName") 51 | if (!logDirectory.exists()) { 52 | logDirectory.mkdir() 53 | } 54 | 55 | for (file in logDirectory.listFiles()) { 56 | file.delete() 57 | } 58 | return logDirectory 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /navigation-base/src/main/java/ru/touchin/roboswag/navigation_base/fragments/FragmentViewBindingDelegate.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.navigation_base.fragments 2 | 3 | import android.view.View 4 | import androidx.fragment.app.Fragment 5 | import androidx.lifecycle.DefaultLifecycleObserver 6 | import androidx.lifecycle.Lifecycle 7 | import androidx.lifecycle.LifecycleOwner 8 | import androidx.lifecycle.observe 9 | import androidx.viewbinding.ViewBinding 10 | import kotlin.properties.ReadOnlyProperty 11 | import kotlin.reflect.KProperty 12 | 13 | class FragmentViewBindingDelegate( 14 | val fragment: Fragment, 15 | val viewBindingFactory: (View) -> T 16 | ) : ReadOnlyProperty { 17 | private var binding: T? = null 18 | 19 | init { 20 | fragment.lifecycle.addObserver(object : DefaultLifecycleObserver { 21 | override fun onCreate(owner: LifecycleOwner) { 22 | fragment.viewLifecycleOwnerLiveData.observe(fragment) { viewLifecycleOwner -> 23 | viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver { 24 | override fun onDestroy(owner: LifecycleOwner) { 25 | binding = null 26 | } 27 | }) 28 | } 29 | } 30 | }) 31 | } 32 | 33 | override fun getValue(thisRef: Fragment, property: KProperty<*>): T { 34 | val binding = binding 35 | if (binding != null) { 36 | return binding 37 | } 38 | 39 | val lifecycle = fragment.viewLifecycleOwner.lifecycle 40 | if (!lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) { 41 | throw IllegalStateException("Should not attempt to get bindings when Fragment views are destroyed.") 42 | } 43 | 44 | return viewBindingFactory(thisRef.requireView()).also { this.binding = it } 45 | } 46 | } 47 | 48 | fun Fragment.viewBinding(viewBindingFactory: (View) -> T) = 49 | FragmentViewBindingDelegate(this, viewBindingFactory) 50 | -------------------------------------------------------------------------------- /bottom-navigation-viewcontroller/src/main/java/ru/touchin/roboswag/bottom_navigation_viewcontroller/BottomNavigationController.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.roboswag.bottom_navigation_viewcontroller 2 | 3 | import android.content.Context 4 | import android.util.SparseArray 5 | import androidx.annotation.IdRes 6 | import androidx.annotation.LayoutRes 7 | import androidx.fragment.app.Fragment 8 | import androidx.fragment.app.FragmentManager 9 | import ru.touchin.roboswag.bottom_navigation_base.BaseBottomNavigationController 10 | import ru.touchin.roboswag.navigation_viewcontroller.fragments.ViewControllerFragment 11 | 12 | class BottomNavigationController( 13 | context: Context, 14 | fragmentManager: FragmentManager, 15 | viewControllers: SparseArray, 16 | private val wrapWithNavigationContainer: Boolean = false, 17 | @IdRes private val defaultTabId: Int = 0, // If it zero back press with empty fragment back stack would close the app 18 | @IdRes private val contentContainerViewId: Int, 19 | @LayoutRes private val contentContainerLayoutId: Int, 20 | private val onReselectListener: (() -> Unit)? = null 21 | ) : BaseBottomNavigationController( 22 | context = context, 23 | fragmentManager = fragmentManager, 24 | tabs = viewControllers, 25 | defaultTabId = defaultTabId, 26 | contentContainerViewId = contentContainerViewId, 27 | contentContainerLayoutId = contentContainerLayoutId, 28 | wrapWithNavigationContainer = wrapWithNavigationContainer 29 | ) { 30 | 31 | override fun onTabReselected() { 32 | onReselectListener?.invoke() 33 | } 34 | 35 | override fun getNavigationContainerClass() = NavigationContainerFragment::class.java 36 | 37 | override fun isTabClass(tab: NavigationTab, fragment: Fragment?): Boolean = 38 | if (wrapWithNavigationContainer) { 39 | super.isTabClass(tab, fragment) 40 | } else { 41 | (fragment as ViewControllerFragment<*, *>).viewControllerClass === tab.cls 42 | } 43 | 44 | } 45 | 46 | -------------------------------------------------------------------------------- /logging/src/main/java/ru/touchin/roboswag/core/log/ConsoleLogProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) 3 | * 4 | * This file is part of RoboSwag library. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | package ru.touchin.roboswag.core.log; 21 | 22 | import androidx.annotation.NonNull; 23 | import androidx.annotation.Nullable; 24 | import android.util.Log; 25 | 26 | /** 27 | * Created by Gavriil Sitnikov on 13/11/2015. 28 | * Simple {@link LogProcessor} implementation which is logging messages to console (logcat). 29 | */ 30 | public class ConsoleLogProcessor extends LogProcessor { 31 | 32 | public ConsoleLogProcessor(@NonNull final LcLevel lclevel) { 33 | super(lclevel); 34 | } 35 | 36 | @NonNull 37 | private String normalize(@NonNull final String message) { 38 | return message.replace("\r\n", "\n").replace("\0", ""); 39 | } 40 | 41 | @Override 42 | @SuppressWarnings({"WrongConstant", "LogConditional"}) 43 | //WrongConstant, LogConditional: level.getPriority() is not wrong constant! 44 | public void processLogMessage(@NonNull final LcGroup group, @NonNull final LcLevel level, 45 | @NonNull final String tag, @NonNull final String message, @Nullable final Throwable throwable) { 46 | final String messageToLog = normalize(message + (throwable != null ? '\n' + Log.getStackTraceString(throwable) : "")); 47 | 48 | Log.println(level.getPriority(), tag, messageToLog); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /recyclerview-calendar/src/main/java/ru/touchin/calendar/CalendarHeaderItem.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) 3 | * 4 | * This file is part of RoboSwag library. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | package ru.touchin.calendar; 21 | 22 | /** 23 | * Created by Ilia Kurtov on 17/03/2016. 24 | * Calendar header item for showing headers for months in calendar. 25 | */ 26 | public class CalendarHeaderItem implements CalendarItem { 27 | 28 | private final int year; 29 | private final int month; 30 | private final int startRange; 31 | private final int endRange; 32 | 33 | public CalendarHeaderItem(final int year, final int month, final int startRange, final int endRange) { 34 | this.year = year; 35 | this.month = month; 36 | this.startRange = startRange; 37 | this.endRange = endRange; 38 | } 39 | 40 | /** 41 | * Returns year. 42 | * 43 | * @return year. 44 | */ 45 | public int getYear() { 46 | return year; 47 | } 48 | 49 | /** 50 | * Returns number of month (where 0 is January and 11 is December). 51 | * 52 | * @return Number of month (where 0 is January and 11 is December). 53 | */ 54 | public int getMonth() { 55 | return month; 56 | } 57 | 58 | @Override 59 | public int getStartRange() { 60 | return startRange; 61 | } 62 | 63 | @Override 64 | public int getEndRange() { 65 | return endRange; 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /lifecycle-viewcontroller/src/main/java/ru/touchin/lifecycle_viewcontroller/viewmodel/LifecycleViewModelProviders.kt: -------------------------------------------------------------------------------- 1 | package ru.touchin.lifecycle_viewcontroller.viewmodel 2 | 3 | import androidx.lifecycle.LifecycleOwner 4 | import androidx.lifecycle.ViewModelProvider 5 | import androidx.lifecycle.ViewModelProviders 6 | import ru.touchin.lifecycle.viewmodel.BaseLifecycleViewModelProviders 7 | import ru.touchin.roboswag.navigation_viewcontroller.viewcontrollers.ViewController 8 | 9 | object LifecycleViewModelProviders : BaseLifecycleViewModelProviders() { 10 | 11 | /** 12 | * Creates a {@link ViewModelProvider}, which retains ViewModels while a scope of given 13 | * {@code lifecycleOwner} is alive. More detailed explanation is in {@link ViewModel}. 14 | *

15 | * It uses the given {@link Factory} to instantiate new ViewModels. 16 | * 17 | * @param lifecycleOwner a lifecycle owner, in whose scope ViewModels should be retained (ViewController, Fragment, Activity) 18 | * @param factory a {@code Factory} to instantiate new ViewModels 19 | * @return a ViewModelProvider instance 20 | */ 21 | override fun of( 22 | lifecycleOwner: LifecycleOwner, 23 | factory: ViewModelProvider.Factory 24 | ): ViewModelProvider = 25 | when (lifecycleOwner) { 26 | is ViewController<*, *> -> ViewModelProviders.of(lifecycleOwner.fragment, factory) 27 | else -> super.of(lifecycleOwner, factory) 28 | } 29 | 30 | /** 31 | * Returns ViewModelProvider.Factory instance from current lifecycleOwner. 32 | * Search #ViewModelFactoryProvider are produced according to priorities: 33 | * 1. View controller; 34 | * 2. Fragment; 35 | * 3. Parent fragment recursively; 36 | * 4. Activity; 37 | * 5. Application. 38 | */ 39 | override fun getViewModelFactory(provider: Any): ViewModelProvider.Factory = 40 | when (provider) { 41 | is ViewController<*, *> -> getViewModelFactory(provider.fragment) 42 | else -> super.getViewModelFactory(provider) 43 | } 44 | 45 | } 46 | --------------------------------------------------------------------------------