├── .editorconfig
├── .github
└── workflows
│ └── android.yml
├── .gitignore
├── .idea
├── codeStyles
│ ├── Project.xml
│ └── codeStyleConfig.xml
└── runConfigurations.xml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ ├── assets
│ │ └── scr.png
│ └── java
│ │ └── de
│ │ └── beatbrot
│ │ └── screenshotassistant
│ │ ├── MainActivityTest.kt
│ │ └── custom
│ │ ├── CustomActions.kt
│ │ └── CustomMatchers.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── ic_launcher-web.png
│ ├── java
│ │ └── de
│ │ │ └── beatbrot
│ │ │ └── screenshotassistant
│ │ │ ├── AboutActivity.kt
│ │ │ ├── EditingMode.kt
│ │ │ ├── MainActivity.kt
│ │ │ ├── ScreenshotActivityViewModel.kt
│ │ │ ├── sheets
│ │ │ ├── ModalSettingsSheet.kt
│ │ │ └── drawsettings
│ │ │ │ ├── DrawMode.kt
│ │ │ │ ├── DrawSettingsSheet.kt
│ │ │ │ └── DrawSettingsViewModel.kt
│ │ │ ├── util
│ │ │ ├── CompressFormat+.kt
│ │ │ ├── Context+.kt
│ │ │ ├── CropImageView+.kt
│ │ │ ├── LiveData+.kt
│ │ │ ├── OpenAnimationListener.kt
│ │ │ └── SharedPreferences+.kt
│ │ │ └── voice
│ │ │ ├── InteractionService.kt
│ │ │ ├── InteractionSession.kt
│ │ │ └── InteractionSessionService.kt
│ └── res
│ │ ├── anim
│ │ ├── slide_down.xml
│ │ └── slide_up.xml
│ │ ├── color
│ │ └── button_color.xml
│ │ ├── drawable-v24
│ │ └── ic_launcher_background.xml
│ │ ├── drawable
│ │ ├── ic_book_gray_24dp.xml
│ │ ├── ic_check_black_24dp.xml
│ │ ├── ic_chevron_left_black_24dp.xml
│ │ ├── ic_chevron_right_black_24dp.xml
│ │ ├── ic_format_shapes_unfocused_24dp.xml
│ │ ├── ic_github_gray_24dp.xml
│ │ ├── ic_grade_unfocused_24dp.xml
│ │ ├── ic_icon_bg.xml
│ │ ├── ic_info_outline_gray_24dp.xml
│ │ ├── ic_launcher_background.xml
│ │ ├── ic_launcher_foreground.xml
│ │ ├── ic_marker_24dp.xml
│ │ ├── ic_more_vert_white_24dp.xml
│ │ ├── ic_pencil_black_24dp.xml
│ │ └── ic_share_black_24dp.xml
│ │ ├── layout
│ │ ├── activity_main.xml
│ │ ├── sheet_colorsettings.xml
│ │ └── titled_modal_sheet.xml
│ │ ├── menu
│ │ ├── about_menu.xml
│ │ └── draw_modes.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── values-de-rDE
│ │ └── strings.xml
│ │ ├── values-night
│ │ └── colors.xml
│ │ ├── values
│ │ ├── arrays.xml
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ │ └── xml
│ │ ├── fileprovider.xml
│ │ ├── root_preferences.xml
│ │ └── voice_interaction_service.xml
│ └── test
│ └── java
│ └── de
│ └── beatbrot
│ └── screenshotassistant
│ └── sheets
│ └── drawsettings
│ └── DrawSettingsViewModelTest.kt
├── art
├── banner.png
├── banner.svg
└── icon
│ ├── background.svg
│ ├── device.svg
│ └── foreground.svg
├── build.gradle
├── fastlane
└── metadata
│ └── android
│ └── en-US
│ ├── changelogs
│ ├── 2.txt
│ └── 3.txt
│ ├── full_description.txt
│ ├── images
│ ├── featureGraphic.png
│ └── phoneScreenshots
│ │ ├── 1 - non-cropped.png
│ │ ├── 2 - cropped.png
│ │ ├── 3 - draw.png
│ │ ├── 4 -share.png
│ │ ├── 5 - cropDark.png
│ │ ├── 6 - drawDark.png
│ │ └── 7 - about.png
│ ├── short_description.txt
│ └── title.txt
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*]
2 | charset = utf-8
3 | indent_size = 4
4 | indent_style = space
5 | insert_final_newline = true
6 | tab_width = 4
7 |
8 | # noinspection EditorConfigKeyCorrectness
9 | [{*.kt, *.kts}]
10 | disabled_rules = no-wildcard-imports,import-ordering
11 |
--------------------------------------------------------------------------------
/.github/workflows/android.yml:
--------------------------------------------------------------------------------
1 | name: Android CI
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: ubuntu-latest
9 |
10 | steps:
11 | - uses: actions/checkout@v1
12 | - name: Build with Gradle
13 | run: ./gradlew build
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.gitignore.io/api/android,androidstudio
3 | # Edit at https://www.gitignore.io/?templates=android,androidstudio
4 |
5 | ### Android ###
6 | # Built application files
7 | *.apk
8 | *.ap_
9 | *.aab
10 |
11 | # Files for the ART/Dalvik VM
12 | *.dex
13 |
14 | # Java class files
15 | *.class
16 |
17 | # Generated files
18 | bin/
19 | gen/
20 | out/
21 | release/
22 |
23 | # Gradle files
24 | .gradle/
25 | build/
26 |
27 | # Local configuration file (sdk path, etc)
28 | local.properties
29 |
30 | # Proguard folder generated by Eclipse
31 | proguard/
32 |
33 | # Log Files
34 | *.log
35 |
36 | # Android Studio Navigation editor temp files
37 | .navigation/
38 |
39 | # Android Studio captures folder
40 | captures/
41 |
42 | # IntelliJ
43 | *.iml
44 | .idea/workspace.xml
45 | .idea/tasks.xml
46 | .idea/gradle.xml
47 | .idea/assetWizardSettings.xml
48 | .idea/dictionaries
49 | .idea/libraries
50 | # Android Studio 3 in .gitignore file.
51 | .idea/caches
52 | .idea/modules.xml
53 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you
54 | .idea/navEditor.xml
55 |
56 | # Keystore files
57 | # Uncomment the following lines if you do not want to check your keystore files in.
58 | #*.jks
59 | #*.keystore
60 |
61 | # External native build folder generated in Android Studio 2.2 and later
62 | .externalNativeBuild
63 |
64 | # Google Services (e.g. APIs or Firebase)
65 | # google-services.json
66 |
67 | # Freeline
68 | freeline.py
69 | freeline/
70 | freeline_project_description.json
71 |
72 | # fastlane
73 | fastlane/report.xml
74 | fastlane/Preview.html
75 | fastlane/screenshots
76 | fastlane/test_output
77 | fastlane/readme.md
78 |
79 | # Version control
80 | vcs.xml
81 |
82 | # lint
83 | lint/intermediates/
84 | lint/generated/
85 | lint/outputs/
86 | lint/tmp/
87 | # lint/reports/
88 |
89 | ### Android Patch ###
90 | gen-external-apklibs
91 | output.json
92 |
93 | ### AndroidStudio ###
94 | # Covers files to be ignored for android development using Android Studio.
95 |
96 | # Built application files
97 |
98 | # Files for the ART/Dalvik VM
99 |
100 | # Java class files
101 |
102 | # Generated files
103 |
104 | # Gradle files
105 | .gradle
106 |
107 | # Signing files
108 | .signing/
109 |
110 | # Local configuration file (sdk path, etc)
111 |
112 | # Proguard folder generated by Eclipse
113 |
114 | # Log Files
115 |
116 | # Android Studio
117 | /*/build/
118 | /*/local.properties
119 | /*/out
120 | /*/*/build
121 | /*/*/production
122 | *.ipr
123 | *~
124 | *.swp
125 |
126 | # Android Patch
127 |
128 | # External native build folder generated in Android Studio 2.2 and later
129 |
130 | # NDK
131 | obj/
132 |
133 | # IntelliJ IDEA
134 | *.iws
135 | /out/
136 |
137 | # User-specific configurations
138 | .idea/caches/
139 | .idea/libraries/
140 | .idea/shelf/
141 | .idea/.name
142 | .idea/compiler.xml
143 | .idea/copyright/profiles_settings.xml
144 | .idea/encodings.xml
145 | .idea/misc.xml
146 | .idea/scopes/scope_settings.xml
147 | .idea/vcs.xml
148 | .idea/jsLibraryMappings.xml
149 | .idea/datasources.xml
150 | .idea/dataSources.ids
151 | .idea/sqlDataSources.xml
152 | .idea/dynamic.xml
153 | .idea/uiDesigner.xml
154 |
155 | # OS-specific files
156 | .DS_Store
157 | .DS_Store?
158 | ._*
159 | .Spotlight-V100
160 | .Trashes
161 | ehthumbs.db
162 | Thumbs.db
163 |
164 | # Legacy Eclipse project files
165 | .classpath
166 | .project
167 | .cproject
168 | .settings/
169 |
170 | # Mobile Tools for Java (J2ME)
171 | .mtj.tmp/
172 |
173 | # Package Files #
174 | *.war
175 | *.ear
176 |
177 | # virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml)
178 | hs_err_pid*
179 |
180 | ## Plugin-specific files:
181 |
182 | # mpeltonen/sbt-idea plugin
183 | .idea_modules/
184 |
185 | # JIRA plugin
186 | atlassian-ide-plugin.xml
187 |
188 | # Mongo Explorer plugin
189 | .idea/mongoSettings.xml
190 |
191 | # Crashlytics plugin (for Android Studio and IntelliJ)
192 | com_crashlytics_export_strings.xml
193 | crashlytics.properties
194 | crashlytics-build.properties
195 | fabric.properties
196 |
197 | ### AndroidStudio Patch ###
198 |
199 | !/gradle/wrapper/gradle-wrapper.jar
200 |
201 | # End of https://www.gitignore.io/api/android,androidstudio
202 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | xmlns:android
17 |
18 | ^$
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | xmlns:.*
28 |
29 | ^$
30 |
31 |
32 | BY_NAME
33 |
34 |
35 |
36 |
37 |
38 |
39 | .*:id
40 |
41 | http://schemas.android.com/apk/res/android
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | .*:name
51 |
52 | http://schemas.android.com/apk/res/android
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | name
62 |
63 | ^$
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | style
73 |
74 | ^$
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 | .*
84 |
85 | ^$
86 |
87 |
88 | BY_NAME
89 |
90 |
91 |
92 |
93 |
94 |
95 | .*
96 |
97 | http://schemas.android.com/apk/res/android
98 |
99 |
100 | ANDROID_ATTRIBUTE_ORDER
101 |
102 |
103 |
104 |
105 |
106 |
107 | .*
108 |
109 | .*
110 |
111 |
112 | BY_NAME
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # 2.0.1 (2019-09-19)
2 |
3 | - 💾 Save editor state accross configuration changes
4 | - 🈂️ Add translation to settings
5 | - 💥 Fix broken About-Activity
6 |
7 | # 2.0 (2019-09-11)
8 |
9 | ### Features
10 |
11 | - ✏️ Paint tools
12 | - 🇩🇪 Added german translation
13 | - ⚙️ Added settings for image format and quality
14 | - 🌙 Complete support for dark mode
15 |
16 | ### BugFixes
17 |
18 | - Do not completely reset image when switching on configuration change (WIP)
--------------------------------------------------------------------------------
/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 2019 beatbrot
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 | Screenshot assistant
5 |
19 |
20 |
27 |
28 | ## Table of Contents
29 | - [See it in action](#see-it-in-action)
30 | - [Setup Guide](#setup-guide)
31 | - [Features](#features)
32 | - [Credits](#credits)
33 |
34 | ## See it in action
35 |
36 |
37 |
38 |
39 |
40 | ## Setup Guide
41 |
42 | 1. Install the app
43 | 2. Set it as assistant via `Apps` > `Default Apps` > `Assist app`
44 | 3. Trigger assistant, crop and share
45 |
46 |
47 | ## Features
48 |
49 | - Screenshot in 100% JPEG-Quality
50 | - Tight system integration via Assist API
51 | - No permission needed
52 |
53 | ## Credits
54 |
55 | - Original [Screenshot Assistant](https://play.google.com/store/apps/details?id=pl.waskysoft.screenshotassistant) - *Unfortunately not open source*
56 | - [Android Image Cropper](https://github.com/ArthurHub/Android-Image-Cropper)
57 | - [Material About Library](https://github.com/daniel-stoneuk/material-about-library)
58 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | apply plugin: 'kotlin-android'
4 |
5 | apply plugin: "org.jmailen.kotlinter"
6 |
7 | android {
8 | compileSdkVersion 29
9 | buildToolsVersion "29.0.3"
10 | defaultConfig {
11 | applicationId "de.beatbrot.screenshotassistant"
12 | minSdkVersion 23
13 | targetSdkVersion 29
14 | versionCode 3
15 | versionName "2.0.1"
16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
17 | }
18 | buildTypes {
19 | release {
20 | minifyEnabled true
21 | shrinkResources true
22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
23 | }
24 | }
25 | buildFeatures {
26 | viewBinding = true
27 | }
28 |
29 | compileOptions {
30 | sourceCompatibility = 1.8
31 | targetCompatibility = 1.8
32 | }
33 | kotlinOptions {
34 | jvmTarget = "1.8"
35 | }
36 | }
37 |
38 | dependencies {
39 | implementation fileTree(dir: 'libs', include: ['*.jar'])
40 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
41 | implementation 'androidx.appcompat:appcompat:1.1.0'
42 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
43 | implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
44 | implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
45 |
46 | implementation "androidx.preference:preference-ktx:1.1.1"
47 | implementation "androidx.fragment:fragment-ktx:1.2.4"
48 |
49 | implementation 'com.google.android.material:material:1.2.0-alpha06'
50 |
51 | implementation 'com.github.beatbrot:ImagePainter:3c3f2b9bcc'
52 | implementation 'com.theartofdev.edmodo:android-image-cropper:2.8.0'
53 |
54 | implementation 'com.github.savvyapps:ToggleButtonLayout:1.3.0'
55 | implementation 'com.thebluealliance:spectrum:0.7.1'
56 |
57 | implementation 'com.github.daniel-stoneuk:material-about-library:2.4.2'
58 |
59 | testImplementation 'androidx.test:core-ktx:1.2.0'
60 | testImplementation "androidx.arch.core:core-testing:2.1.0"
61 | testImplementation 'androidx.test.ext:junit-ktx:1.1.1'
62 | testImplementation 'androidx.test:runner:1.2.0'
63 | testImplementation 'androidx.test:rules:1.2.0'
64 | androidTestImplementation 'androidx.test.ext:junit-ktx:1.1.1'
65 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
66 | androidTestImplementation 'androidx.test.espresso:espresso-intents:3.2.0'
67 | androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
68 | }
69 |
--------------------------------------------------------------------------------
/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.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 | -keep class androidx.appcompat.widget.** { *; }
--------------------------------------------------------------------------------
/app/src/androidTest/assets/scr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/beatbrot/ScreenshotAssistant/3d8ce0dc0e2580a6ba55d3379654b67ad79d9112/app/src/androidTest/assets/scr.png
--------------------------------------------------------------------------------
/app/src/androidTest/java/de/beatbrot/screenshotassistant/MainActivityTest.kt:
--------------------------------------------------------------------------------
1 | package de.beatbrot.screenshotassistant
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import android.net.Uri
6 | import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES
7 | import androidx.test.espresso.Espresso.onView
8 | import androidx.test.espresso.action.ViewActions.click
9 | import androidx.test.espresso.assertion.ViewAssertions.matches
10 | import androidx.test.espresso.intent.Intents.intended
11 | import androidx.test.espresso.intent.matcher.IntentMatchers.*
12 | import androidx.test.espresso.intent.rule.IntentsTestRule
13 | import androidx.test.espresso.matcher.ViewMatchers.*
14 | import androidx.test.ext.junit.runners.AndroidJUnit4
15 | import androidx.test.platform.app.InstrumentationRegistry
16 | import androidx.test.uiautomator.UiDevice
17 | import de.beatbrot.imagepainter.view.ImagePainterView
18 | import de.beatbrot.screenshotassistant.custom.*
19 | import org.hamcrest.Matchers.*
20 | import org.junit.Assert.*
21 | import org.junit.Before
22 | import org.junit.Ignore
23 | import org.junit.Rule
24 | import org.junit.Test
25 | import org.junit.runner.RunWith
26 | import java.io.File
27 |
28 | @RunWith(AndroidJUnit4::class)
29 | class MainActivityTest {
30 |
31 | private val context: Context
32 | get() = InstrumentationRegistry.getInstrumentation().targetContext
33 |
34 | private val testContext: Context
35 | get() = InstrumentationRegistry.getInstrumentation().context
36 |
37 | @get:Rule
38 | val activityRule = IntentsTestRule(MainActivity::class.java, false, false)
39 |
40 | @Before
41 | fun launchActivity() {
42 | val startIntent = Intent(context, MainActivity::class.java)
43 | startIntent.putExtra("screenshot", copyScreenshotInCache(context))
44 | activityRule.launchActivity(startIntent)
45 | }
46 |
47 | @Test
48 | fun testActivityLaunches() {
49 | onView(withId(R.id.cropView))
50 | .check(matches(isDisplayed()))
51 | }
52 |
53 | @Test
54 | fun testImageIsLoaded() {
55 | onView(withId(R.id.cropView))
56 | .check(matches(containsImage()))
57 | }
58 |
59 | @Test
60 | fun testAboutLaunches() {
61 | onView(withId(R.id.menuButton))
62 | .perform(click())
63 |
64 | onView(withText(R.string.about))
65 | .perform(click())
66 |
67 | intended(launchesActivity(AboutActivity::class.java))
68 | }
69 |
70 | @Test
71 | fun testSettingsLoad() {
72 | onView(withId(R.id.menuButton))
73 | .perform(click())
74 |
75 | onView(withText(R.string.title_sheet_settings))
76 | .perform(click())
77 |
78 | onView(withText(R.string.image_quality))
79 | .check(matches(isCompletelyDisplayed()))
80 | }
81 |
82 | @Test
83 | fun testCroppingWorks() {
84 | onView(withId(R.id.cropView))
85 | .perform(cropImageByHalf())
86 | .check(checkCropSuccessful())
87 | }
88 |
89 | @Test
90 | fun testImageEditorLoads() {
91 | onView(withId(R.id.drawButton))
92 | .perform(click())
93 |
94 | onView(withId(R.id.imagePainter))
95 | .check(matches(isCompletelyDisplayed()))
96 | }
97 |
98 | @Test
99 | @Ignore("Messes with other tests")
100 | fun testShareOpens() {
101 | onView(withId(R.id.shareButton))
102 | .perform(click())
103 |
104 | intended(
105 | allOf(
106 | hasAction(Intent.ACTION_CHOOSER),
107 | hasExtra(
108 | equalTo(Intent.EXTRA_INTENT), allOf(
109 | hasExtra(
110 | equalTo(Intent.EXTRA_STREAM),
111 | hasToString(containsString(AUTHORITY_NAME))
112 | ),
113 | hasType(MIME_TYPE)
114 | )
115 | )
116 | )
117 | )
118 |
119 | val dev = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
120 | dev.pressHome()
121 | }
122 |
123 | @Test
124 | fun testCropRectIsPersistent() {
125 | onView(withId(R.id.cropView))
126 | .perform(cropImageByHalf())
127 | .check(checkCropSuccessful())
128 |
129 | enableNightMode()
130 |
131 | onView(withId(R.id.cropView))
132 | .check(checkCropSuccessful())
133 | }
134 |
135 | @Test
136 | fun testDrawStackIsPersistent() {
137 | onView(withId(R.id.drawButton))
138 | .perform(click())
139 |
140 | onView(withId(R.id.imagePainter))
141 | .perform(draw())
142 |
143 | var painter: ImagePainterView = activityRule.activity.findViewById(R.id.imagePainter)
144 | assertTrue(painter.canUndo())
145 | assertFalse(painter.canRedo())
146 | val oldStack = painter.drawStack
147 |
148 | enableNightMode()
149 |
150 | painter = activityRule.activity.findViewById(R.id.imagePainter)
151 | assertTrue(painter.canUndo())
152 | assertFalse(painter.canRedo())
153 | val newStack = painter.drawStack
154 | assertEquals(oldStack, newStack)
155 | assertEquals(oldStack.hashCode(), newStack.hashCode())
156 |
157 | onView(withId(R.id.undoButton))
158 | .perform(click())
159 |
160 | assertTrue(painter.canUndo())
161 | assertFalse(painter.canRedo())
162 | }
163 |
164 | private fun enableNightMode() {
165 | activityRule.runOnUiThread {
166 | activityRule.activity.delegate.apply {
167 | localNightMode = MODE_NIGHT_YES
168 | applyDayNight()
169 | }
170 | }
171 | }
172 |
173 | private fun copyScreenshotInCache(context: Context): Uri {
174 | val scrStream = testContext.assets.open("scr.png")
175 | val buffer = ByteArray(scrStream.available())
176 | scrStream.read(buffer)
177 |
178 | val scrDir = File(context.cacheDir, "screenshots")
179 | val scrFile = File(scrDir, "screen.png")
180 |
181 | scrDir.mkdir()
182 |
183 | scrFile.apply {
184 | if (!exists()) {
185 | delete()
186 | }
187 | createNewFile()
188 |
189 | outputStream().use { stream ->
190 | stream.write(buffer)
191 | }
192 | }
193 |
194 | return Uri.fromFile(scrFile)
195 | }
196 | }
197 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/de/beatbrot/screenshotassistant/custom/CustomActions.kt:
--------------------------------------------------------------------------------
1 | package de.beatbrot.screenshotassistant.custom
2 |
3 | import android.graphics.Rect
4 | import android.view.MotionEvent
5 | import android.view.View
6 | import androidx.test.espresso.UiController
7 | import androidx.test.espresso.ViewAction
8 | import androidx.test.espresso.ViewAssertion
9 | import com.theartofdev.edmodo.cropper.CropImageView
10 | import de.beatbrot.imagepainter.view.ImagePainterView
11 | import org.hamcrest.Matcher
12 | import org.hamcrest.Matchers
13 | import org.junit.Assert.assertEquals
14 |
15 | fun cropImageByHalf() = object : ViewAction {
16 | override fun getDescription() = "Selects the top half of the image"
17 |
18 | override fun getConstraints(): Matcher {
19 | return Matchers.instanceOf(CropImageView::class.java)
20 | }
21 |
22 | override fun perform(uiController: UiController?, view: View?) {
23 | if (view is CropImageView) {
24 | val imgRect = view.wholeImageRect
25 | view.cropRect = Rect(imgRect).apply { bottom /= 2 }
26 | }
27 | }
28 | }
29 |
30 | fun draw() = object : ViewAction {
31 | override fun getDescription() = "Draws on the image painter"
32 |
33 | override fun getConstraints() = Matchers.instanceOf(ImagePainterView::class.java)
34 |
35 | override fun perform(uiController: UiController?, view: View?) {
36 | if (view is ImagePainterView) {
37 | view.apply {
38 | onTouchEvent(motionEvent(MotionEvent.ACTION_DOWN, 10, 20))
39 | onTouchEvent(motionEvent(MotionEvent.ACTION_MOVE, 20, 50))
40 | onTouchEvent(motionEvent(MotionEvent.ACTION_UP, 20, 50))
41 | }
42 | }
43 | }
44 | }
45 |
46 | fun checkCropSuccessful() = ViewAssertion { view, _ ->
47 | if (view is CropImageView) {
48 | val bitmap = view.croppedImage
49 |
50 | assertEquals(1920 / 2, bitmap.height)
51 | }
52 | }
53 |
54 | private fun motionEvent(action: Int, x: Int, y: Int): MotionEvent {
55 | return MotionEvent.obtain(1L, 1L, action, x.toFloat(), y.toFloat(), 0)
56 | }
57 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/de/beatbrot/screenshotassistant/custom/CustomMatchers.kt:
--------------------------------------------------------------------------------
1 | package de.beatbrot.screenshotassistant.custom
2 |
3 | import android.app.Activity
4 | import android.content.Intent
5 | import android.view.View
6 | import com.theartofdev.edmodo.cropper.CropImageView
7 | import org.hamcrest.Description
8 | import org.hamcrest.TypeSafeMatcher
9 |
10 | fun containsImage() = object : TypeSafeMatcher(CropImageView::class.java) {
11 | override fun matchesSafely(item: View?): Boolean {
12 | val crop = item as CropImageView
13 | return crop.croppedImage.byteCount > 10
14 | }
15 |
16 | override fun describeTo(description: Description?) {
17 | description?.appendText("has an image set")
18 | }
19 | }
20 |
21 | fun launchesActivity(activity: Class) = object :
22 | TypeSafeMatcher(Intent::class.java) {
23 | override fun matchesSafely(item: Intent?): Boolean {
24 | val expectedPkg = activity.`package`?.name ?: return false
25 | val actPkg = item?.component?.packageName ?: return false
26 |
27 | val expectedClass = activity.name
28 | val actClass = item.component?.className ?: return false
29 |
30 | return expectedPkg == actPkg && expectedClass == actClass
31 | }
32 |
33 | override fun describeTo(description: Description?) {
34 | description?.appendText("launches ${activity.name}")
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
14 |
19 |
24 |
25 |
28 |
31 |
32 |
33 |
34 |
35 |
36 |
39 |
40 |
45 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/beatbrot/ScreenshotAssistant/3d8ce0dc0e2580a6ba55d3379654b67ad79d9112/app/src/main/ic_launcher-web.png
--------------------------------------------------------------------------------
/app/src/main/java/de/beatbrot/screenshotassistant/AboutActivity.kt:
--------------------------------------------------------------------------------
1 | package de.beatbrot.screenshotassistant
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import android.content.res.Configuration
6 | import android.net.Uri
7 | import android.os.Bundle
8 | import androidx.annotation.StringRes
9 | import com.danielstone.materialaboutlibrary.MaterialAboutActivity
10 | import com.danielstone.materialaboutlibrary.items.MaterialAboutActionItem
11 | import com.danielstone.materialaboutlibrary.items.MaterialAboutTitleItem
12 | import com.danielstone.materialaboutlibrary.model.MaterialAboutCard
13 | import com.danielstone.materialaboutlibrary.model.MaterialAboutList
14 |
15 | class AboutActivity : MaterialAboutActivity() {
16 |
17 | override fun onCreate(savedInstanceState: Bundle?) {
18 | if (isNightModeActive) {
19 | setTheme(R.style.AppTheme_AboutDialog_Dark)
20 | } else {
21 | setTheme(R.style.AppTheme_AboutDialog)
22 | }
23 | super.onCreate(savedInstanceState)
24 | }
25 |
26 | override fun getMaterialAboutList(context: Context): MaterialAboutList {
27 | val mainCard = MaterialAboutCard.Builder()
28 | .addItem(
29 | MaterialAboutTitleItem(
30 | R.string.app_name,
31 | R.string.copyright,
32 | R.mipmap.ic_launcher_round
33 | )
34 | )
35 | .addVersionItem(BuildConfig.VERSION_NAME)
36 | .addGithubItem("beatbrot/ScreenshotAssistant")
37 | .build()
38 |
39 | return MaterialAboutList.Builder()
40 | .addCard(mainCard)
41 | .addCard(ImageCropper.createCard())
42 | .addCard(MaterialAboutLibrary.createCard())
43 | .build()
44 | }
45 |
46 | private val isNightModeActive: Boolean
47 | get() {
48 | val uiMode = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
49 | return uiMode == Configuration.UI_MODE_NIGHT_YES
50 | }
51 |
52 | interface Library {
53 | val name: String
54 | val version: String
55 |
56 | @get:StringRes
57 | val license: Int
58 |
59 | val licenseLink: String
60 |
61 | val github: String
62 | }
63 |
64 | object ImageCropper : Library {
65 | override val name = "Android Image Cropper"
66 | override val version = "2.8.0"
67 |
68 | override val license = R.string.apache_2
69 | override val licenseLink =
70 | "https://github.com/ArthurHub/Android-Image-Cropper/blob/master/LICENSE.txt"
71 |
72 | override val github = "ArthurHub/Android-Image-Cropper"
73 | }
74 |
75 | object MaterialAboutLibrary : Library {
76 | override val name = "material-about-library"
77 | override val version = "2.4.2"
78 | override val license = R.string.apache_2
79 | override val licenseLink =
80 | "https://github.com/daniel-stoneuk/material-about-library/blob/master/LICENSE"
81 | override val github = "daniel-stoneuk/material-about-library"
82 | }
83 |
84 | private fun Library.createCard() = MaterialAboutCard.Builder()
85 | .title(name)
86 | .addVersionItem(version)
87 | .addLicenseItem(license, licenseLink)
88 | .addGithubItem(github)
89 | .build()
90 |
91 | private fun MaterialAboutCard.Builder.addVersionItem(version: String): MaterialAboutCard.Builder {
92 | addItem(
93 | MaterialAboutActionItem(
94 | baseContext.getString(R.string.version),
95 | version,
96 | baseContext.getDrawable(R.drawable.ic_info_outline_gray_24dp)
97 | )
98 | )
99 | return this
100 | }
101 |
102 | private fun MaterialAboutCard.Builder.addLicenseItem(@StringRes license: Int, url: String): MaterialAboutCard.Builder {
103 | addItem(
104 | MaterialAboutActionItem(
105 | R.string.license,
106 | license,
107 | R.drawable.ic_book_gray_24dp
108 | ) { openUrl(url) }
109 | )
110 | return this
111 | }
112 |
113 | private fun MaterialAboutCard.Builder.addGithubItem(project: String): MaterialAboutCard.Builder {
114 | addItem(
115 | MaterialAboutActionItem(
116 | baseContext.getString(R.string.github),
117 | project,
118 | baseContext.getDrawable(R.drawable.ic_github_gray_24dp)
119 | ) { openUrl("http://github.com/$project") }
120 | )
121 | return this
122 | }
123 |
124 | private fun openUrl(url: String) {
125 | startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
126 | }
127 |
128 | override fun getActivityTitle(): String = baseContext.getString(R.string.about)
129 | }
130 |
--------------------------------------------------------------------------------
/app/src/main/java/de/beatbrot/screenshotassistant/EditingMode.kt:
--------------------------------------------------------------------------------
1 | package de.beatbrot.screenshotassistant
2 |
3 | enum class EditingMode {
4 | CROP, PAINT
5 | }
6 |
--------------------------------------------------------------------------------
/app/src/main/java/de/beatbrot/screenshotassistant/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package de.beatbrot.screenshotassistant
2 |
3 | import android.content.Intent
4 | import android.graphics.BitmapFactory
5 | import android.graphics.Rect
6 | import android.net.Uri
7 | import android.os.Bundle
8 | import android.view.View
9 | import android.view.animation.Animation
10 | import android.view.animation.AnimationUtils
11 | import android.view.animation.OvershootInterpolator
12 | import android.widget.Toast
13 | import androidx.activity.viewModels
14 | import androidx.annotation.AnimRes
15 | import androidx.appcompat.app.AppCompatActivity
16 | import androidx.appcompat.widget.PopupMenu
17 | import androidx.lifecycle.Observer
18 | import de.beatbrot.screenshotassistant.databinding.ActivityMainBinding
19 | import de.beatbrot.screenshotassistant.sheets.ModalSettingsSheet
20 | import de.beatbrot.screenshotassistant.sheets.drawsettings.DrawSettingsSheet
21 | import de.beatbrot.screenshotassistant.util.OpenAnimationListener
22 | import de.beatbrot.screenshotassistant.util.tryExport
23 |
24 | class MainActivity : AppCompatActivity() {
25 | private lateinit var v: ActivityMainBinding
26 | private val drawSettingsSheet = DrawSettingsSheet()
27 | private val viewModel by viewModels()
28 |
29 | override fun onCreate(savedInstanceState: Bundle?) {
30 | super.onCreate(savedInstanceState)
31 | initUI()
32 | initViewModel()
33 |
34 | val imgPath = intent.getParcelableExtra("screenshot")
35 | if (imgPath != null) {
36 | loadInitialImage(imgPath)
37 | } else {
38 | Toast.makeText(baseContext, R.string.error_no_screenshot, Toast.LENGTH_SHORT).show()
39 | finish()
40 | }
41 | }
42 |
43 | override fun onDestroy() {
44 | super.onDestroy()
45 | viewModel.cropRect.value = v.cropView.cropRect
46 | viewModel.drawStack.value = v.imagePainter.drawStack
47 | }
48 |
49 | private fun initUI() {
50 | v = ActivityMainBinding.inflate(layoutInflater)
51 | setContentView(v.root)
52 | supportFragmentManager.beginTransaction()
53 | .replace(v.bottomSheet.id, drawSettingsSheet)
54 | .commitNow()
55 |
56 | drawSettingsSheet.onHideListener = { viewModel.editingMode.value = EditingMode.CROP }
57 | drawSettingsSheet.imagePainter = v.imagePainter
58 |
59 | v.cropView.setOnSetImageUriCompleteListener { view, _, _ ->
60 | view.cropRect = Rect(view.wholeImageRect)
61 | }
62 |
63 | v.drawButton.setOnClickListener { viewModel.editingMode.value = EditingMode.PAINT }
64 |
65 | v.menuButton.setOnClickListener {
66 | val popMenu = PopupMenu(baseContext, it)
67 | popMenu.setOnMenuItemClickListener { item ->
68 | when (item.itemId) {
69 | R.id.settings_item -> showSettings()
70 | R.id.about_item -> startActivity(Intent(baseContext, AboutActivity::class.java))
71 | else -> throw IllegalArgumentException()
72 | }
73 | true
74 | }
75 | popMenu.menuInflater.inflate(R.menu.about_menu, popMenu.menu)
76 | popMenu.show()
77 | }
78 |
79 | v.shareButton.setOnClickListener { viewModel.shareImage(v.cropView.croppedImage) }
80 | }
81 |
82 | private fun initViewModel() {
83 | viewModel.currentBitmap.observe(this, Observer { newBitmap ->
84 | when (viewModel.editingMode.value) {
85 | EditingMode.CROP -> {
86 | v.cropView.setImageBitmap(newBitmap)
87 | v.cropView.cropRect = Rect(v.cropView.wholeImageRect)
88 | animateImageView(v.cropView)
89 | }
90 | EditingMode.PAINT -> {
91 | v.imagePainter.setImageBitmap(newBitmap)
92 | animateImageView(v.imagePainter)
93 | }
94 | }
95 | })
96 |
97 | viewModel.drawStack.observe(this, Observer { stack ->
98 | if (stack != null) {
99 | v.imagePainter.drawStack = stack
100 | }
101 | })
102 |
103 | viewModel.cropRect.observe(this, Observer { rect ->
104 | v.cropView.cropRect = rect ?: Rect(v.cropView.wholeImageRect)
105 | })
106 |
107 | viewModel.shareIntent.observe(this, Observer { intent ->
108 | startActivity(Intent.createChooser(intent, baseContext.getString(R.string.share_image)))
109 | })
110 |
111 | viewModel.editingMode.observe(this, Observer { newState ->
112 | when (newState) {
113 | EditingMode.CROP -> {
114 | hideBottomSheet()
115 | v.imagePainter.visibility = View.GONE
116 | v.cropView.visibility = View.VISIBLE
117 | if (v.imagePainter.drawable != null) {
118 | viewModel.currentBitmap.value = v.imagePainter.exportImage()
119 | }
120 | }
121 | EditingMode.PAINT -> {
122 | v.cropView.visibility = View.GONE
123 | v.imagePainter.visibility = View.VISIBLE
124 | v.cropView.tryExport()?.let {
125 | viewModel.currentBitmap.value = it
126 | }
127 | viewModel.cropRect.value = null
128 | showBottomSheet()
129 | }
130 | else -> throw UnsupportedOperationException()
131 | }
132 | })
133 | }
134 |
135 | private fun animateImageView(imageView: View) {
136 | val startValue = 1.3F
137 | imageView.apply {
138 | scaleX = startValue
139 | scaleY = startValue
140 | alpha = 0.5F
141 |
142 | visibility = View.VISIBLE
143 |
144 | animate()
145 | .scaleX(1F)
146 | .scaleY(1F)
147 | .alpha(1F)
148 | .setInterpolator(OvershootInterpolator())
149 | .start()
150 | }
151 | }
152 |
153 | private fun showBottomSheet() {
154 | if (v.bottomSheet.visibility == View.VISIBLE) {
155 | return
156 | }
157 |
158 | runAnimation(v.bottomSheet, R.anim.slide_up) {
159 | v.bottomSheet.visibility = View.VISIBLE
160 | }
161 | }
162 |
163 | private fun hideBottomSheet() {
164 | if (v.bottomSheet.visibility != View.VISIBLE) {
165 | return
166 | }
167 |
168 | runAnimation(v.bottomSheet, R.anim.slide_down) {
169 | v.bottomSheet.visibility = View.GONE
170 | }
171 | }
172 |
173 | private inline fun runAnimation(
174 | view: View,
175 | @AnimRes animRes: Int,
176 | crossinline onDone: () -> Unit
177 | ) {
178 | val anim = AnimationUtils.loadAnimation(baseContext, animRes)
179 | anim.setAnimationListener(object : OpenAnimationListener {
180 | override fun onAnimationEnd(animation: Animation?) {
181 | onDone()
182 | }
183 | })
184 | view.startAnimation(anim)
185 | }
186 |
187 | private fun showSettings() {
188 | ModalSettingsSheet().showNow(supportFragmentManager, "SETTINGS")
189 | }
190 |
191 | private fun loadInitialImage(uri: Uri) {
192 | if (viewModel.currentBitmap.value == null) {
193 | val inputStream = contentResolver.openInputStream(uri)
194 | viewModel.currentBitmap.value = BitmapFactory.decodeStream(inputStream)
195 | }
196 | }
197 | }
198 |
--------------------------------------------------------------------------------
/app/src/main/java/de/beatbrot/screenshotassistant/ScreenshotActivityViewModel.kt:
--------------------------------------------------------------------------------
1 | package de.beatbrot.screenshotassistant
2 |
3 | import android.app.Application
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.graphics.Bitmap
7 | import android.graphics.Rect
8 | import android.net.Uri
9 | import androidx.core.content.FileProvider
10 | import androidx.lifecycle.AndroidViewModel
11 | import androidx.lifecycle.LiveData
12 | import androidx.lifecycle.MutableLiveData
13 | import de.beatbrot.imagepainter.DrawStack
14 | import de.beatbrot.screenshotassistant.util.*
15 | import java.io.File
16 | import java.io.FileOutputStream
17 | import java.text.SimpleDateFormat
18 | import java.util.*
19 |
20 | const val AUTHORITY_NAME = "de.beatbrot.screenshotassistant.fileprovider"
21 | const val MIME_TYPE = "image/jpeg"
22 |
23 | class ScreenshotActivityViewModel(application: Application) : AndroidViewModel(application) {
24 |
25 | private val _shareIntent: MutableLiveData = MutableLiveData()
26 | val shareIntent: LiveData
27 | get() = _shareIntent
28 |
29 | private val context: Context
30 | get() = getApplication().baseContext
31 |
32 | private val imageFormat: Bitmap.CompressFormat
33 | get() = context.sharedPrefs.imageFormat
34 |
35 | private val imageQuality: Int
36 | get() = context.sharedPrefs.imageQuality
37 |
38 | val editingMode = liveDataOf(EditingMode.CROP)
39 |
40 | val currentBitmap = MutableLiveData()
41 |
42 | val drawStack = MutableLiveData()
43 |
44 | val cropRect = MutableLiveData()
45 |
46 | fun shareImage(croppedImage: Bitmap) {
47 | val croppedUri = getScreenshotUri(croppedImage)
48 |
49 | val intent = Intent(Intent.ACTION_SEND).apply {
50 | type = imageFormat.mimeType
51 | putExtra(Intent.EXTRA_STREAM, croppedUri)
52 | }
53 |
54 | _shareIntent.postValue(intent)
55 | }
56 |
57 | private fun getScreenshotUri(croppedImage: Bitmap): Uri {
58 | val scrFile = createScreenshotFile()
59 |
60 | FileOutputStream(scrFile).use { stream ->
61 | croppedImage.compress(Bitmap.CompressFormat.JPEG, imageQuality, stream)
62 | }
63 |
64 | return FileProvider.getUriForFile(context, AUTHORITY_NAME, scrFile)
65 | }
66 |
67 | private fun createScreenshotFile(): File {
68 | val scrDir = File(context.filesDir, "screenshots")
69 | val scrFile = File(
70 | scrDir,
71 | "Screenshot-${currentDateString()}.${imageFormat.fileExtension}"
72 | )
73 |
74 | scrDir.mkdir()
75 | scrDir.deleteContents()
76 | scrFile.createNewFile()
77 |
78 | return scrFile
79 | }
80 |
81 | private fun currentDateString(): String {
82 | val format = SimpleDateFormat("yyyy-MM-dd'-'HH-mm-ss", Locale.US)
83 | return format.format(Date())
84 | }
85 |
86 | private fun File.deleteContents() {
87 | listFiles()?.forEach { file ->
88 | file.deleteRecursively()
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/app/src/main/java/de/beatbrot/screenshotassistant/sheets/ModalSettingsSheet.kt:
--------------------------------------------------------------------------------
1 | package de.beatbrot.screenshotassistant.sheets
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import androidx.preference.PreferenceFragmentCompat
8 | import com.google.android.material.bottomsheet.BottomSheetDialogFragment
9 | import de.beatbrot.screenshotassistant.R
10 | import de.beatbrot.screenshotassistant.databinding.TitledModalSheetBinding
11 |
12 | class ModalSettingsSheet : BottomSheetDialogFragment() {
13 | private lateinit var v: TitledModalSheetBinding
14 | private val settingsSheet = SettingsSheet()
15 |
16 | override fun onCreateView(i: LayoutInflater, root: ViewGroup?, state: Bundle?): View {
17 | v = TitledModalSheetBinding.inflate(i)
18 | return v.root
19 | }
20 |
21 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
22 | super.onViewCreated(view, savedInstanceState)
23 | v.title.text = getString(R.string.title_sheet_settings)
24 |
25 | childFragmentManager.beginTransaction()
26 | .replace(R.id.fragContainer, settingsSheet)
27 | .commitNow()
28 | }
29 |
30 | class SettingsSheet : PreferenceFragmentCompat() {
31 | override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
32 | setPreferencesFromResource(R.xml.root_preferences, rootKey)
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/main/java/de/beatbrot/screenshotassistant/sheets/drawsettings/DrawMode.kt:
--------------------------------------------------------------------------------
1 | package de.beatbrot.screenshotassistant.sheets.drawsettings
2 |
3 | enum class DrawMode {
4 | PEN, MARKER
5 | }
6 |
--------------------------------------------------------------------------------
/app/src/main/java/de/beatbrot/screenshotassistant/sheets/drawsettings/DrawSettingsSheet.kt:
--------------------------------------------------------------------------------
1 | package de.beatbrot.screenshotassistant.sheets.drawsettings
2 |
3 | import android.content.res.ColorStateList
4 | import android.os.Bundle
5 | import android.view.LayoutInflater
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import androidx.core.graphics.ColorUtils
9 | import androidx.fragment.app.Fragment
10 | import androidx.fragment.app.viewModels
11 | import androidx.lifecycle.Observer
12 | import com.google.android.material.floatingactionbutton.FloatingActionButton
13 | import com.thebluealliance.spectrum.SpectrumDialog
14 | import de.beatbrot.imagepainter.view.ImagePainterView
15 | import de.beatbrot.screenshotassistant.R
16 | import de.beatbrot.screenshotassistant.databinding.SheetColorsettingsBinding
17 |
18 | class DrawSettingsSheet : Fragment() {
19 | private lateinit var v: SheetColorsettingsBinding
20 |
21 | var imagePainter: ImagePainterView? = null
22 | set(value) {
23 | field = value
24 | field?.setRedoStatusChangeListener {
25 | if (::v.isInitialized) {
26 | v.redoButton.isEnabled = it
27 | }
28 | }
29 | field?.setUndoStatusChangeListener {
30 | if (::v.isInitialized) {
31 | v.undoButton.isEnabled = it
32 | }
33 | }
34 | }
35 |
36 | var onHideListener: (() -> Unit)? = null
37 |
38 | private lateinit var colorPicker: SpectrumDialog.Builder
39 |
40 | private val viewModel by viewModels()
41 |
42 | override fun onCreateView(i: LayoutInflater, root: ViewGroup?, state: Bundle?): View {
43 | v = SheetColorsettingsBinding.inflate(i, root, false)
44 | return v.root
45 | }
46 |
47 | override fun onCreate(savedInstanceState: Bundle?) {
48 | super.onCreate(savedInstanceState)
49 | initViewModel()
50 | }
51 |
52 | private fun initViewModel() {
53 | viewModel.strokeColor.observe(this, Observer { newValue ->
54 | if (newValue == null) {
55 | return@Observer
56 | }
57 | v.colorButton.color = newValue
58 | colorPicker.setSelectedColor(newValue)
59 | if (viewModel.editingMode.value == DrawMode.PEN) {
60 | imagePainter?.strokeColor = newValue
61 | } else {
62 | imagePainter?.strokeColor = ColorUtils.setAlphaComponent(newValue, 255 / 3)
63 | }
64 | })
65 |
66 | viewModel.strokeCap.observe(this, Observer { newValue ->
67 | imagePainter?.strokeCap = newValue
68 | })
69 |
70 | viewModel.strokeWidth.observe(this, Observer { newValue ->
71 | imagePainter?.strokeWidth = newValue
72 | })
73 | }
74 |
75 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
76 | super.onViewCreated(view, savedInstanceState)
77 | v.undoButton.isEnabled = imagePainter?.canUndo() ?: false
78 | v.redoButton.isEnabled = imagePainter?.canRedo() ?: false
79 |
80 | colorPicker = SpectrumDialog.Builder(context).apply {
81 | setTitle("Pick a color")
82 | setDismissOnColorSelected(true)
83 | setColors(R.array.colorPicker)
84 | setOnColorSelectedListener { positiveResult, color ->
85 | if (positiveResult) {
86 | if (viewModel.editingMode.value == DrawMode.PEN) {
87 | viewModel.penColor.postValue(color)
88 | } else if (viewModel.editingMode.value == DrawMode.MARKER) {
89 | viewModel.markerColor.postValue(color)
90 | }
91 | }
92 | }
93 | }
94 |
95 | v.undoButton.setOnClickListener { imagePainter?.undo() }
96 | v.redoButton.setOnClickListener { imagePainter?.redo() }
97 | v.penSelector.setToggled(R.id.draw_mode, true)
98 | v.penSelector.onToggledListener = { _, toggle, selected ->
99 | if (selected) {
100 | viewModel.editingMode.value = when (toggle.id) {
101 | R.id.draw_mode -> DrawMode.PEN
102 | else -> DrawMode.MARKER
103 | }
104 | }
105 | }
106 |
107 | v.doneButton.setOnClickListener { onHideListener?.invoke() }
108 | v.colorButton.setOnClickListener { colorPicker.show() }
109 | }
110 |
111 | private fun SpectrumDialog.Builder.show() {
112 | build().show(childFragmentManager, "COLOR_PICKER")
113 | }
114 |
115 | private var FloatingActionButton.color: Int
116 | get() = backgroundTintList?.defaultColor ?: throw NullPointerException()
117 | set(value) {
118 | backgroundTintList = ColorStateList.valueOf(value)
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/app/src/main/java/de/beatbrot/screenshotassistant/sheets/drawsettings/DrawSettingsViewModel.kt:
--------------------------------------------------------------------------------
1 | package de.beatbrot.screenshotassistant.sheets.drawsettings
2 |
3 | import android.graphics.Color
4 | import android.graphics.Paint
5 | import androidx.lifecycle.MediatorLiveData
6 | import androidx.lifecycle.ViewModel
7 | import de.beatbrot.screenshotassistant.util.liveDataOf
8 |
9 | class DrawSettingsViewModel : ViewModel() {
10 | val editingMode = liveDataOf(DrawMode.PEN)
11 |
12 | val penColor = liveDataOf(Color.BLACK)
13 | val markerColor = liveDataOf(-0x14C5) // Yellow
14 |
15 | val strokeColor = MediatorLiveData().apply {
16 | addSource(penColor) { newValue ->
17 | if (editingMode.value == DrawMode.PEN) {
18 | value = newValue
19 | }
20 | }
21 | addSource(markerColor) { newValue ->
22 | if (editingMode.value == DrawMode.MARKER) {
23 | value = newValue
24 | }
25 | }
26 | addSource(editingMode) { drawMode ->
27 | value = when (drawMode) {
28 | DrawMode.PEN -> {
29 | penColor.value
30 | }
31 | DrawMode.MARKER -> {
32 | markerColor.value
33 | }
34 | else -> value
35 | }
36 | }
37 | }
38 |
39 | val strokeCap = MediatorLiveData().apply {
40 | addSource(editingMode) { newMode ->
41 | value = when (newMode) {
42 | DrawMode.PEN -> Paint.Cap.ROUND
43 | DrawMode.MARKER -> Paint.Cap.SQUARE
44 | else -> value
45 | }
46 | }
47 | }
48 |
49 | val strokeWidth = MediatorLiveData().apply {
50 | addSource(editingMode) { newMode ->
51 | value = when (newMode) {
52 | DrawMode.PEN -> 3F
53 | DrawMode.MARKER -> 30F
54 | else -> value
55 | }
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/app/src/main/java/de/beatbrot/screenshotassistant/util/CompressFormat+.kt:
--------------------------------------------------------------------------------
1 | package de.beatbrot.screenshotassistant.util
2 |
3 | import android.graphics.Bitmap
4 | import java.util.*
5 |
6 | val Enum.mimeType
7 | get() = "image/${name.toLowerCase(Locale.ROOT)}"
8 |
9 | val Enum.fileExtension
10 | get() = name.toLowerCase(Locale.ROOT)
11 |
--------------------------------------------------------------------------------
/app/src/main/java/de/beatbrot/screenshotassistant/util/Context+.kt:
--------------------------------------------------------------------------------
1 | package de.beatbrot.screenshotassistant.util
2 |
3 | import android.content.Context
4 | import android.content.SharedPreferences
5 | import androidx.preference.PreferenceManager
6 |
7 | val Context.sharedPrefs: SharedPreferences
8 | get() = PreferenceManager.getDefaultSharedPreferences(this)
9 |
--------------------------------------------------------------------------------
/app/src/main/java/de/beatbrot/screenshotassistant/util/CropImageView+.kt:
--------------------------------------------------------------------------------
1 | package de.beatbrot.screenshotassistant.util
2 |
3 | import android.graphics.Bitmap
4 | import com.theartofdev.edmodo.cropper.CropImageView
5 |
6 | fun CropImageView.tryExport(): Bitmap? {
7 | return try {
8 | croppedImage
9 | } catch (exception: Exception) {
10 | null
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/app/src/main/java/de/beatbrot/screenshotassistant/util/LiveData+.kt:
--------------------------------------------------------------------------------
1 | package de.beatbrot.screenshotassistant.util
2 |
3 | import androidx.lifecycle.MutableLiveData
4 |
5 | fun liveDataOf(value: T) = MutableLiveData().apply {
6 | this.value = value
7 | }
8 |
--------------------------------------------------------------------------------
/app/src/main/java/de/beatbrot/screenshotassistant/util/OpenAnimationListener.kt:
--------------------------------------------------------------------------------
1 | package de.beatbrot.screenshotassistant.util
2 |
3 | import android.view.animation.Animation
4 |
5 | interface OpenAnimationListener : Animation.AnimationListener {
6 | override fun onAnimationEnd(animation: Animation?) {
7 | }
8 |
9 | override fun onAnimationRepeat(animation: Animation?) {
10 | }
11 |
12 | override fun onAnimationStart(animation: Animation?) {
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/java/de/beatbrot/screenshotassistant/util/SharedPreferences+.kt:
--------------------------------------------------------------------------------
1 | package de.beatbrot.screenshotassistant.util
2 |
3 | import android.content.SharedPreferences
4 | import android.graphics.Bitmap
5 |
6 | val SharedPreferences.imageQuality: Int
7 | get() = getInt("image_quality", 100)
8 |
9 | val SharedPreferences.imageFormat: Bitmap.CompressFormat
10 | get() = Bitmap.CompressFormat.valueOf(getString("image_format", null) ?: "JPEG")
11 |
--------------------------------------------------------------------------------
/app/src/main/java/de/beatbrot/screenshotassistant/voice/InteractionService.kt:
--------------------------------------------------------------------------------
1 | package de.beatbrot.screenshotassistant.voice
2 |
3 | import android.service.voice.VoiceInteractionService
4 |
5 | class InteractionService : VoiceInteractionService()
6 |
--------------------------------------------------------------------------------
/app/src/main/java/de/beatbrot/screenshotassistant/voice/InteractionSession.kt:
--------------------------------------------------------------------------------
1 | package de.beatbrot.screenshotassistant.voice
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import android.graphics.Bitmap
6 | import android.net.Uri
7 | import android.service.voice.VoiceInteractionSession
8 | import android.widget.Toast
9 | import de.beatbrot.screenshotassistant.MainActivity
10 | import de.beatbrot.screenshotassistant.R
11 | import java.io.File
12 | import java.io.FileOutputStream
13 |
14 | class InteractionSession(context: Context) : VoiceInteractionSession(context) {
15 | override fun onHandleScreenshot(screenshot: Bitmap?) {
16 | if (screenshot != null) {
17 | val startIntent = Intent(context, MainActivity::class.java).apply {
18 | addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
19 | putExtra("screenshot", getScreenshotUri(screenshot))
20 | }
21 | hide()
22 | context.startActivity(startIntent)
23 | } else {
24 | Toast.makeText(context, R.string.enable_screenshot, Toast.LENGTH_SHORT).show()
25 | hide()
26 | }
27 | }
28 |
29 | private fun getScreenshotUri(bitmap: Bitmap): Uri {
30 | val scrFile = createScreenshotFile()
31 |
32 | FileOutputStream(scrFile).use { stream ->
33 | bitmap.compress(Bitmap.CompressFormat.WEBP, 100, stream)
34 | }
35 | return Uri.fromFile(scrFile)
36 | }
37 |
38 | private fun createScreenshotFile(): File {
39 | val scrDir = File(context.cacheDir, "screenshots")
40 | val scrFile = File(scrDir, "scr")
41 |
42 | scrDir.mkdir()
43 | if (scrFile.exists()) {
44 | scrFile.delete()
45 | }
46 | scrFile.createNewFile()
47 |
48 | return scrFile
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/app/src/main/java/de/beatbrot/screenshotassistant/voice/InteractionSessionService.kt:
--------------------------------------------------------------------------------
1 | package de.beatbrot.screenshotassistant.voice
2 |
3 | import android.os.Bundle
4 | import android.service.voice.VoiceInteractionSessionService
5 |
6 | class InteractionSessionService : VoiceInteractionSessionService() {
7 | override fun onNewSession(bundle: Bundle?) = InteractionSession(this)
8 | }
9 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/slide_down.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/slide_up.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/color/button_color.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
17 |
20 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_book_gray_24dp.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_check_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_chevron_left_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_chevron_right_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_format_shapes_unfocused_24dp.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_github_gray_24dp.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_grade_unfocused_24dp.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_icon_bg.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_info_outline_gray_24dp.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_marker_24dp.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_more_vert_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_pencil_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_share_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
22 |
23 |
27 |
28 |
32 |
33 |
34 |
35 |
46 |
47 |
58 |
59 |
70 |
71 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/sheet_colorsettings.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
23 |
24 |
36 |
37 |
38 |
51 |
52 |
62 |
63 |
75 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/titled_modal_sheet.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
15 |
16 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/about_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
8 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/draw_modes.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
8 |
9 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/beatbrot/ScreenshotAssistant/3d8ce0dc0e2580a6ba55d3379654b67ad79d9112/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/beatbrot/ScreenshotAssistant/3d8ce0dc0e2580a6ba55d3379654b67ad79d9112/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/beatbrot/ScreenshotAssistant/3d8ce0dc0e2580a6ba55d3379654b67ad79d9112/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/beatbrot/ScreenshotAssistant/3d8ce0dc0e2580a6ba55d3379654b67ad79d9112/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/beatbrot/ScreenshotAssistant/3d8ce0dc0e2580a6ba55d3379654b67ad79d9112/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/beatbrot/ScreenshotAssistant/3d8ce0dc0e2580a6ba55d3379654b67ad79d9112/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/beatbrot/ScreenshotAssistant/3d8ce0dc0e2580a6ba55d3379654b67ad79d9112/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/beatbrot/ScreenshotAssistant/3d8ce0dc0e2580a6ba55d3379654b67ad79d9112/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/beatbrot/ScreenshotAssistant/3d8ce0dc0e2580a6ba55d3379654b67ad79d9112/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/beatbrot/ScreenshotAssistant/3d8ce0dc0e2580a6ba55d3379654b67ad79d9112/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values-de-rDE/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Screenshot Assistant
4 | Über
5 | Fehler: Screenshot konnte nicht aufgenommen werden
6 | Lizenz
7 | Mehr
8 | Version
9 | Screenshot konnte nicht aufgenommen werden. Bitte aktiere \"Screenshot verwenden\" in den Systemeinstellungen
10 | Bild teilen…
11 | Einstellungen
12 | Rückgängig
13 | Wiederholen
14 | Fertig
15 | Bildqualität
16 | Format
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/values-night/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #121212
4 | #8A8A8A
5 |
6 | #61FFFFFF
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | - JPEG
4 | - PNG
5 | - WEBP
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #ff5722
4 | #ff8a50
5 | #c41c00
6 | #d81b60
7 | #ff5c8d
8 | #a00037
9 | #000000
10 | #ffffff
11 |
12 | #757575
13 | #FFFFFF
14 |
15 | #61000000
16 |
17 |
18 | - #FF000000
19 | - #FFF44336
20 | - #FFE91E63
21 | - #673AB7
22 | - #2196F3
23 | - #4CAF50
24 | - #FFEB3B
25 | - #FF5722
26 | - #795548
27 |
28 |
29 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Screenshot Assistant
3 | © beatbrot 2019
4 | About
5 | GitHub
6 | Apache 2.0
7 | License
8 | Version
9 | More
10 | Error: Screenshot could not be captured
11 | Screenshot could not be taken. Please enable \"Use screenshot\" in assist settings
12 | Share screenshot…
13 | Settings
14 | Undo
15 | Redo
16 | Done
17 | Image Quality
18 | Image Format
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
17 |
18 |
32 |
33 |
38 |
39 |
44 |
45 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/fileprovider.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/root_preferences.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
19 |
20 |
29 |
30 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/voice_interaction_service.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/test/java/de/beatbrot/screenshotassistant/sheets/drawsettings/DrawSettingsViewModelTest.kt:
--------------------------------------------------------------------------------
1 | package de.beatbrot.screenshotassistant.sheets.drawsettings
2 |
3 | import android.graphics.Color
4 | import android.graphics.Paint
5 | import androidx.arch.core.executor.ArchTaskExecutor
6 | import androidx.arch.core.executor.TaskExecutor
7 | import androidx.lifecycle.LiveData
8 | import androidx.lifecycle.Observer
9 | import org.junit.Assert.assertEquals
10 | import org.junit.Before
11 | import org.junit.Test
12 |
13 | class DrawSettingsViewModelTest {
14 |
15 | private lateinit var viewModel: DrawSettingsViewModel
16 |
17 | private val alibiObserver: Observer = Observer { }
18 |
19 | init {
20 | ArchTaskExecutor.getInstance().setDelegate(TestExecutor())
21 | }
22 |
23 | @Before
24 | fun initVM() {
25 | // We need to observe everything we want to access with .value
26 | viewModel = DrawSettingsViewModel().apply {
27 | strokeColor.observeForever(alibiObserver)
28 | editingMode.observeForever(alibiObserver)
29 | markerColor.observeForever(alibiObserver)
30 | penColor.observeForever(alibiObserver)
31 | strokeWidth.observeForever(alibiObserver)
32 | strokeCap.observeForever(alibiObserver)
33 | }
34 | }
35 |
36 | @Test
37 | fun testInitialValue() {
38 | assertLDEquals(Color.BLACK, viewModel.penColor)
39 | assertLDEquals(-0x14C5, viewModel.markerColor)
40 | assertLDEquals(Color.BLACK, viewModel.strokeColor)
41 | }
42 |
43 | @Test
44 | fun testModeSwitch() {
45 | viewModel.penColor.value = Color.RED
46 | viewModel.markerColor.value = Color.MAGENTA
47 | val penStrokeWidth = viewModel.strokeWidth.value!!
48 | assertLDEquals(Color.RED, viewModel.penColor)
49 | assertLDEquals(Color.MAGENTA, viewModel.markerColor)
50 | assertLDEquals(Color.RED, viewModel.strokeColor)
51 | assertLDEquals(Paint.Cap.ROUND, viewModel.strokeCap)
52 |
53 | viewModel.editingMode.value = DrawMode.MARKER
54 |
55 | assertLDEquals(Color.MAGENTA, viewModel.strokeColor)
56 | assertLDEquals(Paint.Cap.SQUARE, viewModel.strokeCap)
57 | assert(penStrokeWidth < viewModel.strokeWidth.value!!)
58 | }
59 |
60 | @Test
61 | fun testOnlyRelevantColorIsApplied() {
62 | assertLDEquals(Color.BLACK, viewModel.penColor)
63 | viewModel.markerColor.value = Color.MAGENTA
64 | assertLDEquals(Color.BLACK, viewModel.penColor)
65 | assertLDEquals(Color.BLACK, viewModel.penColor)
66 |
67 | viewModel.editingMode.value = DrawMode.MARKER
68 | assertLDEquals(Color.MAGENTA, viewModel.markerColor)
69 | viewModel.penColor.value = Color.RED
70 | assertLDEquals(Color.MAGENTA, viewModel.markerColor)
71 | }
72 |
73 | private fun assertLDEquals(expected: T, actual: LiveData) {
74 | try {
75 | assertEquals(expected, actual.value)
76 | } catch (err: AssertionError) {
77 | throw AssertionError(err.message, err.cause)
78 | }
79 | }
80 |
81 | private class TestExecutor : TaskExecutor() {
82 | override fun executeOnDiskIO(r: Runnable) = r.run()
83 |
84 | override fun isMainThread() = true
85 |
86 | override fun postToMainThread(r: Runnable) = r.run()
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/art/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/beatbrot/ScreenshotAssistant/3d8ce0dc0e2580a6ba55d3379654b67ad79d9112/art/banner.png
--------------------------------------------------------------------------------
/art/banner.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
13 |
14 |
17 |
18 |
19 |
20 |
23 |
27 |
28 |
34 |
35 |
36 |
37 | image/svg+xml
38 |
39 |
40 |
41 |
42 |
43 |
44 |
47 |
48 |
49 | Screenshot
56 | Assistant
57 |
58 |
59 |
60 |
61 |
63 |
64 |
65 |
66 |
68 |
70 |
71 |
72 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/art/icon/background.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/art/icon/device.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 | image/svg+xml
12 |
13 |
14 |
15 |
16 |
17 |
23 |
24 |
27 |
28 |
29 |
30 |
31 |
32 |
34 |
35 |
36 |
37 |
39 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/art/icon/foreground.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
16 |
17 |
18 |
19 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | ext.kotlin_version = '1.3.72'
5 | repositories {
6 | google()
7 | jcenter()
8 | maven { url "https://plugins.gradle.org/m2/" }
9 | }
10 | dependencies {
11 | classpath 'com.android.tools.build:gradle:4.0.0-beta05'
12 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
13 | classpath("org.jmailen.gradle:kotlinter-gradle:2.3.2")
14 | // NOTE: Do not place your application dependencies here; they belong
15 | // in the individual module build.gradle files
16 | }
17 | }
18 |
19 | allprojects {
20 | repositories {
21 | google()
22 | jcenter()
23 | maven { url 'https://jitpack.io' }
24 | }
25 | }
26 |
27 | task clean(type: Delete) {
28 | delete rootProject.buildDir
29 | }
30 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/2.txt:
--------------------------------------------------------------------------------
1 | - ✏️ Paint tools
2 | - 🇩🇪 Added german translation
3 | - ⚙️ Added settings for image format and quality
4 | - 🌙 Complete support for dark mode
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/3.txt:
--------------------------------------------------------------------------------
1 | - 💾 Save editor state accross configuration changes
2 | - 🈂️ Add translation to settings
3 | - 💥 Fix broken About-Activity
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/full_description.txt:
--------------------------------------------------------------------------------
1 | Screenshot assistant is a replacement for Google Assistant with the sole purpose of taking, cropping and sharing a screenshot.
2 |
3 | 👉 Setup instructions:
4 |
5 | 1. Install the app
6 | 2. Set it as assistant via Apps > Default Apps > Assist app
7 | 3. Trigger Google Assistant
8 |
9 | 🚀 Features:
10 |
11 | - Screenshot with adjustable quality and image format
12 | - Tight system integration via Assist API
13 | - No permission needed
14 | - No storage used up by old screenshots
15 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/featureGraphic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/beatbrot/ScreenshotAssistant/3d8ce0dc0e2580a6ba55d3379654b67ad79d9112/fastlane/metadata/android/en-US/images/featureGraphic.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/1 - non-cropped.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/beatbrot/ScreenshotAssistant/3d8ce0dc0e2580a6ba55d3379654b67ad79d9112/fastlane/metadata/android/en-US/images/phoneScreenshots/1 - non-cropped.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/2 - cropped.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/beatbrot/ScreenshotAssistant/3d8ce0dc0e2580a6ba55d3379654b67ad79d9112/fastlane/metadata/android/en-US/images/phoneScreenshots/2 - cropped.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/3 - draw.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/beatbrot/ScreenshotAssistant/3d8ce0dc0e2580a6ba55d3379654b67ad79d9112/fastlane/metadata/android/en-US/images/phoneScreenshots/3 - draw.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/4 -share.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/beatbrot/ScreenshotAssistant/3d8ce0dc0e2580a6ba55d3379654b67ad79d9112/fastlane/metadata/android/en-US/images/phoneScreenshots/4 -share.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/5 - cropDark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/beatbrot/ScreenshotAssistant/3d8ce0dc0e2580a6ba55d3379654b67ad79d9112/fastlane/metadata/android/en-US/images/phoneScreenshots/5 - cropDark.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/6 - drawDark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/beatbrot/ScreenshotAssistant/3d8ce0dc0e2580a6ba55d3379654b67ad79d9112/fastlane/metadata/android/en-US/images/phoneScreenshots/6 - drawDark.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/7 - about.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/beatbrot/ScreenshotAssistant/3d8ce0dc0e2580a6ba55d3379654b67ad79d9112/fastlane/metadata/android/en-US/images/phoneScreenshots/7 - about.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/short_description.txt:
--------------------------------------------------------------------------------
1 | An assistant app for creating, cropping and sharing screenshots
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/title.txt:
--------------------------------------------------------------------------------
1 | Screenshot Assistant
2 |
--------------------------------------------------------------------------------
/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=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 | # Kotlin code style for this project: "official" or "obsolete":
21 | kotlin.code.style=official
22 | android.enableR8.fullMode=false
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/beatbrot/ScreenshotAssistant/3d8ce0dc0e2580a6ba55d3379654b67ad79d9112/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu May 07 10:19:35 CEST 2020
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 | # Determine the Java command to use to start the JVM.
86 | if [ -n "$JAVA_HOME" ] ; then
87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
88 | # IBM's JDK on AIX uses strange locations for the executables
89 | JAVACMD="$JAVA_HOME/jre/sh/java"
90 | else
91 | JAVACMD="$JAVA_HOME/bin/java"
92 | fi
93 | if [ ! -x "$JAVACMD" ] ; then
94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
95 |
96 | Please set the JAVA_HOME variable in your environment to match the
97 | location of your Java installation."
98 | fi
99 | else
100 | JAVACMD="java"
101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
102 |
103 | Please set the JAVA_HOME variable in your environment to match the
104 | location of your Java installation."
105 | fi
106 |
107 | # Increase the maximum file descriptors if we can.
108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
109 | MAX_FD_LIMIT=`ulimit -H -n`
110 | if [ $? -eq 0 ] ; then
111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
112 | MAX_FD="$MAX_FD_LIMIT"
113 | fi
114 | ulimit -n $MAX_FD
115 | if [ $? -ne 0 ] ; then
116 | warn "Could not set maximum file descriptor limit: $MAX_FD"
117 | fi
118 | else
119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
120 | fi
121 | fi
122 |
123 | # For Darwin, add options to specify how the application appears in the dock
124 | if $darwin; then
125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
126 | fi
127 |
128 | # For Cygwin or MSYS, switch paths to Windows format before running java
129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
132 | JAVACMD=`cygpath --unix "$JAVACMD"`
133 |
134 | # We build the pattern for arguments to be converted via cygpath
135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
136 | SEP=""
137 | for dir in $ROOTDIRSRAW ; do
138 | ROOTDIRS="$ROOTDIRS$SEP$dir"
139 | SEP="|"
140 | done
141 | OURCYGPATTERN="(^($ROOTDIRS))"
142 | # Add a user-defined pattern to the cygpath arguments
143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
145 | fi
146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
147 | i=0
148 | for arg in "$@" ; do
149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
151 |
152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
154 | else
155 | eval `echo args$i`="\"$arg\""
156 | fi
157 | i=$((i+1))
158 | done
159 | case $i in
160 | (0) set -- ;;
161 | (1) set -- "$args0" ;;
162 | (2) set -- "$args0" "$args1" ;;
163 | (3) set -- "$args0" "$args1" "$args2" ;;
164 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
165 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
166 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
167 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
168 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
169 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
170 | esac
171 | fi
172 |
173 | # Escape application args
174 | save () {
175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
176 | echo " "
177 | }
178 | APP_ARGS=$(save "$@")
179 |
180 | # Collect all arguments for the java command, following the shell quoting and substitution rules
181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
182 |
183 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
184 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
185 | cd "$(dirname "$0")"
186 | fi
187 |
188 | exec "$JAVACMD" "$@"
189 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
34 |
35 | @rem Find java.exe
36 | if defined JAVA_HOME goto findJavaFromJavaHome
37 |
38 | set JAVA_EXE=java.exe
39 | %JAVA_EXE% -version >NUL 2>&1
40 | if "%ERRORLEVEL%" == "0" goto init
41 |
42 | echo.
43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
44 | echo.
45 | echo Please set the JAVA_HOME variable in your environment to match the
46 | echo location of your Java installation.
47 |
48 | goto fail
49 |
50 | :findJavaFromJavaHome
51 | set JAVA_HOME=%JAVA_HOME:"=%
52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
53 |
54 | if exist "%JAVA_EXE%" goto init
55 |
56 | echo.
57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
58 | echo.
59 | echo Please set the JAVA_HOME variable in your environment to match the
60 | echo location of your Java installation.
61 |
62 | goto fail
63 |
64 | :init
65 | @rem Get command-line arguments, handling Windows variants
66 |
67 | if not "%OS%" == "Windows_NT" goto win9xME_args
68 |
69 | :win9xME_args
70 | @rem Slurp the command line arguments.
71 | set CMD_LINE_ARGS=
72 | set _SKIP=2
73 |
74 | :win9xME_args_slurp
75 | if "x%~1" == "x" goto execute
76 |
77 | set CMD_LINE_ARGS=%*
78 |
79 | :execute
80 | @rem Setup the command line
81 |
82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
83 |
84 | @rem Execute Gradle
85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
86 |
87 | :end
88 | @rem End local scope for the variables with windows NT shell
89 | if "%ERRORLEVEL%"=="0" goto mainEnd
90 |
91 | :fail
92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
93 | rem the _cmd.exe /c_ return code!
94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
95 | exit /b 1
96 |
97 | :mainEnd
98 | if "%OS%"=="Windows_NT" endlocal
99 |
100 | :omega
101 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 | rootProject.name='Screenshot Assistant'
3 |
--------------------------------------------------------------------------------