├── .gitignore
├── LICENSE
├── README.md
├── build.gradle
├── edge-to-edge-decorator
├── .gitignore
├── build.gradle
├── library.properties
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── redmadrobot
│ └── e2e
│ └── decorator
│ └── EdgeToEdgeDecorator.kt
├── gradle.properties
├── gradle
├── dependencies.gradle
├── publish.gradle
├── version.gradle
└── wrapper
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── images
├── sample_21_api.gif
├── sample_25_api.gif
├── sample_28_api.gif
└── sample_30_api.gif
├── record_sample_screen.sh
├── sample
├── .gitignore
├── build.gradle
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── redmadrobot
│ │ └── sample
│ │ ├── BaseActivity.kt
│ │ ├── MainActivity.kt
│ │ └── SampleAdapter.kt
│ └── res
│ ├── drawable
│ ├── ic_launcher_background.xml
│ └── ic_launcher_foreground.xml
│ ├── layout
│ ├── activity_main.xml
│ └── item_sample.xml
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-mdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ └── values
│ ├── colors.xml
│ ├── strings.xml
│ └── styles.xml
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/
5 | .DS_Store
6 | /build
7 | /captures
8 | .externalNativeBuild
9 | .cxx
10 | ### Gradle template
11 | .gradle
12 | /build/
13 |
14 | # Ignore Gradle GUI config
15 | gradle-app.setting
16 |
17 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
18 | !gradle-wrapper.jar
19 |
20 | # Cache of project
21 | .gradletasknamecache
22 |
23 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
24 | # gradle/wrapper/gradle-wrapper.properties
25 |
26 | ### Kotlin template
27 | # Compiled class file
28 | *.class
29 |
30 | # Log file
31 | *.log
32 |
33 | # BlueJ files
34 | *.ctxt
35 |
36 | # Mobile Tools for Java (J2ME)
37 | .mtj.tmp/
38 |
39 | # Package Files #
40 | *.jar
41 | *.war
42 | *.nar
43 | *.ear
44 | *.zip
45 | *.tar.gz
46 | *.rar
47 |
48 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
49 | hs_err_pid*
50 |
51 | ### JetBrains template
52 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
53 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
54 |
55 | # User-specific stuff
56 | .idea/**/workspace.xml
57 | .idea/**/tasks.xml
58 | .idea/**/usage.statistics.xml
59 | .idea/**/dictionaries
60 | .idea/**/shelf
61 |
62 | # Generated files
63 | .idea/**/contentModel.xml
64 |
65 | # Sensitive or high-churn files
66 | .idea/**/dataSources/
67 | .idea/**/dataSources.ids
68 | .idea/**/dataSources.local.xml
69 | .idea/**/sqlDataSources.xml
70 | .idea/**/dynamic.xml
71 | .idea/**/uiDesigner.xml
72 | .idea/**/dbnavigator.xml
73 |
74 | # Gradle
75 | .idea/**/gradle.xml
76 | .idea/**/libraries
77 |
78 | # Gradle and Maven with auto-import
79 | # When using Gradle or Maven with auto-import, you should exclude module files,
80 | # since they will be recreated, and may cause churn. Uncomment if using
81 | # auto-import.
82 | # .idea/artifacts
83 | # .idea/compiler.xml
84 | # .idea/modules.xml
85 | # .idea/*.iml
86 | # .idea/modules
87 | # *.iml
88 | # *.ipr
89 |
90 | # CMake
91 | cmake-build-*/
92 |
93 | # Mongo Explorer plugin
94 | .idea/**/mongoSettings.xml
95 |
96 | # File-based project format
97 | *.iws
98 |
99 | # IntelliJ
100 | out/
101 |
102 | # mpeltonen/sbt-idea plugin
103 | .idea_modules/
104 |
105 | # JIRA plugin
106 | atlassian-ide-plugin.xml
107 |
108 | # Cursive Clojure plugin
109 | .idea/replstate.xml
110 |
111 | # Crashlytics plugin (for Android Studio and IntelliJ)
112 | com_crashlytics_export_strings.xml
113 | crashlytics.properties
114 | crashlytics-build.properties
115 | fabric.properties
116 |
117 | # Editor-based Rest Client
118 | .idea/httpRequests
119 |
120 | # Android studio 3.1+ serialized cache file
121 | .idea/caches/build_file_checksums.ser
122 |
123 | ### macOS template
124 | # General
125 | .DS_Store
126 | .AppleDouble
127 | .LSOverride
128 |
129 | # Icon must end with two \r
130 | Icon
131 |
132 | # Thumbnails
133 | ._*
134 |
135 | # Files that might appear in the root of a volume
136 | .DocumentRevisions-V100
137 | .fseventsd
138 | .Spotlight-V100
139 | .TemporaryItems
140 | .Trashes
141 | .VolumeIcon.icns
142 | .com.apple.timemachine.donotpresent
143 |
144 | # Directories potentially created on remote AFP share
145 | .AppleDB
146 | .AppleDesktop
147 | Network Trash Folder
148 | Temporary Items
149 | .apdisk
150 |
151 | ### Android template
152 | # Built application files
153 | *.apk
154 | *.aar
155 | *.ap_
156 | *.aab
157 |
158 | # Files for the ART/Dalvik VM
159 | *.dex
160 |
161 | # Java class files
162 | *.class
163 |
164 | # Generated files
165 | bin/
166 | gen/
167 | out/
168 | # Uncomment the following line in case you need and you don't have the release build type files in your app
169 | # release/
170 |
171 | # Gradle files
172 | .gradle/
173 | build/
174 |
175 | # Local configuration file (sdk path, etc)
176 | local.properties
177 |
178 | # Proguard folder generated by Eclipse
179 | proguard/
180 |
181 | # Log Files
182 | *.log
183 |
184 | # Android Studio Navigation editor temp files
185 | .navigation/
186 |
187 | # Android Studio captures folder
188 | captures/
189 |
190 | # IntelliJ
191 | *.iml
192 | .idea/workspace.xml
193 | .idea/tasks.xml
194 | .idea/gradle.xml
195 | .idea/assetWizardSettings.xml
196 | .idea/dictionaries
197 | .idea/libraries
198 | # Android Studio 3 in .gitignore file.
199 | .idea/caches
200 | .idea/modules.xml
201 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you
202 | .idea/navEditor.xml
203 |
204 | # Keystore files
205 | # Uncomment the following lines if you do not want to check your keystore files in.
206 | #*.jks
207 | #*.keystore
208 |
209 | # External native build folder generated in Android Studio 2.2 and later
210 | .externalNativeBuild
211 | .cxx/
212 |
213 | # Google Services (e.g. APIs or Firebase)
214 | # google-services.json
215 |
216 | # Freeline
217 | freeline.py
218 | freeline/
219 | freeline_project_description.json
220 |
221 | # fastlane
222 | fastlane/report.xml
223 | fastlane/Preview.html
224 | fastlane/screenshots
225 | fastlane/test_output
226 | fastlane/readme.md
227 |
228 | # Version control
229 | vcs.xml
230 |
231 | # lint
232 | lint/intermediates/
233 | lint/generated/
234 | lint/outputs/
235 | lint/tmp/
236 | # lint/reports/
237 |
238 | ### Java template
239 | # Compiled class file
240 | *.class
241 |
242 | # Log file
243 | *.log
244 |
245 | # BlueJ files
246 | *.ctxt
247 |
248 | # Mobile Tools for Java (J2ME)
249 | .mtj.tmp/
250 |
251 | # Package Files #
252 | *.jar
253 | *.war
254 | *.nar
255 | *.ear
256 | *.zip
257 | *.tar.gz
258 | *.rar
259 |
260 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
261 | hs_err_pid*
262 |
263 | /gradle/publish.properties
264 | /publish.sh
265 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Redmadrobot
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # edge-to-edge-decorator
2 |
3 | ## Описание
4 |
5 | **Edge-to-edge-decorator** - это класс-утилита, которая отвечает за окрашивание `statusBar` и `navigationBar` для поддержания **edge-to-edge (e2e)** режима.
6 |
7 | Концепция основана на [WindowPreferencesManager](https://github.com/material-components/material-components-android/blob/master/catalog/java/io/material/catalog/windowpreferences/WindowPreferencesManager.java)
8 | из [приложения-каталога материальных комнонентов](https://github.com/material-components/material-components-android/tree/master/catalog).
9 |
10 | ## Проблема
11 |
12 | С выходом Android 10, компания Google представила жестовую навигацию и edge-to-edge режим. Этот режим означает, что
13 | контент отрисовывается под системными компонентами `statusBar` и `navigationBar`, и телефон становится
14 | визуально более безрамочным, добавляется поддержка вырезов для камер, а сами компоненты окрашиваются в прозрачный цвет.
15 |
16 | Для того, чтобы добавить поддержку edge-to-edge в ваше приложение, нужно сделать 2 вещи:
17 |
18 | ### 1. Добавить поддержку системных отступов (insets)
19 |
20 | Вы получаете размер системных компонентов и вставляете их
21 | как `padding` в верстку для ваших компонентов. Insets поддерживается всеми версиями Android OS,
22 | что позволяет реализовать концепцию edge-to-edge для всех пользователей.
23 |
24 | Подробности можно почитать или посмотреть в [докладе Константина Цховребова](https://habr.com/ru/company/oleg-bunin/blog/488196/).
25 | Для реализации можно использовать библиотеку от Chris Banes [Insetter](https://github.com/chrisbanes/insetter).
26 |
27 | ### 2. Активировать режим edge-to-edge для `statusBar` и `navigationBar`. По факту вам нужно сделать их прозрачными
28 |
29 | Тут существует одна проблема, которая находится глубоко в системе и исправить её после релиза OS уже нельзя.
30 | Это изменение цвета иконок в системных компонентах (`statusBar` и `navigationBar`) со светлого на темный.
31 | Поэтому нужно учитывать следующие правила, в зависимости от версии Android:
32 |
33 | * до 6.0 версии android иконки `statusBar` и `navigationBar` всегда светлые и перекрасить их в темный цвет нельзя.
34 | Флаг `View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR` доступен с 23 API.
35 | Если у вас контент всегда темного цвета, то проблем не будет.
36 | Утилита, чтобы сохранить контрастность иконок на фоне контента, добавляет на системные компоненты наложение черного фона с 50% прозрачности;
37 |
38 | * с версии 6.0 можно задать, белыми или черными будут иконки в `statusBar`.
39 | Однако `navigationBar` будет вести себя как в предыдущих версиях, поэтому наложение можно убрать только для `statusBar`.
40 | Флаг `View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR` доступен с 26 API.
41 |
42 | * с версии 8.0 можно выбрать белый или черный цвет иконок для обоих компонентов.
43 | Поэтому наложения можно убрать полностью.
44 |
45 | Подробнее про edge-to-edge и жестовую навигацию можно почитать в статье, которую написал [Chris Banes](https://medium.com/androiddevelopers/gesture-navigation-going-edge-to-edge-812f62e4e83e).
46 |
47 | ### Пример работы утилиты
48 |
49 | |
|
|
|
|
50 | |:--------------------------------------------------:|:--------------------------------------------------:|:--------------------------------------------------:|----------------------------------------------------|
51 | | Android 5.0 (API level 21) | Android 7.1 (API level 25) | Android 9 (API level 28) | Android 11 (API level 30) |
52 |
53 | ## Как подключить?
54 |
55 | ### 1. Подключение библиотеки
56 |
57 | ```groovy
58 | dependencies {
59 | implementation("com.redmadrobot:edge-to-edge-decorator:1.0.0")
60 | }
61 | ```
62 |
63 | ### 2. Тема приложения должна наследоваться от MaterialComponents
64 |
65 | Для определение атрибутов темы приложения в простом режиме необходимо, чтобы тема вашего приложения
66 | наследовалась от `Theme.MaterialComponents.*`.
67 |
68 | Также стоит явно указать нужный цвет фона для `AppBarLayout` и `background` приложения:
69 |
70 | ```xml
71 | - @color/colorPrimary
72 | - @color/windowBackground
73 | ```
74 |
75 | Или указать свои значения программно в параметрах `appBarColorAttr` и `backgroundColorAttr`.
76 |
77 | ### 3. Выключение режима edge-to-edge
78 |
79 | Если на каком-то экране вы захотите выключить режим edge-to-edge (параметр `isEdgeToEdgeEnabled = false`),
80 | то в теме приложения следует указать цвета `statusBar` и `navigationBar`:
81 |
82 | ```xml
83 | - @android:color/black
84 | - @android:color/black
85 | ```
86 |
87 | ### 4. Включить или выключить флаг дополнительной контрастности для `NavigationBar`
88 |
89 | ```xml
90 | - false
91 | ```
92 |
93 | Подробнее про флаг `enforceNavigationBarContrast` можно почитать в статье, которую написал [Chris Banes](https://medium.com/androiddevelopers/gesture-navigation-going-edge-to-edge-812f62e4e83e).
94 |
95 | ### 5. Настройка утилиты под особенности проекта
96 |
97 | Настройка параметров и активация режима edge-to-edge
98 |
99 | ```kotlin
100 | EdgeToEdgeDecorator
101 | .updateConfig {
102 | // custom config
103 | isEdgeToEdgeEnabled = true
104 | appBarColorAttr = R.color.colorPrimary
105 | backgroundColorAttr = R.color.windowBackground
106 | }
107 | .apply(context, window)
108 | ```
109 |
110 | ### 6. Profit!
111 |
112 | ## Настройки edge-to-edge-decorator
113 |
114 | Утилита может работать в 3-х режимах:
115 |
116 | 1. Простой (работа по умолчанию).
117 | * Цвет `statusBar` и `navigationBar` - прозрачный;
118 | * Цвет иконок `statusBar` определяется по цвету `AppBarLayout`. Параметр `appBarColorAttr` (по умолчанию `R.attr.colorPrimarySurface`)
119 | * Цвет иконок `navigationBar` определяется по цвету фона вашего приложения. Параметр `backgroundColorAttr` (по умолчанию `android.R.attr.windowBackground`).
120 | * Активируется режим edge-to-edge.
121 | В `window.decorView.systemUiVisibility` устанавливаются флаги `View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_STABLE`)
122 |
123 | 2. Кастомный - на экране нет `AppBarLayout` и/или под `navigationBar` должен отрисовываться другой контент, например `BottomNavigationMenu`.
124 | * Цвет `statusBar` и `navigationBar` - прозрачный;
125 | * Цвет иконок `statusBar` определяется по цвету, указанному в параметре `contentUnderStatusBarCustomColor`.
126 | * Цвет иконок `navigationBar` определяется по цвету, указанному в параметре `contentUnderNavBarCustomColor`.
127 | * Активируется режим edge-to-edge.
128 |
129 | 3. Режим дополнителькой контрастности.
130 | Если на экране цвет контента определить нельзя, то для `statusBar` и `navigationBar` можно указать конкретный цвет вместо прозрачного.
131 | Параметры `statusBarEdgeToEdgeColor` и `navBarEdgeToEdgeColor`.
132 |
133 | Для всех режимов можно указать свои цвета для поддержания совместимости на устройствах с более поздними версиями Android OS.
134 | Параметры `statusBarCompatibilityColor` и `navBarCompatibilityColor`.
135 |
136 | ### Утилита имеет dsl интерфейс для редактирования параметров
137 |
138 | Пример:
139 |
140 | ```kotlin
141 | override val edgeToEdgeCompatibilityManager = EdgeToEdgeDecorator.updateConfig {
142 | // custom config
143 | isEdgeToEdgeEnabled = true
144 | appBarColorAttr = R.color.colorPrimary
145 | backgroundColorAttr = R.color.windowBackground
146 | }
147 | ```
148 |
149 | ### Полное описание параметров можно найти в классе [DefaultConfig](https://github.com/RedMadRobot/edge-to-edge-decorator/blob/5776dcd5bb126bdb157f7d08d6f3fa6cfe6f4e88/edge-to-edge-decorator/src/main/java/com/redmadrobot/e2e/decorator/EdgeToEdgeDecorator.kt#L147)
150 |
151 | ```kotlin
152 | class DefaultConfig {
153 |
154 | /**
155 | * Флаг отвечает за включение/выключение edge-to-edge режима.
156 | */
157 | var isEdgeToEdgeEnabled = true
158 |
159 | /**
160 | * В простом edge-to-edge режиме. Цвет иконок statusBar устанавливается в соответствии
161 | * с цветом [com.google.android.material.appbar.AppBarLayout].
162 | *
163 | * Значение по умолчанию равно [R.attr.colorPrimarySurface].
164 | *
165 | * В этом случае цвет самого statusBar равен параметру [statusBarEdgeToEdgeColor],
166 | * по умолчанию [statusBarEdgeToEdgeColor] равен [Color.TRANSPARENT].
167 | *
168 | * Также в простом режиме используется [backgroundColorAttr]
169 | *
170 | * @see backgroundColorAttr
171 | * @see statusBarEdgeToEdgeColor
172 | */
173 | @AttrRes
174 | var appBarColorAttr = R.attr.colorPrimarySurface
175 |
176 | /**
177 | * В простом edge-to-edge режиме. Цвет иконок navigationBar устанавливается в соответствии
178 | * с цветом [android.R.attr.windowBackground]
179 | *
180 | * Значение по умолчанию равно [android.R.attr.windowBackground]
181 | *
182 | * В этом случае цвет самого navigationBar равен параметру [navBarCompatibilityColor],
183 | * по умолчанию [navBarCompatibilityColor] равен [Color.TRANSPARENT].
184 | *
185 | * Также в простом режиме используется [appBarColorAttr]
186 | *
187 | * @see appBarColorAttr
188 | * @see navBarCompatibilityColor
189 | */
190 | @AttrRes
191 | var backgroundColorAttr = android.R.attr.windowBackground
192 |
193 | /**
194 | * Если не подходит простой режим, например, для случаев, когда на экране нет
195 | * [com.google.android.material.appbar.AppBarLayout], можно активировать кастомный режим edge-to-edge.
196 | *
197 | * Для этого нужно передать конкретный цвет контента под statusBar, например, [R.color.windowBackground]
198 | *
199 | * @see contentUnderNavBarCustomColor
200 | */
201 | @ColorRes
202 | var contentUnderStatusBarCustomColor: Int? = null
203 |
204 | /**
205 | * Если не подходит простой режим, например, под navigationBar должен отрисовываться другой контет
206 | * или [BottomNavigationMenu], можно активировать кастомный режим edge-to-edge.
207 | *
208 | * Для этого нужно передать конкретный цвет контента под navigationBar, например, [R.color.bottomMenu]
209 | *
210 | * @see contentUnderStatusBarCustomColor
211 | */
212 | @ColorRes
213 | var contentUnderNavBarCustomColor: Int? = null
214 |
215 | /**
216 | * Если под statusBar контент не сплошного цвета, а, например, картинка,
217 | * то можно активировать режим дополнительной контрастности.
218 | *
219 | * По умолчанию используется [Color.TRANSPARENT]
220 | *
221 | * @see navBarEdgeToEdgeColor
222 | */
223 | @ColorInt
224 | var statusBarEdgeToEdgeColor = Color.TRANSPARENT
225 |
226 | /**
227 | * Если под navigationBar контент не сплошного цвета, а, например, картинка,
228 | * то можно активировать режим дополнительной контрастности.
229 | *
230 | * По умолчанию используется [Color.TRANSPARENT]
231 | *
232 | * @see statusBarEdgeToEdgeColor
233 | */
234 | @ColorInt
235 | var navBarEdgeToEdgeColor = Color.TRANSPARENT
236 |
237 | /**
238 | * Цвет иконок для statusBar можно менять только с 23 API. Для Android с API ниже 23
239 | * используется цвет, который будет хорошо контрастировать с белыми иконками.
240 | *
241 | * По умолчанию, для сохранения эффекта edge-to-edge, используется черный цвет с 50% прозрачностью.
242 | *
243 | * @see navBarCompatibilityColor
244 | */
245 | @ColorInt
246 | var statusBarCompatibilityColor = ColorUtils.setAlphaComponent(Color.BLACK, 128)
247 |
248 | /**
249 | * Цвет иконок для navigationBar можно менять только с 26 API. Для Android с API ниже 26
250 | * используется цвет, который будет хорошо контрастировать с белыми иконками.
251 | *
252 | * По умолчанию, для сохранения эффекта edge-to-edge, используется черный цвет с 50% прозрачностью.
253 | *
254 | * @see statusBarCompatibilityColor
255 | */
256 | @ColorInt
257 | var navBarCompatibilityColor = ColorUtils.setAlphaComponent(Color.BLACK, 128)
258 | }
259 | ```
260 |
261 | ## Зависимости
262 |
263 | Утилита использует следующие зависимости:
264 |
265 | ```kotlin
266 | implementation("org.jetbrains.kotlin:kotlin-stdlib:1.4.10") // Kotlin
267 | implementation("com.google.android.material:material:1.2.1") // Material components
268 | ```
269 |
270 | ## Feedback
271 |
272 | Если вы столкнулись с какими-либо ошибками или у вас есть полезные предложения
273 | по улучшению этой библиотеки, не стесняйтесь создавать
274 | [issue](https://github.com/RedMadRobot/edge-to-edge-decorator/issues).
275 |
276 | ## LICENSE
277 |
278 | >THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
279 | >OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
280 | >MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
281 | >IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
282 | >CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
283 | >TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
284 | >SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
285 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | apply from: "${rootProject.projectDir}/gradle/dependencies.gradle"
3 |
4 | addRepos(repositories)
5 |
6 | dependencies {
7 | classpath deps.gradle.android_plugin
8 | classpath deps.kotlin.plugin
9 | classpath deps.bintray.gradle_bintray_plugin
10 | }
11 | }
12 |
13 | allprojects {
14 | addRepos(repositories)
15 | }
16 |
17 | task clean(type: Delete) {
18 | delete rootProject.buildDir
19 | }
20 |
--------------------------------------------------------------------------------
/edge-to-edge-decorator/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/edge-to-edge-decorator/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 |
4 | apply from: "../gradle/version.gradle"
5 | apply from: '../gradle/publish.gradle'
6 |
7 | android {
8 | compileSdkVersion build_versions.compile_sdk
9 |
10 | defaultConfig {
11 | minSdkVersion build_versions.min_sdk
12 | targetSdkVersion build_versions.target_sdk
13 |
14 | versionCode getVersionCodeFromProperties()
15 | versionName getVersionNameFromProperties()
16 | }
17 |
18 | buildTypes {
19 | release {
20 | minifyEnabled false
21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt')
22 | }
23 | }
24 | }
25 |
26 | dependencies {
27 | implementation(
28 | deps.kotlin.stdlib,
29 | deps.google.material
30 | )
31 | }
32 |
--------------------------------------------------------------------------------
/edge-to-edge-decorator/library.properties:
--------------------------------------------------------------------------------
1 | lib_name=edge-to-edge-decorator
2 | lib_version=1.0.0
3 | lib_description=Edge to edge decorator - is a utility class that is responsible for coloring the statusBar and navigationBar to maintain edge to edge (e2e) mode.
4 | lib_vcs=https://github.com/RedMadRobot/edge-to-edge-decorator.git
5 | lib_issue_tracker=https://github.com/RedMadRobot/edge-to-edge-decorator/issues
6 |
--------------------------------------------------------------------------------
/edge-to-edge-decorator/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/edge-to-edge-decorator/src/main/java/com/redmadrobot/e2e/decorator/EdgeToEdgeDecorator.kt:
--------------------------------------------------------------------------------
1 | package com.redmadrobot.e2e.decorator
2 |
3 | import android.content.Context
4 | import android.graphics.Color
5 | import android.os.Build.VERSION
6 | import android.os.Build.VERSION_CODES
7 | import android.view.View
8 | import android.view.Window
9 | import androidx.annotation.AttrRes
10 | import androidx.annotation.ColorInt
11 | import androidx.annotation.ColorRes
12 | import androidx.core.content.ContextCompat
13 | import androidx.core.graphics.ColorUtils
14 | import com.google.android.material.color.MaterialColors
15 |
16 | /**
17 | * Класс отвечает за окрашивание statusBar и navigationBar для поддержания edge-to-edge режима.
18 | * Концепция основана на WindowPreferencesManager из
19 | * [приложения-каталога материальных комнонентов](https://github.com/material-components/material-components-android/blob/master/catalog/java/io/material/catalog/windowpreferences/WindowPreferencesManager.java)
20 | */
21 | object EdgeToEdgeDecorator {
22 |
23 | private const val EDGE_TO_EDGE_FLAGS = (View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
24 | or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
25 | or View.SYSTEM_UI_FLAG_LAYOUT_STABLE)
26 |
27 | private var config = DefaultConfig()
28 |
29 | /**
30 | * Метод позволяет модифицировать параметры работы edge-to-edge режима.
31 | *
32 | * @see DefaultConfig
33 | */
34 | fun updateConfig(block: DefaultConfig.() -> Unit): EdgeToEdgeDecorator {
35 | config = DefaultConfig().apply(block)
36 |
37 | return this
38 | }
39 |
40 | /**
41 | * Метод активирует edge-to-edge с выбранными параметрами.
42 | *
43 | * @see [updateConfig]
44 | */
45 | fun apply(context: Context, window: Window) {
46 | val decorView = window.decorView
47 |
48 | @ColorInt
49 | val statusBarColor = getStatusBarColor(context)
50 |
51 | @ColorInt
52 | val navBarColor = getNavBarColor(context)
53 |
54 | window.statusBarColor = statusBarColor
55 | window.navigationBarColor = navBarColor
56 |
57 | decorView.systemUiVisibility = getEdgeToEdgeFlag(decorView) or
58 | getStatusBarFlags(context, statusBarColor) or
59 | getNavBarFlags(context, navBarColor)
60 | }
61 |
62 | @ColorInt
63 | private fun getStatusBarColor(context: Context): Int {
64 | val opaqueStatusBarColor =
65 | MaterialColors.getColor(context, android.R.attr.statusBarColor, javaClass.canonicalName)
66 |
67 | return when {
68 | !config.isEdgeToEdgeEnabled -> opaqueStatusBarColor
69 | VERSION.SDK_INT < VERSION_CODES.M -> config.statusBarCompatibilityColor
70 | else -> config.statusBarEdgeToEdgeColor
71 | }
72 | }
73 |
74 | @ColorInt
75 | private fun getNavBarColor(context: Context): Int {
76 | val opaqueNavBarColor =
77 | MaterialColors.getColor(context, android.R.attr.navigationBarColor, javaClass.canonicalName)
78 |
79 | return when {
80 | !config.isEdgeToEdgeEnabled -> opaqueNavBarColor
81 | VERSION.SDK_INT < VERSION_CODES.O -> config.navBarCompatibilityColor
82 | else -> config.navBarEdgeToEdgeColor
83 | }
84 | }
85 |
86 |
87 | @ColorInt
88 | private fun getContentUnderStatusBarColor(context: Context): Int {
89 | val customContentColor = config.contentUnderStatusBarCustomColor
90 |
91 | return if (customContentColor != null) {
92 | ContextCompat.getColor(context, customContentColor)
93 | } else {
94 | MaterialColors.getColor(context, config.appBarColorAttr, javaClass.canonicalName)
95 | }
96 | }
97 |
98 | private fun getStatusBarFlags(context: Context, @ColorInt statusBarColor: Int): Int {
99 | val isLightContent = isColorLight(getContentUnderStatusBarColor(context))
100 | val isLightStatusBar = isColorLight(statusBarColor)
101 |
102 | val needShowDarkStatusBarIcons = isLightStatusBar || (statusBarColor == Color.TRANSPARENT && isLightContent)
103 |
104 | return if (needShowDarkStatusBarIcons && VERSION.SDK_INT >= VERSION_CODES.M) {
105 | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
106 | } else {
107 | 0
108 | }
109 | }
110 |
111 | @ColorInt
112 | private fun getContentUnderNavigationBarColor(context: Context): Int {
113 | val customContentColor = config.contentUnderNavBarCustomColor
114 |
115 | return if (customContentColor != null) {
116 | ContextCompat.getColor(context, customContentColor)
117 | } else {
118 | MaterialColors.getColor(context, config.backgroundColorAttr, javaClass.canonicalName)
119 | }
120 | }
121 |
122 | private fun getNavBarFlags(context: Context, @ColorInt navBarColor: Int): Int {
123 | val isLightContent = isColorLight(getContentUnderNavigationBarColor(context))
124 | val isLightNavBar = isColorLight(navBarColor)
125 |
126 | val showDarkNavBarIcons = isLightNavBar || (navBarColor == Color.TRANSPARENT && isLightContent)
127 |
128 | return if (showDarkNavBarIcons && VERSION.SDK_INT >= VERSION_CODES.O) {
129 | View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
130 | } else {
131 | 0
132 | }
133 | }
134 |
135 | private fun getEdgeToEdgeFlag(decorView: View): Int {
136 | val flagsWithoutEdgeToEdge = decorView.systemUiVisibility and EDGE_TO_EDGE_FLAGS.inv()
137 | val edgeToEdgeFlag = if (config.isEdgeToEdgeEnabled) EDGE_TO_EDGE_FLAGS else View.SYSTEM_UI_FLAG_VISIBLE
138 |
139 | return flagsWithoutEdgeToEdge or edgeToEdgeFlag
140 | }
141 |
142 |
143 | private fun isColorLight(@ColorInt color: Int): Boolean {
144 | return if (Color.alpha(color) != 255) {
145 | color != Color.TRANSPARENT && ColorUtils.calculateLuminance(color) > 0.5
146 | } else {
147 | val contrastWithWhiteText = ColorUtils.calculateContrast(Color.WHITE, color)
148 | val contrastWithBlackText = ColorUtils.calculateContrast(Color.BLACK, color)
149 |
150 | contrastWithBlackText > contrastWithWhiteText
151 | }
152 | }
153 |
154 | class DefaultConfig {
155 |
156 | /**
157 | * Флаг отвечает за включение/выключение edge-to-edge режима.
158 | */
159 | var isEdgeToEdgeEnabled = true
160 |
161 | /**
162 | * В простом edge-to-edge режиме. Цвет иконок statusBar устанавливается в соответствии
163 | * с цветом [com.google.android.material.appbar.AppBarLayout].
164 | *
165 | * Значение по умолчанию равно [R.attr.colorPrimarySurface].
166 | *
167 | * В этом случае цвет самого statusBar равен параметру [statusBarEdgeToEdgeColor],
168 | * по умолчанию [statusBarEdgeToEdgeColor] равен [Color.TRANSPARENT].
169 | *
170 | * Также в простом режиме используется [backgroundColorAttr]
171 | *
172 | * @see backgroundColorAttr
173 | * @see statusBarEdgeToEdgeColor
174 | */
175 | @AttrRes
176 | var appBarColorAttr = R.attr.colorPrimarySurface
177 |
178 | /**
179 | * В простом edge-to-edge режиме. Цвет иконок navigationBar устанавливается в соответствии
180 | * с цветом [android.R.attr.windowBackground]
181 | *
182 | * Значение по умолчанию равно [android.R.attr.windowBackground]
183 | *
184 | * В этом случае цвет самого navigationBar равен параметру [navBarCompatibilityColor],
185 | * по умолчанию [navBarCompatibilityColor] равен [Color.TRANSPARENT].
186 | *
187 | * Также в простом режиме используется [appBarColorAttr]
188 | *
189 | * @see appBarColorAttr
190 | * @see navBarCompatibilityColor
191 | */
192 | @AttrRes
193 | var backgroundColorAttr = android.R.attr.windowBackground
194 |
195 | /**
196 | * Если не подходит простой режим, например, для случаев, когда на экране нет
197 | * [com.google.android.material.appbar.AppBarLayout], можно активировать кастомный режим edge-to-edge.
198 | *
199 | * Для этого нужно передать конкретный цвет контента под statusBar, например, [R.color.windowBackground]
200 | *
201 | * @see contentUnderNavBarCustomColor
202 | */
203 | @ColorRes
204 | var contentUnderStatusBarCustomColor: Int? = null
205 |
206 | /**
207 | * Если не подходит простой режим, например, под navigationBar должен отрисовываться другой контет
208 | * или [BottomNavigationMenu], можно активировать кастомный режим edge-to-edge.
209 | *
210 | * Для этого нужно передать конкретный цвет контента под navigationBar, например, [R.color.bottomMenu]
211 | *
212 | * @see contentUnderStatusBarCustomColor
213 | */
214 | @ColorRes
215 | var contentUnderNavBarCustomColor: Int? = null
216 |
217 | /**
218 | * Если под statusBar контент не сплошного цвета, а, например, картинка,
219 | * то можно активировать режим дополнительной контрастности.
220 | *
221 | * По умолчанию используется [Color.TRANSPARENT]
222 | *
223 | * @see navBarEdgeToEdgeColor
224 | */
225 | @ColorInt
226 | var statusBarEdgeToEdgeColor = Color.TRANSPARENT
227 |
228 | /**
229 | * Если под navigationBar контент не сплошного цвета, а, например, картинка,
230 | * то можно активировать режим дополнительной контрастности.
231 | *
232 | * По умолчанию используется [Color.TRANSPARENT]
233 | *
234 | * @see statusBarEdgeToEdgeColor
235 | */
236 | @ColorInt
237 | var navBarEdgeToEdgeColor = Color.TRANSPARENT
238 |
239 | /**
240 | * Цвет иконок для statusBar можно менять только с 23 API. Для Android с API ниже 23
241 | * используется цвет, который будет хорошо контрастировать с белыми иконками.
242 | *
243 | * По умолчанию, для сохранения эффекта edge-to-edge, используется черный цвет с 50% прозрачностью.
244 | *
245 | * @see navBarCompatibilityColor
246 | */
247 | @ColorInt
248 | var statusBarCompatibilityColor = ColorUtils.setAlphaComponent(Color.BLACK, 128)
249 |
250 | /**
251 | * Цвет иконок для navigationBar можно менять только с 26 API. Для Android с API ниже 26
252 | * используется цвет, который будет хорошо контрастировать с белыми иконками.
253 | *
254 | * По умолчанию, для сохранения эффекта edge-to-edge, используется черный цвет с 50% прозрачностью.
255 | *
256 | * @see statusBarCompatibilityColor
257 | */
258 | @ColorInt
259 | var navBarCompatibilityColor = ColorUtils.setAlphaComponent(Color.BLACK, 128)
260 | }
261 | }
262 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 | # Kotlin code style for this project: "official" or "obsolete":
21 | kotlin.code.style=official
22 |
--------------------------------------------------------------------------------
/gradle/dependencies.gradle:
--------------------------------------------------------------------------------
1 | static def addRepos(RepositoryHandler handler) {
2 | handler.google()
3 | handler.jcenter()
4 | }
5 |
6 | ext.addRepos = this.&addRepos
7 |
8 | //region Build versions
9 | def build_versions = [:]
10 |
11 | build_versions.min_sdk = 21
12 | build_versions.target_sdk = 30
13 | build_versions.compile_sdk = 30
14 |
15 | ext.build_versions = build_versions
16 | //endregion
17 |
18 | def versions = [:]
19 |
20 | ext.deps = [:]
21 | def deps = [:]
22 |
23 | //region Gradle
24 | def gradle = [:]
25 |
26 | versions.android_plugin = '4.0.1'
27 |
28 | gradle.android_plugin = "com.android.tools.build:gradle:$versions.android_plugin"
29 |
30 | deps.gradle = gradle
31 | //endregion
32 |
33 | //region Kotlin
34 | def kotlin = [:]
35 |
36 | versions.kotlin = "1.4.10"
37 |
38 | kotlin.plugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlin"
39 | kotlin.stdlib = "org.jetbrains.kotlin:kotlin-stdlib:$versions.kotlin"
40 |
41 | deps.kotlin = kotlin
42 | //endregion
43 |
44 | //regin Third party
45 |
46 | def google = [:]
47 |
48 | versions.material = "1.2.1"
49 |
50 | google.material = "com.google.android.material:material:$versions.material"
51 |
52 | deps.google = google
53 |
54 | //endregion
55 |
56 | //region Bintray
57 | def bintray = [:]
58 |
59 | versions.bintray_plugin = "1.8.5"
60 |
61 | bintray.gradle_bintray_plugin = "com.jfrog.bintray.gradle:gradle-bintray-plugin:$versions.bintray_plugin"
62 |
63 | deps.bintray = bintray
64 | //endregion
65 |
66 | ext.deps = deps
67 |
--------------------------------------------------------------------------------
/gradle/publish.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: "maven-publish"
2 | apply plugin: "com.jfrog.bintray"
3 |
4 | def publishPropertiesFile = rootProject.file("gradle/publish.properties")
5 | def publishProperties = new Properties()
6 |
7 | if (publishPropertiesFile.canRead()) {
8 | publishProperties.load(new FileInputStream(publishPropertiesFile))
9 | }
10 |
11 | def libraryPropertiesFile = file("library.properties")
12 | def libraryProperties = new Properties()
13 |
14 | if (libraryPropertiesFile.canRead()) {
15 | libraryProperties.load(new FileInputStream(libraryPropertiesFile))
16 | }
17 |
18 | task androidSourcesJar(type: Jar) {
19 | archiveClassifier.set('sources')
20 | from android.sourceSets.main.java.srcDirs
21 | }
22 |
23 | afterEvaluate {
24 | publishing {
25 | publications {
26 | release(MavenPublication) {
27 | from components.release
28 |
29 | artifact androidSourcesJar
30 |
31 | groupId = publishProperties["group_id"]
32 | artifactId = libraryProperties["lib_name"]
33 | version = getVersionNameFromProperties()
34 | }
35 | }
36 | }
37 | }
38 |
39 | bintray {
40 | user = System.getenv("BINTRAY_USER")
41 | key = System.getenv("BINTRAY_KEY")
42 | publications = ["release"]
43 | override = true
44 |
45 | pkg {
46 | repo = publishProperties["repository"]
47 | name = libraryProperties["lib_name"]
48 | description = libraryProperties["lib_description"]
49 | userOrg = publishProperties["organization_id"]
50 | licenses = ["MIT"]
51 | vcsUrl = libraryProperties["lib_vcs"]
52 | issueTrackerUrl = libraryProperties["lib_issue_tracker"]
53 | publish = true
54 | publicDownloadNumbers = true
55 |
56 | version {
57 | name = getVersionNameFromProperties()
58 | released = new Date()
59 | vcsTag = getVersionNameFromProperties()
60 | }
61 |
62 | dryRun = true
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/gradle/version.gradle:
--------------------------------------------------------------------------------
1 | def getVersionCodeFromProperties() {
2 | Properties versionProperties = loadVersionProperties()
3 |
4 | def (major, minor, patch) = versionProperties["lib_version"].tokenize('.')
5 |
6 | (major, minor, patch) = [major, minor, patch].collect { it.toInteger() }
7 |
8 | return (major * 1000000) + (minor * 10000) + (patch * 100)
9 | }
10 |
11 | def getVersionNameFromProperties() {
12 | Properties versionProperties = loadVersionProperties()
13 | return versionProperties["lib_version"]
14 | }
15 |
16 | private Properties loadVersionProperties() {
17 | Properties versionProperties = new Properties()
18 | File propertiesFile = file('library.properties')
19 |
20 | if (propertiesFile.exists()) {
21 | //noinspection GroovyAssignabilityCheck
22 | versionProperties.load(propertiesFile.newDataInputStream())
23 | } else {
24 | // Stub for CI
25 | versionProperties["lib_version"] = "1.0"
26 | }
27 |
28 | versionProperties
29 | }
30 |
31 | ext {
32 | getVersionCodeFromProperties = this.&getVersionCodeFromProperties
33 | getVersionNameFromProperties = this.&getVersionNameFromProperties
34 | }
35 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Sep 25 02:21:51 MSK 2020
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/images/sample_21_api.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RedMadRobot/edge-to-edge-decorator/0e83a382f0113993621bacedad2f38f286aa86ab/images/sample_21_api.gif
--------------------------------------------------------------------------------
/images/sample_25_api.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RedMadRobot/edge-to-edge-decorator/0e83a382f0113993621bacedad2f38f286aa86ab/images/sample_25_api.gif
--------------------------------------------------------------------------------
/images/sample_28_api.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RedMadRobot/edge-to-edge-decorator/0e83a382f0113993621bacedad2f38f286aa86ab/images/sample_28_api.gif
--------------------------------------------------------------------------------
/images/sample_30_api.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RedMadRobot/edge-to-edge-decorator/0e83a382f0113993621bacedad2f38f286aa86ab/images/sample_30_api.gif
--------------------------------------------------------------------------------
/record_sample_screen.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | screen_size="$(adb shell dumpsys window | grep cur= |tr -s " " | cut -d " " -f 4|cut -d "=" -f 2)"
4 |
5 | wight="$(cut -d'x' -f1 <<<"$screen_size")"
6 | height="$(cut -d'x' -f2 <<<"$screen_size")"
7 |
8 | wight_center=$((wight/2))
9 |
10 | echo height: "$height"
11 | echo wight: "$wight"
12 |
13 | echo wight_center: $wight_center
14 |
15 | adb shell content insert --uri content://settings/system --bind name:s:show_touches --bind value:i:1
16 |
17 | sleep 3
18 | adb shell input touchscreen swipe $wight_center $((height*80/100)) $wight_center $((height*5/100)) 700
19 | sleep 1
20 | adb shell input touchscreen swipe $wight_center $((height*80/100)) $wight_center $((height*5/100)) 700
21 | sleep 2
22 |
23 | adb shell input touchscreen swipe $wight_center $((height*20/100)) $wight_center $((height*95/100)) 700
24 | adb shell input touchscreen swipe $wight_center $((height*20/100)) $wight_center $((height*95/100)) 700
25 | sleep 4
26 |
27 | adb shell content insert --uri content://settings/system --bind name:s:show_touches --bind value:i:0
28 |
--------------------------------------------------------------------------------
/sample/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/sample/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'kotlin-android-extensions'
4 |
5 | android {
6 | compileSdkVersion build_versions.compile_sdk
7 |
8 | defaultConfig {
9 | applicationId "com.redmadrobot.sample"
10 |
11 | minSdkVersion build_versions.min_sdk
12 | targetSdkVersion build_versions.target_sdk
13 |
14 | versionCode 1
15 | versionName "1.0"
16 | }
17 |
18 | buildFeatures {
19 | viewBinding true
20 | }
21 |
22 | buildTypes {
23 | release {
24 | minifyEnabled false
25 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt')
26 | }
27 | }
28 | }
29 |
30 | dependencies {
31 | implementation project(':edge-to-edge-decorator')
32 |
33 | implementation(
34 | deps.kotlin.stdlib,
35 | deps.google.material,
36 | "androidx.appcompat:appcompat:1.2.0",
37 | "androidx.constraintlayout:constraintlayout:2.0.2",
38 | "androidx.core:core-ktx:1.3.1",
39 | "dev.chrisbanes:insetter-ktx:0.3.1"
40 | )
41 | }
42 |
--------------------------------------------------------------------------------
/sample/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/redmadrobot/sample/BaseActivity.kt:
--------------------------------------------------------------------------------
1 | package com.redmadrobot.sample
2 |
3 | import android.os.Bundle
4 | import androidx.appcompat.app.AppCompatActivity
5 | import com.redmadrobot.e2e.decorator.EdgeToEdgeDecorator
6 |
7 | abstract class BaseActivity(activityMain: Int) : AppCompatActivity(activityMain) {
8 |
9 | protected open val edgeToEdgeCompatibilityManager = EdgeToEdgeDecorator.updateConfig {
10 | // default config
11 | isEdgeToEdgeEnabled = false
12 | }
13 |
14 | override fun onCreate(savedInstanceState: Bundle?) {
15 | super.onCreate(savedInstanceState)
16 |
17 | edgeToEdgeCompatibilityManager.apply(this, window)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/redmadrobot/sample/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.redmadrobot.sample
2 |
3 | import android.os.Bundle
4 | import androidx.recyclerview.widget.LinearLayoutManager
5 | import com.redmadrobot.e2e.decorator.EdgeToEdgeDecorator
6 | import com.redmadrobot.sample.databinding.ActivityMainBinding
7 | import dev.chrisbanes.insetter.applySystemWindowInsetsToPadding
8 |
9 | class MainActivity : BaseActivity(R.layout.activity_main) {
10 |
11 | private lateinit var binding: ActivityMainBinding
12 |
13 | override val edgeToEdgeCompatibilityManager = EdgeToEdgeDecorator.updateConfig {
14 | // custom config
15 | isEdgeToEdgeEnabled = true
16 | }
17 |
18 | override fun onCreate(savedInstanceState: Bundle?) {
19 | setTheme(R.style.Theme_App)
20 | super.onCreate(savedInstanceState)
21 |
22 | binding = ActivityMainBinding.inflate(layoutInflater)
23 | setContentView(binding.root)
24 |
25 | initViews()
26 | }
27 |
28 | private fun initViews() {
29 | binding.appBar.applySystemWindowInsetsToPadding(top = true)
30 |
31 | val data = (0..10).map {
32 | SampleData(
33 | image = R.drawable.ic_launcher_foreground,
34 | title = "Title $it",
35 | description = "Description $it"
36 | )
37 | }
38 |
39 | with(binding.recycler) {
40 | setHasFixedSize(true)
41 | layoutManager = LinearLayoutManager(context)
42 | adapter = SampleAdapter(data)
43 | applySystemWindowInsetsToPadding(bottom = true)
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/redmadrobot/sample/SampleAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.redmadrobot.sample
2 |
3 | import android.view.LayoutInflater
4 | import android.view.ViewGroup
5 | import androidx.annotation.DrawableRes
6 | import androidx.recyclerview.widget.RecyclerView
7 | import com.redmadrobot.sample.databinding.ItemSampleBinding
8 |
9 | class SampleAdapter(private val data: List) : RecyclerView.Adapter() {
10 |
11 | override fun getItemCount() = data.size
12 |
13 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SampleViewHolder {
14 | val item = ItemSampleBinding.inflate(LayoutInflater.from(parent.context), parent, false)
15 | return SampleViewHolder(item)
16 | }
17 |
18 | override fun onBindViewHolder(holder: SampleViewHolder, position: Int) {
19 | with(holder.item) {
20 | image.setImageResource(data[position].image)
21 |
22 | title.text = data[position].title
23 | description.text = data[position].description
24 | }
25 | }
26 |
27 | class SampleViewHolder(val item: ItemSampleBinding) : RecyclerView.ViewHolder(item.root)
28 | }
29 |
30 | data class SampleData(
31 | @DrawableRes
32 | val image: Int,
33 | val title: String,
34 | val description: String
35 | )
36 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
31 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
19 |
20 |
21 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/item_sample.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
13 |
14 |
26 |
27 |
32 |
33 |
48 |
49 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RedMadRobot/edge-to-edge-decorator/0e83a382f0113993621bacedad2f38f286aa86ab/sample/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RedMadRobot/edge-to-edge-decorator/0e83a382f0113993621bacedad2f38f286aa86ab/sample/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RedMadRobot/edge-to-edge-decorator/0e83a382f0113993621bacedad2f38f286aa86ab/sample/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RedMadRobot/edge-to-edge-decorator/0e83a382f0113993621bacedad2f38f286aa86ab/sample/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RedMadRobot/edge-to-edge-decorator/0e83a382f0113993621bacedad2f38f286aa86ab/sample/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RedMadRobot/edge-to-edge-decorator/0e83a382f0113993621bacedad2f38f286aa86ab/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RedMadRobot/edge-to-edge-decorator/0e83a382f0113993621bacedad2f38f286aa86ab/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RedMadRobot/edge-to-edge-decorator/0e83a382f0113993621bacedad2f38f286aa86ab/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RedMadRobot/edge-to-edge-decorator/0e83a382f0113993621bacedad2f38f286aa86ab/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RedMadRobot/edge-to-edge-decorator/0e83a382f0113993621bacedad2f38f286aa86ab/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/sample/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | #64b5f6
5 | #9be7ff
6 | #2286c3
7 |
8 | #546e7a
9 | #819ca9
10 | #29434e
11 |
12 | #000000
13 | #ffffff
14 |
15 |
16 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | edge-to-edge-decorator
3 |
4 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
20 |
29 |
30 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':edge-to-edge-decorator', ':sample'
2 | rootProject.name = "edge-to-edge-decorator"
3 |
--------------------------------------------------------------------------------