├── .github
└── workflows
│ ├── android.yml
│ └── publish.yml
├── .gitignore
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle.kts
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── gojek
│ │ └── draftsman
│ │ └── demo
│ │ └── LaunchActivity.kt
│ └── res
│ ├── layout
│ └── launch_activity_layout.xml
│ ├── mipmap-hdpi
│ └── ic_launcher.png
│ ├── mipmap-mdpi
│ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxxhdpi
│ └── ic_launcher.png
│ └── values
│ ├── colors.xml
│ ├── strings.xml
│ └── styles.xml
├── build.gradle.kts
├── draftsman-no-op
├── build.gradle.kts
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── kotlin
│ └── com
│ └── gojek
│ └── draftsman
│ └── Draftsman.kt
├── draftsman
├── art
│ ├── draftsman-base.png
│ ├── draftsman-drawer.png
│ ├── draftsman-enabled.png
│ ├── draftsman-grid.png
│ ├── draftsman-margin.png
│ ├── draftsman-nested-views.png
│ ├── draftsman-overlay.png
│ ├── draftsman-padding.png
│ ├── draftsman-text.png
│ ├── draftsman-view.png
│ └── logo.svg
├── build.gradle.kts
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── kotlin
│ └── com
│ │ └── gojek
│ │ └── draftsman
│ │ ├── Draftsman.kt
│ │ └── internal
│ │ ├── Config.kt
│ │ ├── DraftsmanLayout.kt
│ │ ├── constants
│ │ └── VisualizerIds.kt
│ │ ├── drawer
│ │ ├── DefaultAnimationListener.kt
│ │ ├── DrawerAdapter.kt
│ │ ├── DrawerInteractions.kt
│ │ ├── DrawerOptionHandler.kt
│ │ ├── DrawerPathGenerator.kt
│ │ ├── ModernDrawerLayout.kt
│ │ ├── Orientation.kt
│ │ ├── ResourcesUtils.kt
│ │ └── State.kt
│ │ ├── extensions
│ │ ├── SeekBar.kt
│ │ ├── View.kt
│ │ └── ViewGroup.kt
│ │ ├── fragment
│ │ ├── FileChooserFragment.kt
│ │ └── RequestPermissionFragment.kt
│ │ ├── model
│ │ ├── DrawerItem.kt
│ │ └── LayeredView.kt
│ │ ├── utils
│ │ ├── ColorUtils.kt
│ │ ├── DimensionConverter.kt
│ │ ├── DimensionUtils.kt
│ │ ├── DrawerLayoutContentList.kt
│ │ ├── LayeredViewBuilder.kt
│ │ ├── RectUtils.kt
│ │ └── TypeAlias.kt
│ │ ├── visualizers
│ │ ├── GridVisualizer.kt
│ │ ├── InfoVisualizer.kt
│ │ ├── LayeringVisualizer.kt
│ │ ├── MarginVisualizer.kt
│ │ ├── OverlayVisualizer.kt
│ │ ├── PaddingVisualizer.kt
│ │ └── TextVisualizer.kt
│ │ └── widgets
│ │ ├── ColorView.kt
│ │ ├── HShapeView.kt
│ │ ├── InspectionRootItemView.kt
│ │ └── NestedViewBoundView.kt
│ └── res
│ ├── drawable
│ ├── draftsman_drawer_item_bg.xml
│ ├── draftsman_ic_change_root.xml
│ ├── draftsman_ic_close.xml
│ ├── draftsman_ic_drawer_close.xml
│ ├── draftsman_ic_drawer_open.xml
│ ├── draftsman_ic_grid.xml
│ ├── draftsman_ic_margin.xml
│ ├── draftsman_ic_padding.xml
│ ├── draftsman_ic_place_overlay.xml
│ ├── draftsman_info_view_bg.xml
│ ├── draftsman_rounded_btn_bg.xml
│ └── draftsman_vertical_divider.xml
│ ├── layout
│ ├── draftsman_drawer_item_header.xml
│ ├── draftsman_drawer_item_range.xml
│ ├── draftsman_drawer_item_selection.xml
│ ├── draftsman_drawer_item_toggle.xml
│ ├── draftsman_drawer_layout.xml
│ ├── draftsman_info_layout.xml
│ ├── overlay_opacity_control_layout.xml
│ └── view_layering_layout.xml
│ └── values
│ ├── attrs.xml
│ └── styles.xml
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── scripts
├── publish-module.gradle
└── publish-root.gradle
└── settings.gradle.kts
/.github/workflows/android.yml:
--------------------------------------------------------------------------------
1 | name: Android CI
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 | pull_request:
7 | branches: [ main ]
8 |
9 | jobs:
10 | build:
11 |
12 | runs-on: ubuntu-latest
13 |
14 | steps:
15 | - uses: actions/checkout@v2
16 | - name: set up JDK 11
17 | uses: actions/setup-java@v2
18 | with:
19 | java-version: '11'
20 | distribution: 'temurin'
21 | cache: gradle
22 |
23 | - name: Grant execute permission for gradlew
24 | run: chmod +x gradlew
25 | - name: Build with Gradle
26 | run: ./gradlew assemble
27 |
28 | - name: Archive aar
29 | uses: actions/upload-artifact@v2
30 | with:
31 | name: draftsman
32 | path: draftsman/build/outputs/aar/**.aar
33 |
34 | - name: Archive APK
35 | uses: actions/upload-artifact@v2
36 | with:
37 | name: app
38 | path: app/build/outputs/apk/**/**.apk
39 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish
2 |
3 | on:
4 | push:
5 | # Sequence of patterns matched against refs/tags
6 | tags:
7 | - '[0-9]+.[0-9]+.[0-9]+' # Push events to any matching semantic tag. For example, 1.10.1 or 2.0.0.
8 | # For more details, see https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet
9 | # and https://docs.npmjs.com/about-semantic-versioning
10 |
11 | jobs:
12 | publish:
13 | name: Release build and publish
14 | runs-on: ubuntu-latest
15 | steps:
16 | - uses: actions/checkout@v2
17 | - name: set up JDK 11
18 | uses: actions/setup-java@v2
19 | with:
20 | java-version: '11'
21 | distribution: 'temurin'
22 | cache: gradle
23 | - name: Release build
24 | run: ./gradlew :draftsman:assembleRelease :draftsman-no-op:assembleRelease
25 | - name: Publish to MavenCentral
26 | run: ./gradlew draftsman:publishReleasePublicationToSonatypeRepository --max-workers 1 closeAndReleaseSonatypeStagingRepository
27 | env:
28 | OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
29 | OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
30 | SIGNING_KEY_ID: ${{ secrets.SIGNING_KEY_ID }}
31 | SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }}
32 | SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
33 | SONATYPE_STAGING_PROFILE_ID: ${{ secrets.SONATYPE_STAGING_PROFILE_ID }}
34 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | # Built application files
3 | *.apk
4 | *.ap_
5 |
6 | # Files for the ART/Dalvik VM
7 | *.dex
8 |
9 | # Java class files
10 | *.class
11 |
12 | # Generated files
13 | bin/
14 | gen/
15 | out/
16 |
17 | # Gradle files
18 | .gradle/
19 | build/
20 | build-time/
21 | # Local configuration file (sdk path, etc)
22 | local.properties
23 |
24 | # Log Files
25 | *.log
26 |
27 | # Android Studio captures folder
28 | captures/
29 |
30 | # IntelliJ
31 | *.iml
32 | .idea/
33 |
34 | # External native build folder generated in Android Studio 2.2 and later
35 | .externalNativeBuild
36 |
37 | # jacoco
38 | *.exec
39 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright 2022 asphalt
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Draftsman
5 |
6 |
7 | Draftsman is an on-device layout inspector for Android apps. It allows you to view various properties of rendered Android Views such as width, height, class name, paddings, margins etc.
8 |
9 | Draftsman aims to combine capabilities of different Android tools such as `Show Layout Bounds` and Android Studio's layout inspector.
10 |
11 | The primary objective of Draftsman is to provide information around rendered views directly from your app which is easily accessible to devs, designers, PMs or any one who has installed the app. No need to use any external tool or rely on debug variant of your app.
12 |
13 | ## Features
14 |
15 | * Width & Height Info for any view
16 |
17 | * Class name for any view
18 |
19 | * Margin and Padding visualization
20 |
21 | * Dimension values in both Pixel(Px) and dp/sp
22 |
23 | * TextView color and size information
24 |
25 | * Overlay a grid
26 |
27 | * Overlay a image to compare UI
28 |
29 | Check usage guide at bottom for more details.
30 |
31 | ## Integration
32 | [](https://search.maven.org/search?q=g:%22com.gojek.draftsman%22%20AND%20a:%22draftsman%22)
33 |
34 | Add following code to your build.gradle
35 |
36 | ```groovy
37 | repositories {
38 | mavenCentral()
39 | }
40 |
41 | dependencies {
42 | implementation "com.gojek.draftsman:draftsman:x.y.z"
43 | }
44 | ```
45 |
46 | To enable Draftsman in an activity
47 |
48 | ```kotlin
49 | Draftsman.install(activity)
50 | ```
51 |
52 | To exit from Draftsman, there is an exit button in information window. And if you want do it programmatically, you can call
53 |
54 | ```kotlin
55 | Draftsman.uninstall(activity)
56 | ```
57 |
58 | ## Usage Guide
59 |
60 | This section will help you understand what information Draftsman can capture from a view.
61 |
62 | This is our base view
63 |
64 | 
65 |
66 | **When Draftsman is enabled**
67 |
68 | This is how our view looks like once Draftsman is enabled. Inspectable views are highlighted and a arrow appears on right to open drawer.
69 |
70 | 
71 |
72 | **Draftsman Setting Drawer**
73 |
74 | You can use this drawer to customise a few settings.
75 |
76 | 
77 |
78 | **TextView Inspection**
79 |
80 | On tapping of first text "Hi Folks" we can observe this overlay. It provides information on height, width, textsize and textcolor. Dimensions can be observed in dp or px.
81 |
82 | Inspection details overlay can be closed using cross icon and Draftsman can be closed by pressing Exit button.
83 |
84 | 
85 |
86 | **View Inspection**
87 |
88 | Any arbitrary view can be inspected as well allowing us to inspect height, width and class name
89 |
90 | 
91 |
92 | **Margin and Padding**
93 |
94 | Tapping around views highlights margins and paddings if available. Padding is shown with green background and margin with red line.
95 |
96 | 
97 |
98 | 
99 |
100 | **Nested Views**
101 |
102 | If there are multiple views stacked within same bounds, Draftsman will prompt you to select the view to be inspected
103 |
104 | 
105 |
106 | **Grid Overlay**
107 |
108 | You can also add a size configurable grid overlay on your screen from setting drawer.
109 |
110 | 
111 |
112 | **Image Overlay**
113 |
114 | You can also overlay a screenshot on top of existing screen to check for UI differences. A slider on bottom can be used to fade out overlay image.
115 |
116 | Note: Storage read permission should be provided for this to work.
117 |
118 | 
119 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 |
2 | plugins {
3 | id("com.android.application")
4 | kotlin("android")
5 | kotlin("android.extensions")
6 | }
7 |
8 | android {
9 | compileSdkVersion(29)
10 | defaultConfig {
11 | minSdkVersion(21)
12 | targetSdkVersion(29)
13 | applicationId = "com.gojek.draftsman.demo"
14 | }
15 | }
16 |
17 | dependencies {
18 | implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.4.31")
19 | implementation("androidx.core:core-ktx:1.5.0")
20 | implementation("androidx.appcompat:appcompat:1.1.0")
21 | implementation("com.google.android.material:material:1.1.0")
22 | debugImplementation(project(":draftsman"))
23 | releaseImplementation(project(":draftsman-no-op"))
24 | }
25 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
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.kts.
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
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
10 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/java/com/gojek/draftsman/demo/LaunchActivity.kt:
--------------------------------------------------------------------------------
1 | package com.gojek.draftsman.demo
2 |
3 | import android.os.Bundle
4 | import android.widget.Toast
5 | import androidx.appcompat.app.AppCompatActivity
6 | import com.gojek.draftsman.Draftsman
7 | import kotlinx.android.synthetic.main.launch_activity_layout.*
8 |
9 | class LaunchActivity : AppCompatActivity() {
10 |
11 | override fun onCreate(savedInstanceState: Bundle?) {
12 | super.onCreate(savedInstanceState)
13 |
14 | setContentView(R.layout.launch_activity_layout)
15 |
16 | enable.setOnClickListener {
17 | Draftsman.install(this)
18 | Toast.makeText(this, "Added Draftsman overlay", Toast.LENGTH_SHORT).show()
19 | }
20 |
21 | disable.setOnClickListener {
22 | Draftsman.uninstall(this)
23 | Toast.makeText(this, "Removed Draftsman overlay", Toast.LENGTH_SHORT).show()
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/launch_activity_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
17 |
18 |
26 |
27 |
34 |
35 |
39 |
40 |
41 |
42 |
50 |
51 |
52 |
53 |
59 |
60 |
64 |
65 |
70 |
71 |
72 |
73 |
80 |
81 |
88 |
89 |
90 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gojek/draftsman/b0909fb15abad702dd8b8a985f7c60e2f92bdbf0/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gojek/draftsman/b0909fb15abad702dd8b8a985f7c60e2f92bdbf0/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gojek/draftsman/b0909fb15abad702dd8b8a985f7c60e2f92bdbf0/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gojek/draftsman/b0909fb15abad702dd8b8a985f7c60e2f92bdbf0/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gojek/draftsman/b0909fb15abad702dd8b8a985f7c60e2f92bdbf0/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #6200EE
4 | #3700B3
5 | #03DAC5
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Draftsman Demo
3 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | buildscript {
3 | repositories {
4 | maven(url = "https://plugins.gradle.org/m2/")
5 | mavenCentral()
6 | google()
7 | jcenter()
8 | }
9 | dependencies {
10 | classpath("com.android.tools.build:gradle:4.1.1")
11 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.31")
12 | classpath("io.github.gradle-nexus:publish-plugin:1.1.0")
13 |
14 | // NOTE: Do not place your application dependencies here; they belong
15 | // in the individual module build.gradle.kts files
16 | }
17 | }
18 |
19 | allprojects {
20 | repositories {
21 | mavenCentral()
22 | google()
23 | jcenter()
24 | }
25 | }
26 |
27 | tasks.register("clean", Delete::class) {
28 | delete(rootProject.buildDir)
29 | }
30 |
31 | apply(plugin = "io.github.gradle-nexus.publish-plugin")
32 | apply(from = "${rootDir}/scripts/publish-root.gradle")
33 |
--------------------------------------------------------------------------------
/draftsman-no-op/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.android.library")
3 | kotlin("android")
4 | }
5 |
6 | android {
7 | compileSdkVersion(29)
8 | defaultConfig {
9 | minSdkVersion(21)
10 | targetSdkVersion(29)
11 | }
12 |
13 | sourceSets["main"].java.srcDirs(
14 | "src/main/kotlin"
15 | )
16 | }
17 |
18 | ext {
19 | set("PUBLISH_GROUP_ID", "com.gojek.draftsman")
20 | set("PUBLISH_VERSION", "0.0.3")
21 | set("PUBLISH_ARTIFACT_ID", "draftsman-no-op")
22 | }
23 |
24 | apply(from = "${rootProject.projectDir}/scripts/publish-module.gradle")
25 |
--------------------------------------------------------------------------------
/draftsman-no-op/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
--------------------------------------------------------------------------------
/draftsman-no-op/src/main/kotlin/com/gojek/draftsman/Draftsman.kt:
--------------------------------------------------------------------------------
1 | package com.gojek.draftsman
2 |
3 | import android.app.Activity
4 |
5 | class Draftsman {
6 |
7 | companion object {
8 |
9 | fun install(activity: Activity) {}
10 |
11 | fun uninstall(activity: Activity) {}
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/draftsman/art/draftsman-base.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gojek/draftsman/b0909fb15abad702dd8b8a985f7c60e2f92bdbf0/draftsman/art/draftsman-base.png
--------------------------------------------------------------------------------
/draftsman/art/draftsman-drawer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gojek/draftsman/b0909fb15abad702dd8b8a985f7c60e2f92bdbf0/draftsman/art/draftsman-drawer.png
--------------------------------------------------------------------------------
/draftsman/art/draftsman-enabled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gojek/draftsman/b0909fb15abad702dd8b8a985f7c60e2f92bdbf0/draftsman/art/draftsman-enabled.png
--------------------------------------------------------------------------------
/draftsman/art/draftsman-grid.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gojek/draftsman/b0909fb15abad702dd8b8a985f7c60e2f92bdbf0/draftsman/art/draftsman-grid.png
--------------------------------------------------------------------------------
/draftsman/art/draftsman-margin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gojek/draftsman/b0909fb15abad702dd8b8a985f7c60e2f92bdbf0/draftsman/art/draftsman-margin.png
--------------------------------------------------------------------------------
/draftsman/art/draftsman-nested-views.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gojek/draftsman/b0909fb15abad702dd8b8a985f7c60e2f92bdbf0/draftsman/art/draftsman-nested-views.png
--------------------------------------------------------------------------------
/draftsman/art/draftsman-overlay.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gojek/draftsman/b0909fb15abad702dd8b8a985f7c60e2f92bdbf0/draftsman/art/draftsman-overlay.png
--------------------------------------------------------------------------------
/draftsman/art/draftsman-padding.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gojek/draftsman/b0909fb15abad702dd8b8a985f7c60e2f92bdbf0/draftsman/art/draftsman-padding.png
--------------------------------------------------------------------------------
/draftsman/art/draftsman-text.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gojek/draftsman/b0909fb15abad702dd8b8a985f7c60e2f92bdbf0/draftsman/art/draftsman-text.png
--------------------------------------------------------------------------------
/draftsman/art/draftsman-view.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gojek/draftsman/b0909fb15abad702dd8b8a985f7c60e2f92bdbf0/draftsman/art/draftsman-view.png
--------------------------------------------------------------------------------
/draftsman/art/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/draftsman/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.android.library")
3 | kotlin("android")
4 | }
5 |
6 | android {
7 | compileSdkVersion(29)
8 | defaultConfig {
9 | minSdkVersion(21)
10 | targetSdkVersion(29)
11 | }
12 |
13 | sourceSets["main"].java.srcDirs(
14 | "src/main/kotlin"
15 | )
16 | }
17 | dependencies {
18 | implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.4.31")
19 | implementation("androidx.appcompat:appcompat:1.1.0")
20 | implementation("androidx.recyclerview:recyclerview:1.1.0")
21 | }
22 |
23 | ext {
24 | set("PUBLISH_GROUP_ID", "com.gojek.draftsman")
25 | set("PUBLISH_VERSION", "0.0.3")
26 | set("PUBLISH_ARTIFACT_ID", "draftsman")
27 | }
28 |
29 | apply(from = "${rootProject.projectDir}/scripts/publish-module.gradle")
30 |
--------------------------------------------------------------------------------
/draftsman/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/draftsman/src/main/kotlin/com/gojek/draftsman/Draftsman.kt:
--------------------------------------------------------------------------------
1 | package com.gojek.draftsman
2 |
3 | import android.app.Activity
4 | import android.view.View
5 | import android.view.ViewGroup
6 | import com.gojek.draftsman.internal.DraftsmanLayout
7 |
8 | class Draftsman {
9 |
10 | private fun installVision(
11 | activity: Activity,
12 | view: View
13 | ) {
14 |
15 | val draftsman = DraftsmanLayout(activity)
16 | val location = IntArray(2)
17 | activity.findViewById(android.R.id.content).apply {
18 | addView(draftsman)
19 | getLocationOnScreen(location)
20 | draftsman.init(location[1], view) { uninstall(activity) }
21 | }
22 | }
23 |
24 | companion object {
25 |
26 | fun install(activity: Activity) {
27 | val root = activity.findViewById(android.R.id.content)
28 | val content = root.getChildAt(0)
29 | Draftsman().installVision(activity, content)
30 | }
31 |
32 | fun uninstall(activity: Activity) {
33 | val root = activity.findViewById(android.R.id.content)
34 | for (index in 0 until root.childCount) {
35 | val child = root.getChildAt(index)
36 | if (child is DraftsmanLayout) {
37 | root.removeView(child)
38 | break
39 | }
40 | }
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/draftsman/src/main/kotlin/com/gojek/draftsman/internal/Config.kt:
--------------------------------------------------------------------------------
1 | package com.gojek.draftsman.internal
2 |
3 | import android.graphics.Color
4 |
5 | internal object Config {
6 |
7 | var usePx = true
8 |
9 | var paddingEnabled = true
10 |
11 | var marginEnabled = true
12 |
13 | var gridEnabled = false
14 |
15 | val boundLineColor = Color.parseColor("#311B92")
16 |
17 | val selectedBoundColor = Color.parseColor("#2962FF")
18 |
19 | val marginColor = Color.parseColor("#cd0000")
20 |
21 | val paddingColor = Color.parseColor("#6676FF03")
22 |
23 | val gridColor = Color.parseColor("#777777")
24 |
25 | fun reset() {
26 | usePx = true
27 | paddingEnabled = true
28 | marginEnabled = true
29 | gridEnabled = false
30 | }
31 | }
--------------------------------------------------------------------------------
/draftsman/src/main/kotlin/com/gojek/draftsman/internal/DraftsmanLayout.kt:
--------------------------------------------------------------------------------
1 | package com.gojek.draftsman.internal
2 |
3 | import android.content.Context
4 | import android.graphics.Canvas
5 | import android.graphics.Paint
6 | import android.graphics.Paint.ANTI_ALIAS_FLAG
7 | import android.util.AttributeSet
8 | import android.view.LayoutInflater
9 | import android.view.MotionEvent
10 | import android.view.View
11 | import android.view.View.OnClickListener
12 | import android.widget.FrameLayout
13 | import androidx.recyclerview.widget.RecyclerView
14 | import com.gojek.draftsman.R
15 | import com.gojek.draftsman.internal.drawer.DrawerAdapter
16 | import com.gojek.draftsman.internal.drawer.DrawerOptionHandler
17 | import com.gojek.draftsman.internal.drawer.ModernDrawerLayout
18 | import com.gojek.draftsman.internal.model.*
19 | import com.gojek.draftsman.internal.utils.DimensionConverter
20 | import com.gojek.draftsman.internal.utils.ExitListener
21 | import com.gojek.draftsman.internal.utils.LayeredViewBuilder
22 | import com.gojek.draftsman.internal.utils.getDrawerLayoutList
23 | import com.gojek.draftsman.internal.visualizers.*
24 |
25 | internal class DraftsmanLayout @JvmOverloads constructor(
26 | context: Context,
27 | attributeSet: AttributeSet? = null,
28 | defStyleRes: Int = 0
29 | ) : FrameLayout(
30 | context,
31 | attributeSet,
32 | defStyleRes
33 | ), ViewGroupCallback, DraftsmanCallback {
34 |
35 | private val borderPaint = Paint(ANTI_ALIAS_FLAG).apply {
36 | style = Paint.Style.STROKE
37 | }
38 |
39 | private val infoVisualizer: InfoVisualizer by lazy {
40 | InfoVisualizer(
41 | this,
42 | { exitListener?.invoke() },
43 | {
44 | isInfoEnabled = false
45 | infoVisualizer.hide()
46 | },
47 | {
48 | Config.usePx = it
49 | invalidate()
50 | }
51 | )
52 | }
53 |
54 | private var exitListener: ExitListener? = null
55 |
56 | private lateinit var root: View
57 |
58 | private var layeredViewBuilder: LayeredViewBuilder? = null
59 |
60 | private var layerVisualizer: LayeringVisualizer? = null
61 |
62 | private var layeredViews = listOf()
63 |
64 | private var selectedViews = mutableListOf()
65 |
66 | private var isInfoEnabled = true
67 |
68 | private val drawerOptionHandler = DrawerOptionHandler(
69 | context,
70 | this,
71 | this
72 | )
73 |
74 | init {
75 | setWillNotDraw(false)
76 | Config.reset()
77 | addDrawerLayout()
78 | }
79 |
80 | fun init(
81 | topInset: Int,
82 | root: View,
83 | exitListener: ExitListener
84 | ) {
85 | this.root = root
86 | this.exitListener = exitListener
87 | layeredViewBuilder = LayeredViewBuilder(topInset)
88 | invalidate()
89 | }
90 |
91 | override fun onDraw(canvas: Canvas) {
92 | if (::root.isInitialized) {
93 | if (layeredViews.isEmpty()) {
94 | layeredViewBuilder?.let {
95 | layeredViews = it.buildLayeredView(root)
96 | }
97 | }
98 |
99 | layeredViews.forEach {
100 | drawBound(canvas, it)
101 | if (selectedViews.contains(it.view)) {
102 | showProperties(canvas, it)
103 | }
104 | }
105 |
106 | if (isInfoEnabled) {
107 | selectedViews.lastOrNull()?.let {
108 | infoVisualizer.updateInfoView(it)
109 | }
110 | }
111 | }
112 | }
113 |
114 |
115 | override fun onTouchEvent(event: MotionEvent): Boolean {
116 | if (event.actionMasked == MotionEvent.ACTION_UP &&
117 | layerVisualizer == null
118 | ) {
119 | val possibleViews = getSelectedViews(event)
120 | if (possibleViews.size == 1) {
121 | onSelectView(possibleViews[0].view)
122 | } else {
123 | showLayeredViews(possibleViews)
124 | }
125 | }
126 |
127 | return null == layerVisualizer
128 | }
129 |
130 | override fun addView(index: Int, view: View?) {
131 | addView(view, index)
132 | }
133 |
134 | override fun onDetachedFromWindow() {
135 | drawerOptionHandler.cleanUp()
136 | super.onDetachedFromWindow()
137 | }
138 |
139 | override fun changeRootView(view: View) {
140 | root = view
141 | layeredViewBuilder?.let {
142 | layeredViews = it.buildLayeredView(root)
143 | }
144 | selectedViews.clear()
145 | invalidate()
146 | }
147 |
148 | override fun closeDrawer() {
149 | findViewById(R.id.draftsman_drawer).collapse()
150 | }
151 |
152 | private fun drawBound(
153 | canvas: Canvas,
154 | layeredView: LayeredView
155 | ) {
156 | if (selectedViews.contains(layeredView.view)) {
157 | borderPaint.apply {
158 | color = Config.selectedBoundColor
159 | strokeWidth = DimensionConverter.getPxOfDp(2f, context)
160 | }
161 | } else {
162 | borderPaint.apply {
163 | color = Config.boundLineColor
164 | strokeWidth = 1f
165 | }
166 | }
167 | canvas.drawRect(layeredView.position, borderPaint)
168 | }
169 |
170 | private fun showProperties(
171 | canvas: Canvas,
172 | layeredView: LayeredView
173 | ) {
174 | if (Config.paddingEnabled) {
175 | PaddingVisualizer.draw(
176 | canvas,
177 | layeredView.view,
178 | layeredView.position
179 | )
180 | }
181 | if (Config.marginEnabled) {
182 | MarginVisualizer.draw(
183 | canvas,
184 | layeredView.view,
185 | layeredView.position
186 | )
187 | }
188 | }
189 |
190 | private fun getSelectedViews(event: MotionEvent): List {
191 | val possibleViews = mutableListOf()
192 | val views = layeredViews.filter {
193 | it.position.contains(event.x, event.y)
194 | }
195 |
196 | if (views.size == 1) {
197 | possibleViews.add(views[0])
198 | } else {
199 | val smallestArea = views.map { it.position.width().times(it.position.height()) }.min()
200 | val smallestAreaViews = views.filter {
201 | it.position.width().times(it.position.height()) == smallestArea
202 | }
203 | possibleViews.addAll(smallestAreaViews)
204 | }
205 |
206 | return possibleViews
207 | }
208 |
209 | private fun showLayeredViews(views: List) {
210 | layerVisualizer = LayeringVisualizer(this).apply {
211 | showLayeredViews(views)
212 | setOnLayeredViewSelectListener {
213 | onSelectView(it)
214 | remove(this@DraftsmanLayout)
215 | layerVisualizer = null
216 | }
217 | setOnDismissListener(OnClickListener {
218 | remove(this@DraftsmanLayout)
219 | layerVisualizer = null
220 | })
221 | }
222 | }
223 |
224 | private fun onSelectView(view: View) {
225 | if (selectedViews.contains(view)) {
226 | selectedViews.remove(view)
227 | } else {
228 | selectedViews.add(view)
229 | isInfoEnabled = true
230 | }
231 | invalidate()
232 | }
233 |
234 | private fun addDrawerLayout() {
235 | val recyclerView = LayoutInflater.from(context).inflate(
236 | R.layout.draftsman_drawer_layout,
237 | this
238 | ).findViewById(R.id.draftsman_config_drawer)
239 | val adapter = DrawerAdapter(drawerOptionHandler.getDrawerInteractions()).also {
240 | drawerOptionHandler.adapter = it
241 | }
242 | recyclerView.adapter = adapter
243 | adapter.setData(getDrawerLayoutList())
244 | }
245 | }
246 |
247 | internal interface ViewGroupCallback {
248 | fun addView(index: Int, view: View?)
249 | fun invalidate()
250 | fun removeView(view: View?)
251 | }
252 |
253 | internal interface DraftsmanCallback {
254 | fun changeRootView(view: View)
255 | fun closeDrawer()
256 | }
257 |
--------------------------------------------------------------------------------
/draftsman/src/main/kotlin/com/gojek/draftsman/internal/constants/VisualizerIds.kt:
--------------------------------------------------------------------------------
1 | package com.gojek.draftsman.internal.constants
2 |
3 | internal const val PADDING_VISUALIZER = 1
4 |
5 | internal const val MARGIN_VISUALIZER = 2
6 |
7 | internal const val GRID_VISUALIZER = 3
8 |
9 | internal const val GRID_SIZE_SEEKER = 4
10 |
11 | internal const val INSPECTION_ROOT_SELECTION = 5
12 |
13 | internal const val OVERLAY_SELECTION = 6
--------------------------------------------------------------------------------
/draftsman/src/main/kotlin/com/gojek/draftsman/internal/drawer/DefaultAnimationListener.kt:
--------------------------------------------------------------------------------
1 | package com.gojek.draftsman.internal.drawer
2 |
3 | import android.animation.Animator
4 |
5 | internal open class DefaultAnimationListener : Animator.AnimatorListener {
6 |
7 | override fun onAnimationRepeat(animation: Animator?) {
8 |
9 | }
10 |
11 | override fun onAnimationEnd(animation: Animator?) {
12 | }
13 |
14 | override fun onAnimationCancel(animation: Animator?) {
15 | }
16 |
17 | override fun onAnimationStart(animation: Animator?) {
18 | }
19 | }
--------------------------------------------------------------------------------
/draftsman/src/main/kotlin/com/gojek/draftsman/internal/drawer/DrawerAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.gojek.draftsman.internal.drawer
2 |
3 | import android.view.LayoutInflater
4 | import android.view.View
5 | import android.view.ViewGroup
6 | import android.widget.ImageView
7 | import android.widget.TextView
8 | import androidx.appcompat.widget.AppCompatSeekBar
9 | import androidx.appcompat.widget.SwitchCompat
10 | import androidx.recyclerview.widget.RecyclerView
11 | import com.gojek.draftsman.R
12 | import com.gojek.draftsman.internal.extensions.doOnProgressChange
13 | import com.gojek.draftsman.internal.model.*
14 | import com.gojek.draftsman.internal.utils.buildGridSize
15 | import kotlin.math.max
16 |
17 | private const val VIEW_TYPE_HEADER = 1
18 |
19 | private const val VIEW_TYPE_TOGGLE_ITEM = 2
20 |
21 | private const val VIEW_TYPE_RANGE_ITEM = 3
22 |
23 | private const val VIEW_TYPE_SELECTION_ITEM = 4
24 |
25 | internal class DrawerAdapter(
26 | private val interactions: DrawerInteractions
27 | ) : RecyclerView.Adapter() {
28 |
29 | private val items = mutableListOf()
30 |
31 | fun setData(data: List) {
32 | items.apply {
33 | clear()
34 | addAll(data)
35 | }
36 | notifyDataSetChanged()
37 | }
38 |
39 | fun updateItemAtPosition(item: DrawerItem, position: Int) {
40 | items[position] = item
41 | notifyItemChanged(position)
42 | }
43 |
44 | fun getList() = items
45 |
46 | override fun getItemCount() = items.size
47 |
48 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AbsViewHolder {
49 | return when (viewType) {
50 | VIEW_TYPE_TOGGLE_ITEM -> ToggleViewHolder(
51 | getView(R.layout.draftsman_drawer_item_toggle, parent)
52 | )
53 | VIEW_TYPE_RANGE_ITEM -> RangeViewHolder(
54 | getView(R.layout.draftsman_drawer_item_range, parent)
55 | )
56 | VIEW_TYPE_SELECTION_ITEM -> SelectionViewHolder(
57 | getView(R.layout.draftsman_drawer_item_selection, parent)
58 | )
59 | else -> HeaderViewHolder(
60 | getView(R.layout.draftsman_drawer_item_header, parent)
61 | )
62 | }
63 | }
64 |
65 | override fun onBindViewHolder(holder: AbsViewHolder, position: Int) {
66 | holder.bind(items[position])
67 | }
68 |
69 | override fun getItemViewType(position: Int): Int {
70 | return when (items[position]) {
71 | is DrawerHeadingItem -> VIEW_TYPE_HEADER
72 | is DrawerToggleItem -> VIEW_TYPE_TOGGLE_ITEM
73 | is DrawerRangeItem -> VIEW_TYPE_RANGE_ITEM
74 | is DrawerSelectionItem -> VIEW_TYPE_SELECTION_ITEM
75 | }
76 | }
77 |
78 | private fun getView(layoutResId: Int, parent: ViewGroup): View {
79 | return LayoutInflater.from(parent.context).inflate(
80 | layoutResId,
81 | parent,
82 | false
83 | )
84 | }
85 |
86 | internal abstract inner class AbsViewHolder(
87 | itemView: View
88 | ) : RecyclerView.ViewHolder(itemView) {
89 |
90 | abstract fun bind(drawerItem: DrawerItem)
91 | }
92 |
93 | private inner class HeaderViewHolder(
94 | itemView: View
95 | ) : AbsViewHolder(itemView) {
96 | override fun bind(drawerItem: DrawerItem) {
97 | itemView.run {
98 | findViewById(R.id.drawer_item_heading).text =
99 | (drawerItem as DrawerHeadingItem).title
100 | }
101 | }
102 | }
103 |
104 | private inner class ToggleViewHolder(
105 | itemView: View
106 | ) : AbsViewHolder(itemView) {
107 | override fun bind(drawerItem: DrawerItem) {
108 | val item = drawerItem as DrawerToggleItem
109 | itemView.run {
110 | findViewById(R.id.drawer_item_toggle_icon).setImageResource(item.icon)
111 | findViewById(R.id.drawer_item_toggle_label).text = item.title
112 | findViewById(R.id.drawer_item_toggle).apply {
113 | isChecked = item.enabled
114 | setOnCheckedChangeListener { _, isChecked ->
115 | interactions.onToggleChange(
116 | item,
117 | isChecked
118 | )
119 | }
120 | }
121 | }
122 | }
123 | }
124 |
125 | private inner class RangeViewHolder(
126 | itemView: View
127 | ) : AbsViewHolder(itemView) {
128 | private val rangeText = itemView.findViewById(R.id.drawer_item_range_value)
129 | override fun bind(drawerItem: DrawerItem) {
130 | val item = drawerItem as DrawerRangeItem
131 | val seekbar = itemView.findViewById(R.id.drawer_item_seekbar).apply {
132 | max = item.maxValue
133 | doOnProgressChange {
134 | rangeText.text = "${buildGridSize(it + item.minValue)}"
135 | interactions.onRangeChange(item, it + item.minValue)
136 | }
137 | }
138 | rangeText.text =
139 | "${buildGridSize(max(item.minValue, seekbar.progress + item.minValue))}"
140 | itemView.findViewById(R.id.drawer_item_range_title).text = item.title
141 | }
142 | }
143 |
144 | private inner class SelectionViewHolder(
145 | itemView: View
146 | ) : AbsViewHolder(itemView) {
147 | override fun bind(drawerItem: DrawerItem) {
148 | val item = drawerItem as DrawerSelectionItem
149 | itemView.findViewById(R.id.drawer_item_selection_icon)
150 | .setImageResource(item.icon)
151 | itemView.findViewById(R.id.drawer_item_selection).text = item.selection
152 | itemView.setOnClickListener { interactions.onSelectItem(item) }
153 | }
154 | }
155 | }
--------------------------------------------------------------------------------
/draftsman/src/main/kotlin/com/gojek/draftsman/internal/drawer/DrawerInteractions.kt:
--------------------------------------------------------------------------------
1 | package com.gojek.draftsman.internal.drawer
2 |
3 | import com.gojek.draftsman.internal.model.DrawerRangeItem
4 | import com.gojek.draftsman.internal.model.DrawerSelectionItem
5 | import com.gojek.draftsman.internal.model.DrawerToggleItem
6 |
7 | internal interface DrawerInteractions {
8 |
9 | fun onSelectItem(item: DrawerSelectionItem)
10 |
11 | fun onToggleChange(item: DrawerToggleItem, isChecked: Boolean)
12 |
13 | fun onRangeChange(item: DrawerRangeItem, value: Int)
14 | }
--------------------------------------------------------------------------------
/draftsman/src/main/kotlin/com/gojek/draftsman/internal/drawer/DrawerOptionHandler.kt:
--------------------------------------------------------------------------------
1 | package com.gojek.draftsman.internal.drawer
2 |
3 | import android.Manifest
4 | import android.app.Activity
5 | import android.content.Context
6 | import android.graphics.Bitmap
7 | import android.net.Uri
8 | import android.os.Handler
9 | import android.os.HandlerThread
10 | import android.provider.MediaStore
11 | import android.view.View
12 | import android.view.View.GONE
13 | import android.view.View.VISIBLE
14 | import android.view.ViewGroup
15 | import android.widget.FrameLayout.LayoutParams
16 | import android.widget.FrameLayout.LayoutParams.MATCH_PARENT
17 | import android.widget.LinearLayout
18 | import android.widget.Toast
19 | import androidx.appcompat.app.AlertDialog
20 | import androidx.appcompat.app.AppCompatActivity
21 | import androidx.core.app.ActivityCompat
22 | import androidx.core.content.PermissionChecker
23 | import com.gojek.draftsman.internal.Config
24 | import com.gojek.draftsman.internal.DraftsmanCallback
25 | import com.gojek.draftsman.internal.DraftsmanLayout
26 | import com.gojek.draftsman.internal.ViewGroupCallback
27 | import com.gojek.draftsman.internal.constants.*
28 | import com.gojek.draftsman.internal.fragment.FileChooserFragment
29 | import com.gojek.draftsman.internal.fragment.RequestPermissionFragment
30 | import com.gojek.draftsman.internal.model.DrawerItem
31 | import com.gojek.draftsman.internal.model.DrawerRangeItem
32 | import com.gojek.draftsman.internal.model.DrawerSelectionItem
33 | import com.gojek.draftsman.internal.model.DrawerToggleItem
34 | import com.gojek.draftsman.internal.visualizers.GridVisualizer
35 | import com.gojek.draftsman.internal.visualizers.OverlayVisualizer
36 | import com.gojek.draftsman.internal.widgets.InspectionRootItemView
37 |
38 | internal class DrawerOptionHandler(
39 | private val context: Context,
40 | private val viewGroupCallback: ViewGroupCallback,
41 | private val draftsmanCallback: DraftsmanCallback
42 | ) {
43 |
44 | var adapter: DrawerAdapter? = null
45 |
46 | private var gridVisualizer: GridVisualizer? = null
47 |
48 | private var overlayVisualizer: OverlayVisualizer? = null
49 |
50 | private var handler: Handler? = null
51 |
52 | fun getDrawerInteractions() = object : DrawerInteractions {
53 |
54 | override fun onSelectItem(item: DrawerSelectionItem) {
55 | onSelectItemInternal(item)
56 | }
57 |
58 | override fun onToggleChange(item: DrawerToggleItem, isChecked: Boolean) {
59 | onToggleChangeInternal(item, isChecked)
60 | }
61 |
62 | override fun onRangeChange(item: DrawerRangeItem, value: Int) {
63 | onRangeChangeInternal(item, value)
64 | }
65 | }
66 |
67 | fun cleanUp() {
68 | removeOverlay()
69 | }
70 |
71 | private fun onToggleChangeInternal(
72 | item: DrawerToggleItem,
73 | isChecked: Boolean
74 | ) {
75 | when (item.toggleId) {
76 | MARGIN_VISUALIZER -> updateGlobalConfig { Config.marginEnabled = isChecked }
77 | PADDING_VISUALIZER -> updateGlobalConfig { Config.paddingEnabled = isChecked }
78 | GRID_VISUALIZER -> toggleGrid(isChecked)
79 | }
80 | }
81 |
82 | private fun updateGlobalConfig(update: () -> Unit) {
83 | update()
84 | viewGroupCallback.invalidate()
85 | }
86 |
87 | private fun toggleGrid(isEnabled: Boolean) {
88 | Config.gridEnabled = isEnabled
89 | if (isEnabled) {
90 | if (null == gridVisualizer) {
91 | gridVisualizer = GridVisualizer(context).apply {
92 | layoutParams = LayoutParams(MATCH_PARENT, MATCH_PARENT)
93 | }
94 | viewGroupCallback.addView(0, gridVisualizer)
95 | }
96 |
97 | val gridSizeRangeItem = DrawerRangeItem("Grid size", GRID_SIZE_SEEKER, 2, 8)
98 | val index = adapter?.getList()
99 | ?.indexOfFirst { it is DrawerToggleItem && it.toggleId == GRID_VISUALIZER }
100 | ?: 0
101 | adapter?.apply {
102 | getList().add(index + 1, gridSizeRangeItem)
103 | notifyItemRangeChanged(index + 1, getList().size - 1)
104 | }
105 | } else {
106 | val index = adapter?.getList()
107 | ?.indexOfFirst { it is DrawerRangeItem && it.rangeId == GRID_SIZE_SEEKER }
108 | ?: 0
109 | adapter?.apply {
110 | getList().removeAt(index)
111 | notifyItemRemoved(getList().lastIndex + 1)
112 | notifyItemRangeChanged(index, getList().lastIndex)
113 | }
114 |
115 | }
116 | gridVisualizer?.visibility = if (isEnabled) VISIBLE else GONE
117 | }
118 |
119 | private fun onRangeChangeInternal(item: DrawerRangeItem, value: Int) {
120 | when (item.rangeId) {
121 | GRID_SIZE_SEEKER -> gridVisualizer?.updateGridSize(value)
122 | }
123 | }
124 |
125 | private fun onSelectItemInternal(item: DrawerSelectionItem) {
126 | when (item.selectionId) {
127 | INSPECTION_ROOT_SELECTION -> showInspectionRootDialog()
128 | OVERLAY_SELECTION -> selectOverlay()
129 | }
130 | }
131 |
132 | private fun showInspectionRootDialog() {
133 | var alertDialog: AlertDialog? = null
134 | val rootView = LinearLayout(context).apply {
135 | orientation = LinearLayout.VERTICAL
136 | }
137 | val root = (context as Activity).findViewById(android.R.id.content)
138 | for (index in 0 until root.childCount) {
139 | val child = root.getChildAt(index)
140 | if (child !is DraftsmanLayout) {
141 | rootView.addView(InspectionRootItemView(context).apply {
142 | setText(child.javaClass.simpleName)
143 | setOnClickListener {
144 | onClickView(child, index)
145 | alertDialog?.dismiss()
146 | }
147 | })
148 | }
149 | }
150 | alertDialog = AlertDialog.Builder(context)
151 | .setTitle("Select inspection root")
152 | .setView(rootView)
153 | .create().apply { show() }
154 | }
155 |
156 | private fun selectOverlay() {
157 | val storagePermission = Manifest.permission.READ_EXTERNAL_STORAGE
158 | draftsmanCallback.closeDrawer()
159 | if (hasPermission(storagePermission)) {
160 | selectImage()
161 | } else {
162 | requestPermission(storagePermission) {
163 | if (it == PermissionChecker.PERMISSION_GRANTED) {
164 | selectImage()
165 | } else if (!shouldShowPermissionRationale(storagePermission)) {
166 | Toast.makeText(
167 | context,
168 | "Need storage permission to place overlay",
169 | Toast.LENGTH_LONG
170 | ).show()
171 | }
172 | }
173 | }
174 | }
175 |
176 | private fun onClickView(child: View, index: Int) {
177 | draftsmanCallback.run {
178 | closeDrawer()
179 | changeRootView(child)
180 | }
181 | val name = if (index == 0) {
182 | "Activity Layout"
183 | } else {
184 | child.javaClass.simpleName
185 | }
186 |
187 | val item =
188 | findDrawerItem { it is DrawerSelectionItem && it.selectionId == INSPECTION_ROOT_SELECTION }
189 | val index = adapter?.getList()?.indexOf(item) ?: -1
190 | adapter?.updateItemAtPosition(item.copy(selection = name), index)
191 | }
192 |
193 | private fun findDrawerItem(predicate: (DrawerItem) -> Boolean): T {
194 | return adapter?.getList()?.find { predicate(it) } as T
195 | }
196 |
197 | private fun hasPermission(permission: String): Boolean {
198 | return PermissionChecker.checkSelfPermission(
199 | context,
200 | permission
201 | ) == PermissionChecker.PERMISSION_GRANTED
202 | }
203 |
204 | private fun requestPermission(permission: String, callback: (Int) -> Unit) {
205 | if (!RequestPermissionFragment.isAdded) {
206 | val activity = context as AppCompatActivity
207 | activity.supportFragmentManager.beginTransaction().apply {
208 | replace(android.R.id.content, RequestPermissionFragment)
209 | commitNow()
210 | }
211 | }
212 | RequestPermissionFragment.requestPermission(permission, callback)
213 | }
214 |
215 | private fun shouldShowPermissionRationale(permission: String): Boolean {
216 | val activity = context as AppCompatActivity
217 | return ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)
218 | }
219 |
220 | private fun selectImage() {
221 | if (!FileChooserFragment.isAdded) {
222 | val activity = context as AppCompatActivity
223 | activity.supportFragmentManager.beginTransaction().apply {
224 | replace(android.R.id.content, FileChooserFragment)
225 | commitNow()
226 | }
227 | }
228 | FileChooserFragment.selectFile {
229 | if (null != it) {
230 | showImage(it)
231 | }
232 | }
233 | }
234 |
235 | private fun showImage(uri: Uri) {
236 | if (null == handler) {
237 | val handlerThread =
238 | HandlerThread(DrawerOptionHandler::class.java.simpleName).apply { start() }
239 | handler = Handler(handlerThread.looper)
240 | }
241 | if (null == overlayVisualizer) {
242 | overlayVisualizer = OverlayVisualizer(context) { removeOverlay() }.apply {
243 | layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)
244 | viewGroupCallback.addView(0, this)
245 | }
246 | }
247 | handler?.post {
248 | val bitmap = MediaStore.Images.Media.getBitmap(context.contentResolver, uri)
249 | if (null != bitmap) {
250 | resizeBitmap(bitmap)
251 | }
252 | }
253 | }
254 |
255 | private fun resizeBitmap(bitmap: Bitmap) {
256 | val container = (context as Activity).findViewById(android.R.id.content)
257 | val size = getBitmapSize(bitmap, container)
258 | val resizedBitmap = Bitmap.createScaledBitmap(bitmap, size.first, size.second, true)
259 | overlayVisualizer?.setOverlayImage(resizedBitmap)
260 | }
261 |
262 | private fun getBitmapSize(bitmap: Bitmap, container: View): Pair {
263 | var pair = Pair(0, 0)
264 | val aspectRatio = bitmap.width.toFloat() / bitmap.height.toFloat()
265 | if (bitmap.width >= bitmap.height) {
266 | pair = if (bitmap.width >= container.width) {
267 | val height = container.width * aspectRatio
268 | Pair(container.width, height.toInt())
269 |
270 | } else {
271 | Pair(bitmap.width, bitmap.height)
272 | }
273 | } else if (bitmap.height > bitmap.width) {
274 | pair = if (bitmap.height >= container.height) {
275 | val width = container.height * aspectRatio
276 | Pair(width.toInt(), container.height)
277 | } else {
278 | Pair(bitmap.width, bitmap.height)
279 | }
280 | }
281 | return pair
282 | }
283 |
284 | private fun removeOverlay() {
285 | viewGroupCallback.removeView(overlayVisualizer)
286 | (context as AppCompatActivity).supportFragmentManager.popBackStack()
287 | }
288 | }
289 |
--------------------------------------------------------------------------------
/draftsman/src/main/kotlin/com/gojek/draftsman/internal/drawer/DrawerPathGenerator.kt:
--------------------------------------------------------------------------------
1 | package com.gojek.draftsman.internal.drawer
2 |
3 | import android.content.res.Resources
4 | import android.graphics.Path
5 | import android.graphics.RectF
6 | import com.gojek.draftsman.internal.drawer.Orientation.*
7 |
8 | internal class DrawerPathGenerator(
9 | private val height: Int,
10 | private val resources: Resources,
11 | private val sliderShapeDimension: Float,
12 | private val peekDistance: Float
13 | ) {
14 |
15 | fun getPath(orientation: Orientation, childDimension: Int): Pair {
16 | return when (orientation) {
17 | HORIZONTAL_RIGHT -> HorizontalRightPathGenerator(
18 | height,
19 | resources,
20 | sliderShapeDimension,
21 | peekDistance
22 | ).getPath(childDimension)
23 | HORIZONTAL_LEFT -> HorizontalLeftPathGenerator().getPath()
24 | VERTICAL_TOP -> VerticalTopPathGenerator().getPath()
25 | VERTICAL_BOTTOM -> VerticalBottomPathGenerator().getPath()
26 | }
27 | }
28 |
29 | private class HorizontalRightPathGenerator(
30 | private val height: Int,
31 | private val resources: Resources,
32 | private val sliderShapeDimension: Float,
33 | private val peekDistance: Float
34 | ) {
35 |
36 | fun getPath(childWidth: Int): Pair {
37 |
38 | val width = resources.displayMetrics.widthPixels.toFloat()
39 | val heightHalf = height.div(2f)
40 | val sliderShapeDimensionHalf = sliderShapeDimension.div(2)
41 |
42 | val rectF = RectF().apply {
43 | right = width
44 | left = width - sliderShapeDimensionHalf - peekDistance
45 | top = heightHalf - sliderShapeDimensionHalf
46 | bottom = top + sliderShapeDimension
47 | }
48 |
49 | val yOffset: Float = sliderShapeDimension * 0.05f
50 | val xOffset = sliderShapeDimensionHalf * 4 / 3
51 |
52 | val path = Path().apply {
53 | moveTo(sliderShapeDimension, heightHalf - sliderShapeDimensionHalf)
54 | rCubicTo(
55 | -xOffset, yOffset,
56 | -xOffset, sliderShapeDimension - yOffset,
57 | 0f, sliderShapeDimension
58 | )
59 | addRect(
60 | sliderShapeDimension,
61 | 0f,
62 | childWidth + sliderShapeDimension,
63 | height.toFloat(),
64 | Path.Direction.CCW
65 | )
66 | }
67 |
68 | return Pair(path, rectF)
69 | }
70 | }
71 |
72 | private class HorizontalLeftPathGenerator {
73 |
74 | fun getPath(): Pair {
75 | return Pair(Path(), RectF())
76 | }
77 |
78 | }
79 |
80 | private class VerticalTopPathGenerator {
81 |
82 | fun getPath(): Pair {
83 | return Pair(Path(), RectF())
84 | }
85 |
86 | }
87 |
88 | private class VerticalBottomPathGenerator {
89 |
90 | fun getPath(): Pair {
91 | return Pair(Path(), RectF())
92 | }
93 |
94 | }
95 | }
--------------------------------------------------------------------------------
/draftsman/src/main/kotlin/com/gojek/draftsman/internal/drawer/ModernDrawerLayout.kt:
--------------------------------------------------------------------------------
1 | package com.gojek.draftsman.internal.drawer
2 |
3 | import android.animation.Animator
4 | import android.animation.AnimatorSet
5 | import android.animation.ObjectAnimator
6 | import android.animation.ValueAnimator
7 | import android.content.Context
8 | import android.graphics.*
9 | import android.graphics.Paint.ANTI_ALIAS_FLAG
10 | import android.graphics.drawable.ColorDrawable
11 | import android.graphics.drawable.Drawable
12 | import android.util.AttributeSet
13 | import android.view.MotionEvent
14 | import android.view.MotionEvent.*
15 | import android.view.View
16 | import android.view.View.MeasureSpec.*
17 | import android.view.ViewGroup
18 | import android.view.animation.AccelerateDecelerateInterpolator
19 | import com.gojek.draftsman.R
20 | import kotlin.math.abs
21 | import kotlin.math.min
22 |
23 | private val TAG = ModernDrawerLayout::class.java.simpleName
24 |
25 | internal class ModernDrawerLayout @JvmOverloads constructor(
26 | context: Context,
27 | attributeSet: AttributeSet? = null,
28 | defStyleRes: Int = 0
29 | ) : ViewGroup(
30 | context,
31 | attributeSet,
32 | defStyleRes
33 | ) {
34 |
35 | private val sliderShapeDimension = dpToPx(72, resources)
36 |
37 | private var orientation = Orientation.HORIZONTAL_RIGHT
38 |
39 | private var peekDistance = 0f
40 |
41 | private var bgDrawable: Drawable = ColorDrawable(Color.LTGRAY)
42 |
43 | private var sliderBitmap: Bitmap? = null
44 |
45 | private var expanded = false
46 |
47 | private val bitmapStartingPoint = PointF()
48 |
49 | private val touchArea = RectF()
50 |
51 | private val sliderIconRect = Rect()
52 |
53 | private var drawerOpenIcon: Drawable? = null
54 |
55 | private var drawerCloseIcon: Drawable? = null
56 |
57 | private var drawerIcon: Drawable? = null
58 |
59 | private var previousX = 0f
60 |
61 | private var maxTranslation = 0f
62 |
63 | private var animator: Animator? = null
64 |
65 | private var motionDownTime: Long = 0
66 |
67 | private var sliderPath = Path()
68 |
69 | private val shadowPaint = Paint(ANTI_ALIAS_FLAG).apply {
70 | color = Color.WHITE
71 | setShadowLayer(dpToPx(8, context.resources), 0f, 0f, Color.LTGRAY)
72 | }
73 |
74 | private val overlayPaint = Paint(ANTI_ALIAS_FLAG).apply {
75 | color = Color.parseColor("#999999")
76 | alpha = 0
77 | }
78 |
79 | init {
80 | setWillNotDraw(false)
81 | attributeSet?.let(::initAttrs)
82 | }
83 |
84 | fun setIcons(openIcon: Drawable, closeIcon: Drawable) {
85 | drawerOpenIcon = openIcon
86 | drawerCloseIcon = closeIcon
87 | drawerIcon = drawerOpenIcon
88 | }
89 |
90 | fun collapse() {
91 | if (expanded) {
92 | collapseDrawer()
93 | }
94 | }
95 |
96 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
97 |
98 | require(childCount <= 1) { "ModernDrawerLayout can have only one child" }
99 |
100 | var width = getSize(widthMeasureSpec)
101 | var height = getSize(heightMeasureSpec)
102 |
103 | var widthMeasureMode = getMode(widthMeasureSpec)
104 | var heightMeasureMode = getMode(heightMeasureSpec)
105 |
106 | if (widthMeasureMode != EXACTLY && heightMeasureMode != EXACTLY) {
107 | if (isInEditMode) {
108 | if (widthMeasureMode == AT_MOST) {
109 | widthMeasureMode = EXACTLY
110 | } else if (widthMeasureMode == UNSPECIFIED) {
111 | widthMeasureMode = EXACTLY
112 | width = 300
113 | }
114 | if (heightMeasureMode == AT_MOST) {
115 | heightMeasureMode = EXACTLY
116 | } else if (heightMeasureMode == UNSPECIFIED) {
117 | heightMeasureMode = EXACTLY
118 | height = 300
119 | }
120 |
121 | } else {
122 | throw IllegalArgumentException("ModernDrawerLayout has to be measured exactly")
123 | }
124 | }
125 |
126 | if (orientation == Orientation.HORIZONTAL_LEFT ||
127 | orientation == Orientation.HORIZONTAL_RIGHT
128 | ) {
129 | width += resources.displayMetrics.widthPixels - peekDistance.toInt()
130 | } else {
131 | height += resources.displayMetrics.heightPixels - peekDistance.toInt()
132 | }
133 |
134 | setMeasuredDimension(width, height)
135 |
136 |
137 | val childLp = getChildAt(0).layoutParams
138 |
139 | val childWidthMeasureSpec =
140 | getChildMeasureSpec(widthMeasureSpec, paddingLeft + paddingRight, childLp.width)
141 | val childHeightMeasureSpec =
142 | getChildMeasureSpec(heightMeasureSpec, paddingTop + paddingBottom, childLp.height)
143 | getChildAt(0).measure(childWidthMeasureSpec, childHeightMeasureSpec)
144 | maxTranslation = -min(
145 | width - resources.displayMetrics.widthPixels - peekDistance - sliderShapeDimension,
146 | getChildAt(0).measuredWidth - peekDistance
147 | )
148 | }
149 |
150 | override fun onLayout(changed: Boolean, p1: Int, p2: Int, p3: Int, p4: Int) {
151 | val child = getChildAt(0)
152 | child.layout(
153 | resources.displayMetrics.widthPixels - peekDistance.toInt(),
154 | paddingTop,
155 | resources.displayMetrics.widthPixels - peekDistance.toInt() + child.measuredWidth,
156 | paddingTop + measuredHeight
157 | )
158 | if (changed) {
159 | generateSliderBitmap()
160 | }
161 | }
162 |
163 | override fun dispatchDraw(canvas: Canvas) {
164 | canvas.apply {
165 | drawPaint(overlayPaint)
166 | val count = save()
167 | translate(bitmapStartingPoint.x, bitmapStartingPoint.y)
168 | drawPath(sliderPath, shadowPaint)
169 | drawBitmap(sliderBitmap!!, 0f, 0f, null)
170 | restoreToCount(count)
171 | }
172 | drawerIcon?.bounds = sliderIconRect
173 | drawerIcon?.draw(canvas)
174 | super.dispatchDraw(canvas)
175 | }
176 |
177 | override fun onTouchEvent(event: MotionEvent): Boolean {
178 | return when (event.actionMasked) {
179 | ACTION_DOWN -> {
180 | motionDownTime = System.currentTimeMillis()
181 | touchArea.contains(event.x, event.y)
182 | }
183 | ACTION_UP -> {
184 | if (System.currentTimeMillis() - motionDownTime <= 300) {
185 | toggleState()
186 | } else {
187 | expandOrCollapse()
188 | }
189 | previousX = 0f
190 | true
191 | }
192 | ACTION_MOVE -> {
193 | moveDrawer(event.rawX)
194 | false
195 | }
196 | else -> false
197 | }
198 | }
199 |
200 | override fun onDetachedFromWindow() {
201 | if (animator?.isRunning == true) {
202 | animator?.cancel()
203 | }
204 | super.onDetachedFromWindow()
205 | }
206 |
207 | private fun generateSliderBitmap() {
208 |
209 | bitmapStartingPoint.x =
210 | resources.displayMetrics.widthPixels - peekDistance - sliderShapeDimension
211 | bitmapStartingPoint.y = 0f
212 |
213 | val result = DrawerPathGenerator(
214 | height,
215 | resources,
216 | sliderShapeDimension,
217 | peekDistance
218 | ).getPath(
219 | orientation,
220 | getChildAt(0).measuredWidth
221 | )
222 |
223 | sliderBitmap = Bitmap.createBitmap(
224 | getChildAt(0).measuredWidth + sliderShapeDimension.toInt(),
225 | height,
226 | Bitmap.Config.ARGB_8888
227 | )
228 | if (null != background) {
229 | if (bgDrawable != background) {
230 | bgDrawable = background
231 | setBackgroundResource(0)
232 | } else {
233 | bgDrawable = ColorDrawable(Color.WHITE)
234 | }
235 | }
236 | bgDrawable.bounds = Rect(
237 | 0,
238 | 0,
239 | getChildAt(0).measuredWidth + sliderShapeDimension.toInt(),
240 | height
241 | )
242 | val canvas = Canvas(sliderBitmap!!).apply {
243 | clipPath(result.first)
244 | }
245 | bgDrawable.draw(canvas)
246 | touchArea.set(result.second)
247 | calculateIconDrawRect()
248 | sliderPath = result.first
249 | }
250 |
251 | private fun expandDrawer() {
252 | val expandAnimation = ObjectAnimator.ofFloat(
253 | this, View.TRANSLATION_X,
254 | maxTranslation
255 | ).apply {
256 | duration = 500
257 | interpolator = AccelerateDecelerateInterpolator()
258 | addListener(object : DefaultAnimationListener() {
259 | override fun onAnimationEnd(animation: Animator?) {
260 | expanded = true
261 | onStateChanged()
262 | }
263 | })
264 | }
265 |
266 | val overlayAnimation = ValueAnimator.ofInt(overlayPaint.alpha, 127).apply {
267 | duration = 500
268 | interpolator = AccelerateDecelerateInterpolator()
269 | addUpdateListener {
270 | overlayPaint.alpha = it.animatedValue as Int
271 | postInvalidate()
272 | }
273 | }
274 |
275 | animator = AnimatorSet().apply {
276 | playTogether(overlayAnimation, expandAnimation)
277 | start()
278 | }
279 | expanded = true
280 | }
281 |
282 | private fun collapseDrawer() {
283 | val collapseAnimation = ObjectAnimator.ofFloat(
284 | this, View.TRANSLATION_X,
285 | 0f
286 | ).apply {
287 | duration = 500
288 | interpolator = AccelerateDecelerateInterpolator()
289 | addListener(object : DefaultAnimationListener() {
290 | override fun onAnimationEnd(animation: Animator?) {
291 | expanded = false
292 | onStateChanged()
293 | }
294 | })
295 | }
296 |
297 | val overlayAnimation = ValueAnimator.ofInt(overlayPaint.alpha, 0).apply {
298 | duration = 500
299 | interpolator = AccelerateDecelerateInterpolator()
300 | addUpdateListener {
301 | overlayPaint.alpha = it.animatedValue as Int
302 | postInvalidate()
303 | }
304 | }
305 |
306 | animator = AnimatorSet().apply {
307 | playTogether(overlayAnimation, collapseAnimation)
308 | start()
309 | }
310 | expanded = false
311 | }
312 |
313 | private fun toggleState() {
314 | if (expanded) {
315 | collapseDrawer()
316 | } else {
317 | expandDrawer()
318 | }
319 | }
320 |
321 | private fun calculateIconDrawRect() {
322 | val vCenter = (touchArea.top + (touchArea.bottom - touchArea.top) / 2).toInt()
323 | val hCenter = (touchArea.left + (touchArea.right - touchArea.left) / 2).toInt()
324 | val iconDimensionHalf = dpToPx(16, resources).toInt()
325 | sliderIconRect.apply {
326 | top = vCenter - iconDimensionHalf
327 | bottom = vCenter + iconDimensionHalf
328 | left = hCenter - iconDimensionHalf
329 | right = hCenter + iconDimensionHalf
330 | }
331 | }
332 |
333 | private fun initAttrs(attributeSet: AttributeSet) {
334 | val typedArray =
335 | context.obtainStyledAttributes(attributeSet, R.styleable.ModernDrawerLayout)
336 | peekDistance =
337 | typedArray.getDimension(R.styleable.ModernDrawerLayout_peekDistance, peekDistance)
338 | drawerOpenIcon = typedArray.getDrawable(R.styleable.ModernDrawerLayout_drawerOpenIcon)
339 | drawerCloseIcon = typedArray.getDrawable(R.styleable.ModernDrawerLayout_drawerCloseIcon)
340 | typedArray.recycle()
341 | drawerIcon = drawerOpenIcon
342 | }
343 |
344 | private fun onStateChanged() {
345 | drawerIcon = if (expanded) drawerCloseIcon else drawerOpenIcon
346 | invalidate()
347 | }
348 |
349 | private fun moveDrawer(x: Float) {
350 | if (previousX != 0f) {
351 | val dx = x - previousX
352 | previousX = x
353 | var newTranslationX = translationX + dx
354 | if (newTranslationX < maxTranslation) {
355 | newTranslationX = maxTranslation
356 | } else if (newTranslationX > 0) {
357 | newTranslationX = 0f
358 | }
359 | translationX = newTranslationX
360 | overlayPaint.alpha = (127 * abs(newTranslationX / maxTranslation)).toInt()
361 | postInvalidate()
362 | } else {
363 | previousX = x
364 | }
365 | }
366 |
367 | private fun expandOrCollapse() {
368 | if (translationX != 0f || translationX != maxTranslation) {
369 | val distanceToExpand = maxTranslation - translationX
370 | if (abs(distanceToExpand) > abs(translationX)) {
371 | collapseDrawer()
372 | } else {
373 | expandDrawer()
374 | }
375 | }
376 | }
377 | }
--------------------------------------------------------------------------------
/draftsman/src/main/kotlin/com/gojek/draftsman/internal/drawer/Orientation.kt:
--------------------------------------------------------------------------------
1 | package com.gojek.draftsman.internal.drawer
2 |
3 |
4 | internal enum class Orientation {
5 |
6 | HORIZONTAL_LEFT,
7 |
8 | HORIZONTAL_RIGHT,
9 |
10 | VERTICAL_TOP,
11 |
12 | VERTICAL_BOTTOM
13 | }
--------------------------------------------------------------------------------
/draftsman/src/main/kotlin/com/gojek/draftsman/internal/drawer/ResourcesUtils.kt:
--------------------------------------------------------------------------------
1 | package com.gojek.draftsman.internal.drawer
2 |
3 | import android.content.res.Resources
4 | import android.util.TypedValue
5 |
6 | internal fun dpToPx(dp: Int, resources: Resources) =
7 | TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp.toFloat(), resources.displayMetrics)
--------------------------------------------------------------------------------
/draftsman/src/main/kotlin/com/gojek/draftsman/internal/drawer/State.kt:
--------------------------------------------------------------------------------
1 | package com.gojek.draftsman.internal.drawer
2 |
3 | internal object State {
4 |
5 | const val COLLAPSED = 0
6 |
7 | const val EXPANDED = 1
8 |
9 | const val EXPANDING = 2
10 |
11 | const val COLLAPSING = 3
12 | }
--------------------------------------------------------------------------------
/draftsman/src/main/kotlin/com/gojek/draftsman/internal/extensions/SeekBar.kt:
--------------------------------------------------------------------------------
1 | package com.gojek.draftsman.internal.extensions
2 |
3 | import android.widget.SeekBar
4 |
5 | internal fun SeekBar.doOnProgressChange(action: (Int) -> Unit) {
6 | setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
7 | override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
8 | action(progress)
9 | }
10 |
11 | override fun onStartTrackingTouch(seekBar: SeekBar?) {
12 | }
13 |
14 | override fun onStopTrackingTouch(seekBar: SeekBar?) {
15 | }
16 | })
17 | }
--------------------------------------------------------------------------------
/draftsman/src/main/kotlin/com/gojek/draftsman/internal/extensions/View.kt:
--------------------------------------------------------------------------------
1 | package com.gojek.draftsman.internal.extensions
2 |
3 | import android.view.View
4 | import android.view.View.GONE
5 | import android.view.View.VISIBLE
6 |
7 | internal fun View.gone() {
8 | visibility = GONE
9 | }
10 |
11 | internal fun View.visible() {
12 | visibility = VISIBLE
13 | }
--------------------------------------------------------------------------------
/draftsman/src/main/kotlin/com/gojek/draftsman/internal/extensions/ViewGroup.kt:
--------------------------------------------------------------------------------
1 | package com.gojek.draftsman.internal.extensions
2 |
3 | import android.view.ViewGroup
4 |
5 | internal fun ViewGroup.removeAllViewGroups() {
6 | for (index in 0 until childCount) {
7 | if (getChildAt(index) is ViewGroup) {
8 | removeView(getChildAt(index))
9 | }
10 | }
11 | }
--------------------------------------------------------------------------------
/draftsman/src/main/kotlin/com/gojek/draftsman/internal/fragment/FileChooserFragment.kt:
--------------------------------------------------------------------------------
1 | package com.gojek.draftsman.internal.fragment
2 |
3 | import android.app.Activity.RESULT_OK
4 | import android.content.Intent
5 | import android.net.Uri
6 | import androidx.fragment.app.Fragment
7 |
8 | private const val REQUEST_CODE = 1101
9 |
10 | internal object FileChooserFragment : Fragment() {
11 |
12 | private var resultCallback: (Uri?) -> Unit = {}
13 |
14 | fun selectFile(callback: (Uri?) -> Unit) {
15 | resultCallback = callback
16 | startActivityForResult(
17 | Intent(Intent.ACTION_GET_CONTENT).apply { type = "image/*" },
18 | REQUEST_CODE
19 | )
20 | }
21 |
22 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
23 | super.onActivityResult(requestCode, resultCode, data)
24 | if (resultCode == RESULT_OK && null != data) {
25 | resultCallback(data.data)
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/draftsman/src/main/kotlin/com/gojek/draftsman/internal/fragment/RequestPermissionFragment.kt:
--------------------------------------------------------------------------------
1 | package com.gojek.draftsman.internal.fragment
2 |
3 | import androidx.fragment.app.Fragment
4 |
5 | private const val REQUEST_CODE = 1100
6 |
7 | internal object RequestPermissionFragment : Fragment() {
8 |
9 | private var resultCallback: (Int) -> Unit = {}
10 |
11 | fun requestPermission(permission: String, callback: (Int) -> Unit) {
12 | resultCallback = callback
13 | requestPermissions(arrayOf(permission), REQUEST_CODE)
14 | }
15 |
16 | override fun onRequestPermissionsResult(
17 | requestCode: Int,
18 | permissions: Array,
19 | grantResults: IntArray
20 | ) {
21 | resultCallback(grantResults[0])
22 | }
23 | }
--------------------------------------------------------------------------------
/draftsman/src/main/kotlin/com/gojek/draftsman/internal/model/DrawerItem.kt:
--------------------------------------------------------------------------------
1 | package com.gojek.draftsman.internal.model
2 |
3 | sealed class DrawerItem
4 |
5 | internal class DrawerHeadingItem(val title: String) : DrawerItem()
6 |
7 | internal class DrawerToggleItem(
8 | val icon: Int,
9 | val title: String,
10 | val toggleId: Int,
11 | val enabled: Boolean
12 | ) : DrawerItem()
13 |
14 | internal data class DrawerRangeItem(
15 | val title: String,
16 | val rangeId: Int,
17 | val minValue: Int,
18 | val maxValue: Int,
19 | val enabled: Boolean = false
20 | ) : DrawerItem()
21 |
22 | internal data class DrawerSelectionItem(
23 | val icon: Int,
24 | val selection: String,
25 | val selectionId: Int
26 | ) : DrawerItem()
--------------------------------------------------------------------------------
/draftsman/src/main/kotlin/com/gojek/draftsman/internal/model/LayeredView.kt:
--------------------------------------------------------------------------------
1 | package com.gojek.draftsman.internal.model
2 |
3 | import android.graphics.RectF
4 | import android.view.View
5 |
6 | internal data class LayeredView(
7 | val layer: Int,
8 | val view: View,
9 | val position: RectF
10 | )
--------------------------------------------------------------------------------
/draftsman/src/main/kotlin/com/gojek/draftsman/internal/utils/ColorUtils.kt:
--------------------------------------------------------------------------------
1 | package com.gojek.draftsman.internal.utils
2 |
3 | internal fun getColorHex(color: Int): String {
4 | return String.format("#%06X %d%%", 0xFFFFFF and color, getAlphaPercentage(color))
5 | }
6 |
7 | private fun getAlphaPercentage(color: Int): Int {
8 | return (color shr 24 and 0xff) * 100 / 255
9 | }
--------------------------------------------------------------------------------
/draftsman/src/main/kotlin/com/gojek/draftsman/internal/utils/DimensionConverter.kt:
--------------------------------------------------------------------------------
1 | package com.gojek.draftsman.internal.utils
2 |
3 | import android.content.Context
4 | import android.util.SparseArray
5 | import android.util.TypedValue
6 |
7 | internal object DimensionConverter {
8 |
9 | private val dpStore = SparseArray()
10 |
11 | private val spStore = SparseArray()
12 |
13 | fun getDpOf(px: Int, context: Context): Float {
14 | if (dpStore.indexOfKey(px) < 0) {
15 | dpStore.put(
16 | px, px.div(
17 | context.resources.displayMetrics.density
18 | )
19 | )
20 | }
21 |
22 | return dpStore[px]
23 | }
24 |
25 | fun getSpOf(px: Int, context: Context): Float {
26 | if (spStore.indexOfKey(px) < 0) {
27 | spStore.put(
28 | px, px.div(
29 | context.resources.displayMetrics.scaledDensity
30 | )
31 | )
32 | }
33 |
34 | return spStore[px]
35 | }
36 |
37 | fun getPxOfSp(sp: Float, context: Context): Float {
38 | return TypedValue.applyDimension(
39 | TypedValue.COMPLEX_UNIT_SP,
40 | sp,
41 | context.resources.displayMetrics
42 | )
43 | }
44 |
45 | fun getPxOfDp(dp: Float, context: Context): Float {
46 | return TypedValue.applyDimension(
47 | TypedValue.COMPLEX_UNIT_DIP,
48 | dp,
49 | context.resources.displayMetrics
50 | )
51 | }
52 | }
--------------------------------------------------------------------------------
/draftsman/src/main/kotlin/com/gojek/draftsman/internal/utils/DimensionUtils.kt:
--------------------------------------------------------------------------------
1 | package com.gojek.draftsman.internal.utils
2 |
3 | import android.content.Context
4 | import com.gojek.draftsman.internal.Config
5 |
6 | internal fun buildDimenString(
7 | dimension: Int,
8 | context: Context
9 | ) = if (Config.usePx) "$dimension px"
10 | else
11 | "${
12 | DimensionConverter.getDpOf(
13 | dimension,
14 | context
15 | ).toInt()
16 | } dp"
17 |
18 | internal fun buildTextSizeString(
19 | dimension: Float,
20 | context: Context
21 | ) = if (Config.usePx) "$dimension px"
22 | else
23 | "${
24 | DimensionConverter.getSpOf(
25 | dimension.toInt(),
26 | context
27 | ).toInt()
28 | } sp"
29 |
30 | internal fun buildGridSize(size: Int) = "${size}x${size}dp"
31 |
--------------------------------------------------------------------------------
/draftsman/src/main/kotlin/com/gojek/draftsman/internal/utils/DrawerLayoutContentList.kt:
--------------------------------------------------------------------------------
1 | package com.gojek.draftsman.internal.utils
2 |
3 | import com.gojek.draftsman.R
4 | import com.gojek.draftsman.internal.constants.*
5 | import com.gojek.draftsman.internal.model.DrawerHeadingItem
6 | import com.gojek.draftsman.internal.model.DrawerItem
7 | import com.gojek.draftsman.internal.model.DrawerSelectionItem
8 | import com.gojek.draftsman.internal.model.DrawerToggleItem
9 |
10 | internal fun getDrawerLayoutList(): List {
11 | return listOf(
12 | DrawerHeadingItem("Toggles"),
13 | DrawerToggleItem(R.drawable.draftsman_ic_margin, "Margin", MARGIN_VISUALIZER, true),
14 | DrawerToggleItem(R.drawable.draftsman_ic_padding, "Padding", PADDING_VISUALIZER, true),
15 | DrawerHeadingItem("Ruler/Grid"),
16 | DrawerToggleItem(R.drawable.draftsman_ic_grid, "Enable", GRID_VISUALIZER, false),
17 | DrawerHeadingItem("Root Element"),
18 | DrawerSelectionItem(
19 | R.drawable.draftsman_ic_change_root,
20 | "Activity Layout",
21 | INSPECTION_ROOT_SELECTION
22 | ),
23 | DrawerHeadingItem("Overlay"),
24 | DrawerSelectionItem(R.drawable.draftsman_ic_place_overlay, "Place overlay", OVERLAY_SELECTION)
25 | )
26 | }
--------------------------------------------------------------------------------
/draftsman/src/main/kotlin/com/gojek/draftsman/internal/utils/LayeredViewBuilder.kt:
--------------------------------------------------------------------------------
1 | package com.gojek.draftsman.internal.utils
2 |
3 | import android.graphics.RectF
4 | import android.view.View
5 | import android.view.ViewGroup
6 | import com.gojek.draftsman.internal.model.LayeredView
7 |
8 | internal class LayeredViewBuilder(
9 | private val topInset: Int
10 | ) {
11 |
12 | private var locationRectF = RectF()
13 |
14 | private val location = IntArray(2)
15 |
16 | private val layeredViews = mutableListOf()
17 |
18 | fun buildLayeredView(
19 | view: View
20 | ): List {
21 | layeredViews.clear()
22 | locateViews(view, 0)
23 | return layeredViews
24 | }
25 |
26 | private fun locateViews(
27 | view: View,
28 | layer: Int
29 | ) {
30 | if (view is ViewGroup) {
31 | locateViewGroup(view, layer)
32 | } else {
33 | if (view.visibility == View.VISIBLE) {
34 | locate(view, layer)
35 | }
36 | }
37 | }
38 |
39 | private fun locateViewGroup(
40 | viewGroup: ViewGroup,
41 | layer: Int
42 | ) {
43 | locate(viewGroup, layer)
44 | for (index in 0 until viewGroup.childCount) {
45 | locateViews(viewGroup.getChildAt(index), layer.plus(1))
46 | }
47 | }
48 |
49 | private fun locate(
50 | view: View,
51 | layer: Int
52 | ) {
53 | view.getLocationOnScreen(location)
54 | val left = location[0].toFloat()
55 | val top = location[1].minus(topInset).toFloat()
56 | locationRectF = RectF(
57 | left,
58 | top,
59 | left.plus(view.width),
60 | top.plus(view.height)
61 | )
62 |
63 | layeredViews.add(
64 | LayeredView(
65 | layer,
66 | view,
67 | locationRectF
68 | )
69 | )
70 | }
71 | }
--------------------------------------------------------------------------------
/draftsman/src/main/kotlin/com/gojek/draftsman/internal/utils/RectUtils.kt:
--------------------------------------------------------------------------------
1 | package com.gojek.draftsman.internal.utils
2 |
3 | import android.graphics.RectF
4 |
5 | internal fun isAllSame(list: List): Boolean {
6 | var isAllSame = true
7 | for (index in 0 until list.lastIndex) {
8 | isAllSame = list[index] == list[index + 1]
9 | if (!isAllSame) {
10 | break
11 | }
12 | }
13 | return isAllSame
14 | }
--------------------------------------------------------------------------------
/draftsman/src/main/kotlin/com/gojek/draftsman/internal/utils/TypeAlias.kt:
--------------------------------------------------------------------------------
1 | package com.gojek.draftsman.internal.utils
2 |
3 | import android.view.View
4 |
5 | internal typealias DimensionConfigChange = (Boolean) -> Unit
6 |
7 | internal typealias OnLayeredViewSelection = (View) -> Unit
8 |
9 | internal typealias ExitListener = () -> Unit
10 |
11 | internal typealias InfoDismissListener = () -> Unit
--------------------------------------------------------------------------------
/draftsman/src/main/kotlin/com/gojek/draftsman/internal/visualizers/GridVisualizer.kt:
--------------------------------------------------------------------------------
1 | package com.gojek.draftsman.internal.visualizers
2 |
3 | import android.content.Context
4 | import android.graphics.Canvas
5 | import android.graphics.Paint
6 | import android.graphics.Paint.ANTI_ALIAS_FLAG
7 | import android.view.View
8 | import com.gojek.draftsman.internal.Config
9 | import com.gojek.draftsman.internal.utils.DimensionConverter
10 |
11 | internal class GridVisualizer(context: Context) : View(context) {
12 |
13 | private val paint = Paint(ANTI_ALIAS_FLAG).apply {
14 | style = Paint.Style.STROKE
15 | strokeWidth = 1f
16 | color = Config.gridColor
17 | }
18 |
19 | private var gridSize = DimensionConverter.getPxOfDp(2f, context).toInt()
20 |
21 | fun updateGridSize(newSize: Int) {
22 | val newSizePx = DimensionConverter.getPxOfDp(newSize.toFloat(), context).toInt()
23 | if (gridSize != newSizePx) {
24 | gridSize = newSizePx
25 | invalidate()
26 | }
27 | }
28 |
29 | override fun onDraw(canvas: Canvas) {
30 | val heightFloat = height.toFloat()
31 | var indexFloat: Float
32 | for (index in 0..width step gridSize) {
33 | indexFloat = index.toFloat()
34 | canvas.drawLine(
35 | indexFloat,
36 | 0f,
37 | indexFloat,
38 | heightFloat,
39 | paint
40 | )
41 | }
42 | val widthFloat = width.toFloat()
43 | for (index in 0..height step gridSize) {
44 | indexFloat = index.toFloat()
45 | canvas.drawLine(
46 | 0f,
47 | indexFloat,
48 | widthFloat,
49 | indexFloat,
50 | paint
51 | )
52 | }
53 | }
54 | }
--------------------------------------------------------------------------------
/draftsman/src/main/kotlin/com/gojek/draftsman/internal/visualizers/InfoVisualizer.kt:
--------------------------------------------------------------------------------
1 | package com.gojek.draftsman.internal.visualizers
2 |
3 | import android.view.LayoutInflater
4 | import android.view.View
5 | import android.view.ViewGroup
6 | import android.widget.Switch
7 | import android.widget.TextView
8 | import com.gojek.draftsman.R
9 | import com.gojek.draftsman.internal.extensions.gone
10 | import com.gojek.draftsman.internal.extensions.visible
11 | import com.gojek.draftsman.internal.utils.*
12 | import com.gojek.draftsman.internal.widgets.ColorView
13 |
14 | internal class InfoVisualizer(
15 | parent: ViewGroup,
16 | exitListener: ExitListener,
17 | infoDismissListener: InfoDismissListener,
18 | private val configChange: DimensionConfigChange
19 | ) {
20 |
21 | private val layout = LayoutInflater.from(parent.context).inflate(
22 | R.layout.draftsman_info_layout,
23 | parent,
24 | false
25 | ).also {
26 | it.findViewById(R.id.exit_draftsman).setOnClickListener {
27 | exitListener()
28 | }
29 | it.findViewById(R.id.close_info_view).setOnClickListener {
30 | infoDismissListener()
31 | }
32 | parent.addView(it, parent.childCount - 1)
33 | }
34 |
35 | fun updateInfoView(
36 | view: View
37 | ) {
38 |
39 | setText(
40 | R.id.info_widget,
41 | view.javaClass.name
42 | )
43 |
44 | setText(
45 | R.id.info_width,
46 | "Width ${
47 | buildDimenString(
48 | view.width,
49 | view.context
50 | )
51 | }"
52 | )
53 |
54 | setText(
55 | R.id.info_height,
56 | "Height ${
57 | buildDimenString(
58 | view.height,
59 | view.context
60 | )
61 | }"
62 | )
63 |
64 | if (view is TextView &&
65 | view.text.isNotBlank()
66 | ) {
67 | setText(
68 | R.id.info_text_size,
69 | "TextSize ${
70 | buildTextSizeString(
71 | view.textSize,
72 | view.context
73 | )
74 | }"
75 | )
76 |
77 | setText(
78 | R.id.info_text_color,
79 | "TextColor ${getColorHex(view.currentTextColor)}"
80 | )
81 |
82 | with(layout) {
83 | findViewById(R.id.info_text_color_container).visible()
84 | findViewById(R.id.info_text_color_view).color = view.currentTextColor
85 | }
86 | } else {
87 | with(layout) {
88 | findViewById(R.id.info_text_color_container).gone()
89 | findViewById(R.id.info_text_size).gone()
90 | }
91 | }
92 |
93 | with(layout) {
94 | findViewById(R.id.info_view_container).visible()
95 | findViewById(R.id.dop_px_switch).setOnCheckedChangeListener { _, isChecked ->
96 | configChange(isChecked)
97 | }
98 | }
99 | }
100 |
101 | fun hide() {
102 | layout.findViewById(R.id.info_view_container).gone()
103 | }
104 |
105 | private fun setText(
106 | textViewId: Int,
107 | textToShow: String
108 | ) {
109 | layout.findViewById(textViewId).apply {
110 | text = textToShow
111 | visible()
112 | }
113 | }
114 |
115 | }
--------------------------------------------------------------------------------
/draftsman/src/main/kotlin/com/gojek/draftsman/internal/visualizers/LayeringVisualizer.kt:
--------------------------------------------------------------------------------
1 | package com.gojek.draftsman.internal.visualizers
2 |
3 | import android.view.LayoutInflater
4 | import android.view.View
5 | import android.view.ViewGroup
6 | import android.widget.TextView
7 | import com.gojek.draftsman.R
8 | import com.gojek.draftsman.internal.model.LayeredView
9 | import com.gojek.draftsman.internal.utils.OnLayeredViewSelection
10 | import com.gojek.draftsman.internal.widgets.NestedViewBoundView
11 |
12 | internal class LayeringVisualizer(parent: ViewGroup) {
13 |
14 | private val layout = LayoutInflater.from(parent.context).inflate(
15 | R.layout.view_layering_layout,
16 | parent,
17 | false
18 | ).also {
19 | parent.addView(it)
20 | }
21 |
22 | fun showLayeredViews(views: List) {
23 | with(layout) {
24 | findViewById(R.id.nested_view_text).text =
25 | "There are ${views.size} views with same boundaries here. Select the one you want to inspect"
26 | findViewById(R.id.nested_view_bound_view).showViewBounds(views)
27 | }
28 | }
29 |
30 | fun setOnLayeredViewSelectListener(listener: OnLayeredViewSelection) {
31 | layout.findViewById(R.id.nested_view_bound_view).layeredViewSelection =
32 | listener
33 | }
34 |
35 | fun setOnDismissListener(listener: View.OnClickListener) {
36 | layout.findViewById(R.id.close_layering_view).setOnClickListener(listener)
37 | }
38 |
39 | fun remove(parent: ViewGroup) {
40 | parent.removeView(layout)
41 | }
42 | }
--------------------------------------------------------------------------------
/draftsman/src/main/kotlin/com/gojek/draftsman/internal/visualizers/MarginVisualizer.kt:
--------------------------------------------------------------------------------
1 | package com.gojek.draftsman.internal.visualizers
2 |
3 | import android.content.Context
4 | import android.graphics.Canvas
5 | import android.graphics.RectF
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import com.gojek.draftsman.internal.Config
9 | import com.gojek.draftsman.internal.utils.buildDimenString
10 | import com.gojek.draftsman.internal.visualizers.TextVision.ORIENTATION_UNKNOWN
11 | import com.gojek.draftsman.internal.widgets.HShapeView
12 |
13 | internal object MarginVisualizer {
14 |
15 | fun draw(
16 | canvas: Canvas,
17 | view: View,
18 | locationRectF: RectF
19 | ) {
20 | val layoutParams = view.layoutParams
21 | if (layoutParams is ViewGroup.MarginLayoutParams) {
22 | val marginRectF = RectF()
23 | if (layoutParams.leftMargin > 0) {
24 | marginRectF.set(
25 | locationRectF.left.minus(
26 | layoutParams.leftMargin
27 | ),
28 | locationRectF.top,
29 | locationRectF.left,
30 | locationRectF.bottom
31 | )
32 |
33 | drawValues(
34 | canvas,
35 | view.context,
36 | layoutParams.leftMargin,
37 | marginRectF
38 | )
39 | }
40 |
41 | if (layoutParams.rightMargin > 0) {
42 | marginRectF.set(
43 | locationRectF.right,
44 | locationRectF.top,
45 | locationRectF.right.plus(
46 | layoutParams.rightMargin
47 | ),
48 | locationRectF.bottom
49 | )
50 |
51 | drawValues(
52 | canvas,
53 | view.context,
54 | layoutParams.rightMargin,
55 | marginRectF
56 | )
57 | }
58 |
59 | if (layoutParams.topMargin > 0) {
60 | marginRectF.set(
61 | locationRectF.left,
62 | locationRectF.top.minus(
63 | layoutParams.topMargin
64 | ),
65 | locationRectF.right,
66 | locationRectF.top
67 | )
68 |
69 | drawValues(
70 | canvas,
71 | view.context,
72 | layoutParams.topMargin,
73 | marginRectF
74 | )
75 | }
76 |
77 | if (layoutParams.bottomMargin > 0) {
78 | marginRectF.set(
79 | locationRectF.left,
80 | locationRectF.bottom,
81 | locationRectF.right,
82 | locationRectF.bottom.plus(
83 | layoutParams.bottomMargin
84 | )
85 | )
86 |
87 | drawValues(
88 | canvas,
89 | view.context,
90 | layoutParams.bottomMargin,
91 | marginRectF
92 | )
93 | }
94 | }
95 | }
96 |
97 | private fun drawValues(
98 | canvas: Canvas,
99 | context: Context,
100 | margin: Int,
101 | marginRectF: RectF
102 | ) {
103 | val text = buildDimenString(
104 | margin,
105 | context
106 | )
107 | val orientation =
108 | TextVision.measureText(
109 | context,
110 | text,
111 | marginRectF
112 | )
113 |
114 | HShapeView(context).draw(
115 | Config.marginColor,
116 | marginRectF,
117 | margin == marginRectF.height().toInt(),
118 | canvas
119 | )
120 |
121 | if (orientation != ORIENTATION_UNKNOWN) {
122 | TextVision.drawText(
123 | canvas,
124 | text,
125 | orientation,
126 | marginRectF
127 | )
128 | }
129 | }
130 | }
--------------------------------------------------------------------------------
/draftsman/src/main/kotlin/com/gojek/draftsman/internal/visualizers/OverlayVisualizer.kt:
--------------------------------------------------------------------------------
1 | package com.gojek.draftsman.internal.visualizers
2 |
3 | import android.content.Context
4 | import android.graphics.Bitmap
5 | import android.graphics.Canvas
6 | import android.graphics.drawable.BitmapDrawable
7 | import android.view.LayoutInflater
8 | import android.view.View
9 | import android.widget.FrameLayout
10 | import android.widget.SeekBar
11 | import android.widget.TextView
12 | import com.gojek.draftsman.R
13 | import com.gojek.draftsman.internal.extensions.doOnProgressChange
14 | import com.gojek.draftsman.internal.drawer.dpToPx
15 |
16 | class OverlayVisualizer(
17 | context: Context,
18 | onClickClose: () -> Unit
19 | ) : FrameLayout(context) {
20 |
21 | private val opacityControl: View
22 |
23 | private val opacityText: TextView
24 |
25 | init {
26 | setWillNotDraw(false)
27 | opacityControl =
28 | LayoutInflater.from(context).inflate(R.layout.overlay_opacity_control_layout, this)
29 | .findViewById(R.id.opacity_control)
30 | opacityText = findViewById(R.id.opacity)
31 | findViewById(R.id.opacity_seekbar).doOnProgressChange {
32 | opacityText.text = "$it%"
33 | setOpacity()
34 | postInvalidate()
35 | }
36 | findViewById(R.id.close_overlay).setOnClickListener {
37 | onClickClose()
38 | }
39 | }
40 |
41 | private var bitmap: BitmapDrawable? = null
42 |
43 | fun setOverlayImage(image: Bitmap) {
44 | bitmap = BitmapDrawable(resources, image)
45 | setOpacity()
46 | postInvalidate()
47 | }
48 |
49 | override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
50 | val margin = dpToPx(16, resources).toInt()
51 | opacityControl.layout(
52 | l + margin,
53 | b - margin - dpToPx(48, resources).toInt(),
54 | r - margin,
55 | b - margin
56 | )
57 | }
58 |
59 | override fun dispatchDraw(canvas: Canvas) {
60 | canvas.apply {
61 | if (null != bitmap) {
62 | bitmap?.setBounds(0, 0, width, height)
63 | bitmap?.draw(canvas)
64 | }
65 | }
66 |
67 | super.dispatchDraw(canvas)
68 | }
69 |
70 | private fun setOpacity() {
71 | val opacity = opacityText.text.replace(Regex("%"), "").toInt()
72 | bitmap?.alpha = 255 * opacity / 100
73 | }
74 | }
--------------------------------------------------------------------------------
/draftsman/src/main/kotlin/com/gojek/draftsman/internal/visualizers/PaddingVisualizer.kt:
--------------------------------------------------------------------------------
1 | package com.gojek.draftsman.internal.visualizers
2 |
3 | import android.content.Context
4 | import android.graphics.Canvas
5 | import android.graphics.Paint
6 | import android.graphics.Paint.ANTI_ALIAS_FLAG
7 | import android.graphics.RectF
8 | import android.view.View
9 | import com.gojek.draftsman.internal.Config
10 | import com.gojek.draftsman.internal.utils.buildDimenString
11 | import com.gojek.draftsman.internal.visualizers.TextVision.ORIENTATION_UNKNOWN
12 |
13 | internal object PaddingVisualizer {
14 |
15 | private val paddingPaint by lazy {
16 | Paint(ANTI_ALIAS_FLAG).apply {
17 | color = Config.paddingColor
18 | }
19 | }
20 |
21 | fun draw(
22 | canvas: Canvas,
23 | view: View,
24 | locationRectF: RectF
25 | ) {
26 | val paddingRectF = RectF()
27 | if (view.paddingLeft > 0) {
28 | paddingRectF.set(
29 | locationRectF.left,
30 | locationRectF.top,
31 | locationRectF.left.plus(view.paddingLeft),
32 | locationRectF.bottom
33 | )
34 | canvas.drawRect(
35 | paddingRectF,
36 | paddingPaint
37 | )
38 |
39 | drawPaddingValues(
40 | view.context,
41 | canvas,
42 | view.paddingLeft,
43 | paddingRectF
44 | )
45 | }
46 |
47 | if (view.paddingRight > 0) {
48 | paddingRectF.set(
49 | locationRectF.right.minus(view.paddingRight),
50 | locationRectF.top,
51 | locationRectF.right,
52 | locationRectF.bottom
53 | )
54 | canvas.drawRect(
55 | paddingRectF,
56 | paddingPaint
57 | )
58 | drawPaddingValues(
59 | view.context,
60 | canvas,
61 | view.paddingRight,
62 | paddingRectF
63 | )
64 | }
65 |
66 |
67 | if (view.paddingTop > 0) {
68 | paddingRectF.set(
69 | locationRectF.left,
70 | locationRectF.top,
71 | locationRectF.right,
72 | locationRectF.top.plus(view.paddingTop)
73 | )
74 | canvas.drawRect(
75 | paddingRectF,
76 | paddingPaint
77 | )
78 | drawPaddingValues(
79 | view.context,
80 | canvas,
81 | view.paddingTop,
82 | paddingRectF
83 | )
84 | }
85 | if (view.paddingBottom > 0) {
86 | paddingRectF.set(
87 | locationRectF.left,
88 | locationRectF.bottom.minus(view.paddingBottom),
89 | locationRectF.right,
90 | locationRectF.bottom
91 |
92 | )
93 | canvas.drawRect(
94 | paddingRectF,
95 | paddingPaint
96 | )
97 | drawPaddingValues(
98 | view.context,
99 | canvas,
100 | view.paddingBottom,
101 | paddingRectF
102 | )
103 | }
104 | }
105 |
106 | private fun drawPaddingValues(
107 | context: Context,
108 | canvas: Canvas,
109 | padding: Int,
110 | locationRectF: RectF
111 | ) {
112 | val text = buildDimenString(
113 | padding,
114 | context
115 | )
116 |
117 | val orientation =
118 | TextVision.measureText(
119 | context,
120 | text,
121 | locationRectF
122 | )
123 | if (orientation != ORIENTATION_UNKNOWN) {
124 | TextVision.drawText(
125 | canvas,
126 | text,
127 | orientation,
128 | locationRectF
129 | )
130 | }
131 | }
132 | }
--------------------------------------------------------------------------------
/draftsman/src/main/kotlin/com/gojek/draftsman/internal/visualizers/TextVisualizer.kt:
--------------------------------------------------------------------------------
1 | package com.gojek.draftsman.internal.visualizers
2 |
3 | import android.content.Context
4 | import android.graphics.*
5 | import android.text.TextPaint
6 | import com.gojek.draftsman.internal.utils.DimensionConverter
7 |
8 | private const val MAX_TEXT_SIZE = 12f
9 | private const val MIN_TEXT_SIZE = 8f
10 |
11 | internal object TextVision {
12 |
13 | private val textPaint by lazy {
14 | TextPaint(Paint.ANTI_ALIAS_FLAG).apply {
15 | color = Color.BLACK
16 | style = Paint.Style.FILL
17 | }
18 | }
19 |
20 | fun measureText(
21 | context: Context,
22 | text: String,
23 | locationRectF: RectF
24 | ): Int {
25 | textPaint.textSize = DimensionConverter.getPxOfSp(
26 | MAX_TEXT_SIZE,
27 | context
28 | )
29 | var orientation =
30 | ORIENTATION_UNKNOWN
31 | var textWidth: Float
32 | val minTextSize = DimensionConverter.getPxOfSp(
33 | MIN_TEXT_SIZE,
34 | context
35 | )
36 | val textBounds = Rect()
37 | while (textPaint.textSize >= minTextSize
38 | ) {
39 | textWidth = textPaint.measureText(text)
40 | textPaint.getTextBounds(
41 | text,
42 | 0,
43 | text.length,
44 | textBounds
45 | )
46 | if (locationRectF.width() > textWidth &&
47 | locationRectF.height() > textBounds.height()
48 | ) {
49 | orientation =
50 | ORIENTATION_HORIZONTAL
51 | break
52 | } else if (locationRectF.height() > textWidth &&
53 | locationRectF.width() > textBounds.height()
54 | ) {
55 | orientation =
56 | ORIENTATION_VERTICAL
57 | break
58 | }
59 |
60 | textPaint.textSize -= context.resources.displayMetrics.scaledDensity
61 | }
62 |
63 | return orientation
64 | }
65 |
66 | fun drawText(
67 | canvas: Canvas,
68 | text: String,
69 | orientation: Int,
70 | locationRectF: RectF
71 | ) {
72 | val textWidth = textPaint.measureText(text)
73 | val textBounds = Rect()
74 | textPaint.getTextBounds(
75 | text,
76 | 0,
77 | text.length,
78 | textBounds
79 | )
80 | val textHeight = textBounds.height()
81 |
82 | if (orientation == ORIENTATION_HORIZONTAL) {
83 |
84 | val startOffset = locationRectF.width()
85 | .minus(textWidth)
86 | .div(2f)
87 |
88 |
89 | canvas.drawText(
90 | text,
91 | locationRectF.left.plus(startOffset),
92 | locationRectF.centerY().plus(
93 | textHeight.div(2f)
94 | ),
95 | textPaint
96 | )
97 | } else {
98 |
99 | val path = Path().apply {
100 | moveTo(
101 | locationRectF.centerX().minus(textHeight.div(2f)),
102 | locationRectF.centerY().minus(textWidth.div(2f))
103 | )
104 | lineTo(
105 | locationRectF.centerX().minus(textHeight.div(2f)),
106 | locationRectF.centerY().plus(textWidth.div(2f))
107 | )
108 | }
109 |
110 | canvas.drawTextOnPath(
111 | text,
112 | path,
113 | 0f,
114 | 0f,
115 | textPaint
116 | )
117 | }
118 | }
119 |
120 | const val ORIENTATION_HORIZONTAL = 1
121 | const val ORIENTATION_VERTICAL = 2
122 | const val ORIENTATION_UNKNOWN = -1
123 | }
--------------------------------------------------------------------------------
/draftsman/src/main/kotlin/com/gojek/draftsman/internal/widgets/ColorView.kt:
--------------------------------------------------------------------------------
1 | package com.gojek.draftsman.internal.widgets
2 |
3 | import android.content.Context
4 | import android.graphics.Canvas
5 | import android.graphics.Color
6 | import android.graphics.Paint
7 | import android.graphics.Paint.ANTI_ALIAS_FLAG
8 | import android.util.AttributeSet
9 | import android.view.View
10 | import com.gojek.draftsman.internal.utils.DimensionConverter
11 |
12 | internal class ColorView @JvmOverloads constructor(
13 | context: Context,
14 | attributeSet: AttributeSet? = null,
15 | defStyleRes: Int = 0
16 | ) : View(
17 | context,
18 | attributeSet,
19 | defStyleRes
20 | ) {
21 | private val strokePaint = Paint(ANTI_ALIAS_FLAG).apply {
22 | style = Paint.Style.STROKE
23 | strokeWidth = DimensionConverter.getPxOfDp(1f, context)
24 | color = Color.WHITE
25 | }
26 |
27 | var color: Int? = null
28 | set(value) {
29 | field = value
30 | invalidate()
31 | }
32 |
33 | override fun onDraw(canvas: Canvas) {
34 | color?.let {
35 | canvas.run {
36 | drawColor(it)
37 | drawRect(
38 | strokePaint.strokeWidth,
39 | strokePaint.strokeWidth,
40 | width - strokePaint.strokeWidth,
41 | height - strokePaint.strokeWidth,
42 | strokePaint
43 | )
44 | }
45 | }
46 | }
47 | }
--------------------------------------------------------------------------------
/draftsman/src/main/kotlin/com/gojek/draftsman/internal/widgets/HShapeView.kt:
--------------------------------------------------------------------------------
1 | package com.gojek.draftsman.internal.widgets
2 |
3 | import android.content.Context
4 | import android.graphics.Canvas
5 | import android.graphics.Paint
6 | import android.graphics.Paint.ANTI_ALIAS_FLAG
7 | import android.graphics.Path
8 | import android.graphics.RectF
9 | import com.gojek.draftsman.internal.utils.DimensionConverter
10 |
11 | internal class HShapeView(context: Context) {
12 |
13 | private val length = DimensionConverter.getPxOfDp(16f, context)
14 |
15 | private val paint = Paint(ANTI_ALIAS_FLAG).apply {
16 | strokeWidth = DimensionConverter.getPxOfDp(1f, context)
17 | style = Paint.Style.STROKE
18 | }
19 |
20 | fun draw(
21 | color: Int,
22 | rectF: RectF,
23 | isVertical: Boolean,
24 | canvas: Canvas
25 | ) {
26 | paint.color = color
27 | canvas.drawPath(
28 | getPath(
29 | rectF,
30 | isVertical
31 | ),
32 | paint
33 | )
34 | }
35 |
36 | private fun getPath(
37 | rectF: RectF,
38 | isVertical: Boolean
39 | ) = if (isVertical) getVerticalH(rectF)
40 | else getHorizontalH(rectF)
41 |
42 | private fun getVerticalH(rectF: RectF): Path {
43 | val halfStroke = paint.strokeWidth.div(2)
44 | val path = Path()
45 | path.apply {
46 | moveTo(
47 | rectF.centerX() - length.div(2),
48 | rectF.bottom - halfStroke
49 | )
50 | rLineTo(length, 0f)
51 | moveTo(
52 | rectF.centerX(),
53 | rectF.bottom - halfStroke
54 | )
55 | rLineTo(0f, -(rectF.height() - halfStroke))
56 | rMoveTo(-length.div(2f), 0f)
57 | rLineTo(length, 0f)
58 | }
59 | return path
60 | }
61 |
62 | private fun getHorizontalH(rectF: RectF): Path {
63 | val halfStroke = paint.strokeWidth.div(2)
64 | val path = Path()
65 | path.apply {
66 | moveTo(
67 | rectF.left + halfStroke,
68 | rectF.centerY() - length.div(2)
69 | )
70 | rLineTo(0f, length)
71 | moveTo(
72 | rectF.left + halfStroke,
73 | rectF.centerY()
74 | )
75 | rLineTo(rectF.width() - halfStroke, 0f)
76 | rMoveTo(0f, -length.div(2f))
77 | rLineTo(0f, length)
78 | }
79 | return path
80 | }
81 | }
--------------------------------------------------------------------------------
/draftsman/src/main/kotlin/com/gojek/draftsman/internal/widgets/InspectionRootItemView.kt:
--------------------------------------------------------------------------------
1 | package com.gojek.draftsman.internal.widgets
2 |
3 | import android.content.Context
4 | import android.graphics.Color
5 | import android.widget.LinearLayout
6 | import android.widget.LinearLayout.LayoutParams.WRAP_CONTENT
7 | import android.widget.TextView
8 | import com.gojek.draftsman.internal.utils.DimensionConverter
9 |
10 | internal class InspectionRootItemView(context: Context) : LinearLayout(context) {
11 |
12 | init {
13 | val padding = DimensionConverter.getPxOfDp(8f, context).toInt()
14 | setPadding(padding, padding, padding, padding)
15 | val textView = TextView(context).apply {
16 | layoutParams = LayoutParams(WRAP_CONTENT, WRAP_CONTENT)
17 | textSize = 18f
18 | setTextColor(Color.parseColor("#555555"))
19 | setPadding(DimensionConverter.getPxOfDp(16f, context).toInt(), 0, 0, 0)
20 | }
21 | addView(textView)
22 | }
23 |
24 | fun setText(text: String) {
25 | (getChildAt(0) as TextView).text = text
26 | }
27 | }
--------------------------------------------------------------------------------
/draftsman/src/main/kotlin/com/gojek/draftsman/internal/widgets/NestedViewBoundView.kt:
--------------------------------------------------------------------------------
1 | package com.gojek.draftsman.internal.widgets
2 |
3 | import android.content.Context
4 | import android.graphics.Canvas
5 | import android.graphics.Color
6 | import android.graphics.Paint
7 | import android.graphics.Paint.ANTI_ALIAS_FLAG
8 | import android.graphics.RectF
9 | import android.util.AttributeSet
10 | import android.view.MotionEvent
11 | import android.view.View
12 | import android.view.View.MeasureSpec.getSize
13 | import com.gojek.draftsman.internal.model.LayeredView
14 | import com.gojek.draftsman.internal.utils.DimensionConverter
15 | import com.gojek.draftsman.internal.utils.OnLayeredViewSelection
16 | import com.gojek.draftsman.internal.drawer.dpToPx
17 |
18 | internal class NestedViewBoundView @JvmOverloads constructor(
19 | context: Context,
20 | attributeSet: AttributeSet? = null,
21 | defStyleRes: Int = 0
22 | ) : View(
23 | context,
24 | attributeSet,
25 | defStyleRes
26 | ) {
27 | private val strokePaint = Paint(ANTI_ALIAS_FLAG).apply {
28 | style = Paint.Style.STROKE
29 | strokeWidth = DimensionConverter.getPxOfDp(2f, context)
30 | color = Color.WHITE
31 | }
32 |
33 | private val textPaint = Paint(ANTI_ALIAS_FLAG).apply {
34 | textSize = DimensionConverter.getPxOfSp(14f, context)
35 | color = Color.WHITE
36 | }
37 |
38 | private val padding = DimensionConverter.getPxOfDp(32f, context)
39 | private val textPadding = DimensionConverter.getPxOfDp(8f, context)
40 |
41 | private val layeredViews = mutableListOf()
42 |
43 | private var viewProcessing: Runnable? = null
44 |
45 | private var numberOfViews = 0
46 |
47 | var layeredViewSelection: OnLayeredViewSelection = {}
48 |
49 | fun showViewBounds(views: List) {
50 | viewProcessing = Runnable {
51 | val strokeHalf = strokePaint.strokeWidth.div(2)
52 | var spacing: Float
53 | views.sortedBy { it.layer }
54 | .forEachIndexed { index, layeredView ->
55 | spacing = index.times(padding).plus(strokeHalf)
56 | layeredViews.add(
57 | LayeredView(
58 | layeredView.layer,
59 | layeredView.view,
60 | RectF(
61 | spacing,
62 | spacing,
63 | width.minus(spacing),
64 | height.minus(spacing)
65 | )
66 | )
67 | )
68 | }
69 | invalidate()
70 | viewProcessing = null
71 | }
72 |
73 | numberOfViews = views.size
74 | requestLayout()
75 | }
76 |
77 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
78 | val w = getSize(widthMeasureSpec)
79 | var h = dpToPx(220, resources).toInt()
80 | if (numberOfViews > 2) {
81 | h += (numberOfViews - 2) * padding.toInt() * 2
82 | }
83 | setMeasuredDimension(w, h)
84 | }
85 |
86 | override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
87 | super.onLayout(changed, left, top, right, bottom)
88 | if (changed) {
89 | if (null != viewProcessing) {
90 | consumeViewProcessing()
91 | }
92 | }
93 | }
94 |
95 | override fun onDraw(canvas: Canvas) {
96 | var name: String
97 | layeredViews.forEach {
98 | name = it.view.javaClass.simpleName
99 | canvas.run {
100 | drawRect(it.position, strokePaint)
101 | drawText(
102 | name,
103 | it.position.left + textPadding,
104 | it.position.top + textPaint.textSize + textPadding,
105 | textPaint
106 | )
107 | }
108 |
109 | }
110 | }
111 |
112 | override fun onTouchEvent(event: MotionEvent): Boolean {
113 | if (event.action == MotionEvent.ACTION_UP) {
114 | for (index in layeredViews.lastIndex downTo 0) {
115 | if (layeredViews[index].position.contains(event.x, event.y)) {
116 | layeredViewSelection(layeredViews[index].view)
117 | break
118 | }
119 | }
120 | }
121 |
122 | return true
123 | }
124 |
125 | private fun consumeViewProcessing() {
126 | post(viewProcessing)
127 | }
128 | }
--------------------------------------------------------------------------------
/draftsman/src/main/res/drawable/draftsman_drawer_item_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/draftsman/src/main/res/drawable/draftsman_ic_change_root.xml:
--------------------------------------------------------------------------------
1 |
6 |
12 |
18 |
19 |
--------------------------------------------------------------------------------
/draftsman/src/main/res/drawable/draftsman_ic_close.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/draftsman/src/main/res/drawable/draftsman_ic_drawer_close.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
13 |
19 |
20 |
--------------------------------------------------------------------------------
/draftsman/src/main/res/drawable/draftsman_ic_drawer_open.xml:
--------------------------------------------------------------------------------
1 |
6 |
13 |
20 |
21 |
--------------------------------------------------------------------------------
/draftsman/src/main/res/drawable/draftsman_ic_grid.xml:
--------------------------------------------------------------------------------
1 |
6 |
12 |
19 |
26 |
33 |
40 |
47 |
54 |
61 |
68 |
69 |
--------------------------------------------------------------------------------
/draftsman/src/main/res/drawable/draftsman_ic_margin.xml:
--------------------------------------------------------------------------------
1 |
6 |
12 |
19 |
26 |
33 |
40 |
47 |
54 |
61 |
68 |
75 |
82 |
89 |
96 |
103 |
110 |
117 |
124 |
131 |
138 |
145 |
152 |
153 |
--------------------------------------------------------------------------------
/draftsman/src/main/res/drawable/draftsman_ic_padding.xml:
--------------------------------------------------------------------------------
1 |
6 |
12 |
19 |
26 |
33 |
40 |
47 |
54 |
61 |
68 |
75 |
82 |
89 |
96 |
103 |
110 |
117 |
124 |
131 |
138 |
145 |
152 |
153 |
--------------------------------------------------------------------------------
/draftsman/src/main/res/drawable/draftsman_ic_place_overlay.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
13 |
19 |
26 |
33 |
40 |
46 |
52 |
59 |
67 |
75 |
83 |
90 |
91 |
--------------------------------------------------------------------------------
/draftsman/src/main/res/drawable/draftsman_info_view_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/draftsman/src/main/res/drawable/draftsman_rounded_btn_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
--------------------------------------------------------------------------------
/draftsman/src/main/res/drawable/draftsman_vertical_divider.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/draftsman/src/main/res/layout/draftsman_drawer_item_header.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
17 |
18 |
--------------------------------------------------------------------------------
/draftsman/src/main/res/layout/draftsman_drawer_item_range.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
17 |
18 |
24 |
25 |
33 |
--------------------------------------------------------------------------------
/draftsman/src/main/res/layout/draftsman_drawer_item_selection.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
17 |
18 |
19 |
30 |
31 |
37 |
38 |
--------------------------------------------------------------------------------
/draftsman/src/main/res/layout/draftsman_drawer_item_toggle.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
16 |
17 |
23 |
24 |
36 |
37 |
--------------------------------------------------------------------------------
/draftsman/src/main/res/layout/draftsman_drawer_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/draftsman/src/main/res/layout/draftsman_info_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
19 |
20 |
30 |
31 |
37 |
38 |
39 |
40 |
41 |
47 |
48 |
54 |
55 |
62 |
63 |
70 |
71 |
77 |
78 |
83 |
84 |
85 |
86 |
90 |
91 |
97 |
98 |
105 |
106 |
113 |
114 |
115 |
116 |
117 |
130 |
131 |
--------------------------------------------------------------------------------
/draftsman/src/main/res/layout/overlay_opacity_control_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
20 |
21 |
31 |
32 |
40 |
41 |
--------------------------------------------------------------------------------
/draftsman/src/main/res/layout/view_layering_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
16 |
17 |
24 |
25 |
33 |
34 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/draftsman/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/draftsman/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
13 |
14 |
--------------------------------------------------------------------------------
/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 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=officiaL
20 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gojek/draftsman/b0909fb15abad702dd8b8a985f7c60e2f92bdbf0/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Feb 26 15:31:14 IST 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.5.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 |
--------------------------------------------------------------------------------
/scripts/publish-module.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'maven-publish'
2 | apply plugin: 'signing'
3 |
4 | task androidSourcesJar(type: Jar) {
5 | archiveClassifier.set('sources')
6 | if (project.plugins.findPlugin("com.android.library")) {
7 | // For Android libraries
8 | from android.sourceSets.main.java.srcDirs
9 | from android.sourceSets.main.kotlin.srcDirs
10 | } else {
11 | // For pure Kotlin libraries, in case you have them
12 | from sourceSets.main.java.srcDirs
13 | from sourceSets.main.kotlin.srcDirs
14 | }
15 | }
16 |
17 | artifacts {
18 | archives androidSourcesJar
19 | }
20 |
21 | group = PUBLISH_GROUP_ID
22 | version = PUBLISH_VERSION
23 |
24 | afterEvaluate {
25 | publishing {
26 | publications {
27 | release(MavenPublication) {
28 | groupId PUBLISH_GROUP_ID
29 | artifactId PUBLISH_ARTIFACT_ID
30 | version PUBLISH_VERSION
31 |
32 | // Two artifacts, the `aar` (or `jar`) and the sources
33 | if (project.plugins.findPlugin("com.android.library")) {
34 | from components.release
35 | } else {
36 | from components.java
37 | }
38 |
39 | artifact androidSourcesJar
40 | //artifact javadocJar
41 |
42 | pom {
43 | name = PUBLISH_ARTIFACT_ID
44 | description = 'Draftsman'
45 | url = 'https://github.com/gojek/draftsman'
46 | licenses {
47 | license {
48 | name = 'Draftsman License'
49 | url = 'https://github.com/gojek/draftsman/blob/main/LICENSE'
50 | }
51 | }
52 | developers {
53 | developer {
54 | id = 'jitinsharma'
55 | name = 'Jitin Sharma'
56 | email = 'jitin.sharma@gojek.com'
57 | }
58 | }
59 | scm {
60 | connection = 'scm:git:github.com/gojek/draftsman.git'
61 | developerConnection = 'scm:git:ssh://github.com/gojek/draftsman.git'
62 | url = 'https://github.com/gojek/draftsman/tree/main'
63 | }
64 | }
65 | }
66 | }
67 | }
68 | }
69 |
70 | signing {
71 | useInMemoryPgpKeys(
72 | rootProject.ext["signing.keyId"],
73 | rootProject.ext["signing.key"],
74 | rootProject.ext["signing.password"],
75 | )
76 | sign publishing.publications
77 | }
78 |
--------------------------------------------------------------------------------
/scripts/publish-root.gradle:
--------------------------------------------------------------------------------
1 | // Create variables with empty default values
2 | ext["ossrhUsername"] = ''
3 | ext["ossrhPassword"] = ''
4 | ext["sonatypeStagingProfileId"] = ''
5 | ext["signing.keyId"] = ''
6 | ext["signing.password"] = ''
7 | ext["signing.key"] = ''
8 |
9 | File secretPropsFile = project.rootProject.file('local.properties')
10 | if (secretPropsFile.exists()) {
11 | // Read local.properties file first if it exists
12 | Properties p = new Properties()
13 | new FileInputStream(secretPropsFile).withCloseable { is -> p.load(is) }
14 | p.each { name, value -> ext[name] = value }
15 | } else {
16 | // Use system environment variables
17 | ext["ossrhUsername"] = System.getenv('OSSRH_USERNAME')
18 | ext["ossrhPassword"] = System.getenv('OSSRH_PASSWORD')
19 | ext["sonatypeStagingProfileId"] = System.getenv('SONATYPE_STAGING_PROFILE_ID')
20 | ext["signing.keyId"] = System.getenv('SIGNING_KEY_ID')
21 | ext["signing.password"] = System.getenv('SIGNING_PASSWORD')
22 | ext["signing.key"] = System.getenv('SIGNING_KEY')
23 | }
24 |
25 | // Set up Sonatype repository
26 | nexusPublishing {
27 | repositories {
28 | sonatype {
29 | stagingProfileId = sonatypeStagingProfileId
30 | username = ossrhUsername
31 | password = ossrhPassword
32 | nexusUrl.set(uri("https://oss.sonatype.org/service/local/"))
33 | snapshotRepositoryUrl.set(uri("https://oss.sonatype.org/content/repositories/snapshots/"))
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | include ( ":app", ":draftsman", ":draftsman-no-op")
2 | rootProject.name = "Draftsman"
3 |
--------------------------------------------------------------------------------