├── README.md ├── docs └── style-guide │ ├── style_guide.md │ ├── style_guide_android.md │ ├── style_guide_files_structure.md │ ├── style_guide_kotlin.md │ └── style_guide_xml.md └── tools ├── garcon ├── garcon_config.yaml └── templates │ ├── rv_item_page_object.ftl │ └── screen_page_object.ftl ├── geminio ├── geminio_config.yaml ├── modules_templates │ └── Empty Module │ │ ├── recipe.yaml │ │ └── root │ │ ├── README.md │ │ ├── build.gradle.kts.ftl │ │ ├── gitignore.ftl │ │ ├── proguard-rules.pro.ftl │ │ └── src │ │ └── main │ │ └── AndroidManifest.xml.ftl └── templates │ ├── BaseFragment │ ├── recipe.yaml │ └── root │ │ ├── res │ │ └── layout │ │ │ └── fragment_blank.xml.ftl │ │ └── src │ │ └── app_package │ │ ├── BlankFragment.kt.ftl │ │ └── BlankModule.kt.ftl │ ├── Cell │ ├── recipe.yaml │ └── root │ │ ├── res │ │ └── layout │ │ │ └── cell_layout.xml.ftl │ │ └── src │ │ └── app_package │ │ └── Cell.kt.ftl │ ├── ContainerFragment │ ├── recipe.yaml │ └── root │ │ ├── res │ │ └── layout │ │ │ └── container_fragment.xml.ftl │ │ └── src │ │ └── app_package │ │ └── ui │ │ └── container │ │ ├── ContainerFragment.kt.ftl │ │ ├── ContainerFragmentNavScreen.kt.ftl │ │ ├── ContainerFragmentNavigator.kt.ftl │ │ ├── ContainerFragmentVM.kt.ftl │ │ └── di │ │ └── ContainerFragmentModule.kt.ftl │ ├── Custom View │ ├── recipe.yaml │ └── root │ │ ├── res │ │ ├── layout │ │ │ └── view.xml.ftl │ │ └── values │ │ │ └── attrs.xml.ftl │ │ └── src │ │ └── app_package │ │ └── View.kt.ftl │ ├── Feature Facade │ ├── recipe.yaml │ └── root │ │ └── src │ │ └── app_package │ │ ├── FeatureApi.kt.ftl │ │ ├── FeatureApiImpl.kt.ftl │ │ ├── FeatureDeps.kt.ftl │ │ ├── FeatureFacade.kt.ftl │ │ └── FeatureModule.kt.ftl │ ├── Fragment + ViewModel │ ├── recipe.yaml │ └── root │ │ ├── res │ │ └── layout │ │ │ ├── design_fragment.xml.ftl │ │ │ └── simple_fragment.xml.ftl │ │ └── src │ │ └── app_package │ │ ├── Fragment.kt.ftl │ │ ├── Module.kt.ftl │ │ ├── UiConverter.kt.ftl │ │ ├── ViewModel.kt.ftl │ │ └── model │ │ ├── Params.kt.ftl │ │ ├── UiEvent.kt.ftl │ │ └── UiState.kt.ftl │ ├── MVI Compact Feature │ ├── recipe.yaml │ └── root │ │ └── src │ │ └── app_package │ │ └── Feature.kt.ftl │ ├── MVI Multi File Feature │ ├── recipe.yaml │ └── root │ │ └── src │ │ └── app_package │ │ ├── Actor.kt.ftl │ │ ├── Bootstrapper.kt.ftl │ │ ├── Feature.kt.ftl │ │ ├── NewsPublisher.kt.ftl │ │ ├── Reducer.kt.ftl │ │ └── model │ │ ├── Effect.kt.ftl │ │ ├── News.kt.ftl │ │ ├── State.kt.ftl │ │ └── Wish.kt.ftl │ └── Network │ ├── recipe.yaml │ └── root │ └── src │ └── app_package │ ├── NetworkApi.kt.ftl │ ├── Repository.kt.ftl │ ├── converter │ └── Converter.kt.ftl │ ├── di │ ├── ApiProvider.kt.ftl │ └── DataModule.kt.ftl │ └── model │ └── Model.kt.ftl └── ide-settings ├── Headhunter_Android_Style.xml ├── ide_settings.md └── settings.zip /README.md: -------------------------------------------------------------------------------- 1 | # Android Style Guide 2 | Правила написания кода и утилиты, помогающие их поддерживать 3 | 4 | # Содержание 5 | - [Style Guide](docs/style-guide/style_guide.md) - правила написания кода 6 | - [Настройка IDEA](tools/ide-settings/ide_settings.md) - настройки кодстайла для Android Studio 7 | -------------------------------------------------------------------------------- /docs/style-guide/style_guide.md: -------------------------------------------------------------------------------- 1 | # Android Style Guide 2 | 3 | Правила написания кода и утилиты, помогающие их поддерживать 4 | 5 | # Содержание 6 | 7 | 1. [Стандарт оформления кода на языке Kotlin](style_guide_kotlin.md) 8 | 1. [Стандарт оформления кода на Android проектах](style_guide_android.md) 9 | 1. [Стандарт оформления кода на языке XML](style_guide_xml.md) 10 | 1. [Стандарты структуры файлов](style_guide_files_structure.md) 11 | 4. [Правила настройки IDE](/tools/ide-settings/ide_settings.md) 12 | -------------------------------------------------------------------------------- /docs/style-guide/style_guide_android.md: -------------------------------------------------------------------------------- 1 | # Набор соглашений по написанию кода на android-проектах 2 | 3 | # Содержание 4 | 1. [Константы](#constants) 5 | 2. [Фрагменты](#fragments) 6 | 3. [Логирование](#log) 7 | 4. [RxJava](#rxjava) 8 | 5. [Общие соглашения](#common) 9 | 10 | ## Константы 11 | * Для констант, которые используются как **ключи к `SharedPreferences`** необходимо придерживатся следующих соглашений: 12 | * Имя константы должно начинаться с префикса `PREF_` 13 | * Значение константы должно формироваться по следующему правилу: `{feature package name}.{flag}`. Это необходимо, что бы ключи не пересекались, если используется один файл для SharedPreferences 14 | 15 | ```kotlin 16 | const val PREF_IS_OAUTH_ENABLED = "ru.hh.network_source.is_oauth_enabled" 17 | ``` 18 | * Для констант, которые используются как **тэг при логировании** необходимо именовать константу `LOG_TAG`. Если названием для константы является название класса, и оно не помещается в 23 символа (ограничение LogCat-a), то можно удалить символы с конца строки. 19 | ```kotlin 20 | private const val LOG_TAG = "InternetAvailabilityInt" 21 | ``` 22 | * Для констант, которые используются как **тэг для экземпляра класса `Fragment`**, стоит именовать тэг `TAG` и оставлять его с публичным доступом. Т.о. при добавлении экземпляра `Fragment` в `FragmentManager` можно будет использовать этот тэг. 23 | 24 | * Константы, которые используются как значения ключей для объектов типа `Bundle`, должны иметь модификатор `private` и именоваться: 25 | * с префиксом `ARGS_`, если объект используется для предачи параметров между компонентами приложения или при передачи аргументов в объект класса `Fragment` 26 | * с префиксом `STATE_`, если объект используется для сохранения состояния экрана внутри метода `onSaveInstanceState` 27 | * Константы, которые используются как **аргумент `requestCode` в методе `startActivityForResult`**, именуются с префиксом `REQUEST_CODE_` 28 | * При первой имплементации интерфейса `Serializable` для какого-л. класса проставлять значение константы `serialVersionUID` равной **`-1L`** 29 | ```kotlin 30 | private const val serialVersionUID = -1L 31 | ``` 32 | 33 | ## Фрагменты 34 | * Создавать экземпляры класса `Fragment` всегда необходимо через статический метод `newInstance` самого класса с передачей всех аргументов в этот метод. Аргументы не должны быть типа `Bundle`. 35 | * Для передачи множества аргументов между фрагментами создаем `Serializable`-модель: 36 | 37 | ```kotlin 38 | data class NavModel( 39 | val arg1: Int, 40 | val arg2: String 41 | ): Serializable { 42 | 43 | companion object { 44 | private const val serialVersionUID: Long = -1L 45 | } 46 | 47 | } 48 | 49 | ``` 50 | 51 | ## Логирование 52 | * При логгировании всегда проставлять тэг: `Timber.tag(LOG_TAG).d("Some message")` 53 | 54 | * Если требуется залогировать параметры, используем интерполяцию строк в Kotlin-е: 55 | ```kotlin 56 | Timber.tag(LOG_TAG).d("Some message with params: $param1, $param2 and $param3") 57 | ``` 58 | 59 | * При использовании статического метода `Timber.e(args)` в качестве аргументов всегда передавать экземпляр класса `Throwable`, даже несмотря на то, что в качестве аргументов можно передать только строку текста. Это необходимо, т.к. все ошибки у нас логируются в Crashlytics и затем отображаются как non-fatal, и важно что бы вершина stacktrace-a была в том месте, где ошибка возникает, т.о. ошибки будут выделяться отдельным элементом в Crashlytics. 60 | ```kotlin 61 | Timber.tag(LOG_TAG).e(Exception("Unknown currency code: $currency")) 62 | ``` 63 | Если же у нас уже есть экземпляр класса `Throwable`, то можно использовать метод `Timber.e(t: Throwable, message: String)` 64 | 65 | ```kotlin 66 | try { 67 | jsonValidator.validate(veryLongJSON) 68 | } catch (e: JSONException) { 69 | Timber.tag(LOG_TAG).e(e, "Cannot validate input JSON") 70 | } 71 | ``` 72 | 73 | ## RxJava 74 | * Для создания `Single`-ов в общем случае нужно использовать метод `.fromCallable`: 75 | 76 | ```kotlin 77 | fun createSingle(): Single { 78 | return Single.fromCallable { 79 | createMyModel() 80 | } 81 | } 82 | ``` 83 | 84 | Однако в тестах, или в случаях, когда нам нужно просто пробросить non-null значение в `Single`, 85 | можно использовать функцию `.just`: 86 | 87 | ```kotlin 88 | fun createSingle(value: myModel): Single { 89 | return Single.just(value) 90 | } 91 | ``` 92 | 93 | ## Общие соглашения 94 | * При описании условий вида `if-else` сначала описываем позитивный сценарий, а потом негативный. 95 | 96 | То есть, НЕ так: 97 | 98 | ```kotlin 99 | if (!myList.contains(1)) { 100 | negativeScenario() 101 | } else { 102 | positiveScenario() 103 | } 104 | ``` 105 | 106 | А вот так: 107 | 108 | ```kotlin 109 | if (myList.contains(1)) { 110 | positiveScenario() 111 | } else { 112 | negativeScenario() 113 | } 114 | ``` 115 | -------------------------------------------------------------------------------- /docs/style-guide/style_guide_files_structure.md: -------------------------------------------------------------------------------- 1 | # Структура файлов в feature-модуле и feature:core-модуле 2 | 3 | ## Корневая директория 4 | 5 | Корневая директория модуля должна иметь следующую структуру (квадратные скобки - директория): 6 | 7 | ``` 8 | - [data] 9 | - [domain] 10 | - [presentation] 11 | - [facade] 12 | - [analytics] 13 | - [experiment] 14 | ``` 15 | 16 | - `[data]` - data-слой, место для источников данных, внутри могут появляться директории `[network]`, `[local]` и другие; 17 | - `[domain]` - domain-слой приложения, место для интеракторов, domain-моделей и прочего; 18 | - `[presentation]` - presentation-слой, место для `Fragment`-ов, `ViewModel`, `View` и все что угодно, связанное с UI; 19 | - `[facade]` - место для `FeatureFacade` и его компонентов, а также для вспомогательных классов, являющихся частью api 20 | или deps фичи; 21 | - `[analytics]` - место для аналитики; 22 | - `[experiment]` - классы экспериментов. 23 | 24 | Если какие-то директории не требуются, то их не нужно добавлять в корневую директорию модуля. 25 | 26 | **Пример** 27 | 28 | - нет эксперимента в фиче - директория `[experiment]` не нужна; 29 | - feature:core модуль, где нет presentation-слоя - директория `[presentation]` не нужна. 30 | 31 | ## Структура внутри директорий 32 | 33 | В каждой директории могут появляться дополнительные директории для удобства навигации по файлам в зависимости от 34 | сложности вашего модуля. 35 | 36 | **Примеры** 37 | 38 | - Если в вашем domain-слое присутствуют несколько интеракторов, моделей, утилитных классов, то лучше их положить в 39 | отдельные директории внутри `[domain]`, т.е.: 40 | 41 | ``` 42 | - [domain] 43 | - [interactor] 44 | - Interactor1.kt 45 | - Interactor2.kt 46 | - [model] 47 | - Model1.kt 48 | - Model2.kt 49 | - [utils] 50 | - Utils1.kt 51 | - Utils2.kt 52 | ``` 53 | 54 | - Если `FeatureFacade` имеет `Deps` разбитый на несколько интерфейсов, то следует их сложить в отдельную директорию 55 | внутри `[facade]`: 56 | 57 | ``` 58 | - [facade] 59 | - [deps] 60 | - Deps1.kt 61 | - Deps2.kt 62 | - Deps3.kt 63 | - FeatureDeps.kt 64 | - FeatureApi.kt 65 | - FeatureFacade.kt 66 | - FeatureModule.kt 67 | ``` 68 | 69 | ## Директория для компонентов MVI 70 | 71 | Обычно MVI может появляться только в 2 слоях приложения: `[domain]` (стандартный MVI с бизнес-логикой экрана) 72 | и `[data]` (`StoreFeature`). Компоненты MVI необходимо складывать в директорию `[mvi]`, а эта директория дополнительно 73 | помещается в директорию `[domain]` / `[data]` слоя. 74 | 75 | ``` 76 | - [data] 77 | - [mvi] 78 | - DataFeature.kt 79 | - DataActor.kt 80 | - DataReducer.kt 81 | - [domain] 82 | - [mvi] 83 | - DomainFeature.kt 84 | - DomainActor.kt 85 | - DomainReducer.kt 86 | ``` 87 | 88 | ## Директории для DI-модулей 89 | 90 | DI-модули делим на 4-типа в зависимости от кого, какие компоненты в него установлены: 91 | 92 | - рутовый модуль для FeatureFacade c байндингом `Api` модуля; 93 | - модуль data-слоя 94 | - модуль domain-слоя 95 | - модуль presentation-слоя 96 | 97 | Следовательно, DI-модули требуется складывать в соответствующие директории: 98 | 99 | - `[facade/di]` 100 | - `[data/di]` 101 | - `[domain/di]` 102 | - `[presentation/di]` 103 | 104 | При этом могут появляться дополнительные директории внутри каждого слоя для лучшего структурирования компонентов. 105 | 106 | **Пример** 107 | 108 | Фича-модуль, где есть несколько экранов в рамках этой фичи. DI-модуль presentation-слоя конкретного экрана можно сложить 109 | в `[presentation/screen_name/di]`: 110 | 111 | ``` 112 | - [presentation] 113 | - [screen_1] 114 | - [di] 115 | - Module1.kt 116 | - Fragment1.kt 117 | - ViewModel1.kt 118 | - [screen_2] 119 | - [di] 120 | - Module2.kt 121 | - Fragment2.kt 122 | - ViewModel2.kt 123 | - [screen_3] 124 | - [di] 125 | - Module3.kt 126 | - Fragment3.kt 127 | - ViewModel3.kt 128 | ``` 129 | 130 | -------------------------------------------------------------------------------- /docs/style-guide/style_guide_kotlin.md: -------------------------------------------------------------------------------- 1 | Набор соглашений по оформлению кода на языке Kotlin. 2 | 3 | 4 | Этот список правил расширяет предложенные [Google](https://android.github.io/kotlin-guides/style.html) и [командой разработки Kotlin](https://kotlinlang.org/docs/reference/coding-conventions.html) гайды и пересматривает в них некоторые неоднозначные моменты. 5 | За основу взят [список гайдов предложенных REDMADROBOT](https://github.com/RedMadRobot/kotlin-style-guide) 6 | 7 | 8 | # Содержание 9 | 1. [Длина строки](#linelength) 10 | 2. [Правила именования](#naming) 11 | 3. [Порядок следования модификаторов](#modifier_order) 12 | 4. [Форматирование выражений](#expression_formating) 13 | 5. [Функции](#function) 14 | * 5.1 [Функции с одним выражением](#function_expression) 15 | * 5.2 [Форматирование вызова функции](#formating_function_calling) 16 | * 5.3 [Форматирование описания функции](#formating_function_declaration) 17 | * 5.4 [Вызов переменной функционального типа](#calling_function_variable) 18 | 6. [Классы](#classes) 19 | 7. [Аннотации](#annotation) 20 | 8. [Структура класса](#class_member_order) 21 | 9. [Форматирование лямбда-выражений](#lambda_formating) 22 | 10. [Использование условных операторов](#condition_operator) 23 | 11. [Template header](#template_header) 24 | 12. [Файлы](#files) 25 | 13. [Использование Properties](#properties) 26 | * 13.1 [Форматирование properties](#properties_formatting) 27 | * 13.2 [Functions VS Properties](#functions_vs_properties) 28 | 29 | 30 | # Длина строки 31 | - Максимальная длина строки: 120 символов. 32 | 33 | # Правила именования 34 | - Неизменяемые поля в (Companion) Object и compile-time константы именуются в стиле SCREAMING_SNAKE_CASE 35 | - Для полей View из Kotlin Extension используется стиль lower_snake_case 36 | - Любые другие поля именуются в стиле lowerCamelCase 37 | - Функции именуются в стиле lowerCamelCase 38 | - Классы именуются в стиле UpperCamelCase 39 | - Пакеты именуются в стиле lower_snake_case 40 | 41 | # Порядок следования модификаторов 42 | 1) override 43 | 2) public / protected / private / internal 44 | 3) final / open / abstract 45 | 4) const / lateinit 46 | 5) inner 47 | 6) enum / annotation / sealed / data 48 | 7) companion 49 | 8) inline 50 | 9) infix 51 | 10) operator 52 | 53 | # Форматирование выражений 54 | 55 | При переносе на новую строку цепочки вызова методов символ `.` или оператор `?.` переносятся на следующую строку, property при этом разрешается оставлять на одной строке: 56 | ```kotlin 57 | val collectionItem = source.collectionItems 58 | ?.dropLast(10) 59 | ?.sortedBy { it.progress } 60 | ``` 61 | Элвис оператор `?:` при разрыве выражения также переносится на новую строку: 62 | ```kotlin 63 | val promoItemDistanceTradeLink: String = promoItem.distanceTradeLinks?.appLink 64 | ?: String.EMPTY 65 | ``` 66 | При описании переменной с делегатом, не помещающимися на одной строке, оставлять описание с открывающейся фигурной скобкой на одной строке, перенося остальное выражение на следующую строку: 67 | ```kotlin 68 | private val promoItem: MarkPromoItem by lazy { 69 | extractNotNull(BUNDLE_FEED_UNIT_KEY) as MarkPromoItem 70 | } 71 | ``` 72 | 73 | # Функции 74 | ## Функции с одним выражением 75 | * Позволительно использовать функцию с одним выражением только в том случае, если она помещается в одну строку. 76 | 77 | ## Форматирование вызова функции 78 | * Использование именованного синтаксиса аргументов остается на усмотрение разработчика. Стоит руководствоваться сложностью вызываемого метода: если вызов метода с переданными в него параметрами понятен и очевиден, нет необходимости использовать именованные параметры. 79 | При написании именованных аргументов делать перенос каждого аргумента на новую строку с двойным отступом и переносом закрывающейся круглой скобки на следующую строку: 80 | 81 | ```kotlin 82 | runOperation( 83 | method = operation::run, 84 | consumer = consumer, 85 | errorHandler = errorHandler, 86 | tag = tag, 87 | cache = cache, 88 | cacheMode = cacheMode 89 | ) 90 | ``` 91 | 92 | ## Форматирование описания функции 93 | 94 | * При необходимости разрыва строки осуществляется перенос каждого аргумента функции на новую строку с двойным отступом и переносом закрывающей круглой скобки на следующую строку. 95 | 96 | 97 | ```kotlin 98 | fun functionName( 99 | paramName: String, 100 | paramSecondName: String, 101 | intParam: Int 102 | ): Int { 103 | // do somthing 104 | return 1 105 | } 106 | ``` 107 | 108 | ## Вызов переменной функционального типа 109 | 110 | * Всегда использовать полный вариант с написанием `invoke` у переменной вместо использования сокращенного варианта: 111 | ```kotlin 112 | fun runAndCall(expression: () -> Unit): Result { 113 | val result = run() 114 | 115 | //Bad 116 | expression() 117 | //Good 118 | expression.invoke() 119 | 120 | return result 121 | } 122 | ``` 123 | 124 | # Классы 125 | - При необходимости разрыва строки осуществляется перенос каждого параметра класса на новую строку с двойным отступом и переносом закрывающейся круглой скобки на следующую строку: 126 | ```kotlin 127 | data class CategoryStatistic( 128 | val id: String, 129 | val title: String, 130 | val imageUrl: String, 131 | val percent: Double 132 | ) : Serializable 133 | ``` 134 | - Если в описании класса родительский класс не помещается на одной строке, также осуществляется перенос каждого из его параметров на новую строку с переносом закрывающей круглой скобки на следующую строку. 135 | - Если описание класса не помещается в одну строку и реализует несколько интерфейсов, то применять стандартные правила переносов, т.е. делать перенос только в случае, когда не помещается на одну строку, и продолжать перечисление интерфейсов на следующей строке. 136 | - Использование именованного синтаксиса аргументов остается на усмотрение разработчика. Стоит руководствоваться сложностью используемого конструктора класса: если конструктор с переданными в него параметрами понятен и очевиден, нет необходимости использовать именованные параметры. 137 | 138 | # Аннотации 139 | - Аннотации как правило располагаются над описанием класса/поля/метода, к которому они применяются. 140 | - Если к классу/полю/методу есть несколько аннотаций, размещать каждую аннотацию с новой строки: 141 | ```kotlin 142 | @JsonValue 143 | @JvmField 144 | var promoItem: PromoItem? = null 145 | ``` 146 | - Если к полю/методу применяется только одна аннотация без параметров, указывать ее над полем/методом. 147 | - Аннотации, относящиеся к файлу, располагаются сразу после комментария к файлу, и перед package, с разделителем в виде пустой строки. 148 | 149 | # Структура класса 150 | 1) companion object 151 | 2) Поля: abstract, override, public, internal, protected, private 152 | 3) Properties: abstract, override, public, internal, protected, private 153 | 4) Блок инициализации: init, конструкторы 154 | 5) Абстрактные методы 155 | 6) Переопределенные методы родительского класса(желательно в том же порядке, в каком они следуют в родительском классе) 156 | 7) Реализации методов интерфейсов(желательно в том же порядке, в каком они следуют в описании класса, соблюдая при этом порядок описания этих методов в самом интерфейсе) 157 | 8) public методы 158 | 9) internal методы 159 | 10) protected методы 160 | 11) private методы 161 | 12) inner классы 162 | 163 | # Форматирование лямбда-выражений 164 | 165 | - При возможности оставлять лямбда-выражение на одной строке, используя `it` в качестве аргумента. 166 | - При использовании лямба-функции в качестве аругмента выносить её за скобки если этот параметр единственный. 167 | - Если выражение возможно написать с передачей метода по ссылке, передавать метод по ссылке (Доступно с 1.1): 168 | ```kotlin 169 | viewPager.adapter = QuestAdapter(quest, this::onQuestClicked) 170 | ``` 171 | - При написании лямбда-выражения более чем в одну строку всегда использовать именованный аргумент, вместо `it`: 172 | ```kotlin 173 | viewPager.adapter = QuestAdapter(quest, { quest -> 174 | onQuestClicked(quest) 175 | }) 176 | ``` 177 | - Неиспользуемые параметры лямбда-выражений всегда заменять символом `_`. 178 | - Избегать использования Destrucion Declaration в лямбда-выражениях. 179 | 180 | # Использование условных операторов 181 | Не обрамлять `if` выражения в фигурные скобки только если условный оператор `if` помещается в одну строку. 182 | При возможности использовать условные операторы, как выражение: 183 | ```kotlin 184 | return if (condition) foo() else bar() 185 | ``` 186 | У оператора `when` для коротких выражениях ветвей условия размещать их на одной строке с условием без фигурных скобок: 187 | ```kotlin 188 | when (somenCondition) { 189 | 0 -> fooFunction() 190 | 1 -> barFunction() 191 | else -> exitFunction() 192 | } 193 | ``` 194 | Если хоть в одной из ветвей есть фигурные скобки, обрамлять ими все остальные ветки. 195 | У оператора `when` для блоков с выражениями, которые состоят более чем из одной строки использовать для этих блоков фигурные скобки и отделять смежные case-блоки пустой строкой: 196 | ```kotlin 197 | when (feed.type) { 198 | FeedType.PERSONAL -> { 199 | with(feed as PersonalFeed) { 200 | datePopupStart = dateBegin 201 | datePopupEnd = dateEnd 202 | } 203 | } 204 | 205 | 206 | FeedType.SUM -> { 207 | with(feed as SumFeed) { 208 | datePopupStart = dateBegin 209 | datePopupEnd = dateEnd 210 | } 211 | } 212 | 213 | FeedType.CARD -> { 214 | with(feed as CardFeed) { 215 | datePopupStart = dateBegin 216 | datePopupEnd = dateEnd 217 | } 218 | } 219 | 220 | else -> { 221 | Feed.EMPTY 222 | } 223 | } 224 | ``` 225 | 226 | # Template header 227 | 228 | - Не использовать Template Header для классов (касается авторства и даты создания файла). 229 | 230 | # Файлы 231 | 232 | - Возможно описывать несколько классов в одном файле только для `sealed` классов. В остальных случаях для каждого класса необходимо использовать отдельный файл (не относится к `inner` классам). 233 | 234 | # Использование Properties 235 | 236 | ## Форматирование properties 237 | 238 | * Всегда указывать тип ```property``` явно, даже если ```get```-блок помещается на одной строчке. 239 | 240 | * Если `get`-блок помещается на одной строчке и не требует нескольких действий - можно писать его в одну строчку: 241 | 242 | ```kotlin 243 | class User( 244 | var firstName: String?, 245 | var lastName: String? 246 | ) { 247 | 248 | val fullName: String get() = "$firstName $lastName" 249 | 250 | } 251 | ``` 252 | 253 | * Если ```get```-блок не помещается на одной строчке - помещаем его под определением property, отделяя от следующего блока кода одной строчкой: 254 | 255 | ```kotlin 256 | class User( 257 | var firstName: String?, 258 | var lastName: String? 259 | ) { 260 | 261 | val lastNameValue: Int 262 | get() { 263 | return when (lastName) { 264 | null -> 100 265 | isNullOrBlank() -> 200 266 | else -> 300 267 | } 268 | } 269 | 270 | val fullName: String get() = "$firstName $lastName" 271 | 272 | } 273 | ``` 274 | 275 | * Два ```property``` разделяются отдельной строкой если их нужно отделить логически друг от друга, или же у ```property``` есть выделенный ```get```-блок: 276 | 277 | ```kotlin 278 | class User( 279 | var firstName: String?, 280 | var lastName: String? 281 | ) { 282 | 283 | val firstNameValue: String get() = "First name: $firstName" 284 | val lastNameHashCode: Int? get() = lastName?.hashCode() 285 | 286 | val lastNameValue: Int 287 | get() { 288 | return when (lastName) { 289 | null -> 100 290 | isNullOrBlank() -> 200 291 | else -> 300 292 | } 293 | } 294 | 295 | val fullName: String 296 | get() { 297 | val superValue = firstName?.hashCode()?.plus(lastName?.hashCode() ?: 0) 298 | return "$superValue" 299 | } 300 | 301 | } 302 | ``` 303 | 304 | ## Functions VS Properties 305 | 306 | * Разрешается использовать только **read-only properties**, то есть только те, у которых есть только getter. 307 | 308 | * В getter-е property не должны изменяться поля класса. Если требуется менять внутренние поля класса - нужно использовать функции. 309 | 310 | * В getter-е property не должны бросаться исключения. Если нужно бросить исключение в getter-е, лучше используйте функцию с говорящим названием. 311 | 312 | * Get-блок property не должен содержать сложной логики. Если требуется описать некоторый сложный алгоритм, то лучше написать функцию. 313 | -------------------------------------------------------------------------------- /docs/style-guide/style_guide_xml.md: -------------------------------------------------------------------------------- 1 | # Набор соглашений по оформлению кода на языке XML. 2 | 3 | ## Содержание 4 | 1. [Общие положения](#common_rules) 5 | 1. [Длина строки](#line_length) 6 | 1. [Правила именования xml-ресурсов](#resources_naming) 7 | 1. [Правила именования id](#id_naming) 8 | 1. [Порядок следования модификаторов](#modifier_order) 9 | 1. [Текстовые поля](#text_fields) 10 | 11 | ## Общие положения 12 | - В файле не должно быть предупреждений 13 | 14 | ## Длина строки 15 | - Максимальная длина строки: 120 символов. 16 | 17 | ## Правила именования xml-ресурсов 18 | - Имя записано в стиле lower_snake_case.xml 19 | 20 | ### drawable 21 | - Имя имеет маску { название ресурса }\_{ описание }\_{ свойства: размер, цвет и пр. } 22 | - Сокращенные названия ресурсов: 23 | 24 | | Полное название | Сокращенное название | 25 | | --------------- | -------------------- | 26 | | icon | ic | 27 | | background | bg | 28 | 29 | - Последовательность указания свойств: 30 | 1. размер 31 | 2. цвет 32 | 3. прочие 33 | 34 | Пример:\ 35 | ic_favorite_gray_24dp\ 36 | bg_button_blue.xml 37 | 38 | ### menu 39 | - Имя имеет маску { имя фрагмента (но без надписи fragment) }_{ описание (опционально) } 40 | 41 | Пример:\ 42 | resume_contacts.xml\ 43 | vacancy_actions.xml 44 | 45 | ### anim 46 | - Имя имеет маску { описание }\_{ свойства: вид анимации(fade, transform и пр.), время действия (опционально) } 47 | 48 | Пример:\ 49 | enter_from_left_250ms.xml\ 50 | hide_icon_fade_out.xml 51 | 52 | ## Правила именования Id 53 | - Имя записано в стиле lower_snake_case 54 | - Имя имеет маску (название файла)(сокращенное название виджета)(название элемента) 55 | - Сокращенное название виджета: 56 | 57 | | Сокращенное название | Виджет | 58 | | ------ | ------ | 59 | | view | View | 60 | | text_view | TextView | 61 | | edit_text | EditText | 62 | | recycler | RecyclerView | 63 | | button | Button | 64 | | container | ViewGroup и все его наследники контейнеры | 65 | - Название элемента отражает его предназначение. 66 | 67 | 68 | ```xml 69 | 74 | ``` 75 | 76 | ## Порядок следования элементов 77 | В разработке 78 | 79 | ## Текстовые поля 80 | - Все тексты должны быть вынесены в ресурсы 81 | - Исключение составляет текст для верстки, помеченный xml схемой **tools** 82 | -------------------------------------------------------------------------------- /tools/garcon/garcon_config.yaml: -------------------------------------------------------------------------------- 1 | enableDebugMode: false 2 | 3 | templatesPaths: 4 | screenPageObjectTemplatePath: ./templates/screen_page_object.ftl 5 | rvItemPageObjectTemplatePath: ./templates/rv_item_page_object.ftl 6 | 7 | widgetsClassesMap: 8 | # BottomNavigationView 9 | com.google.android.material.bottomnavigation.BottomNavigationView: 10 | kakaoWidgetFQN: com.agoda.kakao.bottomnav.KBottomNavigationView 11 | idSuffixes: 12 | - bottom_navigation_view 13 | 14 | # NavigationView 15 | com.google.android.material.navigation.NavigationView: 16 | kakaoWidgetFQN: com.agoda.kakao.navigation.KNavigationView 17 | idSuffixes: 18 | - navigation_view 19 | 20 | # CollapsingToolbarLayout 21 | com.google.android.material.appbar.CollapsingToolbarLayout: 22 | kakaoWidgetFQN: com.agoda.kakao.common.views.KView 23 | idSuffixes: 24 | - collapsing_toolbar_layout 25 | 26 | # AppBarLayout 27 | com.google.android.material.appbar.AppBarLayout: 28 | kakaoWidgetFQN: com.agoda.kakao.common.views.KView 29 | idSuffixes: 30 | - app_bar_layout 31 | 32 | # Toolbar 33 | androidx.appcompat.widget.Toolbar: &ToolbarNode 34 | kakaoWidgetFQN: com.agoda.kakao.common.views.KView 35 | idSuffixes: 36 | - toolbar 37 | com.google.android.material.appbar.MaterialToolbar: 38 | <<: *ToolbarNode 39 | 40 | # RecyclerView 41 | androidx.recyclerview.widget.RecyclerView: 42 | kakaoWidgetFQN: com.agoda.kakao.recycler.KRecyclerView 43 | idSuffixes: 44 | - recycler_view 45 | - recycler 46 | 47 | # ViewPager 48 | androidx.viewpager.widget.ViewPager: 49 | kakaoWidgetFQN: com.agoda.kakao.pager.KViewPager 50 | idSuffixes: 51 | - view_pager 52 | 53 | # SwipeRefreshLayout 54 | androidx.swiperefreshlayout.widget.SwipeRefreshLayout: 55 | kakaoWidgetFQN: com.agoda.kakao.swiperefresh.KSwipeRefreshLayout 56 | idSuffixes: 57 | - swipe_refresh_layout 58 | 59 | # Switch 60 | android.widget.Switch: &SwitchNode 61 | kakaoWidgetFQN: com.agoda.kakao.switch.KSwitch 62 | idSuffixes: 63 | - switch 64 | androidx.appcompat.widget.SwitchCompat: 65 | <<: *SwitchNode 66 | com.google.android.material.switchmaterial.SwitchMaterial: 67 | <<: *SwitchNode 68 | 69 | # SeekBar 70 | android.widget.SeekBar: &SeekBarNode 71 | kakaoWidgetFQN: com.agoda.kakao.progress.KSeekBar 72 | idSuffixes: 73 | - seek_bar 74 | androidx.appcompat.widget.AppCompatSeekBar: 75 | <<: *SeekBarNode 76 | 77 | # TabLayout 78 | com.google.android.material.tabs.TabLayout: 79 | kakaoWidgetFQN: com.agoda.kakao.tabs.KTabLayout 80 | idSuffixes: 81 | - tab_layout 82 | 83 | # TextInputLayout && TextInputEditText 84 | com.google.android.material.textfield.TextInputLayout: &TextInputNode 85 | kakaoWidgetFQN: com.agoda.kakao.edit.KTextInputLayout 86 | idSuffixes: 87 | - text_input_layout 88 | com.google.android.material.textfield.TextInputEditText: 89 | <<: *TextInputNode 90 | 91 | # ProgressBar 92 | android.widget.ProgressBar: 93 | kakaoWidgetFQN: com.agoda.kakao.progress.KProgressBar 94 | idSuffixes: 95 | - progress_bar 96 | 97 | # RatingBar 98 | android.widget.RatingBar: &RatingBarNode 99 | kakaoWidgetFQN: com.agoda.kakao.rating.KRatingBar 100 | idSuffixes: 101 | - rating_bar 102 | androidx.appcompat.widget.AppCompatRatingBar: 103 | <<: *RatingBarNode 104 | 105 | # ScrollView 106 | android.widget.ScrollView: &ScrollViewNode 107 | kakaoWidgetFQN: com.agoda.kakao.scroll.KScrollView 108 | idSuffixes: 109 | - scroll_view 110 | android.widget.HorizontalScrollView: 111 | <<: *ScrollViewNode 112 | androidx.core.widget.NestedScrollView: 113 | <<: *ScrollViewNode 114 | 115 | # ImageView 116 | android.widget.ImageView: &ImageViewNode 117 | kakaoWidgetFQN: com.agoda.kakao.image.KImageView 118 | idSuffixes: 119 | - image 120 | androidx.appcompat.widget.AppCompatImageView: 121 | <<: *ImageViewNode 122 | 123 | # CheckBox 124 | android.widget.CheckBox: &CheckBoxNode 125 | kakaoWidgetFQN: com.agoda.kakao.check.KCheckBox 126 | idSuffixes: 127 | - checkbox 128 | - check_box 129 | androidx.appcompat.widget.AppCompatCheckBox: 130 | <<: *CheckBoxNode 131 | 132 | # Button 133 | android.widget.Button: &ButtonNode 134 | kakaoWidgetFQN: com.agoda.kakao.text.KButton 135 | idSuffixes: 136 | - button 137 | androidx.appcompat.widget.AppCompatButton: 138 | <<: *ButtonNode 139 | 140 | # DatePicker 141 | android.widget.DatePicker: 142 | kakaoWidgetFQN: com.agoda.kakao.picker.date.KDatePicker 143 | idSuffixes: 144 | - date_picker 145 | 146 | # DrawerLayout 147 | androidx.drawerlayout.widget.DrawerLayout: 148 | kakaoWidgetFQN: com.agoda.kakao.drawer.KDrawerView 149 | idSuffixes: 150 | - drawer_view 151 | 152 | # EditText 153 | android.widget.EditText: &EditTextNode 154 | kakaoWidgetFQN: com.agoda.kakao.edit.KEditText 155 | idSuffixes: 156 | - edit_text 157 | androidx.appcompat.widget.AppCompatEditText: 158 | <<: *EditTextNode 159 | 160 | # TextView: 161 | android.widget.TextView: &TextViewNode 162 | kakaoWidgetFQN: com.agoda.kakao.text.KTextView 163 | idSuffixes: 164 | - text_view 165 | androidx.appcompat.widget.AppCompatTextView: 166 | <<: *TextViewNode 167 | 168 | # TimePicker 169 | android.widget.TimePicker: 170 | kakaoWidgetFQN: com.agoda.kakao.picker.time.KTimePicker 171 | idSuffixes: 172 | - time_picker 173 | 174 | # ViewGroup 175 | android.view.ViewGroup: 176 | kakaoWidgetFQN: com.agoda.kakao.common.views.KView 177 | idSuffixes: 178 | - container 179 | 180 | # View 181 | android.view.View: 182 | kakaoWidgetFQN: com.agoda.kakao.common.views.KView 183 | idSuffixes: 184 | - view -------------------------------------------------------------------------------- /tools/garcon/templates/rv_item_page_object.ftl: -------------------------------------------------------------------------------- 1 | class ${class_name}(parent: org.hamcrest.Matcher) : com.agoda.kakao.recycler.KRecyclerItem<${class_name}>(parent) { 2 | 3 | <#list properties_declarations_list as property_declaration> 4 | ${property_declaration} 5 | 6 | 7 | } 8 | -------------------------------------------------------------------------------- /tools/garcon/templates/screen_page_object.ftl: -------------------------------------------------------------------------------- 1 | package ${package_name} 2 | 3 | class ${class_name} : com.agoda.kakao.screen.Screen<${class_name}>() { 4 | 5 | <#list properties_declarations_list as property_declaration> 6 | ${property_declaration} 7 | 8 | 9 | 10 | val actions = Action() 11 | val checks = Check() 12 | 13 | 14 | inner class Action : ru.hh.android.core_tests.page.ScreenIntentions() { 15 | // TODO - add your actions 16 | } 17 | 18 | inner class Check : ru.hh.android.core_tests.page.ScreenIntentions() { 19 | // TODO - add your checks 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /tools/geminio/geminio_config.yaml: -------------------------------------------------------------------------------- 1 | templatesRootDirPath: /android-style-guide/tools/geminio/templates 2 | modulesTemplatesRootDirPath: /android-style-guide/tools/geminio/modules_templates 3 | 4 | groupsNames: 5 | forNewGroup: HH Templates 6 | forNewModulesGroup: HH Modules Templates -------------------------------------------------------------------------------- /tools/geminio/modules_templates/Empty Module/recipe.yaml: -------------------------------------------------------------------------------- 1 | requiredParams: 2 | name: HeadHunter Empty Module 3 | description: Creates empty Gradle-module for general purposes 4 | 5 | predefinedFeatures: 6 | - enableModuleCreationParams 7 | 8 | widgets: 9 | - booleanParameter: 10 | id: needFeatureStructure 11 | name: Generate feature structure? 12 | help: 13 | Generates root packages - api (for feature facade), data (for network/database), 14 | feature (for domain logic/mvi), ui (for fragments/viewmodels) 15 | default: false 16 | 17 | - booleanParameter: 18 | id: needTests 19 | name: Add dependencies for tests? 20 | help: Adds typical test-framework dependencies 21 | default: false 22 | 23 | - booleanParameter: 24 | id: needRx 25 | name: Add Rx dependencies? 26 | help: Adds rx dependencies 27 | default: false 28 | 29 | - booleanParameter: 30 | id: needToothpick 31 | name: Add Toothpick? 32 | help: Adds Toothpick dependencies 33 | default: false 34 | 35 | - booleanParameter: 36 | id: needNetworkLibraries 37 | name: Add network libraries? 38 | help: Adds typical network dependencies (GSON, Retrofit) 39 | default: false 40 | 41 | - booleanParameter: 42 | id: needDbLibraries 43 | name: Add Room? 44 | help: Adds Room dependencies 45 | default: false 46 | 47 | - booleanParameter: 48 | id: needUiLibraries 49 | name: Add UI dependencies (BaseFragment + MVVM)? 50 | help: Adds typical view layer dependencies 51 | default: false 52 | 53 | - booleanParameter: 54 | id: needMvi 55 | name: Add MVI dependencies? 56 | help: Adds dependencies for MVI 57 | default: false 58 | 59 | 60 | recipe: 61 | - mkDirs: 62 | - ${srcOut} 63 | 64 | - predicate: 65 | validIf: ${needUiLibraries} 66 | commands: 67 | - mkDirs: 68 | - ${resOut}: 69 | - layout 70 | - values 71 | - drawables 72 | 73 | - predicate: 74 | validIf: ${needFeatureStructure} 75 | commands: 76 | - mkDirs: 77 | - ${srcOut}: 78 | - api 79 | - data 80 | - feature 81 | - ui 82 | 83 | - instantiate: 84 | from: root/gitignore.ftl 85 | to: ${rootOut}/.gitignore 86 | 87 | - instantiateAndOpen: 88 | from: root/build.gradle.kts.ftl 89 | to: ${rootOut}/build.gradle.kts 90 | 91 | - instantiateAndOpen: 92 | from: root/README.md 93 | to: ${rootOut}/README.md 94 | 95 | - instantiate: 96 | from: root/proguard-rules.pro.ftl 97 | to: ${rootOut}/proguard-rules.pro 98 | 99 | - instantiate: 100 | from: root/src/main/AndroidManifest.xml.ftl 101 | to: ${manifestOut}/AndroidManifest.xml 102 | -------------------------------------------------------------------------------- /tools/geminio/modules_templates/Empty Module/root/README.md: -------------------------------------------------------------------------------- 1 | # ${__formattedModuleName} 2 | TODO -------------------------------------------------------------------------------- /tools/geminio/modules_templates/Empty Module/root/build.gradle.kts.ftl: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("convention.kotlin-android-library") 3 | <#if needUiLibraries> 4 | id("kotlin-android-extensions") 5 | 6 | id("kotlin-kapt") 7 | <#if needNetworkLibraries> 8 | id("kotlinx-serialization") 9 | 10 | <#if needTests> 11 | id("de.mannodermaus.android-junit5") 12 | 13 | } 14 | 15 | dependencies { 16 | compileOnly(project(":shared:core:logger")) 17 | compileOnly(project(":shared:core:utils")) 18 | 19 | <#if needRx> 20 | // RxJava 21 | compileOnly(Libs.rx.rxJava) 22 | compileOnly(Libs.rx.rxAndroid) 23 | compileOnly(project(":shared:core:rx-core")) 24 | 25 | 26 | <#if needToothpick> 27 | // DI 28 | compileOnly(Libs.toothpick.core) 29 | compileOnly(Libs.toothpick.ktp) 30 | kapt(Libs.toothpick.compiler) 31 | compileOnly(project(":shared:core:di-core")) 32 | 33 | 34 | <#if needNetworkLibraries> 35 | // Network 36 | compileOnly(project(":shared:core:network")) 37 | compileOnly(project(":shared:core:network-model")) 38 | compileOnly(Libs.network.kotlinxSerializationJson) 39 | compileOnly(Libs.network.retrofit) 40 | compileOnly(Libs.network.retrofitKotlinxConverter) 41 | <#if needRx> 42 | compileOnly(Libs.network.adapterRxJava2) 43 | 44 | 45 | 46 | <#if needDbLibraries> 47 | // Database 48 | api(Libs.room.runtime) 49 | kapt(Libs.room.compiller) 50 | implementation(Libs.room.rxJava) 51 | 52 | 53 | <#if needUiLibraries> 54 | // UI 55 | compileOnly(project(":shared:core:ui:framework")) 56 | compileOnly(project(":shared:core:mvvm-core")) 57 | compileOnly(Libs.support.appCompat) 58 | 59 | 60 | <#if needMvi> 61 | // MVI 62 | compileOnly(project(":shared:core:mvi-core")) 63 | compileOnly(Libs.mviCore.binder) 64 | 65 | 66 | <#if needTests> 67 | // Tests 68 | testRuntimeOnly(Libs.test.junit5Engine) 69 | testImplementation(Libs.test.junit5Api) 70 | testImplementation(project(":shared:core:tests:base-logic")) 71 | <#if needRx> 72 | testImplementation(Libs.rx.rxJava) 73 | testImplementation(project(":shared:core:rx-core")) 74 | 75 | <#if needMvi> 76 | testImplementation(project(":shared:core:mvi-core")) 77 | 78 | 79 | 80 | } 81 | -------------------------------------------------------------------------------- /tools/geminio/modules_templates/Empty Module/root/gitignore.ftl: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /tools/geminio/modules_templates/Empty Module/root/proguard-rules.pro.ftl: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /tools/geminio/modules_templates/Empty Module/root/src/main/AndroidManifest.xml.ftl: -------------------------------------------------------------------------------- 1 | <#if needUiLibraries> 2 | 4 | 5 | 6 | 7 | 8 | <#else> 9 | 10 | -------------------------------------------------------------------------------- /tools/geminio/templates/BaseFragment/recipe.yaml: -------------------------------------------------------------------------------- 1 | requiredParams: 2 | name: HeadHunter BaseFragment 3 | description: Creates HeadHunter BaseFragment 4 | 5 | optionalParams: 6 | revision: 1 7 | category: fragment 8 | formFactor: mobile 9 | constraints: 10 | - kotlin 11 | screens: 12 | - fragment_gallery 13 | - menu_entry 14 | minApi: 7 15 | minBuildApi: 8 16 | 17 | widgets: 18 | - stringParameter: 19 | id: className 20 | name: Fragment Name 21 | help: The name of the fragment class to create 22 | constraints: 23 | - class 24 | - nonempty 25 | - unique 26 | default: BlankFragment 27 | 28 | - stringParameter: 29 | id: fragmentName 30 | name: Fragment Layout Name 31 | help: The name of the layout to create 32 | constraints: 33 | - layout 34 | - nonempty 35 | - unique 36 | default: fragment_blank 37 | suggest: ${className.fragmentToLayout()} 38 | 39 | - booleanParameter: 40 | id: includeFactory 41 | name: Include fragment factory method? 42 | help: Generate static fragment factory method for easy instantiation 43 | default: true 44 | 45 | - booleanParameter: 46 | id: includeModule 47 | name: Include Toothpick Module class? 48 | help: Generate fragment Toothpick Module for easy instantiation 49 | default: true 50 | 51 | - stringParameter: 52 | id: moduleName 53 | name: Fragment Toothpick Module 54 | help: The name of the Fragment Toothpick Module to create 55 | constraints: 56 | - class 57 | - nonempty 58 | - unique 59 | default: BlankModule 60 | visibility: ${includeModule} 61 | suggest: ${className.classToResource().underlinesToCamelCase()}Module 62 | 63 | recipe: 64 | - instantiateAndOpen: 65 | from: root/src/app_package/BlankFragment.kt.ftl 66 | to: ${srcOut}/${className}.kt 67 | - instantiateAndOpen: 68 | from: root/res/layout/fragment_blank.xml.ftl 69 | to: ${resOut}/layout/${fragmentName}.xml 70 | - predicate: 71 | validIf: ${includeModule} 72 | commands: 73 | - instantiateAndOpen: 74 | from: root/src/app_package/BlankModule.kt.ftl 75 | to: ${srcOut}/di/${moduleName}.kt -------------------------------------------------------------------------------- /tools/geminio/templates/BaseFragment/root/res/layout/fragment_blank.xml.ftl: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /tools/geminio/templates/BaseFragment/root/src/app_package/BlankFragment.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName} 2 | 3 | <#if applicationPackage??> 4 | import ${applicationPackage}.R 5 | 6 | <#if includeModule??>import ${packageName}.di.${moduleName} 7 | import ru.hh.shared.core.ui.framework.fragment.BaseFragment 8 | <#if includeFactory??>import ru.hh.shared.core.ui.framework.fragment.withArgs 9 | import ru.hh.shared.core.ui.framework.fragment_plugin.common.di.diPlugin 10 | 11 | internal class ${className} : BaseFragment(R.layout.${fragmentName}) { 12 | 13 | companion object { 14 | 15 | private const val LOG_TAG = "${className}" 16 | 17 | <#if includeFactory> 18 | fun newInstance() = ${className}().withArgs { } 19 | 20 | } 21 | 22 | @Suppress("unused") 23 | private val di by diPlugin( 24 | parentScopeNameProvider = { TODO("Scope name of feature facade / Delete this for scope of parent fragment") }, 25 | <#if includeModule>modulesProvider = { arrayOf(${moduleName}()) } 26 | ) 27 | 28 | } 29 | -------------------------------------------------------------------------------- /tools/geminio/templates/BaseFragment/root/src/app_package/BlankModule.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.di 2 | 3 | import toothpick.config.Module 4 | 5 | internal class ${moduleName} : Module() { 6 | 7 | init { 8 | 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tools/geminio/templates/Cell/recipe.yaml: -------------------------------------------------------------------------------- 1 | requiredParams: 2 | name: HeadHunter Cell 3 | description: Creates simple Cell for RecyclerView 4 | 5 | widgets: 6 | - stringParameter: 7 | id: cellClassName 8 | name: Cell class name 9 | help: The name of the Cell 10 | constraints: 11 | - class 12 | - nonempty 13 | - unique 14 | default: BaseCell 15 | 16 | - stringParameter: 17 | id: cellLayoutName 18 | name: Cell's layout name 19 | help: The name of the layout to create 20 | constraints: 21 | - layout 22 | - nonempty 23 | - unique 24 | default: cell_base 25 | suggest: ${cellClassName.classToResource()} 26 | 27 | recipe: 28 | # kotlin files 29 | - instantiateAndOpen: 30 | from: root/src/app_package/Cell.kt.ftl 31 | to: ${srcOut}/${cellClassName}.kt 32 | # layouts 33 | - instantiateAndOpen: 34 | from: root/res/layout/cell_layout.xml.ftl 35 | to: ${resOut}/layout/${cellLayoutName}.xml 36 | # dependencies 37 | - addDependencies: 38 | # projects 39 | - compileOnly: :shared:core:ui:design-system 40 | - compileOnly: :shared:core:ui:cells-framework 41 | # libraries 42 | - compileOnly: Libs.support.recyclerView 43 | -------------------------------------------------------------------------------- /tools/geminio/templates/Cell/root/res/layout/cell_layout.xml.ftl: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /tools/geminio/templates/Cell/root/src/app_package/Cell.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName} 2 | 3 | import androidx.recyclerview.widget.RecyclerView 4 | import kotlinx.android.synthetic.main.${cellLayoutName}.view.* 5 | import ru.hh.shared.core.ui.cells_framework.cells.diffing.CellDiffingStrategy 6 | import ru.hh.shared.core.ui.cells_framework.cells.diffing.strategies.IdContentDiffingStrategy 7 | import ru.hh.shared.core.ui.cells_framework.cells.interfaces.Cell 8 | <#if applicationPackage??> 9 | import ${applicationPackage}.R 10 | 11 | 12 | internal class ${cellClassName}( 13 | private val diffId: String 14 | ) : Cell { 15 | 16 | override val diffingStrategy: CellDiffingStrategy by IdContentDiffingStrategy( 17 | diffId = diffId, 18 | diffContent = TODO() 19 | ) 20 | 21 | override fun getLayoutId(): Int = R.layout.${cellLayoutName} 22 | 23 | override fun bind(viewHolder: RecyclerView.ViewHolder, payloads: List) { 24 | with(viewHolder.itemView) { 25 | // TODO bind fields from model to layout 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tools/geminio/templates/ContainerFragment/recipe.yaml: -------------------------------------------------------------------------------- 1 | requiredParams: 2 | name: HeadHunter Container Fragment 3 | description: Creates HeadHunter Container Fragment 4 | 5 | widgets: 6 | - stringParameter: 7 | id: featureName 8 | name: Feature name 9 | help: The name of your feature as base for generating container 10 | constraints: 11 | - class 12 | - nonempty 13 | - unique 14 | default: BaseFlow 15 | 16 | globals: 17 | - stringParameter: 18 | id: featureFacadeName 19 | value: ${featureName}Facade 20 | 21 | - stringParameter: 22 | id: featureDepsName 23 | value: ${featureName}Deps 24 | 25 | - stringParameter: 26 | id: containerFragmentName 27 | value: ${featureName}ContainerFragment 28 | 29 | - stringParameter: 30 | id: containerFragmentLayoutName 31 | value: ${containerFragmentName.fragmentToLayout()} 32 | 33 | - stringParameter: 34 | id: containerFragmentContainerId 35 | value: ${containerFragmentLayoutName} 36 | 37 | - stringParameter: 38 | id: containerFragmentModuleName 39 | value: ${featureName}ContainerModule 40 | 41 | - stringParameter: 42 | id: containerFragmentNavigatorName 43 | value: ${featureName}ContainerNavigator 44 | 45 | - stringParameter: 46 | id: containerFragmentVMName 47 | value: ${featureName}ContainerVM 48 | 49 | - stringParameter: 50 | id: containerFragmentNavScreenName 51 | value: ${featureName}ContainerNavScreen 52 | 53 | recipe: 54 | # kotlin files 55 | - instantiateAndOpen: 56 | from: root/src/app_package/ui/container/ContainerFragment.kt.ftl 57 | to: ${srcOut}/ui/container/${containerFragmentName}.kt 58 | - instantiateAndOpen: 59 | from: root/src/app_package/ui/container/ContainerFragmentNavigator.kt.ftl 60 | to: ${srcOut}/ui/container/${containerFragmentNavigatorName}.kt 61 | - instantiateAndOpen: 62 | from: root/src/app_package/ui/container/ContainerFragmentNavScreen.kt.ftl 63 | to: ${srcOut}/ui/container/${containerFragmentNavScreenName}.kt 64 | - instantiateAndOpen: 65 | from: root/src/app_package/ui/container/ContainerFragmentVM.kt.ftl 66 | to: ${srcOut}/ui/container/${containerFragmentVMName}.kt 67 | - instantiateAndOpen: 68 | from: root/src/app_package/ui/container/di/ContainerFragmentModule.kt.ftl 69 | to: ${srcOut}/ui/container/di/${containerFragmentModuleName}.kt 70 | # layouts 71 | - instantiateAndOpen: 72 | from: root/res/layout/container_fragment.xml.ftl 73 | to: ${resOut}/layout/${containerFragmentLayoutName}.xml 74 | # dependencies 75 | - addDependencies: 76 | # projects 77 | - compileOnly: :shared:core:ui:framework 78 | - compileOnly: :shared:core:mvvm-core 79 | - compileOnly: :shared:core:rx-core 80 | - compileOnly: :shared:core:logger 81 | # libraries 82 | - compileOnly: Libs.toothpick.core 83 | - kapt: Libs.toothpick.compiler 84 | - compileOnly: Libs.ui.cicerone 85 | -------------------------------------------------------------------------------- /tools/geminio/templates/ContainerFragment/root/res/layout/container_fragment.xml.ftl: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tools/geminio/templates/ContainerFragment/root/src/app_package/ui/container/ContainerFragment.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.ui.container 2 | 3 | <#if applicationPackage??> 4 | import ${applicationPackage}.R 5 | 6 | import ${packageName}.api.${featureFacadeName} 7 | import ${packageName}.ui.container.di.${containerFragmentModuleName} 8 | import ru.hh.shared.core.mvvm.plugin.viewModelPlugin 9 | import ru.hh.shared.core.ui.framework.fragment.BaseFragment 10 | import ru.hh.shared.core.ui.framework.fragment_plugin.common.NavigationFragmentPlugin 11 | import ru.hh.shared.core.ui.framework.fragment_plugin.common.di.DiScopeOwner 12 | import ru.hh.shared.core.ui.framework.fragment_plugin.common.di.diPlugin 13 | import ru.hh.shared.core.ui.framework.fragment_plugin.plugin 14 | import ru.hh.shared.core.ui.framework.navigation.AppRouter 15 | import ru.terrakok.cicerone.NavigatorHolder 16 | import toothpick.Scope 17 | 18 | internal class ${containerFragmentName} : BaseFragment(R.layout.${containerFragmentLayoutName}), DiScopeOwner { 19 | 20 | companion object { 21 | fun newInstance() = ${containerFragmentName}() 22 | } 23 | 24 | 25 | private val di by diPlugin( 26 | parentScopeNameProvider = { ${featureFacadeName}().internalScope.name }, 27 | modulesProvider = { arrayOf(${containerFragmentModuleName}()) } 28 | ) 29 | 30 | private val navPlugin by plugin { 31 | NavigationFragmentPlugin( 32 | navigatorProvider = { 33 | ${containerFragmentNavigatorName}( 34 | fragment = this, 35 | containerId = R.id.${containerFragmentContainerId} 36 | ) 37 | }, 38 | navigationHolderProvider = { di.scope.getInstance(NavigatorHolder::class.java) }, 39 | routerProvider = { di.scope.getInstance(AppRouter::class.java) } 40 | ) 41 | } 42 | 43 | override val scope: Scope by di::scope 44 | 45 | @Suppress("detekt.UnusedPrivateMember", "unused") 46 | private val viewModel by viewModelPlugin( 47 | handleEvent = {}, 48 | viewModelProvider = { di.scope.getInstance(${containerFragmentVMName}::class.java) } 49 | ) 50 | 51 | override fun onBackPressedInternal(): Boolean { 52 | return navPlugin.onBackPressed() 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /tools/geminio/templates/ContainerFragment/root/src/app_package/ui/container/ContainerFragmentNavScreen.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.ui.container 2 | 3 | import ru.hh.shared.core.ui.framework.navigation.NavScreen 4 | 5 | internal class ${containerFragmentNavScreenName} : NavScreen { 6 | override fun getFragment() = ${containerFragmentName}.newInstance() 7 | } 8 | -------------------------------------------------------------------------------- /tools/geminio/templates/ContainerFragment/root/src/app_package/ui/container/ContainerFragmentNavigator.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.ui.container 2 | 3 | import androidx.fragment.app.Fragment 4 | import ru.hh.shared.core.ui.framework.fragment.addFragmentIfNotExist 5 | import ru.hh.shared.core.ui.framework.navigation.FixSupportFragmentNavigator 6 | 7 | internal class ${containerFragmentNavigatorName}( 8 | fragment: Fragment, 9 | containerId: Int 10 | ) : FixSupportFragmentNavigator( 11 | activity = fragment.requireActivity(), 12 | fragmentManager = fragment.childFragmentManager, 13 | containerId = containerId 14 | ) { 15 | 16 | init { 17 | addFragmentIfNotExist( 18 | containerViewId = containerId, 19 | fragmentManager = fragmentManager, 20 | fragment = getFirstFragment(), 21 | tag = getFirstFragmentTag(), 22 | executeNow = true 23 | ) 24 | } 25 | 26 | 27 | private fun getFirstFragment(): Fragment { 28 | TODO("Return first fragment for navigation inside container, e.g. ChooseFragment.newInstance()") 29 | } 30 | 31 | private fun getFirstFragmentTag(): String { 32 | TODO("Return first fragment TAG, e.g. ChooseFragment.TAG") 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /tools/geminio/templates/ContainerFragment/root/src/app_package/ui/container/ContainerFragmentVM.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.ui.container 2 | 3 | import ${packageName}.api.${featureDepsName} 4 | import ru.hh.shared.core.mvvm.viewmodel.BaseViewModel 5 | import ru.hh.shared.core.rx.SchedulersProvider 6 | import timber.log.Timber 7 | import toothpick.InjectConstructor 8 | 9 | @InjectConstructor 10 | internal class ${containerFragmentVMName}( 11 | private val deps: ${featureDepsName}, 12 | private val schedulersProvider: SchedulersProvider, 13 | ) : BaseViewModel() { 14 | 15 | companion object { 16 | private const val LOG_TAG = "${containerFragmentVMName}" 17 | } 18 | 19 | 20 | override fun onFirstAttach() { 21 | super.onFirstAttach() 22 | Timber.tag(LOG_TAG).d("On first attach") 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tools/geminio/templates/ContainerFragment/root/src/app_package/ui/container/di/ContainerFragmentModule.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.ui.container.di 2 | 3 | import ru.hh.shared.core.ui.framework.navigation.AppRouter 4 | import ru.terrakok.cicerone.Cicerone 5 | import ru.terrakok.cicerone.NavigatorHolder 6 | import toothpick.config.Module 7 | import toothpick.ktp.binding.bind 8 | import ${packageName}.ui.container.${containerFragmentVMName} 9 | 10 | internal class ${containerFragmentModuleName} : Module() { 11 | 12 | private val cicerone = Cicerone.create(AppRouter()) 13 | 14 | init { 15 | bind().toInstance(cicerone.navigatorHolder) 16 | bind().toInstance(cicerone.router) 17 | 18 | bind<${containerFragmentVMName}>().singleton() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tools/geminio/templates/Custom View/recipe.yaml: -------------------------------------------------------------------------------- 1 | requiredParams: 2 | name: HeadHunter Custom View 3 | description: Generates empty custom view 4 | 5 | widgets: 6 | - stringParameter: 7 | id: viewPrefix 8 | name: View name prefix 9 | help: View name prefix 10 | constraints: 11 | - nonempty 12 | - unique 13 | - class 14 | default: Blank 15 | 16 | - stringParameter: 17 | id: baseViewName 18 | name: Base view name 19 | help: Base view name 20 | constraints: 21 | - nonempty 22 | - unique 23 | - class 24 | default: FrameLayout 25 | 26 | - booleanParameter: 27 | id: generateAttrs 28 | name: Generate attrs? 29 | help: If true, xml-file for view attributes will be generated 30 | default: false 31 | 32 | globals: 33 | - stringParameter: 34 | id: viewName 35 | value: ${viewPrefix}View 36 | 37 | - stringParameter: 38 | id: viewLayoutName 39 | value: view_${viewPrefix.camelCaseToUnderlines()} 40 | 41 | - stringParameter: 42 | id: attrsName 43 | value: widget_${viewPrefix.camelCaseToUnderlines()} 44 | 45 | recipe: 46 | - instantiate: 47 | from: root/res/layout/view.xml.ftl 48 | to: ${resOut}/layout/${viewLayoutName}.xml 49 | 50 | - instantiateAndOpen: 51 | from: root/src/app_package/View.kt.ftl 52 | to: ${srcOut}/${viewName}.kt 53 | 54 | - predicate: 55 | validIf: ${generateAttrs} 56 | commands: 57 | - instantiate: 58 | from: root/res/values/attrs.xml.ftl 59 | to: ${resOut}/values/${attrsName}.xml 60 | 61 | - addDependencies: 62 | - compileOnly: :shared:core:ui:design-system -------------------------------------------------------------------------------- /tools/geminio/templates/Custom View/root/res/layout/view.xml.ftl: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /tools/geminio/templates/Custom View/root/res/values/attrs.xml.ftl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /tools/geminio/templates/Custom View/root/src/app_package/View.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName} 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import ru.hh.shared.core.ui.design_system.utils.widget.autoRecycleAttrs 6 | import ru.hh.shared.core.ui.design_system.utils.widget.inflateView 7 | <#if applicationPackage??> 8 | import ${applicationPackage}.R 9 | 10 | 11 | internal class ${viewName} @JvmOverloads constructor( 12 | context: Context, 13 | attrs: AttributeSet? = null, 14 | defStyleAttr: Int = 0 15 | ) : ${baseViewName}(context, attrs, defStyleAttr) { 16 | 17 | init { 18 | inflateView(R.layout.${viewLayoutName}) 19 | <#if generateAttrs == true> 20 | 21 | autoRecycleAttrs(context, attrs, R.styleable.${viewName}, defStyleAttr) { typedArray -> 22 | val yourCustomAttr = typedArray.getString(R.styleable.${viewName}_yourCustomAttr) 23 | TODO() 24 | } 25 | 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /tools/geminio/templates/Feature Facade/recipe.yaml: -------------------------------------------------------------------------------- 1 | requiredParams: 2 | name: Feature Facade 3 | description: Creates feature facade for interactions between modules 4 | 5 | widgets: 6 | - stringParameter: 7 | id: feature 8 | name: Feature Prefix 9 | help: Prefix of the feature class to create 10 | constraints: 11 | - class 12 | - nonempty 13 | - unique 14 | default: Feature 15 | 16 | globals: 17 | - stringParameter: 18 | id: featureApi 19 | value: ${feature}Api 20 | 21 | - stringParameter: 22 | id: featureApiImpl 23 | value: ${featureApi}Impl 24 | 25 | - stringParameter: 26 | id: featureDefScreen 27 | value: ${feature}NavScreen 28 | 29 | - stringParameter: 30 | id: featureDeps 31 | value: ${feature}Deps 32 | 33 | - stringParameter: 34 | id: featureFacade 35 | value: ${feature}Facade 36 | 37 | - stringParameter: 38 | id: featureModuleName 39 | value: ${feature}Module 40 | 41 | recipe: 42 | - instantiate: 43 | from: root/src/app_package/FeatureApi.kt.ftl 44 | to: ${srcOut}/${featureApi}.kt 45 | 46 | - instantiate: 47 | from: root/src/app_package/FeatureApiImpl.kt.ftl 48 | to: ${srcOut}/${featureApi}Impl.kt 49 | 50 | - instantiate: 51 | from: root/src/app_package/FeatureDeps.kt.ftl 52 | to: ${srcOut}/${featureDeps}.kt 53 | 54 | - instantiateAndOpen: 55 | from: root/src/app_package/FeatureFacade.kt.ftl 56 | to: ${srcOut}/${featureFacade}.kt 57 | 58 | - instantiate: 59 | from: root/src/app_package/FeatureModule.kt.ftl 60 | to: ${srcOut}/${featureModuleName}.kt 61 | 62 | - addDependencies: 63 | - compileOnly: Libs.support.appCompat 64 | - compileOnly: :shared:core:di-core 65 | - compileOnly: Libs.toothpick.core 66 | - compileOnly: Libs.toothpick.ktp 67 | -------------------------------------------------------------------------------- /tools/geminio/templates/Feature Facade/root/src/app_package/FeatureApi.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName} 2 | 3 | import androidx.fragment.app.Fragment 4 | 5 | interface ${featureApi} { 6 | 7 | fun getFragment(): Fragment 8 | 9 | } 10 | -------------------------------------------------------------------------------- /tools/geminio/templates/Feature Facade/root/src/app_package/FeatureApiImpl.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName} 2 | 3 | import androidx.fragment.app.Fragment 4 | import toothpick.InjectConstructor 5 | 6 | @InjectConstructor 7 | internal class ${featureApiImpl} : ${featureApi} { 8 | 9 | override fun getFragment(): Fragment { 10 | return TODO() 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /tools/geminio/templates/Feature Facade/root/src/app_package/FeatureDeps.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName} 2 | 3 | interface ${featureDeps} 4 | -------------------------------------------------------------------------------- /tools/geminio/templates/Feature Facade/root/src/app_package/FeatureFacade.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName} 2 | 3 | import ru.hh.shared.core.di.FeatureFacade 4 | 5 | class ${featureFacade} : FeatureFacade<${featureDeps}, ${featureApi}>( 6 | apiClass = ${featureApi}::class.java, 7 | depsClass = ${featureDeps}::class.java, 8 | featureScopeName = "${featureApi}Scope", 9 | featureScopeModules = { 10 | arrayOf(${featureModuleName}()) 11 | } 12 | ) 13 | -------------------------------------------------------------------------------- /tools/geminio/templates/Feature Facade/root/src/app_package/FeatureModule.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName} 2 | 3 | import toothpick.config.Module 4 | import toothpick.ktp.binding.bind 5 | 6 | internal class ${featureModuleName} : Module() { 7 | 8 | init { 9 | bind<${featureApi}>().toClass<${featureApiImpl}>() 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /tools/geminio/templates/Fragment + ViewModel/recipe.yaml: -------------------------------------------------------------------------------- 1 | requiredParams: 2 | name: HeadHunter Fragment + ViewModel 3 | description: Creates Fragment + ViewModel 4 | 5 | widgets: 6 | - stringParameter: 7 | id: fragmentPrefix 8 | name: Fragment Prefix 9 | help: Prefix of the fragment and other classes to create 10 | constraints: 11 | - nonempty 12 | - unique 13 | - class 14 | default: Blank 15 | 16 | - booleanParameter: 17 | id: needDesignSample 18 | name: Need design sample? 19 | help: If true, add Toolbar, RecyclerView and ZeroScrenView to layout and fragment 20 | default: true 21 | 22 | - booleanParameter: 23 | id: useMviViewModel 24 | name: Use MviViewModel? 25 | help: If true, the MviViewModel will be generated 26 | default: false 27 | 28 | - stringParameter: 29 | id: mviStateName 30 | name: MVI state class name 31 | help: MVI state class name 32 | default: MviStateName 33 | visibility: ${useMviViewModel} 34 | 35 | - stringParameter: 36 | id: mviNewsName 37 | name: MVI news class name 38 | help: MVI news class name 39 | default: MviNewsName 40 | visibility: ${useMviViewModel} 41 | 42 | globals: 43 | - stringParameter: 44 | id: fragmentPackage 45 | value: ${fragmentPrefix.camelCaseToUnderlines()} 46 | 47 | - stringParameter: 48 | id: fragmentName 49 | value: ${fragmentPrefix}Fragment 50 | 51 | - stringParameter: 52 | id: fragmentLayoutResName 53 | value: ${fragmentName.fragmentToLayout()} 54 | 55 | - stringParameter: 56 | id: paramsName 57 | value: ${fragmentPrefix}Params 58 | 59 | - stringParameter: 60 | id: moduleName 61 | value: ${fragmentPrefix}Module 62 | 63 | - stringParameter: 64 | id: uiStateName 65 | value: ${fragmentPrefix}UiState 66 | 67 | - stringParameter: 68 | id: uiEventName 69 | value: ${fragmentPrefix}UiEvent 70 | 71 | - stringParameter: 72 | id: viewModelName 73 | value: ${fragmentPrefix}ViewModel 74 | 75 | - stringParameter: 76 | id: uiConverterName 77 | value: ${fragmentPrefix}UiConverter 78 | 79 | recipe: 80 | - instantiateAndOpen: 81 | from: root/src/app_package/ViewModel.kt.ftl 82 | to: ${srcOut}/${fragmentPackage}/${viewModelName}.kt 83 | 84 | - instantiateAndOpen: 85 | from: root/src/app_package/Fragment.kt.ftl 86 | to: ${srcOut}/${fragmentPackage}/${fragmentName}.kt 87 | 88 | - instantiate: 89 | from: root/src/app_package/model/Params.kt.ftl 90 | to: ${srcOut}/${fragmentPackage}/model/${paramsName}.kt 91 | 92 | - instantiate: 93 | from: root/src/app_package/Module.kt.ftl 94 | to: ${srcOut}/${fragmentPackage}/${moduleName}.kt 95 | 96 | - instantiate: 97 | from: root/src/app_package/model/UiEvent.kt.ftl 98 | to: ${srcOut}/${fragmentPackage}/model/${uiEventName}.kt 99 | 100 | - instantiate: 101 | from: root/src/app_package/model/UiState.kt.ftl 102 | to: ${srcOut}/${fragmentPackage}/model/${uiStateName}.kt 103 | 104 | - predicate: 105 | validIf: ${useMviViewModel} 106 | commands: 107 | - instantiate: 108 | from: root/src/app_package/UiConverter.kt.ftl 109 | to: ${srcOut}/${fragmentPackage}/${uiConverterName}.kt 110 | 111 | - predicate: 112 | validIf: ${needDesignSample} 113 | commands: 114 | - instantiate: 115 | from: root/res/layout/design_fragment.xml.ftl 116 | to: ${resOut}/layout/${fragmentLayoutResName}.xml 117 | elseCommands: 118 | - instantiate: 119 | from: root/res/layout/simple_fragment.xml.ftl 120 | to: ${resOut}/layout/${fragmentLayoutResName}.xml 121 | 122 | - addDependencies: 123 | - implementation: :shared:core:ui:framework 124 | - implementation: :shared:core:ui:design-system 125 | - compileOnly: Libs.support.materialComponents 126 | - compileOnly: :shared:core:mvvm-core 127 | - compileOnly: Libs.support.appCompat 128 | - compileOnly: :shared:core:di-core 129 | - compileOnly: Libs.toothpick.core 130 | - compileOnly: Libs.toothpick.ktp 131 | - kapt: Libs.toothpick.compiler 132 | 133 | - predicate: 134 | validIf: ${useMviViewModel} 135 | commands: 136 | - addDependencies: 137 | - compileOnly: Libs.rx.rxJava 138 | - compileOnly: Libs.rx.rxKotlin 139 | - compileOnly: :shared:core:rx-core 140 | 141 | - predicate: 142 | validIf: ${needDesignSample} 143 | commands: 144 | - addDependencies: 145 | - compileOnly: Libs.ui.stateDelegator 146 | - compileOnly: Libs.support.swipeRefreshLayout 147 | - compileOnly: :shared:core:model 148 | - compileOnly: :shared:core:ui:cells-framework 149 | -------------------------------------------------------------------------------- /tools/geminio/templates/Fragment + ViewModel/root/res/layout/design_fragment.xml.ftl: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 | 26 | 27 | 37 | 38 | 39 | 40 | 41 | 42 | 47 | 48 | 53 | 54 | 58 | 59 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /tools/geminio/templates/Fragment + ViewModel/root/res/layout/simple_fragment.xml.ftl: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /tools/geminio/templates/Fragment + ViewModel/root/src/app_package/Fragment.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.${fragmentPackage} 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | <#if applicationPackage??> 6 | import ${applicationPackage}.R 7 | 8 | <#if needDesignSample == true> 9 | import androidx.recyclerview.widget.LinearLayoutManager 10 | import com.redmadrobot.lib.sd.LoadingStateDelegate 11 | import ru.hh.shared.core.ui.cells_framework.delegationadapter.DelegationAdapter 12 | import ru.hh.shared.core.ui.cells_framework.cells.interfaces.Cell 13 | import ru.hh.shared.core.mvvm.LCE 14 | import ru.hh.shared.core.ui.design_system.utils.widget.toolbar.initBoldTitleLayout 15 | import ru.hh.shared.core.ui.design_system.utils.widget.gone 16 | import ru.hh.shared.core.ui.framework.fragment_plugin.common.viewRetained 17 | import ru.hh.shared.core.ui.framework.keyboard.KeyboardRecyclerViewListener 18 | 19 | import kotlinx.android.synthetic.main.${fragmentLayoutResName}.* 20 | import ${packageName}.${fragmentPackage}.model.${paramsName} 21 | import ${packageName}.${fragmentPackage}.model.${uiEventName} 22 | import ${packageName}.${fragmentPackage}.model.${uiStateName} 23 | import ru.hh.shared.core.mvvm.plugin.viewModelPlugin 24 | import ru.hh.shared.core.ui.framework.fragment.BaseFragment 25 | import ru.hh.shared.core.ui.framework.fragment.withParams 26 | import ru.hh.shared.core.ui.framework.fragment.params 27 | import ru.hh.shared.core.ui.framework.fragment_plugin.common.di.diPlugin 28 | import ru.hh.shared.core.ui.framework.fragment_plugin.common.di.getInstance 29 | 30 | internal class ${fragmentName} : BaseFragment(R.layout.${fragmentLayoutResName}) { 31 | 32 | companion object { 33 | fun newInstance(params: ${paramsName}) = ${fragmentName}().withParams(params) 34 | } 35 | 36 | private val params by params<${paramsName}>() 37 | 38 | private val di by diPlugin( 39 | parentScopeNameProvider = { TODO("Scope name of feature facade / Delete this for scope of parent fragment") }, 40 | modulesProvider = { arrayOf(${moduleName}(params)) } 41 | ) 42 | 43 | @Suppress("detekt.UnusedPrivateMember") 44 | private val viewModel: ${viewModelName} by viewModelPlugin( 45 | renderState = this::renderState, 46 | handleEvent = this::handleEvent, 47 | viewModelProvider = { di.getInstance() } 48 | ) 49 | 50 | <#if needDesignSample == true> 51 | private val delegateAdapter by lazy { DelegationAdapter() } 52 | 53 | private val stateDelegate: LoadingStateDelegate? by viewRetained( 54 | { 55 | LoadingStateDelegate( 56 | contentView = ${fragmentLayoutResName}_recycler_view, 57 | loadingView = null, // можно подставить что-то свое 58 | stubView = ${fragmentLayoutResName}_zero_state_view 59 | ) 60 | } 61 | ) 62 | 63 | 64 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 65 | super.onViewCreated(view, savedInstanceState) 66 | <#if needDesignSample == true> 67 | 68 | ${fragmentLayoutResName}_toolbar.setNavigationOnClickListener { activity?.onBackPressed() } 69 | ${fragmentLayoutResName}_collapsing_toolbar.initBoldTitleLayout() 70 | 71 | ${fragmentLayoutResName}_recycler_view.apply { 72 | adapter = delegateAdapter 73 | layoutManager = LinearLayoutManager(context) 74 | addOnScrollListener(KeyboardRecyclerViewListener()) 75 | } 76 | 77 | ${fragmentLayoutResName}_swipe_refresh_layout.setOnRefreshListener { TODO() } 78 | <#else> 79 | TODO() 80 | 81 | } 82 | 83 | <#if needDesignSample == false> 84 | @Suppress("detekt.UnusedPrivateMember") 85 | 86 | private fun renderState(state: ${uiStateName}) { 87 | <#if needDesignSample == true> 88 | ${fragmentLayoutResName}_swipe_refresh_layout.isRefreshing = false 89 | ${fragmentLayoutResName}_swipe_refresh_layout.isEnabled = false 90 | when (val list = state.listCells) { 91 | is LCE.Data -> { 92 | ${fragmentLayoutResName}_swipe_refresh_layout.isEnabled = true 93 | stateDelegate?.showContent() 94 | delegateAdapter.submitList(list.value) 95 | } 96 | 97 | is LCE.Error -> { 98 | stateDelegate?.showStub() 99 | 100 | ${fragmentLayoutResName}_zero_state_view.setStubTitle("TODO: Error text") 101 | ${fragmentLayoutResName}_zero_state_view.setMainAction("TODO: Retry text", TODO()) 102 | } 103 | 104 | is LCE.Loading -> { 105 | stateDelegate?.showLoading() 106 | } 107 | } 108 | <#else> 109 | TODO("Render your state here") 110 | 111 | } 112 | 113 | @Suppress("detekt.UnusedPrivateMember") 114 | private fun handleEvent(event: ${uiEventName}) { 115 | TODO("Handle your events here") 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /tools/geminio/templates/Fragment + ViewModel/root/src/app_package/Module.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.${fragmentPackage} 2 | 3 | import ${packageName}.${fragmentPackage}.model.${paramsName} 4 | import toothpick.config.Module 5 | import toothpick.ktp.binding.bind 6 | 7 | internal class ${moduleName}(params: ${paramsName}) : Module() { 8 | 9 | init { 10 | bind<${paramsName}>().toInstance(params) 11 | bind<${viewModelName}>().singleton() 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /tools/geminio/templates/Fragment + ViewModel/root/src/app_package/UiConverter.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.${fragmentPackage} 2 | 3 | import ${packageName}.${fragmentPackage}.model.${uiStateName} 4 | import toothpick.InjectConstructor 5 | 6 | @InjectConstructor 7 | internal class ${uiConverterName}() { 8 | 9 | fun convert(state: ${mviStateName}): ${uiStateName} { 10 | return TODO() 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /tools/geminio/templates/Fragment + ViewModel/root/src/app_package/ViewModel.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.${fragmentPackage} 2 | 3 | <#if useMviViewModel == true> 4 | import io.reactivex.Observable 5 | import ru.hh.shared.core.mvvm.viewmodel.MviViewModel 6 | import ru.hh.shared.core.rx.SchedulersProvider 7 | <#else> 8 | import ru.hh.shared.core.mvvm.viewmodel.ManualStateViewModel 9 | 10 | import ${packageName}.${fragmentPackage}.model.${uiStateName} 11 | import ${packageName}.${fragmentPackage}.model.${uiEventName} 12 | import ${packageName}.${fragmentPackage}.model.${paramsName} 13 | import toothpick.InjectConstructor 14 | 15 | <#if useMviViewModel == true> 16 | @InjectConstructor 17 | internal class ${viewModelName}( 18 | override val schedulers: SchedulersProvider, 19 | private val params: ${paramsName}, 20 | private val uiConverter: ${uiConverterName} 21 | ) : MviViewModel<${uiEventName}, ${uiStateName}, ${mviStateName}, ${mviNewsName}>() { 22 | 23 | override val featureStateObservable: Observable<${mviStateName}> = Observable.wrap(TODO()) 24 | 25 | override val featureNewsObservable: Observable<${mviNewsName}> = Observable.wrap(TODO()) 26 | 27 | override val uiStateConverter: (${mviStateName}) -> ${uiStateName} = { state -> 28 | uiConverter.convert(state) 29 | } 30 | 31 | override fun processNews(news: ${mviNewsName}) { 32 | TODO() 33 | } 34 | 35 | override fun onCleared() { 36 | super.onCleared() 37 | // TODO Here you need to dispose of features that belong only to the current screen 38 | } 39 | 40 | } 41 | <#else> 42 | @InjectConstructor 43 | internal class ${viewModelName}( 44 | private val params: ${paramsName}, 45 | ) : ManualStateViewModel<${uiEventName}, ${uiStateName}>( 46 | initialState = TODO() 47 | ) { 48 | 49 | override fun onFirstAttach() { 50 | TODO() 51 | } 52 | } 53 | 54 | -------------------------------------------------------------------------------- /tools/geminio/templates/Fragment + ViewModel/root/src/app_package/model/Params.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.${fragmentPackage}.model 2 | 3 | import java.io.Serializable 4 | 5 | internal data class ${paramsName}( 6 | val todo: Nothing 7 | ) : Serializable { 8 | 9 | companion object { 10 | private const val serialVersionUID = 1L 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /tools/geminio/templates/Fragment + ViewModel/root/src/app_package/model/UiEvent.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.${fragmentPackage}.model 2 | 3 | internal sealed class ${uiEventName} -------------------------------------------------------------------------------- /tools/geminio/templates/Fragment + ViewModel/root/src/app_package/model/UiState.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.${fragmentPackage}.model 2 | 3 | <#if needDesignSample == true> 4 | import ru.hh.shared.core.mvvm.LCE 5 | import ru.hh.shared.core.ui.cells_framework.cells.interfaces.Cell 6 | 7 | 8 | internal data class ${uiStateName}( 9 | <#if needDesignSample == true> 10 | val listCells: LCE> 11 | <#else> 12 | val todo: Nothing 13 | 14 | ) 15 | -------------------------------------------------------------------------------- /tools/geminio/templates/MVI Compact Feature/recipe.yaml: -------------------------------------------------------------------------------- 1 | requiredParams: 2 | name: 'MVI: Compact Feature' 3 | description: Creates ActorReducer feature in one file 4 | 5 | widgets: 6 | - stringParameter: 7 | id: featurePrefix 8 | name: Feature Prefix 9 | help: Prefix of the feature 10 | constraints: 11 | - nonempty 12 | - unique 13 | - class 14 | default: Blank 15 | 16 | globals: 17 | - stringParameter: 18 | id: featureName 19 | value: ${featurePrefix}Feature 20 | 21 | recipe: 22 | - instantiateAndOpen: 23 | from: root/src/app_package/Feature.kt.ftl 24 | to: ${srcOut}/${featureName}.kt 25 | 26 | - addDependencies: 27 | - compileOnly: :shared:core:mvi-core 28 | - compileOnly: Libs.mviCore.binder 29 | - compileOnly: Libs.rx.rxJava 30 | - compileOnly: Libs.rx.rxKotlin 31 | - compileOnly: :shared:core:rx-core 32 | - compileOnly: Libs.toothpick.core 33 | - compileOnly: Libs.toothpick.ktp 34 | - kapt: Libs.toothpick.compiler 35 | 36 | -------------------------------------------------------------------------------- /tools/geminio/templates/MVI Compact Feature/root/src/app_package/Feature.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName} 2 | 3 | import com.badoo.mvicore.element.Actor 4 | import com.badoo.mvicore.element.Bootstrapper 5 | import com.badoo.mvicore.element.NewsPublisher 6 | import com.badoo.mvicore.element.Reducer 7 | import com.badoo.mvicore.feature.ActorReducerFeature 8 | import io.reactivex.Observable 9 | import ${packageName}.${featureName}.Effect 10 | import ${packageName}.${featureName}.News 11 | import ${packageName}.${featureName}.State 12 | import ${packageName}.${featureName}.Wish 13 | import ru.hh.shared.core.rx.SchedulersProvider 14 | import toothpick.InjectConstructor 15 | 16 | @InjectConstructor 17 | internal class ${featureName}( 18 | actor: ActorImpl 19 | ) : ActorReducerFeature( 20 | initialState = State(), 21 | bootstrapper = BootstrapperImpl(), 22 | actor = actor, 23 | reducer = ReducerImpl(), 24 | newsPublisher = NewsPublisherImpl() 25 | ) { 26 | 27 | data class State( 28 | val yourData: Any? = null 29 | ) 30 | 31 | sealed class Wish { 32 | 33 | } 34 | 35 | sealed class Effect { 36 | 37 | } 38 | 39 | sealed class News { 40 | 41 | } 42 | 43 | class BootstrapperImpl : Bootstrapper { 44 | override fun invoke(): Observable { 45 | TODO() 46 | } 47 | } 48 | 49 | @InjectConstructor 50 | class ActorImpl( 51 | private val schedulersProvider: SchedulersProvider 52 | ) : Actor { 53 | override fun invoke(state: State, wish: Wish): Observable { 54 | return when(wish) { 55 | TODO() -> Observable.just(TODO()) 56 | } 57 | } 58 | } 59 | 60 | class ReducerImpl : Reducer { 61 | override fun invoke(state: State, effect: Effect): State { 62 | return when(effect) { 63 | TODO() -> state.copy() 64 | } 65 | } 66 | } 67 | 68 | class NewsPublisherImpl : NewsPublisher { 69 | override fun invoke(wish: Wish, effect: Effect, state: State): News? { 70 | return when(effect) { 71 | TODO() -> TODO() 72 | else -> null 73 | } 74 | } 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /tools/geminio/templates/MVI Multi File Feature/recipe.yaml: -------------------------------------------------------------------------------- 1 | requiredParams: 2 | name: HeadHunter MVI Feature 3 | description: Creates HeadHunter MVI Feature 4 | 5 | widgets: 6 | - stringParameter: 7 | id: featurePrefix 8 | name: Feature Prefix 9 | help: Prefix of the feature 10 | constraints: 11 | - nonempty 12 | - unique 13 | - class 14 | default: Blank 15 | 16 | globals: 17 | - stringParameter: 18 | id: featureName 19 | value: ${featurePrefix}Feature 20 | 21 | - stringParameter: 22 | id: featurePackage 23 | value: ${featurePrefix.camelCaseToUnderlines()} 24 | 25 | - stringParameter: 26 | id: bootstrapperName 27 | value: ${featurePrefix}Bootstrapper 28 | 29 | - stringParameter: 30 | id: actorName 31 | value: ${featurePrefix}Actor 32 | 33 | - stringParameter: 34 | id: reducerName 35 | value: ${featurePrefix}Reducer 36 | 37 | - stringParameter: 38 | id: newsPublisherName 39 | value: ${featurePrefix}NewsPublisher 40 | 41 | - stringParameter: 42 | id: stateName 43 | value: ${featurePrefix}State 44 | 45 | - stringParameter: 46 | id: wishName 47 | value: ${featurePrefix}Wish 48 | 49 | - stringParameter: 50 | id: effectName 51 | value: ${featurePrefix}Effect 52 | 53 | - stringParameter: 54 | id: newsName 55 | value: ${featurePrefix}News 56 | 57 | recipe: 58 | - instantiate: 59 | from: root/src/app_package/model/Effect.kt.ftl 60 | to: ${srcOut}/${featurePackage}/model/${effectName}.kt 61 | 62 | - instantiate: 63 | from: root/src/app_package/model/News.kt.ftl 64 | to: ${srcOut}/${featurePackage}/model/${newsName}.kt 65 | 66 | - instantiate: 67 | from: root/src/app_package/model/State.kt.ftl 68 | to: ${srcOut}/${featurePackage}/model/${stateName}.kt 69 | 70 | - instantiate: 71 | from: root/src/app_package/model/Wish.kt.ftl 72 | to: ${srcOut}/${featurePackage}/model/${wishName}.kt 73 | 74 | - instantiateAndOpen: 75 | from: root/src/app_package/Feature.kt.ftl 76 | to: ${srcOut}/${featurePackage}/${featureName}.kt 77 | 78 | - instantiate: 79 | from: root/src/app_package/Actor.kt.ftl 80 | to: ${srcOut}/${featurePackage}/${actorName}.kt 81 | 82 | - instantiate: 83 | from: root/src/app_package/Bootstrapper.kt.ftl 84 | to: ${srcOut}/${featurePackage}/${bootstrapperName}.kt 85 | 86 | - instantiate: 87 | from: root/src/app_package/NewsPublisher.kt.ftl 88 | to: ${srcOut}/${featurePackage}/${newsPublisherName}.kt 89 | 90 | - instantiate: 91 | from: root/src/app_package/Reducer.kt.ftl 92 | to: ${srcOut}/${featurePackage}/${reducerName}.kt 93 | 94 | - addDependencies: 95 | - compileOnly: :shared:core:mvi-core 96 | - compileOnly: Libs.mviCore.binder 97 | - compileOnly: Libs.rx.rxJava 98 | - compileOnly: Libs.rx.rxKotlin 99 | - compileOnly: :shared:core:rx-core 100 | - compileOnly: Libs.toothpick.core 101 | - compileOnly: Libs.toothpick.ktp 102 | - kapt: Libs.toothpick.compiler 103 | -------------------------------------------------------------------------------- /tools/geminio/templates/MVI Multi File Feature/root/src/app_package/Actor.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.${featurePackage} 2 | 3 | import com.badoo.mvicore.element.Actor 4 | import io.reactivex.Observable 5 | import ${packageName}.${featurePackage}.model.${effectName} 6 | import ${packageName}.${featurePackage}.model.${stateName} 7 | import ${packageName}.${featurePackage}.model.${wishName} 8 | import ru.hh.shared.core.rx.SchedulersProvider 9 | import toothpick.InjectConstructor 10 | 11 | @InjectConstructor 12 | internal class ${actorName}( 13 | private val schedulersProvider: SchedulersProvider 14 | ) : Actor<${stateName}, ${wishName}, ${effectName}> { 15 | 16 | override fun invoke(state: ${stateName}, wish: ${wishName}): Observable<${effectName}> { 17 | return when(wish) { 18 | TODO() -> Observable.just(TODO()) 19 | } 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /tools/geminio/templates/MVI Multi File Feature/root/src/app_package/Bootstrapper.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.${featurePackage} 2 | 3 | import ${packageName}.${featurePackage}.model.${wishName} 4 | import com.badoo.mvicore.element.Bootstrapper 5 | import io.reactivex.Observable 6 | 7 | internal class ${bootstrapperName} : Bootstrapper<${wishName}> { 8 | 9 | override fun invoke(): Observable<${wishName}> { 10 | TODO() 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /tools/geminio/templates/MVI Multi File Feature/root/src/app_package/Feature.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.${featurePackage} 2 | 3 | import com.badoo.mvicore.feature.ActorReducerFeature 4 | import ${packageName}.${featurePackage}.model.${effectName} 5 | import ${packageName}.${featurePackage}.model.${newsName} 6 | import ${packageName}.${featurePackage}.model.${stateName} 7 | import ${packageName}.${featurePackage}.model.${wishName} 8 | import toothpick.InjectConstructor 9 | 10 | @InjectConstructor 11 | internal class ${featureName}( 12 | actor: ${actorName} 13 | ) : ActorReducerFeature<${wishName}, ${effectName}, ${stateName}, ${newsName}> ( 14 | actor = actor, 15 | bootstrapper = ${bootstrapperName}(), 16 | initialState = ${stateName}(), 17 | newsPublisher = ${newsPublisherName}(), 18 | reducer = ${reducerName}() 19 | ) 20 | -------------------------------------------------------------------------------- /tools/geminio/templates/MVI Multi File Feature/root/src/app_package/NewsPublisher.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.${featurePackage} 2 | 3 | import com.badoo.mvicore.element.NewsPublisher 4 | import ${packageName}.${featurePackage}.model.${effectName} 5 | import ${packageName}.${featurePackage}.model.${newsName} 6 | import ${packageName}.${featurePackage}.model.${stateName} 7 | import ${packageName}.${featurePackage}.model.${wishName} 8 | 9 | internal class ${newsPublisherName} : NewsPublisher<${wishName}, ${effectName}, ${stateName}, ${newsName}> { 10 | 11 | override fun invoke(wish: ${wishName}, effect: ${effectName}, state: ${stateName}): ${newsName}? { 12 | return when(effect) { 13 | TODO() -> TODO() 14 | else -> null 15 | } 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /tools/geminio/templates/MVI Multi File Feature/root/src/app_package/Reducer.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.${featurePackage} 2 | 3 | import com.badoo.mvicore.element.Reducer 4 | import ${packageName}.${featurePackage}.model.${effectName} 5 | import ${packageName}.${featurePackage}.model.${stateName} 6 | 7 | internal class ${reducerName} : Reducer<${stateName}, ${effectName}> { 8 | 9 | override fun invoke(state: ${stateName}, effect: ${effectName}): ${stateName} { 10 | return when(effect) { 11 | TODO() -> state.copy() 12 | } 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /tools/geminio/templates/MVI Multi File Feature/root/src/app_package/model/Effect.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.${featurePackage}.model 2 | 3 | internal sealed class ${effectName} { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /tools/geminio/templates/MVI Multi File Feature/root/src/app_package/model/News.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.${featurePackage}.model 2 | 3 | internal sealed class ${newsName} { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /tools/geminio/templates/MVI Multi File Feature/root/src/app_package/model/State.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.${featurePackage}.model 2 | 3 | internal data class ${stateName}( 4 | val yourData: Any? = null 5 | ) 6 | -------------------------------------------------------------------------------- /tools/geminio/templates/MVI Multi File Feature/root/src/app_package/model/Wish.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.${featurePackage}.model 2 | 3 | internal sealed class ${wishName} { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /tools/geminio/templates/Network/recipe.yaml: -------------------------------------------------------------------------------- 1 | requiredParams: 2 | name: Network for some entity 3 | description: Creates network boilerplate for some entity 4 | 5 | widgets: 6 | - stringParameter: 7 | id: entityPrefix 8 | name: Entity Prefix 9 | help: Prefix that will be used to create network-layer classes 10 | constraints: 11 | - class 12 | - nonempty 13 | - unique 14 | default: Entity 15 | 16 | globals: 17 | - stringParameter: 18 | id: networkModelName 19 | value: ${entityPrefix}Network 20 | 21 | - stringParameter: 22 | id: networkApiName 23 | value: ${entityPrefix}HttpApi 24 | 25 | - stringParameter: 26 | id: repositoryName 27 | value: ${entityPrefix}Repository 28 | 29 | - stringParameter: 30 | id: converterName 31 | value: ${entityPrefix}NetworkConverter 32 | 33 | - stringParameter: 34 | id: apiProviderName 35 | value: ${networkApiName}Provider 36 | 37 | - stringParameter: 38 | id: moduleName 39 | value: ${entityPrefix}DataModule 40 | 41 | recipe: 42 | - instantiate: 43 | from: root/src/app_package/NetworkApi.kt.ftl 44 | to: ${srcOut}/${networkApiName}.kt 45 | 46 | - instantiate: 47 | from: root/src/app_package/Repository.kt.ftl 48 | to: ${srcOut}/${repositoryName}.kt 49 | 50 | - instantiate: 51 | from: root/src/app_package/converter/Converter.kt.ftl 52 | to: ${srcOut}/converter/${converterName}.kt 53 | 54 | - instantiate: 55 | from: root/src/app_package/di/ApiProvider.kt.ftl 56 | to: ${srcOut}/di/${apiProviderName}.kt 57 | 58 | - instantiate: 59 | from: root/src/app_package/di/DataModule.kt.ftl 60 | to: ${srcOut}/di/${moduleName}.kt 61 | 62 | - instantiate: 63 | from: root/src/app_package/model/Model.kt.ftl 64 | to: ${srcOut}/model/${networkModelName}.kt 65 | 66 | - addDependencies: 67 | - compileOnly: :shared:core:network 68 | - compileOnly: :shared:core:network-model 69 | - compileOnly: Libs.network.kotlinxSerializationJson 70 | - compileOnly: Libs.network.retrofit 71 | - compileOnly: Libs.network.retrofitKotlinxConverter 72 | - compileOnly: Libs.toothpick.core 73 | - compileOnly: Libs.toothpick.ktp 74 | - kapt: Libs.toothpick.compiler 75 | -------------------------------------------------------------------------------- /tools/geminio/templates/Network/root/src/app_package/NetworkApi.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName} 2 | 3 | import io.reactivex.Single 4 | import retrofit2.http.GET 5 | import retrofit2.http.Query 6 | import ${packageName}.model.${networkModelName} 7 | 8 | internal interface ${networkApiName} { 9 | 10 | @GET("path") 11 | fun get${entityPrefix}(@Query("param") param: String): Single<${networkModelName}> 12 | 13 | } 14 | -------------------------------------------------------------------------------- /tools/geminio/templates/Network/root/src/app_package/Repository.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName} 2 | 3 | import io.reactivex.Single 4 | import ${packageName}.converter.${converterName} 5 | import ${packageName}.model.${networkModelName} 6 | import toothpick.InjectConstructor 7 | 8 | @InjectConstructor 9 | internal class ${repositoryName}( 10 | private val api: ${networkApiName}, 11 | private val converter: ${converterName} 12 | ) { 13 | 14 | fun get${entityPrefix}(): Single<${entityPrefix}> { 15 | return api.get${entityPrefix}("todo") 16 | .map { converter.convert(it) } 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /tools/geminio/templates/Network/root/src/app_package/converter/Converter.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.converter 2 | 3 | import ${packageName}.model.${networkModelName} 4 | import toothpick.InjectConstructor 5 | 6 | @InjectConstructor 7 | internal class ${converterName} { 8 | 9 | fun convert(networkModel: ${networkModelName}): ${entityPrefix} { 10 | return ${entityPrefix}( 11 | id = networkModel.id 12 | ) 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /tools/geminio/templates/Network/root/src/app_package/di/ApiProvider.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.di 2 | 3 | import ${packageName}.${networkApiName} 4 | import ru.hh.shared.core.network.network_auth_source.AuthZoneApiFactory 5 | import ru.hh.shared.core.network.network_source.ServerUrl 6 | import toothpick.InjectConstructor 7 | import javax.inject.Provider 8 | 9 | @InjectConstructor 10 | internal class ${apiProviderName}( 11 | private val authZoneApiFactory: AuthZoneApiFactory, 12 | private val serverUrl: ServerUrl 13 | ) : Provider<${networkApiName}> { 14 | 15 | override fun get(): ${networkApiName} { 16 | return authZoneApiFactory.createAuthZone(${networkApiName}::class.java, serverUrl) 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /tools/geminio/templates/Network/root/src/app_package/di/DataModule.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.di 2 | 3 | import ${packageName}.${networkApiName} 4 | import ${packageName}.${repositoryName} 5 | import toothpick.config.Module 6 | import toothpick.ktp.binding.bind 7 | 8 | internal class ${moduleName} : Module() { 9 | 10 | init { 11 | bind<${networkApiName}>().toProvider(${apiProviderName}::class).singleton() 12 | bind<${repositoryName}>().singleton() 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /tools/geminio/templates/Network/root/src/app_package/model/Model.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.model 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | internal data class ${networkModelName}( 7 | val id: String 8 | ) 9 | -------------------------------------------------------------------------------- /tools/ide-settings/Headhunter_Android_Style.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tools/ide-settings/ide_settings.md: -------------------------------------------------------------------------------- 1 | Правила настройки IDE. 2 | 3 | Шаги настройки: 4 | 1. Файлы настройки IDE лежат в директории [tools/ide-settings](/tools/ide-settings/). 5 | 2. Скачиваем их на компьютер 6 | 3. В Android Studio импортируем при помощи File -> Import Settings 7 | 4. Для импорта только настроек style guide-a необходимо импортировать файл Headhunter_Android_Style.xml 8 | * Preferences -> Editor -> Code Style 9 | * В блоке Scheme нажать на иконку шестеренки и выбрать пункт Import scheme... 10 | * Выбрать Headhunter_Android_Style.xml 11 | -------------------------------------------------------------------------------- /tools/ide-settings/settings.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhru/android-style-guide/93bbe5a21a425ed9a5d9f56354aa0166ab85e84d/tools/ide-settings/settings.zip --------------------------------------------------------------------------------