├── 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 | #list>
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 | #list>
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 | #if>
6 | id("kotlin-kapt")
7 | <#if needNetworkLibraries>
8 | id("kotlinx-serialization")
9 | #if>
10 | <#if needTests>
11 | id("de.mannodermaus.android-junit5")
12 | #if>
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 | #if>
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 | #if>
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 | #if>
44 | #if>
45 |
46 | <#if needDbLibraries>
47 | // Database
48 | api(Libs.room.runtime)
49 | kapt(Libs.room.compiller)
50 | implementation(Libs.room.rxJava)
51 | #if>
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 | #if>
59 |
60 | <#if needMvi>
61 | // MVI
62 | compileOnly(project(":shared:core:mvi-core"))
63 | compileOnly(Libs.mviCore.binder)
64 | #if>
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 | #if>
75 | <#if needMvi>
76 | testImplementation(project(":shared:core:mvi-core"))
77 | #if>
78 | #if>
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 | #if>
--------------------------------------------------------------------------------
/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 | #if>
6 | <#if includeModule??>import ${packageName}.di.${moduleName}#if>
7 | import ru.hh.shared.core.ui.framework.fragment.BaseFragment
8 | <#if includeFactory??>import ru.hh.shared.core.ui.framework.fragment.withArgs#if>
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 | #if>
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}()) }#if>
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 | #if>
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 | #if>
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 | #if>
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 | #if>
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 | #if>
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 | #if>
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 | #if>
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 | #if>
81 | }
82 |
83 | <#if needDesignSample == false>
84 | @Suppress("detekt.UnusedPrivateMember")
85 | #if>
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 | #if>
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 | #if>
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 | #if>
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 | #if>
7 |
8 | internal data class ${uiStateName}(
9 | <#if needDesignSample == true>
10 | val listCells: LCE>
11 | <#else>
12 | val todo: Nothing
13 | #if>
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 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
45 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 | GETTERS_AND_SETTERS
126 | KEEP
127 |
128 |
129 | OVERRIDDEN_METHODS
130 | KEEP
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
403 |
404 |
405 |
406 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 |
418 | xmlns:android
419 |
420 | ^$
421 |
422 |
423 |
424 |
425 |
426 |
427 |
428 |
429 | xmlns:.*
430 |
431 | ^$
432 |
433 |
434 | BY_NAME
435 |
436 |
437 |
438 |
439 |
440 |
441 | .*:id
442 |
443 | http://schemas.android.com/apk/res/android
444 |
445 |
446 |
447 |
448 |
449 |
450 |
451 |
452 | .*:name
453 |
454 | http://schemas.android.com/apk/res/android
455 |
456 |
457 |
458 |
459 |
460 |
461 |
462 |
463 | name
464 |
465 | ^$
466 |
467 |
468 |
469 |
470 |
471 |
472 |
473 |
474 | style
475 |
476 | ^$
477 |
478 |
479 |
480 |
481 |
482 |
483 |
484 |
485 | .*
486 |
487 | ^$
488 |
489 |
490 | BY_NAME
491 |
492 |
493 |
494 |
495 |
496 |
497 | .*
498 |
499 | http://schemas.android.com/apk/res/android
500 |
501 |
502 | ANDROID_ATTRIBUTE_ORDER
503 |
504 |
505 |
506 |
507 |
508 |
509 | .*
510 |
511 | .*
512 |
513 |
514 | BY_NAME
515 |
516 |
517 |
518 |
519 |
520 |
521 |
522 |
523 |
524 |
525 |
526 |
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
|