├── .editorconfig
├── .github
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── renovate.json
└── workflows
│ └── build.yml
├── .gitignore
├── LICENSE
├── README.md
├── build.gradle.kts
├── detekt.yml
├── gradle.properties
├── gradle
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── quickie
├── build.gradle.kts
└── src
│ ├── bundled
│ └── kotlin
│ │ └── io
│ │ └── github
│ │ └── g00fy2
│ │ └── quickie
│ │ └── utils
│ │ └── MlKitErrorHandler.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── kotlin
│ │ └── io
│ │ │ └── github
│ │ │ └── g00fy2
│ │ │ └── quickie
│ │ │ ├── QRCodeAnalyzer.kt
│ │ │ ├── QROverlayView.kt
│ │ │ ├── QRResult.kt
│ │ │ ├── QRScannerActivity.kt
│ │ │ ├── ScanCustomCode.kt
│ │ │ ├── ScanQRCode.kt
│ │ │ ├── config
│ │ │ ├── BarcodeFormat.kt
│ │ │ ├── ParcelableScannerConfig.kt
│ │ │ └── ScannerConfig.kt
│ │ │ ├── content
│ │ │ ├── ParcelableContent.kt
│ │ │ └── QRContent.kt
│ │ │ └── extensions
│ │ │ ├── BarcodeExtensions.kt
│ │ │ ├── IntentExtensions.kt
│ │ │ └── ScannerConfigExtensions.kt
│ └── res
│ │ ├── drawable
│ │ ├── quickie_bg_round.xml
│ │ ├── quickie_ic_close.xml
│ │ ├── quickie_ic_qrcode.xml
│ │ └── quickie_ic_torch.xml
│ │ ├── layout
│ │ ├── quickie_overlay_view.xml
│ │ └── quickie_scanner_activity.xml
│ │ ├── values-af
│ │ └── strings.xml
│ │ ├── values-am
│ │ └── strings.xml
│ │ ├── values-ar
│ │ └── strings.xml
│ │ ├── values-as
│ │ └── strings.xml
│ │ ├── values-az
│ │ └── strings.xml
│ │ ├── values-be
│ │ └── strings.xml
│ │ ├── values-bg
│ │ └── strings.xml
│ │ ├── values-bn
│ │ └── strings.xml
│ │ ├── values-bs
│ │ └── strings.xml
│ │ ├── values-ca
│ │ └── strings.xml
│ │ ├── values-cs
│ │ └── strings.xml
│ │ ├── values-da
│ │ └── strings.xml
│ │ ├── values-de
│ │ └── strings.xml
│ │ ├── values-el
│ │ └── strings.xml
│ │ ├── values-en
│ │ └── strings.xml
│ │ ├── values-es-rUS
│ │ └── strings.xml
│ │ ├── values-es
│ │ └── strings.xml
│ │ ├── values-et
│ │ └── strings.xml
│ │ ├── values-eu
│ │ └── strings.xml
│ │ ├── values-fa
│ │ └── strings.xml
│ │ ├── values-fi
│ │ └── strings.xml
│ │ ├── values-fr-rCA
│ │ └── strings.xml
│ │ ├── values-fr
│ │ └── strings.xml
│ │ ├── values-gl
│ │ └── strings.xml
│ │ ├── values-gu
│ │ └── strings.xml
│ │ ├── values-hi
│ │ └── strings.xml
│ │ ├── values-hr
│ │ └── strings.xml
│ │ ├── values-hu
│ │ └── strings.xml
│ │ ├── values-hy
│ │ └── strings.xml
│ │ ├── values-in
│ │ └── strings.xml
│ │ ├── values-is
│ │ └── strings.xml
│ │ ├── values-it
│ │ └── strings.xml
│ │ ├── values-iw
│ │ └── strings.xml
│ │ ├── values-ja
│ │ └── strings.xml
│ │ ├── values-ka
│ │ └── strings.xml
│ │ ├── values-kk
│ │ └── strings.xml
│ │ ├── values-km
│ │ └── strings.xml
│ │ ├── values-kn
│ │ └── strings.xml
│ │ ├── values-ko
│ │ └── strings.xml
│ │ ├── values-ky
│ │ └── strings.xml
│ │ ├── values-lo
│ │ └── strings.xml
│ │ ├── values-lt
│ │ └── strings.xml
│ │ ├── values-lv
│ │ └── strings.xml
│ │ ├── values-mk
│ │ └── strings.xml
│ │ ├── values-ml
│ │ └── strings.xml
│ │ ├── values-mn
│ │ └── strings.xml
│ │ ├── values-mr
│ │ └── strings.xml
│ │ ├── values-ms
│ │ └── strings.xml
│ │ ├── values-my
│ │ └── strings.xml
│ │ ├── values-nb
│ │ └── strings.xml
│ │ ├── values-ne
│ │ └── strings.xml
│ │ ├── values-nl
│ │ └── strings.xml
│ │ ├── values-or
│ │ └── strings.xml
│ │ ├── values-pa
│ │ └── strings.xml
│ │ ├── values-pl
│ │ └── strings.xml
│ │ ├── values-pt
│ │ └── strings.xml
│ │ ├── values-ro
│ │ └── strings.xml
│ │ ├── values-ru
│ │ └── strings.xml
│ │ ├── values-si
│ │ └── strings.xml
│ │ ├── values-sk
│ │ └── strings.xml
│ │ ├── values-sl
│ │ └── strings.xml
│ │ ├── values-sq
│ │ └── strings.xml
│ │ ├── values-sr
│ │ └── strings.xml
│ │ ├── values-sv
│ │ └── strings.xml
│ │ ├── values-sw
│ │ └── strings.xml
│ │ ├── values-ta
│ │ └── strings.xml
│ │ ├── values-te
│ │ └── strings.xml
│ │ ├── values-th
│ │ └── strings.xml
│ │ ├── values-tl
│ │ └── strings.xml
│ │ ├── values-tr
│ │ └── strings.xml
│ │ ├── values-uk
│ │ └── strings.xml
│ │ ├── values-ur
│ │ └── strings.xml
│ │ ├── values-uz
│ │ └── strings.xml
│ │ ├── values-v23
│ │ └── themes.xml
│ │ ├── values-v27
│ │ └── themes.xml
│ │ ├── values-v29
│ │ └── themes.xml
│ │ ├── values-vi
│ │ └── strings.xml
│ │ ├── values-zh-rCN
│ │ └── strings.xml
│ │ ├── values-zh-rHK
│ │ └── strings.xml
│ │ ├── values-zh-rTW
│ │ └── strings.xml
│ │ ├── values-zu
│ │ └── strings.xml
│ │ └── values
│ │ ├── colors.xml
│ │ ├── public.xml
│ │ ├── strings.xml
│ │ └── themes.xml
│ ├── test
│ └── kotlin
│ │ └── io
│ │ └── github
│ │ └── g00fy2
│ │ └── quickie
│ │ └── BarcodeFormatsTest.kt
│ └── unbundled
│ ├── AndroidManifest.xml
│ └── kotlin
│ └── io
│ └── github
│ └── g00fy2
│ └── quickie
│ └── utils
│ └── MlKitErrorHandler.kt
├── sample
├── build.gradle.kts
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── kotlin
│ └── io
│ │ └── github
│ │ └── g00fy2
│ │ └── quickiesample
│ │ ├── MainActivity.kt
│ │ ├── SampleApp.kt
│ │ └── quicksettingstile
│ │ └── QuickieTileService.kt
│ └── res
│ ├── drawable
│ ├── ic_launcher_foreground.xml
│ ├── ic_qs_qrcode.xml
│ └── ic_scan_barcode.xml
│ ├── layout
│ └── activity_main.xml
│ ├── mipmap-anydpi-v26
│ └── ic_launcher.xml
│ ├── mipmap-hdpi
│ └── ic_launcher.png
│ ├── mipmap-mdpi
│ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxxhdpi
│ └── ic_launcher.png
│ ├── raw
│ └── keep.xml
│ └── values
│ ├── colors.xml
│ └── strings.xml
└── settings.gradle.kts
/.editorconfig:
--------------------------------------------------------------------------------
1 | root=true
2 |
3 | [*]
4 | charset=utf-8
5 | end_of_line=lf
6 | insert_final_newline=false
7 |
8 | max_line_length=120
9 |
10 | indent_size=2
11 | indent_style=space
12 |
13 | [*.{kt,kts}]
14 | ij_kotlin_name_count_to_use_star_import=2147483647
15 | ij_kotlin_name_count_to_use_star_import_for_members=2147483647
16 | ij_kotlin_imports_layout=*,java.**,javax.**,kotlin.**,^
17 | ij_kotlin_line_break_after_multiline_when_entry=false
18 |
19 | [*.xml]
20 | ij_continuation_indent_size=4
21 | ij_xml_keep_line_breaks=true
22 | ij_xml_space_inside_empty_tag=false
--------------------------------------------------------------------------------
/.github/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | twirth.development@gmail.com.
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series
86 | of actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or
93 | permanent ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to quickie
2 |
3 | Welcome and thanks for being interested in contributing to quickie!
4 |
5 | Quickie is a minimal barcode scanning library with an opinionated design.
6 | Therefore there are currently no plans to allow enhanced customization of the scanner overview ui.
7 | See this comment for further explanation [#14](https://github.com/G00fY2/quickie/pull/14#issuecomment-877804346).
8 |
9 | ## Creating Pull Requests
10 | * This project uses Git Flow as branching strategy. Make sure to use the **develop** branch as the base branch when creating a pull request.
11 | * For first time contributors GitHub will not run the GitHub Actions automatically. You should make sure that all tasks succeed before committing (see [Code Contributions](#code-contributions)).
12 |
13 | ## Code Contributions
14 | Make sure to get working code on a personal branch with tests and sanity checks passing before you submit a PR:
15 | ```shell
16 | ./gradlew detektBundledDebug detektUnbundledDebug
17 | ./gradlew test
18 | ./gradlew :sample:lintBundledDebug
19 | ./gradlew :sample:lintUnbundledDebug
20 | ./gradlew :sample:assembleBundledDebug
21 | ./gradlew :sample:assembleUnbundledDebug
22 | ```
23 | Please make every effort to follow existing conventions and style in order to keep the code as readable as possible.
24 |
25 | Contribute code changes through GitHub by forking the repository and sending a pull request. I will squash all pull requests on merge.
26 |
--------------------------------------------------------------------------------
/.github/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "config:base",
4 | ":disableDependencyDashboard"
5 | ],
6 | "baseBranches": [
7 | "develop"
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 |
3 | on:
4 | push:
5 | branches:
6 | - develop
7 | - main
8 | pull_request:
9 | branches:
10 | - develop
11 | - main
12 |
13 | env:
14 | JAVA_DISTRO: 'zulu'
15 | JAVA_VERSION: '21'
16 |
17 | jobs:
18 | detekt:
19 | name: Run detekt
20 | runs-on: ubuntu-latest
21 |
22 | steps:
23 | - uses: actions/checkout@v4
24 | - uses: actions/setup-java@v4
25 | with:
26 | distribution: ${{ env.JAVA_DISTRO }}
27 | java-version: ${{ env.JAVA_VERSION }}
28 | - name: Run detekt with ktlint
29 | run: ./gradlew detektBundledDebug detektUnbundledDebug
30 |
31 | unit_tests:
32 | name: Run unit tests
33 | runs-on: ubuntu-latest
34 |
35 | steps:
36 | - uses: actions/checkout@v4
37 | - uses: actions/setup-java@v4
38 | with:
39 | distribution: ${{ env.JAVA_DISTRO }}
40 | java-version: ${{ env.JAVA_VERSION }}
41 | - name: Run bundled and unbundled unit tests
42 | run: ./gradlew test
43 |
44 | android_lint:
45 | name: Android lint
46 | runs-on: ubuntu-latest
47 |
48 | steps:
49 | - uses: actions/checkout@v4
50 | - uses: actions/setup-java@v4
51 | with:
52 | distribution: ${{ env.JAVA_DISTRO }}
53 | java-version: ${{ env.JAVA_VERSION }}
54 | - name: Run Android lint
55 | run: ./gradlew :sample:lintBundledDebug :sample:lintUnbundledDebug
56 |
57 | build_bundled:
58 | name: Build bundled debug
59 | runs-on: ubuntu-latest
60 |
61 | steps:
62 | - uses: actions/checkout@v4
63 | - uses: actions/setup-java@v4
64 | with:
65 | distribution: ${{ env.JAVA_DISTRO }}
66 | java-version: ${{ env.JAVA_VERSION }}
67 | - name: Build debug bundled sample app
68 | run: ./gradlew :sample:assembleBundledDebug
69 |
70 | build_unbundled:
71 | name: Build unbundled debug
72 | runs-on: ubuntu-latest
73 |
74 | steps:
75 | - uses: actions/checkout@v4
76 | - uses: actions/setup-java@v4
77 | with:
78 | distribution: ${{ env.JAVA_DISTRO }}
79 | java-version: ${{ env.JAVA_VERSION }}
80 | - name: Build debug unbundled sample app
81 | run: ./gradlew :sample:assembleUnbundledDebug
82 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Reference gitignore https://gist.github.com/G00fY2/81c0aa67943cca021e915840bf73ee1b
2 |
3 | # Built application files
4 | *.apk
5 | *.ap_
6 | *.aab
7 |
8 | # Files for the ART/Dalvik VM
9 | *.dex
10 |
11 | # Java class files
12 | *.class
13 |
14 | # Generated files
15 | bin/
16 | gen/
17 | out/
18 |
19 | # Gradle files
20 | .gradle
21 | .gradle/
22 | build/
23 | gradle-app.setting
24 | .gradletasknamecache
25 |
26 | # Signing files
27 | .signing/
28 |
29 | # Local configuration file (sdk path, etc)
30 | local.properties
31 |
32 | # Proguard folder generated by Eclipse
33 | proguard/
34 |
35 | # Log Files
36 | *.log
37 |
38 | # Android Studio
39 | /*/bundled/
40 | /*/unbundled/
41 | /*/build/
42 | /*/local.properties
43 | /*/out
44 | /*/*/build
45 | /*/*/production
46 | /*/*/staging
47 | *.ipr
48 | *~
49 | *.swp
50 |
51 | # Android Patch
52 | gen-external-apklibs
53 |
54 | # Android Studio Navigation editor temp files
55 | .navigation/
56 |
57 | # Android Studio captures folder
58 | captures/
59 |
60 | # IntelliJ
61 | *.iml
62 | *.iws
63 | .idea/*
64 |
65 | # Keystore files
66 | *.jks
67 | *.keystore
68 |
69 | # External native build folder generated in Android Studio 2.2 and later
70 | .externalNativeBuild
71 |
72 | # NDK
73 | obj/
74 |
75 | # CMake
76 | cmake-build-*/
77 |
78 | # Google Services (e.g. APIs or Firebase)
79 | google-services.json
80 |
81 | # Freeline
82 | freeline.py
83 | freeline/
84 | freeline_project_description.json
85 |
86 | # fastlane
87 | fastlane/report.xml
88 | fastlane/Preview.html
89 | fastlane/screenshots
90 | fastlane/test_output
91 | fastlane/readme.md
92 |
93 | #Crashlytics
94 | crashlytics-build.properties
95 | com_crashlytics_export_strings.xml
96 | crashlytics.properties
97 | fabric.properties
98 |
99 | ## Plugin-specific files:
100 |
101 | # mpeltonen/sbt-idea plugin
102 | .idea_modules/
103 |
104 | # Mongo Explorer plugin
105 | .idea/mongoSettings.xml
106 |
107 | # JIRA plugin
108 | atlassian-ide-plugin.xml
109 |
110 | # Cursive Clojure plugin
111 | .idea/replstate.xml
112 |
113 | # Editor-based Rest Client
114 | .idea/httpRequests
115 |
116 | ## OS specific files:
117 |
118 | # General
119 | .DS_Store
120 | .DS_Store?
121 | .AppleDouble
122 | .LSOverride
123 |
124 | # Icon must end with two \r
125 | Icon
126 |
127 |
128 | # Thumbnails
129 | ._*
130 |
131 | # Files that might appear in the root of a volume
132 | .DocumentRevisions-V100
133 | .fseventsd
134 | .Spotlight-V100
135 | .TemporaryItems
136 | .Trashes
137 | .VolumeIcon.icns
138 | .com.apple.timemachine.donotpresent
139 |
140 | # Directories potentially created on remote AFP share
141 | .AppleDB
142 | .AppleDesktop
143 | Network Trash Folder
144 | Temporary Items
145 | .apdisk
146 |
147 | # Windows thumbnail cache files
148 | Thumbs.db
149 | ehthumbs.db
150 | ehthumbs_vista.db
151 |
152 | # Dump file
153 | *.stackdump
154 |
155 | # Folder config file
156 | [Dd]esktop.ini
157 |
158 | # Recycle Bin used on file shares
159 | $RECYCLE.BIN/
160 |
161 | # Windows Installer files
162 | *.cab
163 | *.msi
164 | *.msix
165 | *.msm
166 | *.msp
167 |
168 | # Windows shortcuts
169 | *.lnk
170 |
171 | # temporary files which can be created if a process still has a handle open of a deleted file
172 | .fuse_hidden*
173 |
174 | # KDE directory preferences
175 | .directory
176 |
177 | # Linux trash folder which might appear on any partition or disk
178 | .Trash-*
179 |
180 | # .nfs files are created when an open file is removed but is still being accessed
181 | .nfs*
182 |
183 | # Directory for Kotlin data in Gradle projects
184 | .kotlin/
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (C) 2021 Thomas Wirth
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
6 | associated documentation files (the "Software"), to deal in the Software without restriction,
7 | including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 | and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
9 | subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in all copies or substantial
12 | portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
15 | LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
16 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
17 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
18 | OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 | 
3 |
4 | **quickie** is a Quick Response (QR) Code scanning library for Android that is based on CameraX and ML Kit on-device barcode detection. It's an alternative to ZXing based libraries and written in Kotlin. **quickie** features:
5 | - Easy API for launching the QR scanner and receiving results by using the new Activity Result API
6 | - Modern design, edge-to-edge scanning view with multilingual user hint
7 | - Android Jetpack CameraX for communicating with the camera and showing the preview
8 | - ML Kit Vision API for best, fully on-device barcode recognition and decoding
9 |
10 | ## Download [](https://search.maven.org/search?q=g:io.github.g00fy2.quickie)
11 | There are two different flavors available on `mavenCentral()`:
12 |
13 | | Bundled | Unbundled |
14 | |-------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------|
15 | | ML Kit model is bundled inside app (independent of Google Services) | ML Kit model will be automatically downloaded via Play Services (once while installing/updating the app) |
16 | | About 2.5 MB app size increase per ABI (you should use App Bundle or ABI splitting) | About 550 KB app size increase |
17 | | V3 model is used (faster, more accurate) | Currently V1 model will be downloaded |
18 |
19 | ```kotlin
20 | // bundled:
21 | implementation("io.github.g00fy2.quickie:quickie-bundled:1.11.0")
22 |
23 | // unbundled:
24 | implementation("io.github.g00fy2.quickie:quickie-unbundled:1.11.0")
25 | ```
26 |
27 | ## Quick Start
28 |
29 | #### View-based
30 | To use the QR scanner simply register the `ScanQRCode()` ActivityResultContract together with a callback during `init` or `onCreate()` lifecycle of your Activity/Fragment and use the returned ActivityResultLauncher to launch the QR scanner Activity.
31 | ```kotlin
32 | val scanQrCodeLauncher = registerForActivityResult(ScanQRCode()) { result ->
33 | // handle QRResult
34 | }
35 |
36 | override fun onCreate(savedInstanceState: Bundle?) {
37 | super.onCreate(savedInstanceState)
38 | …
39 | binding.button.setOnClickListener { scanQrCodeLauncher.launch(null) }
40 | }
41 | ```
42 |
43 | Check out the [sample](https://github.com/G00fY2/quickie/tree/develop/sample) inside this repo or visit the official [Activity Result API documentation](https://developer.android.com/training/basics/intents/result) for more information.
44 |
45 | #### Jetpack Compose
46 | Use the `rememberLauncherForActivityResult()` API to register the `ScanQRCode()` ActivityResultContract together with a callback in your composable:
47 | ```kotlin
48 | @Composable
49 | fun GetQRCodeExample() {
50 | val scanQrCodeLauncher = rememberLauncherForActivityResult(ScanQRCode()) { result ->
51 | // handle QRResult
52 | }
53 |
54 | Button(onClick = { scanQrCodeLauncher.launch(null) }) {
55 | …
56 | }
57 | ```
58 | Check out the official [Compose Activity Result documentation](https://developer.android.com/jetpack/compose/libraries#activity_result) for more information.
59 |
60 | ### Responses
61 | The activity result is a subclass of the sealed `QRResult` class:
62 |
63 | 1. `QRSuccess` when ML Kit successfully detected a QR code
64 | * wraps a `QRContent` object
65 | 2. `QRUserCanceled` when the Activity got canceled by the user
66 | 3. `QRMissingPermission` when the user didn't accept the camera permission
67 | 4. `QRError` when CameraX or ML Kit threw an exception
68 | * wraps the `exception`
69 |
70 | #### Content
71 | The content type of the QR code detected by ML Kit is wrapped inside a subclass of the sealed `QRContent` class which always provides a `rawBytes` and `rawValue` (will only be `null` for non-UTF8 barcodes).
72 |
73 | Currently, supported subtypes are:
74 | `Plain`, `Wifi`, `Url`, `Sms`, `GeoPoint`, `Email`, `Phone`, `ContactInfo`, `CalendarEvent`
75 |
76 | See the ML Kit [Barcode documentation](https://developers.google.com/android/reference/com/google/mlkit/vision/barcode/common/Barcode#nested-class-summary) for further details.
77 |
78 | ### Customization
79 | Use the `ScanCustomCode()` ActivityResultContract to create a configurable barcode scan. When launching the ActivityResultLauncher pass in a `ScannerConfig` object:
80 |
81 |
82 | BarcodeFormat options
83 |
84 | ```kotlin
85 | BarcodeFormat.FORMAT_ALL_FORMATS
86 | BarcodeFormat.FORMAT_CODE_128
87 | BarcodeFormat.FORMAT_CODE_39
88 | BarcodeFormat.FORMAT_CODE_93
89 | BarcodeFormat.FORMAT_CODABAR
90 | BarcodeFormat.FORMAT_DATA_MATRIX
91 | BarcodeFormat.FORMAT_EAN_13
92 | BarcodeFormat.FORMAT_EAN_8
93 | BarcodeFormat.FORMAT_ITF
94 | BarcodeFormat.FORMAT_QR_CODE
95 | BarcodeFormat.FORMAT_UPC_A
96 | BarcodeFormat.FORMAT_UPC_E
97 | BarcodeFormat.FORMAT_PDF417
98 | BarcodeFormat.FORMAT_AZTEC
99 | ```
100 |
101 |
102 | ```kotlin
103 | val scanCustomCode = registerForActivityResult(ScanCustomCode(), ::handleResult)
104 |
105 | override fun onCreate(savedInstanceState: Bundle?) {
106 | super.onCreate(savedInstanceState)
107 | …
108 | binding.button.setOnClickListener {
109 | scanCustomCode.launch(
110 | ScannerConfig.build {
111 | setBarcodeFormats(listOf(BarcodeFormat.FORMAT_CODE_128)) // set interested barcode formats
112 | setOverlayStringRes(R.string.scan_barcode) // string resource used for the scanner overlay
113 | setOverlayDrawableRes(R.drawable.ic_scan_barcode) // drawable resource used for the scanner overlay
114 | setHapticSuccessFeedback(false) // enable (default) or disable haptic feedback when a barcode was detected
115 | setShowTorchToggle(true) // show or hide (default) torch/flashlight toggle button
116 | setShowCloseButton(true) // show or hide (default) close button
117 | setHorizontalFrameRatio(2.2f) // set the horizontal overlay ratio (default is 1 / square frame)
118 | setUseFrontCamera(true) // use the front camera
119 | setKeepScreenOn(true) // keep the device's screen turned on
120 | }
121 | )
122 | }
123 | }
124 |
125 | fun handleResult(result: QRResult) {
126 | …
127 | ```
128 | > [!TIP]
129 | > You can optionally [pass in an ActivityOptionsCompat object](https://developer.android.com/reference/androidx/activity/result/ActivityResultLauncher#launch(I,%20androidx.core.app.ActivityOptionsCompat)) when launching the ActivityResultLauncher to control the scanner launch animation.
130 |
131 | ## Screenshots / Sample App
132 | You can find the sample app APKs inside the [release](https://github.com/G00fY2/quickie/releases) assets.
133 |
134 | 
135 |
136 | ## Requirements
137 | * AndroidX
138 | * Min SDK 21+ (required by CameraX)
139 | * (Google Play Services available on the end device if using `quickie-unbundled`)
140 |
141 | ## Terms & Privacy
142 | See [ML Kit Terms & Privacy](https://developers.google.com/ml-kit/terms)
143 |
144 | ## Contributing
145 | See [CONTRIBUTING](.github/CONTRIBUTING.md)
146 |
147 | Thanks to everyone who contributed to quickie!
148 |
149 | ## License
150 | The MIT License (MIT)
151 |
152 | Copyright (C) 2022 Thomas Wirth
153 |
154 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
155 | associated documentation files (the "Software"), to deal in the Software without restriction,
156 | including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
157 | and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
158 | subject to the following conditions:
159 |
160 | The above copyright notice and this permission notice shall be included in all copies or substantial
161 | portions of the Software.
162 |
163 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
164 | LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
165 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
166 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
167 | OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
168 |
169 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import com.android.build.gradle.BaseExtension
2 | import com.android.build.gradle.BasePlugin
3 | import io.gitlab.arturbosch.detekt.Detekt
4 | import io.gitlab.arturbosch.detekt.extensions.DetektExtension
5 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget
6 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
7 |
8 | plugins {
9 | alias(libs.plugins.android.application) apply false
10 | alias(libs.plugins.android.library) apply false
11 | alias(libs.plugins.kotlin.android) apply false
12 | alias(libs.plugins.kotlin.parcelize) apply false
13 | alias(libs.plugins.kotlin.dokka) apply false
14 | alias(libs.plugins.detekt) apply false
15 | }
16 |
17 | subprojects {
18 | tasks.withType().configureEach {
19 | compilerOptions {
20 | allWarningsAsErrors = true
21 | progressiveMode = true
22 | jvmTarget = JvmTarget.JVM_11
23 | if (this@subprojects.name != "sample") {
24 | freeCompilerArgs.add("-Xexplicit-api=strict")
25 | }
26 | }
27 | }
28 |
29 | plugins.withType().configureEach {
30 | extensions.configure {
31 | compileSdkVersion(libs.versions.androidconfig.compileSdk.get().toInt())
32 | buildToolsVersion(libs.versions.androidconfig.buildTools.get())
33 | defaultConfig {
34 | minSdk = libs.versions.androidconfig.minSdk.get().toInt()
35 | targetSdk = libs.versions.androidconfig.targetSdk.get().toInt()
36 | }
37 | compileOptions {
38 | sourceCompatibility = JavaVersion.VERSION_11
39 | targetCompatibility = JavaVersion.VERSION_11
40 | }
41 | }
42 | }
43 |
44 | apply(plugin = rootProject.libs.plugins.detekt.get().pluginId)
45 | extensions.configure {
46 | toolVersion = rootProject.libs.versions.detekt.get()
47 | config.setFrom(files("$rootDir/detekt.yml"))
48 | buildUponDefaultConfig = true
49 | ignoredBuildTypes = listOf("release")
50 | }
51 | dependencies {
52 | add("detektPlugins", rootProject.libs.detektFormatting)
53 | }
54 | tasks.withType().configureEach {
55 | jvmTarget = JvmTarget.JVM_11.target
56 | }
57 | }
--------------------------------------------------------------------------------
/detekt.yml:
--------------------------------------------------------------------------------
1 | complexity:
2 | CyclomaticComplexMethod:
3 | ignoreSingleWhenExpression: true
4 | LongParameterList:
5 | ignoreAnnotated: ['Parcelize']
6 | constructorThreshold: 8
7 |
8 | exceptions:
9 | TooGenericExceptionCaught:
10 | active: false
11 |
12 | formatting:
13 | android: true
14 | Indentation:
15 | active: false
16 | FinalNewline:
17 | insertFinalNewLine: false
18 |
19 | performance:
20 | SpreadOperator:
21 | active: false
22 |
23 | style:
24 | MagicNumber:
25 | active: false
26 | NewLineAtEndOfFile:
27 | active: false
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Set the build VMs heap size.
2 | # For more information about how Gradle memory options were chosen: https://github.com/android/nowinandroid/blob/689ef92e41427ab70f82e2c9fe59755441deae92/gradle.properties#L12
3 | org.gradle.jvmargs=-Xmx3g -Dfile.encoding=UTF-8 -XX:+UseG1GC -XX:SoftRefLRUPolicyMSPerMB=1 -XX:ReservedCodeCacheSize=256m -XX:+HeapDumpOnOutOfMemoryError
4 | kotlin.daemon.jvmargs=-Xmx3g -Dfile.encoding=UTF-8 -XX:+UseG1GC -XX:SoftRefLRUPolicyMSPerMB=1 -XX:ReservedCodeCacheSize=320m -XX:+HeapDumpOnOutOfMemoryError
5 | # Controls whether Gradle should print a welcome message
6 | org.gradle.welcome=never
7 | # Allow usage of AndroidX instead of the old support libraries.
8 | android.useAndroidX=true
9 | # Disable Dokka Gradle plugin V1 deprecation warning until stable v2 is available
10 | org.jetbrains.dokka.experimental.gradle.pluginMode.nowarn=true
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | quickie = "1.11.0"
3 |
4 | androidconfig-minSdk = "21"
5 | androidconfig-compileSdk = "35"
6 | androidconfig-targetSdk = "35"
7 | androidconfig-buildTools = "36.0.0"
8 |
9 | androidGradle = "8.10.0"
10 | kotlin = "2.1.21"
11 |
12 | appcompat = "1.7.0"
13 | core = "1.16.0"
14 |
15 | cameraX = "1.4.2"
16 |
17 | barcodeScanning = "17.3.0"
18 | barcodeScanningGms = "18.3.1"
19 |
20 | materialDesign = "1.12.0"
21 |
22 | detekt = "1.23.8"
23 | dokka = "2.0.0"
24 |
25 | junit = "5.12.2"
26 |
27 | [libraries]
28 | androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }
29 | androidx-camera = { module = "androidx.camera:camera-camera2", version.ref = "cameraX" }
30 | androidx-cameraLifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "cameraX" }
31 | androidx-cameraPreview = { module = "androidx.camera:camera-view", version.ref = "cameraX" }
32 | androidx-core = { module = "androidx.core:core-ktx", version.ref = "core" }
33 |
34 | mlkit-barcodeScanning = { module = "com.google.mlkit:barcode-scanning", version.ref = "barcodeScanning" }
35 | mlkit-barcodeScanningGms = { module = "com.google.android.gms:play-services-mlkit-barcode-scanning", version.ref = "barcodeScanningGms" }
36 |
37 | google-materialDesign = { module = "com.google.android.material:material", version.ref = "materialDesign" }
38 |
39 | test-junit = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" }
40 | test-junit-platformLauncher = { module = "org.junit.platform:junit-platform-launcher" }
41 |
42 | detektFormatting = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", version.ref = "detekt" }
43 |
44 | [plugins]
45 | android-application = { id = "com.android.application", version.ref = "androidGradle" }
46 | android-library = { id = "com.android.library", version.ref = "androidGradle" }
47 |
48 | kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
49 | kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" }
50 | kotlin-dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" }
51 |
52 | detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" }
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/G00fY2/quickie/5b9cb9538c10617eae8ce2d7416c191e83860f02/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/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 | @rem SPDX-License-Identifier: Apache-2.0
17 | @rem
18 |
19 | @if "%DEBUG%"=="" @echo off
20 | @rem ##########################################################################
21 | @rem
22 | @rem Gradle startup script for Windows
23 | @rem
24 | @rem ##########################################################################
25 |
26 | @rem Set local scope for the variables with windows NT shell
27 | if "%OS%"=="Windows_NT" setlocal
28 |
29 | set DIRNAME=%~dp0
30 | if "%DIRNAME%"=="" set DIRNAME=.
31 | @rem This is normally unused
32 | set APP_BASE_NAME=%~n0
33 | set APP_HOME=%DIRNAME%
34 |
35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
37 |
38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
40 |
41 | @rem Find java.exe
42 | if defined JAVA_HOME goto findJavaFromJavaHome
43 |
44 | set JAVA_EXE=java.exe
45 | %JAVA_EXE% -version >NUL 2>&1
46 | if %ERRORLEVEL% equ 0 goto execute
47 |
48 | echo. 1>&2
49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
50 | echo. 1>&2
51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
52 | echo location of your Java installation. 1>&2
53 |
54 | goto fail
55 |
56 | :findJavaFromJavaHome
57 | set JAVA_HOME=%JAVA_HOME:"=%
58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
59 |
60 | if exist "%JAVA_EXE%" goto execute
61 |
62 | echo. 1>&2
63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
64 | echo. 1>&2
65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
66 | echo location of your Java installation. 1>&2
67 |
68 | goto fail
69 |
70 | :execute
71 | @rem Setup the command line
72 |
73 | set CLASSPATH=
74 |
75 |
76 | @rem Execute Gradle
77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
78 |
79 | :end
80 | @rem End local scope for the variables with windows NT shell
81 | if %ERRORLEVEL% equ 0 goto mainEnd
82 |
83 | :fail
84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
85 | rem the _cmd.exe /c_ return code!
86 | set EXIT_CODE=%ERRORLEVEL%
87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
89 | exit /b %EXIT_CODE%
90 |
91 | :mainEnd
92 | if "%OS%"=="Windows_NT" endlocal
93 |
94 | :omega
95 |
--------------------------------------------------------------------------------
/quickie/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.library)
3 | alias(libs.plugins.kotlin.android)
4 | alias(libs.plugins.kotlin.parcelize)
5 | alias(libs.plugins.kotlin.dokka)
6 | `maven-publish`
7 | signing
8 | }
9 |
10 | android {
11 | namespace = "io.github.g00fy2.quickie"
12 | resourcePrefix = "quickie"
13 | buildFeatures {
14 | viewBinding = true
15 | }
16 | flavorDimensions += "mlkit"
17 | productFlavors {
18 | create("bundled").dimension = "mlkit"
19 | create("unbundled").dimension = "mlkit"
20 | }
21 | sourceSets {
22 | getByName("bundled").java.srcDirs("src/bundled/kotlin")
23 | getByName("unbundled").java.srcDirs("src/unbundled/kotlin")
24 | }
25 | publishing {
26 | singleVariant("bundledRelease")
27 | singleVariant("unbundledRelease")
28 | }
29 | }
30 |
31 | dependencies {
32 | implementation(libs.androidx.appcompat)
33 | implementation(libs.androidx.core)
34 |
35 | implementation(libs.androidx.camera)
36 | implementation(libs.androidx.cameraLifecycle)
37 | implementation(libs.androidx.cameraPreview)
38 |
39 | add("bundledImplementation", libs.mlkit.barcodeScanning)
40 | add("unbundledImplementation", libs.mlkit.barcodeScanningGms)
41 |
42 | testImplementation(libs.test.junit)
43 | testRuntimeOnly(libs.test.junit.platformLauncher)
44 | }
45 |
46 | group = "io.github.g00fy2.quickie"
47 | version = libs.versions.quickie.get()
48 |
49 | tasks.register("androidJavadocJar") {
50 | archiveClassifier = "javadoc"
51 | from(layout.buildDirectory.dir("dokka/javadoc"))
52 | dependsOn("dokkaJavadoc")
53 | }
54 |
55 | tasks.register("androidBundledSourcesJar") {
56 | archiveClassifier = "sources"
57 | from(android.sourceSets.getByName("main").java.srcDirs, android.sourceSets.getByName("bundled").java.srcDirs)
58 | }
59 |
60 | tasks.register("androidUnbundledSourcesJar") {
61 | archiveClassifier = "sources"
62 | from(android.sourceSets.getByName("main").java.srcDirs, android.sourceSets.getByName("unbundled").java.srcDirs)
63 | }
64 |
65 | afterEvaluate {
66 | tasks.withType().configureEach {
67 | useJUnitPlatform()
68 | testLogging.events("failed", "passed", "skipped")
69 | enabled = name.endsWith("DebugUnitTest")
70 | }
71 |
72 | publishing {
73 | publications {
74 | create("bundledRelease") { commonConfig("bundled") }
75 | create("unbundledRelease") { commonConfig("unbundled") }
76 | }
77 | repositories {
78 | maven {
79 | name = "sonatype"
80 | url = uri("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/")
81 | credentials {
82 | username = findStringProperty("sonatypeUsername")
83 | password = findStringProperty("sonatypePassword")
84 | }
85 | }
86 | }
87 | }
88 | }
89 |
90 | signing {
91 | findStringProperty("signing.keyId")
92 | findStringProperty("signing.password")
93 | findStringProperty("signing.secretKeyRingFile")
94 | sign(publishing.publications)
95 | }
96 |
97 | fun MavenPublication.commonConfig(flavor: String) {
98 | from(components["${flavor}Release"])
99 | artifactId = "quickie-$flavor"
100 | artifact(tasks.named("androidJavadocJar"))
101 | artifact(tasks.named("android${flavor.replaceFirstChar { it.titlecase() }}SourcesJar"))
102 | pom {
103 | name = "quickie-$flavor"
104 | description = "Android QR code scanning library"
105 | url = "https://github.com/G00fY2/quickie"
106 | licenses {
107 | license {
108 | name = "MIT License"
109 | url = "https://opensource.org/licenses/MIT"
110 | }
111 | }
112 | developers {
113 | developer {
114 | id = "g00fy2"
115 | name = "Thomas Wirth"
116 | email = "twirth.development@gmail.com"
117 | }
118 | }
119 | scm {
120 | connection = "https://github.com/G00fY2/quickie.git"
121 | developerConnection = "https://github.com/G00fY2/quickie.git"
122 | url = "https://github.com/G00fY2/quickie"
123 | }
124 | }
125 | }
126 |
127 | fun Project.findStringProperty(propertyName: String): String? {
128 | return findProperty(propertyName) as String? ?: run {
129 | println("$propertyName missing in gradle.properties")
130 | null
131 | }
132 | }
--------------------------------------------------------------------------------
/quickie/src/bundled/kotlin/io/github/g00fy2/quickie/utils/MlKitErrorHandler.kt:
--------------------------------------------------------------------------------
1 | package io.github.g00fy2.quickie.utils
2 |
3 | import io.github.g00fy2.quickie.QRScannerActivity
4 |
5 | internal object MlKitErrorHandler {
6 |
7 | @Suppress("UNUSED_PARAMETER", "FunctionOnlyReturningConstant")
8 | fun isResolvableError(activity: QRScannerActivity, exception: Exception) = false // always false when bundled
9 | }
--------------------------------------------------------------------------------
/quickie/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
8 |
9 |
10 |
11 |
12 |
13 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/quickie/src/main/kotlin/io/github/g00fy2/quickie/QRCodeAnalyzer.kt:
--------------------------------------------------------------------------------
1 | package io.github.g00fy2.quickie
2 |
3 | import androidx.camera.core.ExperimentalGetImage
4 | import androidx.camera.core.ImageAnalysis
5 | import androidx.camera.core.ImageProxy
6 | import com.google.mlkit.vision.barcode.BarcodeScannerOptions
7 | import com.google.mlkit.vision.barcode.BarcodeScanning
8 | import com.google.mlkit.vision.barcode.common.Barcode
9 | import com.google.mlkit.vision.common.InputImage
10 |
11 | internal class QRCodeAnalyzer(
12 | private val barcodeFormats: IntArray,
13 | private val onSuccess: ((Barcode) -> Unit),
14 | private val onFailure: ((Exception) -> Unit),
15 | private val onPassCompleted: ((Boolean) -> Unit)
16 | ) : ImageAnalysis.Analyzer {
17 |
18 | private val barcodeScanner by lazy {
19 | val optionsBuilder = if (barcodeFormats.size > 1) {
20 | BarcodeScannerOptions.Builder().setBarcodeFormats(barcodeFormats.first(), *barcodeFormats.drop(1).toIntArray())
21 | } else {
22 | BarcodeScannerOptions.Builder().setBarcodeFormats(barcodeFormats.firstOrNull() ?: Barcode.FORMAT_UNKNOWN)
23 | }
24 | try {
25 | BarcodeScanning.getClient(optionsBuilder.build())
26 | } catch (e: Exception) { // catch if for some reason MlKitContext has not been initialized
27 | onFailure(e)
28 | null
29 | }
30 | }
31 |
32 | private var failureOccurred = false
33 | private var failureTimestamp = 0L
34 |
35 | @ExperimentalGetImage
36 | override fun analyze(imageProxy: ImageProxy) {
37 | val mediaImage = imageProxy.image
38 |
39 | // skip analysis if no media image or error occurred in previous pass
40 | if (mediaImage == null || (failureOccurred && System.currentTimeMillis() - failureTimestamp < 1000L)) {
41 | imageProxy.close()
42 | return
43 | }
44 |
45 | failureOccurred = false
46 | barcodeScanner?.let { scanner ->
47 | scanner.process(InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees))
48 | .addOnSuccessListener { codes -> codes.firstNotNullOfOrNull { it }?.let { onSuccess(it) } }
49 | .addOnFailureListener {
50 | failureOccurred = true
51 | failureTimestamp = System.currentTimeMillis()
52 | onFailure(it)
53 | }
54 | .addOnCompleteListener {
55 | onPassCompleted(failureOccurred)
56 | imageProxy.close()
57 | }
58 | }
59 | }
60 | }
--------------------------------------------------------------------------------
/quickie/src/main/kotlin/io/github/g00fy2/quickie/QROverlayView.kt:
--------------------------------------------------------------------------------
1 | package io.github.g00fy2.quickie
2 |
3 | import android.content.Context
4 | import android.content.res.ColorStateList
5 | import android.content.res.Resources.NotFoundException
6 | import android.graphics.Bitmap
7 | import android.graphics.Canvas
8 | import android.graphics.Color
9 | import android.graphics.Paint
10 | import android.graphics.PorterDuff.Mode.CLEAR
11 | import android.graphics.PorterDuffXfermode
12 | import android.graphics.RectF
13 | import android.graphics.drawable.Drawable
14 | import android.util.AttributeSet
15 | import android.util.TypedValue
16 | import android.view.LayoutInflater
17 | import android.view.View
18 | import android.widget.FrameLayout
19 | import androidx.core.content.ContextCompat
20 | import androidx.core.content.res.ResourcesCompat
21 | import androidx.core.graphics.ColorUtils
22 | import androidx.core.graphics.createBitmap
23 | import androidx.core.graphics.drawable.DrawableCompat
24 | import io.github.g00fy2.quickie.databinding.QuickieOverlayViewBinding
25 | import kotlin.math.min
26 | import kotlin.math.roundToInt
27 |
28 | @Suppress("TooManyFunctions")
29 | internal class QROverlayView @JvmOverloads constructor(
30 | context: Context,
31 | attrs: AttributeSet? = null,
32 | defStyleAttr: Int = 0
33 | ) : FrameLayout(context, attrs, defStyleAttr) {
34 |
35 | private val binding = QuickieOverlayViewBinding.inflate(LayoutInflater.from(context), this)
36 | private val grayColor = ContextCompat.getColor(context, R.color.quickie_gray)
37 | private val accentColor = getAccentColor()
38 | private val backgroundColor = ColorUtils.setAlphaComponent(Color.BLACK, BACKGROUND_ALPHA.roundToInt())
39 | private val alphaPaint = Paint().apply { alpha = BACKGROUND_ALPHA.roundToInt() }
40 | private val strokePaint = Paint(Paint.ANTI_ALIAS_FLAG)
41 | private val loadingBackgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { color = backgroundColor }
42 | private val transparentPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
43 | color = Color.TRANSPARENT
44 | xfermode = PorterDuffXfermode(CLEAR)
45 | }
46 | private val outerRadius = OUT_RADIUS.toPx()
47 | private val innerRadius = (OUT_RADIUS - STROKE_WIDTH).toPx()
48 | private val outerFrame = RectF()
49 | private val innerFrame = RectF()
50 | private var maskBitmap: Bitmap? = null
51 | private var maskCanvas: Canvas? = null
52 | private var horizontalFrameRatio = 1f
53 | var isHighlighted = false
54 | set(value) {
55 | if (field != value) {
56 | field = value
57 | invalidate()
58 | }
59 | }
60 | var isLoading = false
61 | set(value) {
62 | if (field != value) {
63 | field = value
64 | binding.progressView.visibility = if (value) View.VISIBLE else View.GONE
65 | }
66 | }
67 |
68 | init {
69 | setWillNotDraw(false)
70 | }
71 |
72 | override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
73 | super.onLayout(changed, left, top, right, bottom)
74 |
75 | if (maskBitmap == null && width > 0 && height > 0) {
76 | maskBitmap = createBitmap(width, height).apply { maskCanvas = Canvas(this) }
77 | calculateFrameAndTitlePos()
78 | }
79 | }
80 |
81 | @Suppress("UnsafeCallOnNullableType")
82 | override fun onDraw(canvas: Canvas) {
83 | strokePaint.color = if (isHighlighted) accentColor else grayColor
84 | maskCanvas!!.drawColor(backgroundColor)
85 | maskCanvas!!.drawRoundRect(outerFrame, outerRadius, outerRadius, strokePaint)
86 | maskCanvas!!.drawRoundRect(innerFrame, innerRadius, innerRadius, transparentPaint)
87 | if (isLoading) maskCanvas!!.drawRoundRect(innerFrame, innerRadius, innerRadius, loadingBackgroundPaint)
88 | canvas.drawBitmap(maskBitmap!!, 0f, 0f, alphaPaint)
89 | super.onDraw(canvas)
90 | }
91 |
92 | fun setCustomText(stringRes: Int) {
93 | if (stringRes != 0) {
94 | try {
95 | binding.titleTextView.setText(stringRes)
96 | } catch (ignore: NotFoundException) {
97 | // string resource not found
98 | }
99 | }
100 | }
101 |
102 | fun setCustomIcon(drawableRes: Int?) {
103 | if (drawableRes == null) {
104 | binding.titleTextView.setCompoundDrawables(null, null, null, null)
105 | } else if (drawableRes != 0) {
106 | try {
107 | ResourcesCompat.getDrawable(resources, drawableRes, null)?.limitDrawableSize()?.let {
108 | binding.titleTextView.setCompoundDrawables(null, it, null, null)
109 | }
110 | } catch (ignore: NotFoundException) {
111 | // drawable resource not found
112 | }
113 | }
114 | }
115 |
116 | fun setHorizontalFrameRatio(ratio: Float) {
117 | if (ratio > 1f) {
118 | horizontalFrameRatio = ratio
119 | calculateFrameAndTitlePos()
120 | }
121 | }
122 |
123 | fun setCloseVisibilityAndOnClick(visible: Boolean, action: () -> Unit = {}) {
124 | binding.closeImageView.visibility = if (visible) View.VISIBLE else View.GONE
125 | binding.closeImageView.setOnClickListener { action() }
126 | if (visible) binding.closeImageView.setTintAndStateAwareBackground()
127 | }
128 |
129 | fun setTorchVisibilityAndOnClick(visible: Boolean, action: (Boolean) -> Unit = {}) {
130 | binding.torchImageView.visibility = if (visible) View.VISIBLE else View.GONE
131 | binding.torchImageView.setOnClickListener { action(!it.isSelected) }
132 | if (visible) binding.torchImageView.setTintAndStateAwareBackground()
133 | }
134 |
135 | fun setTorchState(on: Boolean) {
136 | binding.torchImageView.isSelected = on
137 | }
138 |
139 | private fun calculateFrameAndTitlePos() {
140 | val centralX = width / 2
141 | val centralY = height / 2
142 | val minLength = min(centralX, centralY)
143 | val marginRatio = if (horizontalFrameRatio > 1f) {
144 | FRAME_MARGIN_RATIO * ((1f / horizontalFrameRatio) * 1.5f)
145 | } else {
146 | FRAME_MARGIN_RATIO
147 | }
148 | val strokeLength = minLength - (minLength * marginRatio)
149 | val strokeWidth = STROKE_WIDTH.toPx()
150 | outerFrame.set(
151 | centralX - strokeLength,
152 | centralY - strokeLength / horizontalFrameRatio,
153 | centralX + strokeLength,
154 | centralY + strokeLength / horizontalFrameRatio
155 | )
156 | innerFrame.set(
157 | outerFrame.left + strokeWidth,
158 | outerFrame.top + strokeWidth,
159 | outerFrame.right - strokeWidth,
160 | outerFrame.bottom - strokeWidth
161 | )
162 |
163 | val topInsetsToOuterFrame = (-paddingTop + centralY - strokeLength).roundToInt()
164 | val titleCenter = (topInsetsToOuterFrame - binding.titleTextView.height) / 2
165 | binding.titleTextView.updateTopMargin(titleCenter)
166 | // hide title text if not enough vertical space
167 | binding.titleTextView.visibility =
168 | if (topInsetsToOuterFrame < binding.titleTextView.height) View.INVISIBLE else View.VISIBLE
169 | }
170 |
171 | private fun getAccentColor(): Int {
172 | return TypedValue().let {
173 | if (context.theme.resolveAttribute(android.R.attr.colorAccent, it, true)) {
174 | it.data
175 | } else {
176 | ContextCompat.getColor(context, R.color.quickie_accent_fallback)
177 | }
178 | }
179 | }
180 |
181 | private fun View.updateTopMargin(topPx: Int) {
182 | val params = layoutParams as MarginLayoutParams
183 | params.topMargin = topPx
184 | layoutParams = params
185 | }
186 |
187 | private fun Drawable.limitDrawableSize(): Drawable {
188 | val heightLimit = ICON_MAX_HEIGHT.toPx()
189 | val scale = heightLimit / minimumHeight
190 | if (scale < 1) {
191 | setBounds(0, 0, (minimumWidth * scale).roundToInt(), (minimumHeight * scale).roundToInt())
192 | } else {
193 | setBounds(0, 0, minimumWidth, minimumHeight)
194 | }
195 | return this
196 | }
197 |
198 | private fun View.setTintAndStateAwareBackground() {
199 | background?.let { drawable ->
200 | val wrappedDrawable = DrawableCompat.wrap(drawable)
201 |
202 | val states = arrayOf(
203 | intArrayOf(android.R.attr.state_pressed, android.R.attr.state_selected),
204 | intArrayOf(android.R.attr.state_pressed, -android.R.attr.state_selected),
205 | intArrayOf(-android.R.attr.state_pressed, android.R.attr.state_selected),
206 | intArrayOf()
207 | )
208 | val stateColors = intArrayOf(grayColor, accentColor, accentColor, grayColor)
209 | val colorStateList = ColorStateList(states, stateColors).withAlpha(BUTTON_BACKGROUND_ALPHA.roundToInt())
210 |
211 | DrawableCompat.setTintList(wrappedDrawable, colorStateList)
212 | background = wrappedDrawable
213 | }
214 | }
215 |
216 | private fun Float.toPx() =
217 | TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this, resources.displayMetrics)
218 |
219 | companion object {
220 | private const val BACKGROUND_ALPHA = 0.77 * 255
221 | private const val BUTTON_BACKGROUND_ALPHA = 0.6 * 255
222 | private const val STROKE_WIDTH = 4f
223 | private const val OUT_RADIUS = 16f
224 | private const val FRAME_MARGIN_RATIO = 1f / 4
225 | private const val ICON_MAX_HEIGHT = 56f
226 | }
227 | }
--------------------------------------------------------------------------------
/quickie/src/main/kotlin/io/github/g00fy2/quickie/QRResult.kt:
--------------------------------------------------------------------------------
1 | package io.github.g00fy2.quickie
2 |
3 | import io.github.g00fy2.quickie.content.QRContent
4 |
5 | public sealed class QRResult {
6 |
7 | /**
8 | * MLKit successfully detected a QR code.
9 | *
10 | * @property content the wrapped MLKit response.
11 | */
12 | public data class QRSuccess(val content: QRContent) : QRResult()
13 |
14 | /**
15 | * Activity got cancelled by the user.
16 | */
17 | public data object QRUserCanceled : QRResult()
18 |
19 | /**
20 | * Camera permission was not granted.
21 | */
22 | public data object QRMissingPermission : QRResult()
23 |
24 | /**
25 | * Error while setting up CameraX or while MLKit analysis.
26 | *
27 | * @property exception the cause why the Activity was finished.
28 | */
29 | public data class QRError(val exception: Exception) : QRResult()
30 | }
--------------------------------------------------------------------------------
/quickie/src/main/kotlin/io/github/g00fy2/quickie/QRScannerActivity.kt:
--------------------------------------------------------------------------------
1 | package io.github.g00fy2.quickie
2 |
3 | import android.Manifest.permission.CAMERA
4 | import android.app.Activity
5 | import android.app.Dialog
6 | import android.content.Intent
7 | import android.content.pm.PackageManager
8 | import android.os.Bundle
9 | import android.util.Size
10 | import android.view.HapticFeedbackConstants
11 | import android.view.KeyEvent
12 | import android.view.View
13 | import android.view.WindowManager
14 | import androidx.activity.result.contract.ActivityResultContracts
15 | import androidx.appcompat.app.AppCompatActivity
16 | import androidx.appcompat.view.ContextThemeWrapper
17 | import androidx.camera.core.CameraSelector
18 | import androidx.camera.core.ImageAnalysis
19 | import androidx.camera.core.Preview
20 | import androidx.camera.core.TorchState
21 | import androidx.camera.core.resolutionselector.ResolutionSelector
22 | import androidx.camera.core.resolutionselector.ResolutionStrategy
23 | import androidx.camera.lifecycle.ProcessCameraProvider
24 | import androidx.core.content.ContextCompat
25 | import androidx.core.content.IntentCompat
26 | import androidx.core.view.ViewCompat
27 | import androidx.core.view.WindowCompat
28 | import androidx.core.view.WindowInsetsCompat
29 | import com.google.mlkit.vision.barcode.common.Barcode
30 | import io.github.g00fy2.quickie.config.ParcelableScannerConfig
31 | import io.github.g00fy2.quickie.databinding.QuickieScannerActivityBinding
32 | import io.github.g00fy2.quickie.extensions.toParcelableContentType
33 | import io.github.g00fy2.quickie.utils.MlKitErrorHandler
34 | import java.util.concurrent.ExecutorService
35 | import java.util.concurrent.Executors
36 |
37 | internal class QRScannerActivity : AppCompatActivity() {
38 |
39 | private lateinit var binding: QuickieScannerActivityBinding
40 | private lateinit var analysisExecutor: ExecutorService
41 | private var barcodeFormats = intArrayOf(Barcode.FORMAT_QR_CODE)
42 | private var hapticFeedback = true
43 | private var showTorchToggle = false
44 | private var showCloseButton = false
45 | private var useFrontCamera = false
46 | internal var errorDialog: Dialog? = null
47 | set(value) {
48 | field = value
49 | value?.show()
50 | value?.setOnKeyListener { dialog, keyCode, _ ->
51 | if (keyCode == KeyEvent.KEYCODE_BACK) {
52 | finish()
53 | dialog.dismiss()
54 | true
55 | } else {
56 | false
57 | }
58 | }
59 | }
60 |
61 | override fun onCreate(savedInstanceState: Bundle?) {
62 | super.onCreate(savedInstanceState)
63 | val appThemeLayoutInflater = applicationInfo.theme.let { appThemeRes ->
64 | if (appThemeRes != 0) layoutInflater.cloneInContext(ContextThemeWrapper(this, appThemeRes)) else layoutInflater
65 | }
66 | binding = QuickieScannerActivityBinding.inflate(appThemeLayoutInflater)
67 | setContentView(binding.root)
68 |
69 | setupEdgeToEdgeUI()
70 | applyScannerConfig()
71 |
72 | analysisExecutor = Executors.newSingleThreadExecutor()
73 |
74 | requestCameraPermissionIfMissing { granted ->
75 | if (granted) {
76 | startCamera()
77 | } else {
78 | setResult(RESULT_MISSING_PERMISSION, null)
79 | finish()
80 | }
81 | }
82 | }
83 |
84 | override fun onDestroy() {
85 | super.onDestroy()
86 | analysisExecutor.shutdown()
87 | }
88 |
89 | private fun startCamera() {
90 | val cameraProviderFuture = try {
91 | ProcessCameraProvider.getInstance(this)
92 | } catch (e: Exception) {
93 | onFailure(e)
94 | return
95 | }
96 |
97 | cameraProviderFuture.addListener({
98 | val cameraProvider = try {
99 | cameraProviderFuture.get()
100 | } catch (e: Exception) {
101 | onFailure(e)
102 | return@addListener
103 | }
104 |
105 | val preview = Preview.Builder().build().also { it.surfaceProvider = binding.previewView.surfaceProvider }
106 | val imageAnalysis = ImageAnalysis.Builder()
107 | .setResolutionSelector(
108 | ResolutionSelector.Builder().setResolutionStrategy(
109 | ResolutionStrategy(
110 | Size(1280, 720),
111 | ResolutionStrategy.FALLBACK_RULE_CLOSEST_HIGHER_THEN_LOWER
112 | )
113 | ).build()
114 | )
115 | .build()
116 | .also {
117 | it.setAnalyzer(
118 | analysisExecutor,
119 | QRCodeAnalyzer(
120 | barcodeFormats = barcodeFormats,
121 | onSuccess = { barcode ->
122 | it.clearAnalyzer()
123 | onSuccess(barcode)
124 | },
125 | onFailure = { exception -> onFailure(exception) },
126 | onPassCompleted = { failureOccurred -> onPassCompleted(failureOccurred) }
127 | )
128 | )
129 | }
130 |
131 | cameraProvider.unbindAll()
132 |
133 | val cameraSelector =
134 | if (useFrontCamera) CameraSelector.DEFAULT_FRONT_CAMERA else CameraSelector.DEFAULT_BACK_CAMERA
135 |
136 | try {
137 | val camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageAnalysis)
138 | binding.overlayView.visibility = View.VISIBLE
139 | binding.overlayView.setCloseVisibilityAndOnClick(showCloseButton) { finish() }
140 | if (showTorchToggle && camera.cameraInfo.hasFlashUnit()) {
141 | binding.overlayView.setTorchVisibilityAndOnClick(true) { camera.cameraControl.enableTorch(it) }
142 | camera.cameraInfo.torchState.observe(this) { binding.overlayView.setTorchState(it == TorchState.ON) }
143 | } else {
144 | binding.overlayView.setTorchVisibilityAndOnClick(false)
145 | }
146 | } catch (e: Exception) {
147 | binding.overlayView.visibility = View.INVISIBLE
148 | onFailure(e)
149 | }
150 | }, ContextCompat.getMainExecutor(this))
151 | }
152 |
153 | private fun onSuccess(result: Barcode) {
154 | binding.overlayView.isHighlighted = true
155 | if (hapticFeedback) {
156 | @Suppress("DEPRECATION")
157 | val flags = HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING or HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING
158 | binding.overlayView.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP, flags)
159 | }
160 | setResult(
161 | Activity.RESULT_OK,
162 | Intent().apply {
163 | putExtra(EXTRA_RESULT_BYTES, result.rawBytes)
164 | putExtra(EXTRA_RESULT_VALUE, result.rawValue)
165 | putExtra(EXTRA_RESULT_TYPE, result.valueType)
166 | putExtra(EXTRA_RESULT_PARCELABLE, result.toParcelableContentType())
167 | }
168 | )
169 | finish()
170 | }
171 |
172 | private fun onFailure(exception: Exception) {
173 | setResult(RESULT_ERROR, Intent().putExtra(EXTRA_RESULT_EXCEPTION, exception))
174 | if (!MlKitErrorHandler.isResolvableError(this, exception)) finish()
175 | }
176 |
177 | private fun onPassCompleted(failureOccurred: Boolean) {
178 | if (!isFinishing) binding.overlayView.isLoading = failureOccurred
179 | }
180 |
181 | private fun setupEdgeToEdgeUI() {
182 | WindowCompat.setDecorFitsSystemWindows(window, false)
183 | ViewCompat.setOnApplyWindowInsetsListener(binding.overlayView) { v, insets ->
184 | insets.getInsets(WindowInsetsCompat.Type.systemBars()).let { v.setPadding(it.left, it.top, it.right, it.bottom) }
185 | WindowInsetsCompat.CONSUMED
186 | }
187 | }
188 |
189 | private fun applyScannerConfig() {
190 | intent?.let { IntentCompat.getParcelableExtra(it, EXTRA_CONFIG, ParcelableScannerConfig::class.java) }?.let {
191 | barcodeFormats = it.formats
192 | binding.overlayView.setCustomText(it.stringRes)
193 | binding.overlayView.setCustomIcon(it.drawableRes)
194 | binding.overlayView.setHorizontalFrameRatio(it.horizontalFrameRatio)
195 | hapticFeedback = it.hapticFeedback
196 | showTorchToggle = it.showTorchToggle
197 | useFrontCamera = it.useFrontCamera
198 | showCloseButton = it.showCloseButton
199 |
200 | if (it.keepScreenOn) window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
201 | }
202 | }
203 |
204 | private fun requestCameraPermissionIfMissing(onResult: ((Boolean) -> Unit)) {
205 | if (ContextCompat.checkSelfPermission(this, CAMERA) == PackageManager.PERMISSION_GRANTED) {
206 | onResult(true)
207 | } else {
208 | registerForActivityResult(ActivityResultContracts.RequestPermission()) { onResult(it) }.launch(CAMERA)
209 | }
210 | }
211 |
212 | companion object {
213 | const val EXTRA_CONFIG = "quickie-config"
214 | const val EXTRA_RESULT_BYTES = "quickie-bytes"
215 | const val EXTRA_RESULT_VALUE = "quickie-value"
216 | const val EXTRA_RESULT_TYPE = "quickie-type"
217 | const val EXTRA_RESULT_PARCELABLE = "quickie-parcelable"
218 | const val EXTRA_RESULT_EXCEPTION = "quickie-exception"
219 | const val RESULT_MISSING_PERMISSION = RESULT_FIRST_USER + 1
220 | const val RESULT_ERROR = RESULT_FIRST_USER + 2
221 | }
222 | }
--------------------------------------------------------------------------------
/quickie/src/main/kotlin/io/github/g00fy2/quickie/ScanCustomCode.kt:
--------------------------------------------------------------------------------
1 | package io.github.g00fy2.quickie
2 |
3 | import android.app.Activity.RESULT_CANCELED
4 | import android.app.Activity.RESULT_OK
5 | import android.content.Context
6 | import android.content.Intent
7 | import androidx.activity.result.contract.ActivityResultContract
8 | import io.github.g00fy2.quickie.QRResult.QRError
9 | import io.github.g00fy2.quickie.QRResult.QRMissingPermission
10 | import io.github.g00fy2.quickie.QRResult.QRSuccess
11 | import io.github.g00fy2.quickie.QRResult.QRUserCanceled
12 | import io.github.g00fy2.quickie.QRScannerActivity.Companion.EXTRA_CONFIG
13 | import io.github.g00fy2.quickie.QRScannerActivity.Companion.RESULT_ERROR
14 | import io.github.g00fy2.quickie.QRScannerActivity.Companion.RESULT_MISSING_PERMISSION
15 | import io.github.g00fy2.quickie.config.ScannerConfig
16 | import io.github.g00fy2.quickie.extensions.getRootException
17 | import io.github.g00fy2.quickie.extensions.toParcelableConfig
18 | import io.github.g00fy2.quickie.extensions.toQuickieContentType
19 |
20 | public class ScanCustomCode : ActivityResultContract() {
21 |
22 | override fun createIntent(context: Context, input: ScannerConfig): Intent {
23 | return Intent(context, QRScannerActivity::class.java).apply {
24 | putExtra(EXTRA_CONFIG, input.toParcelableConfig())
25 | }
26 | }
27 |
28 | override fun parseResult(resultCode: Int, intent: Intent?): QRResult {
29 | return when (resultCode) {
30 | RESULT_OK -> QRSuccess(intent.toQuickieContentType())
31 | RESULT_CANCELED -> QRUserCanceled
32 | RESULT_MISSING_PERMISSION -> QRMissingPermission
33 | RESULT_ERROR -> QRError(intent.getRootException())
34 | else -> QRError(IllegalStateException("Unknown activity result code $resultCode"))
35 | }
36 | }
37 | }
--------------------------------------------------------------------------------
/quickie/src/main/kotlin/io/github/g00fy2/quickie/ScanQRCode.kt:
--------------------------------------------------------------------------------
1 | package io.github.g00fy2.quickie
2 |
3 | import android.app.Activity.RESULT_CANCELED
4 | import android.app.Activity.RESULT_OK
5 | import android.content.Context
6 | import android.content.Intent
7 | import androidx.activity.result.contract.ActivityResultContract
8 | import io.github.g00fy2.quickie.QRResult.QRError
9 | import io.github.g00fy2.quickie.QRResult.QRMissingPermission
10 | import io.github.g00fy2.quickie.QRResult.QRSuccess
11 | import io.github.g00fy2.quickie.QRResult.QRUserCanceled
12 | import io.github.g00fy2.quickie.QRScannerActivity.Companion.RESULT_ERROR
13 | import io.github.g00fy2.quickie.QRScannerActivity.Companion.RESULT_MISSING_PERMISSION
14 | import io.github.g00fy2.quickie.extensions.getRootException
15 | import io.github.g00fy2.quickie.extensions.toQuickieContentType
16 |
17 | public class ScanQRCode : ActivityResultContract() {
18 |
19 | override fun createIntent(context: Context, input: Nothing?): Intent =
20 | Intent(context, QRScannerActivity::class.java)
21 |
22 | override fun parseResult(resultCode: Int, intent: Intent?): QRResult {
23 | return when (resultCode) {
24 | RESULT_OK -> QRSuccess(intent.toQuickieContentType())
25 | RESULT_CANCELED -> QRUserCanceled
26 | RESULT_MISSING_PERMISSION -> QRMissingPermission
27 | RESULT_ERROR -> QRError(intent.getRootException())
28 | else -> QRError(IllegalStateException("Unknown activity result code $resultCode"))
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/quickie/src/main/kotlin/io/github/g00fy2/quickie/config/BarcodeFormat.kt:
--------------------------------------------------------------------------------
1 | package io.github.g00fy2.quickie.config
2 |
3 | import com.google.mlkit.vision.barcode.common.Barcode
4 |
5 | /**
6 | * Wrapper class to access the ML Kit BarcodeFormat constants.
7 | */
8 | public enum class BarcodeFormat(internal val value: Int) {
9 | FORMAT_ALL_FORMATS(Barcode.FORMAT_ALL_FORMATS),
10 | FORMAT_CODE_128(Barcode.FORMAT_CODE_128),
11 | FORMAT_CODE_39(Barcode.FORMAT_CODE_39),
12 | FORMAT_CODE_93(Barcode.FORMAT_CODE_93),
13 | FORMAT_CODABAR(Barcode.FORMAT_CODABAR),
14 | FORMAT_DATA_MATRIX(Barcode.FORMAT_DATA_MATRIX),
15 | FORMAT_EAN_13(Barcode.FORMAT_EAN_13),
16 | FORMAT_EAN_8(Barcode.FORMAT_EAN_8),
17 | FORMAT_ITF(Barcode.FORMAT_ITF),
18 | FORMAT_QR_CODE(Barcode.FORMAT_QR_CODE),
19 | FORMAT_UPC_A(Barcode.FORMAT_UPC_A),
20 | FORMAT_UPC_E(Barcode.FORMAT_UPC_E),
21 | FORMAT_PDF417(Barcode.FORMAT_PDF417),
22 | FORMAT_AZTEC(Barcode.FORMAT_AZTEC)
23 | }
--------------------------------------------------------------------------------
/quickie/src/main/kotlin/io/github/g00fy2/quickie/config/ParcelableScannerConfig.kt:
--------------------------------------------------------------------------------
1 | package io.github.g00fy2.quickie.config
2 |
3 | import android.os.Parcelable
4 | import kotlinx.parcelize.Parcelize
5 |
6 | @Parcelize
7 | internal class ParcelableScannerConfig(
8 | val formats: IntArray,
9 | val stringRes: Int,
10 | val drawableRes: Int?,
11 | val hapticFeedback: Boolean,
12 | val showTorchToggle: Boolean,
13 | val horizontalFrameRatio: Float,
14 | val useFrontCamera: Boolean,
15 | val showCloseButton: Boolean,
16 | val keepScreenOn: Boolean,
17 | ) : Parcelable
--------------------------------------------------------------------------------
/quickie/src/main/kotlin/io/github/g00fy2/quickie/config/ScannerConfig.kt:
--------------------------------------------------------------------------------
1 | package io.github.g00fy2.quickie.config
2 |
3 | import androidx.annotation.DrawableRes
4 | import androidx.annotation.StringRes
5 |
6 | /**
7 | * Builder for ScannerConfig used in ScanBarcode ActivityResultContract.
8 | */
9 | @Suppress("LongParameterList")
10 | public class ScannerConfig(
11 | internal val formats: IntArray,
12 | internal val stringRes: Int,
13 | internal val drawableRes: Int?,
14 | internal val hapticFeedback: Boolean,
15 | internal val showTorchToggle: Boolean,
16 | internal val horizontalFrameRatio: Float,
17 | internal val useFrontCamera: Boolean,
18 | internal val showCloseButton: Boolean,
19 | internal val keepScreenOn: Boolean,
20 | ) {
21 |
22 | public class Builder {
23 | private var barcodeFormats: List = listOf(BarcodeFormat.FORMAT_ALL_FORMATS)
24 | private var overlayStringRes: Int = 0
25 | private var overlayDrawableRes: Int? = 0
26 | private var hapticSuccessFeedback: Boolean = true
27 | private var showTorchToggle: Boolean = false
28 | private var horizontalFrameRatio: Float = 1f
29 | private var useFrontCamera: Boolean = false
30 | private var showCloseButton: Boolean = false
31 | private var keepScreenOn: Boolean = false
32 |
33 | /**
34 | * Set a list of interested barcode formats. List must not be empty.
35 | * Reducing the number of supported formats will make the barcode scanner faster.
36 | */
37 | public fun setBarcodeFormats(formats: List): Builder = apply { barcodeFormats = formats }
38 |
39 | /**
40 | * Set a string resource used for the scanner overlay.
41 | */
42 | public fun setOverlayStringRes(@StringRes stringRes: Int): Builder = apply { overlayStringRes = stringRes }
43 |
44 | /**
45 | * Set a drawable resource used for the scanner overlay.
46 | * If null is passed, no icon will be shown.
47 | */
48 | public fun setOverlayDrawableRes(@DrawableRes drawableRes: Int?): Builder =
49 | apply { overlayDrawableRes = drawableRes }
50 |
51 | /**
52 | * Set the horizontal overlay ratio (default is 1 / square frame).
53 | */
54 | public fun setHorizontalFrameRatio(ratio: Float): Builder = apply { horizontalFrameRatio = ratio }
55 |
56 | /**
57 | * Enable (default) or disable haptic feedback when a barcode was detected.
58 | */
59 | public fun setHapticSuccessFeedback(enable: Boolean): Builder = apply { hapticSuccessFeedback = enable }
60 |
61 | /**
62 | * Show or hide (default) torch/flashlight toggle button.
63 | */
64 | public fun setShowTorchToggle(enable: Boolean): Builder = apply { showTorchToggle = enable }
65 |
66 | /**
67 | * Use the front camera.
68 | */
69 | public fun setUseFrontCamera(enable: Boolean): Builder = apply { useFrontCamera = enable }
70 |
71 | /**
72 | * Show or hide (default) close button.
73 | */
74 | public fun setShowCloseButton(enable: Boolean): Builder = apply { showCloseButton = enable }
75 |
76 | /**
77 | * Keep the device's screen turned on as long as the scanner is visible.
78 | */
79 | public fun setKeepScreenOn(enable: Boolean): Builder = apply { keepScreenOn = enable }
80 |
81 | /**
82 | * Build the BarcodeConfig required by the ScanBarcode ActivityResultContract.
83 | */
84 | public fun build(): ScannerConfig =
85 | ScannerConfig(
86 | formats = barcodeFormats.map { it.value }.toIntArray(),
87 | stringRes = overlayStringRes,
88 | drawableRes = overlayDrawableRes,
89 | hapticFeedback = hapticSuccessFeedback,
90 | showTorchToggle = showTorchToggle,
91 | horizontalFrameRatio = horizontalFrameRatio,
92 | useFrontCamera = useFrontCamera,
93 | showCloseButton = showCloseButton,
94 | keepScreenOn = keepScreenOn,
95 | )
96 | }
97 |
98 | public companion object {
99 | /**
100 | * Kotlin friendly method to build the BarcodeConfig required by the ScanBarcode ActivityResultContract.
101 | */
102 | public fun build(func: Builder.() -> Unit): ScannerConfig = Builder().apply { func() }.build()
103 | }
104 | }
--------------------------------------------------------------------------------
/quickie/src/main/kotlin/io/github/g00fy2/quickie/content/ParcelableContent.kt:
--------------------------------------------------------------------------------
1 | package io.github.g00fy2.quickie.content
2 |
3 | import android.os.Parcelable
4 | import kotlinx.parcelize.Parcelize
5 |
6 | @Parcelize
7 | internal class WifiParcelable(val encryptionType: Int, val password: String, val ssid: String) : Parcelable
8 |
9 | @Parcelize
10 | internal class UrlBookmarkParcelable(val title: String, val url: String) : Parcelable
11 |
12 | @Parcelize
13 | internal class SmsParcelable(val message: String, val phoneNumber: String) : Parcelable
14 |
15 | @Parcelize
16 | internal class GeoPointParcelable(val lat: Double, val lng: Double) : Parcelable
17 |
18 | @Parcelize
19 | internal class ContactInfoParcelable(
20 | val addressParcelables: List,
21 | val emailParcelables: List,
22 | val nameParcelable: PersonNameParcelable,
23 | val organization: String,
24 | val phoneParcelables: List,
25 | val title: String,
26 | val urls: List
27 | ) : Parcelable
28 |
29 | @Parcelize
30 | internal class EmailParcelable(val address: String, val body: String, val subject: String, val type: Int) :
31 | Parcelable
32 |
33 | @Parcelize
34 | internal class PhoneParcelable(val number: String, val type: Int) : Parcelable
35 |
36 | @Parcelize
37 | internal class PersonNameParcelable(
38 | val first: String,
39 | val formattedName: String,
40 | val last: String,
41 | val middle: String,
42 | val prefix: String,
43 | val pronunciation: String,
44 | val suffix: String
45 | ) : Parcelable
46 |
47 | @Parcelize
48 | internal class CalendarEventParcelable(
49 | val description: String,
50 | val end: CalendarDateTimeParcelable,
51 | val location: String,
52 | val organizer: String,
53 | val start: CalendarDateTimeParcelable,
54 | val status: String,
55 | val summary: String
56 | ) : Parcelable
57 |
58 | @Parcelize
59 | internal class CalendarDateTimeParcelable(
60 | val day: Int,
61 | val hours: Int,
62 | val minutes: Int,
63 | val month: Int,
64 | val seconds: Int,
65 | val year: Int,
66 | val utc: Boolean
67 | ) : Parcelable
68 |
69 | @Parcelize
70 | internal class AddressParcelable(val addressLines: List, val type: Int) : Parcelable
--------------------------------------------------------------------------------
/quickie/src/main/kotlin/io/github/g00fy2/quickie/content/QRContent.kt:
--------------------------------------------------------------------------------
1 | package io.github.g00fy2.quickie.content
2 |
3 | @Suppress("ArrayInDataClass")
4 | public sealed class QRContent(
5 | public open val rawBytes: ByteArray?,
6 | public open val rawValue: String?,
7 | ) {
8 |
9 | /**
10 | * Plain text or unknown content QR Code type.
11 | */
12 | public data class Plain(
13 | override val rawBytes: ByteArray?,
14 | override val rawValue: String?
15 | ) : QRContent(rawBytes, rawValue)
16 |
17 | /**
18 | * Wi-Fi access point details from a 'WIFI:' or similar QR Code type.
19 | */
20 | public data class Wifi(
21 | override val rawBytes: ByteArray?,
22 | override val rawValue: String?,
23 | val encryptionType: Int,
24 | val password: String,
25 | val ssid: String
26 | ) : QRContent(rawBytes, rawValue)
27 |
28 | /**
29 | * A URL or URL bookmark from a 'MEBKM:' or similar QR Code type.
30 | */
31 | public data class Url(
32 | override val rawBytes: ByteArray?,
33 | override val rawValue: String?,
34 | val title: String,
35 | val url: String
36 | ) : QRContent(rawBytes, rawValue)
37 |
38 | /**
39 | * An SMS message from an 'SMS:' or similar QR Code type.
40 | */
41 | public data class Sms(
42 | override val rawBytes: ByteArray?,
43 | override val rawValue: String?,
44 | val message: String,
45 | val phoneNumber: String
46 | ) : QRContent(rawBytes, rawValue)
47 |
48 | /**
49 | * GPS coordinates from a 'GEO:' or similar QR Code type.
50 | */
51 | public data class GeoPoint(
52 | override val rawBytes: ByteArray?,
53 | override val rawValue: String?,
54 | val lat: Double,
55 | val lng: Double
56 | ) : QRContent(rawBytes, rawValue)
57 |
58 | /**
59 | * An email message from a 'MAILTO:' or similar QR Code type.
60 | */
61 | public data class Email(
62 | override val rawBytes: ByteArray?,
63 | override val rawValue: String?,
64 | val address: String,
65 | val body: String,
66 | val subject: String,
67 | val type: EmailType
68 | ) : QRContent(rawBytes, rawValue) {
69 | public enum class EmailType {
70 | UNKNOWN, WORK, HOME
71 | }
72 | }
73 |
74 | /**
75 | * A phone number from a 'TEL:' or similar QR Code type.
76 | */
77 | public data class Phone(
78 | override val rawBytes: ByteArray?,
79 | override val rawValue: String?,
80 | val number: String,
81 | val type: PhoneType
82 | ) : QRContent(rawBytes, rawValue) {
83 | public enum class PhoneType {
84 | UNKNOWN, WORK, HOME, FAX, MOBILE
85 | }
86 | }
87 |
88 | /**
89 | * A person's or organization's business card.
90 | */
91 | public data class ContactInfo(
92 | override val rawBytes: ByteArray?,
93 | override val rawValue: String?,
94 | val addresses: List,
95 | val emails: List,
96 | val name: PersonName,
97 | val organization: String,
98 | val phones: List,
99 | val title: String,
100 | val urls: List
101 | ) : QRContent(rawBytes, rawValue) {
102 |
103 | public data class Address(val addressLines: List, val type: AddressType) {
104 | public enum class AddressType {
105 | UNKNOWN, WORK, HOME
106 | }
107 | }
108 |
109 | public data class PersonName(
110 | val first: String,
111 | val formattedName: String,
112 | val last: String,
113 | val middle: String,
114 | val prefix: String,
115 | val pronunciation: String,
116 | val suffix: String
117 | )
118 | }
119 |
120 | /**
121 | * A calendar event extracted from a QR Code.
122 | */
123 | public data class CalendarEvent(
124 | override val rawBytes: ByteArray?,
125 | override val rawValue: String?,
126 | val description: String,
127 | val end: CalendarDateTime,
128 | val location: String,
129 | val organizer: String,
130 | val start: CalendarDateTime,
131 | val status: String,
132 | val summary: String
133 | ) : QRContent(rawBytes, rawValue) {
134 |
135 | public data class CalendarDateTime(
136 | val day: Int,
137 | val hours: Int,
138 | val minutes: Int,
139 | val month: Int,
140 | val seconds: Int,
141 | val year: Int,
142 | val utc: Boolean
143 | )
144 | }
145 | }
--------------------------------------------------------------------------------
/quickie/src/main/kotlin/io/github/g00fy2/quickie/extensions/BarcodeExtensions.kt:
--------------------------------------------------------------------------------
1 | package io.github.g00fy2.quickie.extensions
2 |
3 | import android.os.Parcelable
4 | import com.google.mlkit.vision.barcode.common.Barcode
5 | import io.github.g00fy2.quickie.content.AddressParcelable
6 | import io.github.g00fy2.quickie.content.CalendarDateTimeParcelable
7 | import io.github.g00fy2.quickie.content.CalendarEventParcelable
8 | import io.github.g00fy2.quickie.content.ContactInfoParcelable
9 | import io.github.g00fy2.quickie.content.EmailParcelable
10 | import io.github.g00fy2.quickie.content.GeoPointParcelable
11 | import io.github.g00fy2.quickie.content.PersonNameParcelable
12 | import io.github.g00fy2.quickie.content.PhoneParcelable
13 | import io.github.g00fy2.quickie.content.SmsParcelable
14 | import io.github.g00fy2.quickie.content.UrlBookmarkParcelable
15 | import io.github.g00fy2.quickie.content.WifiParcelable
16 |
17 | internal fun Barcode.toParcelableContentType(): Parcelable? {
18 | return when (valueType) {
19 | Barcode.TYPE_CONTACT_INFO -> {
20 | ContactInfoParcelable(
21 | addressParcelables = contactInfo?.addresses?.map { it.toParcelableAddress() }.orEmpty(),
22 | emailParcelables = contactInfo?.emails?.map { it.toParcelableEmail() }.orEmpty(),
23 | nameParcelable = contactInfo?.name.toParcelablePersonName(),
24 | organization = contactInfo?.organization.orEmpty(),
25 | phoneParcelables = contactInfo?.phones?.map { it.toParcelablePhone() }.orEmpty(),
26 | title = contactInfo?.title.orEmpty(),
27 | urls = contactInfo?.urls?.mapNotNull { it }.orEmpty()
28 | )
29 | }
30 | Barcode.TYPE_EMAIL -> {
31 | EmailParcelable(
32 | address = email?.address.orEmpty(),
33 | body = email?.body.orEmpty(),
34 | subject = email?.subject.orEmpty(),
35 | type = email?.type ?: 0
36 | )
37 | }
38 | Barcode.TYPE_PHONE -> PhoneParcelable(number = phone?.number.orEmpty(), type = phone?.type ?: 0)
39 | Barcode.TYPE_SMS -> SmsParcelable(message = sms?.message.orEmpty(), phoneNumber = sms?.phoneNumber.orEmpty())
40 | Barcode.TYPE_URL -> UrlBookmarkParcelable(title = url?.title.orEmpty(), url = url?.url.orEmpty())
41 | Barcode.TYPE_WIFI -> {
42 | WifiParcelable(
43 | encryptionType = wifi?.encryptionType ?: 0,
44 | password = wifi?.password.orEmpty(),
45 | ssid = wifi?.ssid.orEmpty()
46 | )
47 | }
48 | Barcode.TYPE_GEO -> GeoPointParcelable(lat = geoPoint?.lat ?: 0.0, lng = geoPoint?.lng ?: 0.0)
49 | Barcode.TYPE_CALENDAR_EVENT -> {
50 | CalendarEventParcelable(
51 | description = calendarEvent?.description.orEmpty(),
52 | end = calendarEvent?.end.toParcelableCalendarEvent(),
53 | location = calendarEvent?.location.orEmpty(),
54 | organizer = calendarEvent?.organizer.orEmpty(),
55 | start = calendarEvent?.start.toParcelableCalendarEvent(),
56 | status = calendarEvent?.status.orEmpty(),
57 | summary = calendarEvent?.summary.orEmpty()
58 | )
59 | }
60 | else -> null // TYPE_TEXT, TYPE_ISBN, TYPE_PRODUCT, TYPE_DRIVER_LICENSE, TYPE_UNKNOWN
61 | }
62 | }
63 |
64 | private fun Barcode.Address?.toParcelableAddress() =
65 | AddressParcelable(
66 | addressLines = this?.addressLines?.toList()?.mapNotNull { it }.orEmpty(),
67 | type = this?.type ?: 0
68 | )
69 |
70 | private fun Barcode.Phone?.toParcelablePhone() =
71 | PhoneParcelable(number = this?.number.orEmpty(), type = this?.type ?: 0)
72 |
73 | private fun Barcode.PersonName?.toParcelablePersonName() =
74 | PersonNameParcelable(
75 | first = this?.first.orEmpty(),
76 | formattedName = this?.formattedName.orEmpty(),
77 | last = this?.last.orEmpty(),
78 | middle = this?.middle.orEmpty(),
79 | prefix = this?.prefix.orEmpty(),
80 | pronunciation = this?.pronunciation.orEmpty(),
81 | suffix = this?.suffix.orEmpty()
82 | )
83 |
84 | private fun Barcode.Email?.toParcelableEmail() =
85 | EmailParcelable(
86 | address = this?.address.orEmpty(),
87 | body = this?.body.orEmpty(),
88 | subject = this?.subject.orEmpty(),
89 | type = this?.type ?: 0
90 | )
91 |
92 | private fun Barcode.CalendarDateTime?.toParcelableCalendarEvent() =
93 | CalendarDateTimeParcelable(
94 | day = this?.day ?: -1,
95 | hours = this?.hours ?: -1,
96 | minutes = this?.minutes ?: -1,
97 | month = this?.month ?: -1,
98 | seconds = this?.seconds ?: -1,
99 | year = this?.year ?: -1,
100 | utc = this?.isUtc ?: false
101 | )
--------------------------------------------------------------------------------
/quickie/src/main/kotlin/io/github/g00fy2/quickie/extensions/IntentExtensions.kt:
--------------------------------------------------------------------------------
1 | package io.github.g00fy2.quickie.extensions
2 |
3 | import android.content.Intent
4 | import androidx.core.content.IntentCompat
5 | import com.google.mlkit.vision.barcode.common.Barcode
6 | import io.github.g00fy2.quickie.QRScannerActivity
7 | import io.github.g00fy2.quickie.QRScannerActivity.Companion.EXTRA_RESULT_BYTES
8 | import io.github.g00fy2.quickie.QRScannerActivity.Companion.EXTRA_RESULT_EXCEPTION
9 | import io.github.g00fy2.quickie.QRScannerActivity.Companion.EXTRA_RESULT_PARCELABLE
10 | import io.github.g00fy2.quickie.QRScannerActivity.Companion.EXTRA_RESULT_VALUE
11 | import io.github.g00fy2.quickie.content.AddressParcelable
12 | import io.github.g00fy2.quickie.content.CalendarDateTimeParcelable
13 | import io.github.g00fy2.quickie.content.CalendarEventParcelable
14 | import io.github.g00fy2.quickie.content.ContactInfoParcelable
15 | import io.github.g00fy2.quickie.content.EmailParcelable
16 | import io.github.g00fy2.quickie.content.GeoPointParcelable
17 | import io.github.g00fy2.quickie.content.PersonNameParcelable
18 | import io.github.g00fy2.quickie.content.PhoneParcelable
19 | import io.github.g00fy2.quickie.content.QRContent
20 | import io.github.g00fy2.quickie.content.QRContent.CalendarEvent
21 | import io.github.g00fy2.quickie.content.QRContent.CalendarEvent.CalendarDateTime
22 | import io.github.g00fy2.quickie.content.QRContent.ContactInfo
23 | import io.github.g00fy2.quickie.content.QRContent.ContactInfo.Address
24 | import io.github.g00fy2.quickie.content.QRContent.ContactInfo.Address.AddressType
25 | import io.github.g00fy2.quickie.content.QRContent.ContactInfo.PersonName
26 | import io.github.g00fy2.quickie.content.QRContent.Email
27 | import io.github.g00fy2.quickie.content.QRContent.Email.EmailType
28 | import io.github.g00fy2.quickie.content.QRContent.GeoPoint
29 | import io.github.g00fy2.quickie.content.QRContent.Phone
30 | import io.github.g00fy2.quickie.content.QRContent.Phone.PhoneType
31 | import io.github.g00fy2.quickie.content.QRContent.Plain
32 | import io.github.g00fy2.quickie.content.QRContent.Sms
33 | import io.github.g00fy2.quickie.content.QRContent.Url
34 | import io.github.g00fy2.quickie.content.QRContent.Wifi
35 | import io.github.g00fy2.quickie.content.SmsParcelable
36 | import io.github.g00fy2.quickie.content.UrlBookmarkParcelable
37 | import io.github.g00fy2.quickie.content.WifiParcelable
38 |
39 | internal fun Intent?.toQuickieContentType(): QRContent {
40 | val rawBytes = this?.getByteArrayExtra(EXTRA_RESULT_BYTES)
41 | val rawValue = this?.getStringExtra(EXTRA_RESULT_VALUE)
42 | return this?.toQuickieContentType(rawBytes, rawValue) ?: Plain(rawBytes, rawValue)
43 | }
44 |
45 | @Suppress("LongMethod")
46 | private fun Intent.toQuickieContentType(rawBytes: ByteArray?, rawValue: String?): QRContent? {
47 | return when (extras?.getInt(QRScannerActivity.EXTRA_RESULT_TYPE, Barcode.TYPE_UNKNOWN)) {
48 | Barcode.TYPE_CONTACT_INFO -> {
49 | IntentCompat.getParcelableExtra(this, EXTRA_RESULT_PARCELABLE, ContactInfoParcelable::class.java)?.let {
50 | ContactInfo(
51 | rawBytes = rawBytes,
52 | rawValue = rawValue,
53 | addresses = it.addressParcelables.map { address -> address.toAddress() },
54 | emails = it.emailParcelables.map { mail -> mail.toEmail(rawBytes, rawValue) },
55 | name = it.nameParcelable.toPersonName(),
56 | organization = it.organization,
57 | phones = it.phoneParcelables.map { phone -> phone.toPhone(rawBytes, rawValue) },
58 | title = it.title,
59 | urls = it.urls
60 | )
61 | }
62 | }
63 | Barcode.TYPE_EMAIL -> {
64 | IntentCompat.getParcelableExtra(this, EXTRA_RESULT_PARCELABLE, EmailParcelable::class.java)?.let {
65 | Email(
66 | rawBytes = rawBytes,
67 | rawValue = rawValue,
68 | address = it.address,
69 | body = it.body,
70 | subject = it.subject,
71 | type = EmailType.entries.getOrElse(it.type) { EmailType.UNKNOWN }
72 | )
73 | }
74 | }
75 | Barcode.TYPE_PHONE -> {
76 | IntentCompat.getParcelableExtra(this, EXTRA_RESULT_PARCELABLE, PhoneParcelable::class.java)?.let {
77 | Phone(
78 | rawBytes = rawBytes,
79 | rawValue = rawValue,
80 | number = it.number,
81 | type = PhoneType.entries.getOrElse(it.type) { PhoneType.UNKNOWN }
82 | )
83 | }
84 | }
85 | Barcode.TYPE_SMS -> {
86 | IntentCompat.getParcelableExtra(this, EXTRA_RESULT_PARCELABLE, SmsParcelable::class.java)?.let {
87 | Sms(
88 | rawBytes = rawBytes,
89 | rawValue = rawValue,
90 | message = it.message,
91 | phoneNumber = it.phoneNumber
92 | )
93 | }
94 | }
95 | Barcode.TYPE_URL -> {
96 | IntentCompat.getParcelableExtra(this, EXTRA_RESULT_PARCELABLE, UrlBookmarkParcelable::class.java)?.let {
97 | Url(
98 | rawBytes = rawBytes,
99 | rawValue = rawValue,
100 | title = it.title,
101 | url = it.url
102 | )
103 | }
104 | }
105 | Barcode.TYPE_WIFI -> {
106 | IntentCompat.getParcelableExtra(this, EXTRA_RESULT_PARCELABLE, WifiParcelable::class.java)?.let {
107 | Wifi(
108 | rawBytes = rawBytes,
109 | rawValue = rawValue,
110 | encryptionType = it.encryptionType,
111 | password = it.password,
112 | ssid = it.ssid
113 | )
114 | }
115 | }
116 | Barcode.TYPE_GEO -> {
117 | IntentCompat.getParcelableExtra(this, EXTRA_RESULT_PARCELABLE, GeoPointParcelable::class.java)?.let {
118 | GeoPoint(
119 | rawBytes = rawBytes,
120 | rawValue = rawValue,
121 | lat = it.lat,
122 | lng = it.lng
123 | )
124 | }
125 | }
126 | Barcode.TYPE_CALENDAR_EVENT -> {
127 | IntentCompat.getParcelableExtra(this, EXTRA_RESULT_PARCELABLE, CalendarEventParcelable::class.java)?.let {
128 | CalendarEvent(
129 | rawBytes = rawBytes,
130 | rawValue = rawValue,
131 | description = it.description,
132 | end = it.end.toCalendarEvent(),
133 | location = it.location,
134 | organizer = it.organizer,
135 | start = it.start.toCalendarEvent(),
136 | status = it.status,
137 | summary = it.summary
138 | )
139 | }
140 | }
141 | else -> null
142 | }
143 | }
144 |
145 | internal fun Intent?.getRootException(): Exception {
146 | return this?.let { IntentCompat.getParcelableExtra(it, EXTRA_RESULT_EXCEPTION, Exception::class.java) }
147 | ?: IllegalStateException("Could retrieve root exception")
148 | }
149 |
150 | private fun PhoneParcelable.toPhone(rawBytes: ByteArray?, rawValue: String?) =
151 | Phone(
152 | rawBytes = rawBytes,
153 | rawValue = rawValue,
154 | number = number,
155 | type = PhoneType.entries.getOrElse(type) { PhoneType.UNKNOWN }
156 | )
157 |
158 | private fun EmailParcelable.toEmail(rawBytes: ByteArray?, rawValue: String?) =
159 | Email(
160 | rawBytes = rawBytes,
161 | rawValue = rawValue,
162 | address = address,
163 | body = body,
164 | subject = subject,
165 | type = EmailType.entries.getOrElse(type) { EmailType.UNKNOWN }
166 | )
167 |
168 | private fun AddressParcelable.toAddress() =
169 | Address(
170 | addressLines = addressLines,
171 | type = AddressType.entries.getOrElse(type) { AddressType.UNKNOWN }
172 | )
173 |
174 | private fun PersonNameParcelable.toPersonName() =
175 | PersonName(
176 | first = first,
177 | formattedName = formattedName,
178 | last = last,
179 | middle = middle,
180 | prefix = prefix,
181 | pronunciation = pronunciation,
182 | suffix = suffix
183 | )
184 |
185 | private fun CalendarDateTimeParcelable.toCalendarEvent() =
186 | CalendarDateTime(
187 | day = day,
188 | hours = hours,
189 | minutes = minutes,
190 | month = month,
191 | seconds = seconds,
192 | year = year,
193 | utc = utc
194 | )
--------------------------------------------------------------------------------
/quickie/src/main/kotlin/io/github/g00fy2/quickie/extensions/ScannerConfigExtensions.kt:
--------------------------------------------------------------------------------
1 | package io.github.g00fy2.quickie.extensions
2 |
3 | import io.github.g00fy2.quickie.config.ParcelableScannerConfig
4 | import io.github.g00fy2.quickie.config.ScannerConfig
5 |
6 | internal fun ScannerConfig.toParcelableConfig() =
7 | ParcelableScannerConfig(
8 | formats = formats,
9 | stringRes = stringRes,
10 | drawableRes = drawableRes,
11 | hapticFeedback = hapticFeedback,
12 | showTorchToggle = showTorchToggle,
13 | horizontalFrameRatio = horizontalFrameRatio,
14 | useFrontCamera = useFrontCamera,
15 | showCloseButton = showCloseButton,
16 | keepScreenOn = keepScreenOn,
17 | )
--------------------------------------------------------------------------------
/quickie/src/main/res/drawable/quickie_bg_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
--------------------------------------------------------------------------------
/quickie/src/main/res/drawable/quickie_ic_close.xml:
--------------------------------------------------------------------------------
1 |
2 |
18 |
23 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/drawable/quickie_ic_qrcode.xml:
--------------------------------------------------------------------------------
1 |
2 |
18 |
23 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/drawable/quickie_ic_torch.xml:
--------------------------------------------------------------------------------
1 |
2 |
18 |
23 |
26 |
29 |
--------------------------------------------------------------------------------
/quickie/src/main/res/layout/quickie_overlay_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
28 |
29 |
41 |
42 |
54 |
55 |
64 |
65 |
73 |
74 |
88 |
89 |
90 |
--------------------------------------------------------------------------------
/quickie/src/main/res/layout/quickie_scanner_activity.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
22 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-af/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "Skandeer QR-kode"
24 | "Wag asseblief…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-am/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "QR ኮድን ይቃኙ"
24 | "እባክዎ ይጠብቁ…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-ar/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "مسح رمز الاستجابة السريعة"
24 | "يرجى الانتظار…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-as/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "কিউআৰ ক’ড স্কেন কৰক"
24 | "অনুগ্রহ কৰি অপেক্ষা কৰক…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-az/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "QR kodunu skan edin"
24 | "Lütfən, gözləyin…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-be/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "Сканіраваць QR-код"
24 | "Калі ласка, пачакайце…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-bg/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "Сканиране на QR кода"
24 | "Моля, изчакайте…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-bn/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "QR কোড স্ক্যান করুন"
24 | "দয়া করে অপেক্ষা করুন…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-bs/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "Skenirajte QR kôd"
24 | "Pričekajte…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-ca/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "Escaneja un codi QR"
24 | "Espera…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-cs/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "Naskenovat QR kód"
24 | "Prosím čekejte…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-da/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "Scan QR-kode"
24 | "Vent et øjeblik…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-de/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "QR-Code scannen"
24 | "Bitte warten…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-el/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "Σάρωση κωδικού QR"
24 | "Περιμένετε…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-en/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | Scan QR code
24 | Please wait…
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-es-rUS/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "Escanear código QR"
24 | "Espera un momento…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-es/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "Escanea el código QR"
24 | "Espera…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-et/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "QR-koodi skannimine"
24 | "Oodake…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-eu/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "Eskaneatu QR kodea"
24 | "Itxaron, mesedez…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-fa/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "اسکن رمزینه پاسخسریع"
24 | "لطفاً صبر کنید…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-fi/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "Lue QR-koodi"
24 | "Odota…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-fr-rCA/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "Numériser le code QR"
24 | "Veuillez patienter…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-fr/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "Scanner le code QR"
24 | "Veuillez patienter…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-gl/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "Escanear código QR"
24 | "Agarda…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-gu/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "QR કોડ સ્કૅન કરો"
24 | "કૃપા કરીને રાહ જુઓ…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-hi/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "QR कोड स्कैन करें"
24 | "कृपया प्रतीक्षा करें…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-hr/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "Skenirajte QR kôd"
24 | "Pričekajte…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-hu/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "QR-kód beolvasása"
24 | "Kérjük, várjon…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-hy/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "Սկանավորեք QR կոդը"
24 | "Խնդրում ենք սպասել…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-in/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "Pindai kode QR"
24 | "Harap tunggu…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-is/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "Skanna QR-kóða"
24 | "Augnablik…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-it/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "Scansiona codice QR"
24 | "Attendere prego…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-iw/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "סריקת קוד QR"
24 | "אנא המתן…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-ja/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "QR コードのスキャン"
24 | "お待ちください…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-ka/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "QR კოდის სკანირება"
24 | "გთხოვთ, დაელოდოთ…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-kk/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "QR кодын сканерлеу"
24 | "Күте тұрыңыз…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-km/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "ស្កេនកូដ QR"
24 | "សូមរង់ចាំ…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-kn/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "QR ಕೋಡ್ ಸ್ಕ್ಯಾನ್ ಮಾಡಿ"
24 | "ದಯವಿಟ್ಟು ನಿರೀಕ್ಷಿಸಿ…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-ko/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "QR 코드 스캔"
24 | "잠시 기다려 주세요…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-ky/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "QR кодун скандоо"
24 | "Күтө туруңуз…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-lo/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "ສະແກນລະຫັດ QR"
24 | "ກະລຸນາລໍຖ້າ…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-lt/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "QR kodo nuskaitymas"
24 | "Palaukite…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-lv/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "QR koda skenēšana"
24 | "Lūdzu, uzgaidiet…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-mk/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "Скенирајте QR-код"
24 | "Почекајте…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-ml/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "QR കോഡ് സ്കാൻ ചെയ്യുക"
24 | "കാത്തിരിക്കുക…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-mn/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "QR кодыг скан хийх"
24 | "Түр хүлээнэ үү…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-mr/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "QR कोड स्कॅन करा"
24 | "कृपया प्रतीक्षा करा…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-ms/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "Imbas kod QR"
24 | "Sila tunggu…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-my/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "QR ကုဒ်ကို စကင်ဖတ်ပါ"
24 | "ခဏစောင့်ပါ…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-nb/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "Skann QR-koden"
24 | "Vent litt…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-ne/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "QR कोड स्क्यान गर्नुहोस्"
24 | "कृपया प्रतीक्षा गर्नुहोला…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-nl/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "QR-code scannen"
24 | "Even geduld…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-or/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "QR କୋଡ୍ ସ୍କାନ୍ କରନ୍ତୁ"
24 | "ଦୟାକରି ଅପେକ୍ଷା କରନ୍ତୁ…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-pa/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "QR ਕੋਡ ਸਕੈਨ ਕਰੋ"
24 | "ਕਿਰਪਾ ਕਰਕੇ ਠਹਿਰੋ…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-pl/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "Zeskanuj kod QR"
24 | "Zaczekaj…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-pt/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "Ler código QR"
24 | "Aguarde…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-ro/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "Scanați codul QR"
24 | "Așteptați…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-ru/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "Сканируйте QR-код"
24 | "Подождите…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-si/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "QR කේතය ස්කෑන් කරන්න"
24 | "කරුණාකර රැඳී සිටින්න…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-sk/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "Skenovanie QR kódu"
24 | "Čakajte…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-sl/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "Branje kode QR"
24 | "Počakajte…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-sq/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "Skano kodin QR"
24 | "Qëndro në pritje…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-sr/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "Скенирај QR кôд"
24 | "Сачекајте…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-sv/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "Skanna QR-kod"
24 | "Vänta…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-sw/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "Changanua msimbo wa QR"
24 | "Tafadhali subiri…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-ta/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "QR குறியீட்டை ஸ்கேன் செய்தல்"
24 | "காத்திருக்கவும்…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-te/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "QR కోడ్ని స్కాన్ చేయండి"
24 | "దయచేసి వేచి ఉండండి…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-th/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "สแกนคิวอาร์โค้ด"
24 | "โปรดรอสักครู่…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-tl/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "I-scan ang QR code"
24 | "Mangyaring maghintay…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-tr/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "QR kodu tarayın"
24 | "Lütfen bekleyin…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-uk/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "Відскануйте QR-код"
24 | "Зачекайте…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-ur/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "QR کوڈ اسکین کریں"
24 | "براہ کرم انتظار کریں…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-uz/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "QR kodni skanerlash"
24 | "Iltimos, kuting…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-v23/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-v27/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-v29/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-vi/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "Quét mã QR"
24 | "Vui lòng chờ…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-zh-rCN/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "扫描二维码"
24 | "请稍候…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-zh-rHK/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "掃瞄二維條碼"
24 | "請稍候…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-zh-rTW/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "掃描 QR 圖碼"
24 | "請稍候…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values-zu/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | "Skena ikhodi ye-QR"
24 | "Sicela ulinde…"
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #dadce0
4 | #ffffff
5 | #00000000
6 | #03dac5
7 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values/public.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 | Scan QR code
24 | Please wait…
25 |
26 |
--------------------------------------------------------------------------------
/quickie/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
--------------------------------------------------------------------------------
/quickie/src/test/kotlin/io/github/g00fy2/quickie/BarcodeFormatsTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.g00fy2.quickie
2 |
3 | import com.google.mlkit.vision.barcode.common.Barcode
4 | import io.github.g00fy2.quickie.config.BarcodeFormat
5 | import org.junit.jupiter.api.Assertions.assertEquals
6 | import org.junit.jupiter.api.Test
7 |
8 | internal class BarcodeFormatsTest {
9 |
10 | @Test
11 | fun `Ml kit barcode formats are fully mapped`() {
12 | val mlKitBarcodeFormats: Map = Barcode::class.java.declaredFields
13 | .filter { it.type == Int::class.java }
14 | .filter { it.name.startsWith("FORMAT_") }
15 | .filter { it.name != "FORMAT_UNKNOWN" }
16 | .associate { it.name to it.getInt(null) }
17 |
18 | val quickieBarcodeFormats: Map = BarcodeFormat.entries.associate { it.name to it.value }
19 |
20 | assertEquals(mlKitBarcodeFormats, quickieBarcodeFormats)
21 | }
22 | }
--------------------------------------------------------------------------------
/quickie/src/unbundled/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
--------------------------------------------------------------------------------
/quickie/src/unbundled/kotlin/io/github/g00fy2/quickie/utils/MlKitErrorHandler.kt:
--------------------------------------------------------------------------------
1 | package io.github.g00fy2.quickie.utils
2 |
3 | import com.google.android.gms.common.ConnectionResult
4 | import com.google.android.gms.common.GoogleApiAvailability
5 | import com.google.mlkit.common.MlKitException
6 | import io.github.g00fy2.quickie.QRScannerActivity
7 |
8 | internal object MlKitErrorHandler {
9 |
10 | // version 20.12.14 (as suggested https://github.com/firebase/firebase-android-sdk/issues/407#issuecomment-632288258)
11 | private const val MIN_SERVICES_VERSION = 201214 * 1000
12 | private const val REQUEST_CODE = 9000
13 |
14 | internal fun isResolvableError(activity: QRScannerActivity, exception: Exception): Boolean {
15 | if (exception is MlKitException && exception.errorCode == MlKitException.UNAVAILABLE) {
16 | // check if Google Play services is available and its version is at least MIN_SERVICES_VERSION
17 | val gmsCode = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(activity, MIN_SERVICES_VERSION)
18 |
19 | if (activity.errorDialog?.isShowing != true && gmsCode != ConnectionResult.SUCCESS &&
20 | GoogleApiAvailability.getInstance().isUserResolvableError(gmsCode)
21 | ) {
22 | activity.errorDialog = GoogleApiAvailability.getInstance().getErrorDialog(activity, gmsCode, REQUEST_CODE)
23 | }
24 | return true
25 | }
26 | return false
27 | }
28 | }
--------------------------------------------------------------------------------
/sample/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.application)
3 | alias(libs.plugins.kotlin.android)
4 | }
5 |
6 | android {
7 | namespace = "io.github.g00fy2.quickiesample"
8 | defaultConfig {
9 | applicationId = "io.github.g00fy2.quickiesample"
10 | versionCode = 1
11 | versionName = "1.0"
12 | }
13 | buildTypes {
14 | getByName("release") {
15 | isShrinkResources = true
16 | isMinifyEnabled = true
17 | proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
18 | }
19 | }
20 | splits {
21 | abi {
22 | isEnable = true
23 | reset()
24 | include("x86", "armeabi-v7a", "arm64-v8a", "x86_64")
25 | isUniversalApk = true
26 | }
27 | }
28 | flavorDimensions += "mlkit"
29 | productFlavors {
30 | create("bundled").dimension = "mlkit"
31 | create("unbundled").dimension = "mlkit"
32 | }
33 | buildFeatures {
34 | buildConfig = true
35 | viewBinding = true
36 | }
37 | lint {
38 | abortOnError = true
39 | warningsAsErrors = true
40 | checkDependencies = true
41 | disable.addAll(
42 | listOf(
43 | "RtlEnabled",
44 | "GradleDependency",
45 | "AndroidGradlePluginVersion",
46 | "OldTargetApi",
47 | )
48 | )
49 | }
50 | }
51 |
52 | dependencies {
53 | implementation(project(":quickie"))
54 |
55 | implementation(libs.google.materialDesign)
56 | }
--------------------------------------------------------------------------------
/sample/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | -renamesourcefileattribute SourceFile
2 | -keepattributes SourceFile,LineNumberTable
--------------------------------------------------------------------------------
/sample/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
13 |
14 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
32 |
33 |
35 |
36 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/sample/src/main/kotlin/io/github/g00fy2/quickiesample/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package io.github.g00fy2.quickiesample
2 |
3 | import android.content.ActivityNotFoundException
4 | import android.content.Intent
5 | import android.os.Bundle
6 | import android.widget.ArrayAdapter
7 | import android.widget.TextView
8 | import androidx.appcompat.app.AppCompatActivity
9 | import androidx.core.net.toUri
10 | import com.google.android.material.snackbar.Snackbar
11 | import io.github.g00fy2.quickie.QRResult
12 | import io.github.g00fy2.quickie.QRResult.QRError
13 | import io.github.g00fy2.quickie.QRResult.QRMissingPermission
14 | import io.github.g00fy2.quickie.QRResult.QRSuccess
15 | import io.github.g00fy2.quickie.QRResult.QRUserCanceled
16 | import io.github.g00fy2.quickie.ScanCustomCode
17 | import io.github.g00fy2.quickie.ScanQRCode
18 | import io.github.g00fy2.quickie.config.BarcodeFormat
19 | import io.github.g00fy2.quickie.config.ScannerConfig
20 | import io.github.g00fy2.quickie.content.QRContent
21 | import io.github.g00fy2.quickiesample.databinding.ActivityMainBinding
22 |
23 | class MainActivity : AppCompatActivity() {
24 |
25 | private lateinit var binding: ActivityMainBinding
26 | private var selectedBarcodeFormat = BarcodeFormat.FORMAT_ALL_FORMATS
27 |
28 | private val scanQrCode = registerForActivityResult(ScanQRCode(), ::showSnackbar)
29 | private val scanCustomCode = registerForActivityResult(ScanCustomCode(), ::showSnackbar)
30 |
31 | override fun onCreate(savedInstanceState: Bundle?) {
32 | super.onCreate(savedInstanceState)
33 | binding = ActivityMainBinding.inflate(layoutInflater)
34 | setContentView(binding.root)
35 | setBarcodeFormatDropdown()
36 |
37 | binding.qrScannerButton.setOnClickListener {
38 | scanQrCode.launch(null)
39 | }
40 |
41 | binding.customScannerButton.setOnClickListener {
42 | scanCustomCode.launch(
43 | ScannerConfig.build {
44 | setBarcodeFormats(listOf(selectedBarcodeFormat)) // set interested barcode formats
45 | setOverlayStringRes(R.string.scan_barcode) // string resource used for the scanner overlay
46 | setOverlayDrawableRes(R.drawable.ic_scan_barcode) // drawable resource used for the scanner overlay
47 | setHapticSuccessFeedback(false) // enable (default) or disable haptic feedback when a barcode was detected
48 | setShowTorchToggle(true) // show or hide (default) torch/flashlight toggle button
49 | setShowCloseButton(true) // show or hide (default) close button
50 | setHorizontalFrameRatio(2.2f) // set the horizontal overlay ratio (default is 1 / square frame)
51 | setUseFrontCamera(false) // use the front camera
52 | setKeepScreenOn(true) // keep the device's screen turned on
53 | }
54 | )
55 | }
56 |
57 | if (intent.extras?.getBoolean(OPEN_SCANNER) == true) scanQrCode.launch(null)
58 | }
59 |
60 | private fun showSnackbar(result: QRResult) {
61 | val text = when (result) {
62 | is QRSuccess -> {
63 | result.content.rawValue
64 | // decoding with default UTF-8 charset when rawValue is null will not result in meaningful output, demo purpose
65 | ?: result.content.rawBytes?.let { String(it) }.orEmpty()
66 | }
67 | QRUserCanceled -> "User canceled"
68 | QRMissingPermission -> "Missing permission"
69 | is QRError -> "${result.exception.javaClass.simpleName}: ${result.exception.localizedMessage}"
70 | }
71 |
72 | Snackbar.make(binding.root, text, Snackbar.LENGTH_INDEFINITE).apply {
73 | view.findViewById(com.google.android.material.R.id.snackbar_text)?.run {
74 | maxLines = 5
75 | setTextIsSelectable(true)
76 | }
77 | if (result is QRSuccess) {
78 | val content = result.content
79 | if (content is QRContent.Url) {
80 | setAction(R.string.open_action) { openUrl(content.url) }
81 | return@apply
82 | }
83 | }
84 | setAction(R.string.ok_action) { }
85 | }.show()
86 | }
87 |
88 | private fun openUrl(url: String) {
89 | try {
90 | startActivity(Intent(Intent.ACTION_VIEW, url.toUri()))
91 | } catch (ignored: ActivityNotFoundException) {
92 | // no Activity found to run the given Intent
93 | }
94 | }
95 |
96 | private fun setBarcodeFormatDropdown() {
97 | ArrayAdapter(this, android.R.layout.simple_spinner_dropdown_item, BarcodeFormat.entries.map { it.name }).let {
98 | binding.barcodeFormatsAutoCompleteTextView.setAdapter(it)
99 | binding.barcodeFormatsAutoCompleteTextView.setText(it.getItem(it.getPosition(selectedBarcodeFormat.name)), false)
100 | }
101 | binding.barcodeFormatsAutoCompleteTextView.setOnItemClickListener { _, _, position, _ ->
102 | selectedBarcodeFormat = BarcodeFormat.entries[position]
103 | }
104 | }
105 |
106 | companion object {
107 | const val OPEN_SCANNER = "open_scanner"
108 | }
109 | }
--------------------------------------------------------------------------------
/sample/src/main/kotlin/io/github/g00fy2/quickiesample/SampleApp.kt:
--------------------------------------------------------------------------------
1 | package io.github.g00fy2.quickiesample
2 |
3 | import android.app.Application
4 | import android.os.StrictMode
5 |
6 | class SampleApp : Application() {
7 |
8 | override fun onCreate() {
9 | if (BuildConfig.DEBUG) {
10 | StrictMode.setThreadPolicy(
11 | StrictMode.ThreadPolicy.Builder()
12 | .detectAll()
13 | .penaltyLog()
14 | .build()
15 | )
16 | StrictMode.setVmPolicy(
17 | StrictMode.VmPolicy.Builder()
18 | .detectAll()
19 | .penaltyLog()
20 | .build()
21 | )
22 | }
23 |
24 | super.onCreate()
25 | }
26 | }
--------------------------------------------------------------------------------
/sample/src/main/kotlin/io/github/g00fy2/quickiesample/quicksettingstile/QuickieTileService.kt:
--------------------------------------------------------------------------------
1 | package io.github.g00fy2.quickiesample.quicksettingstile
2 |
3 | import android.app.PendingIntent
4 | import android.content.ComponentName
5 | import android.content.Intent
6 | import android.os.Build
7 | import android.os.IBinder
8 | import android.service.quicksettings.Tile
9 | import android.service.quicksettings.TileService
10 | import androidx.annotation.RequiresApi
11 | import androidx.core.service.quicksettings.PendingIntentActivityWrapper
12 | import androidx.core.service.quicksettings.TileServiceCompat
13 | import io.github.g00fy2.quickiesample.MainActivity
14 |
15 | // optional service to allow launching the sample app from the quick settings
16 | @RequiresApi(Build.VERSION_CODES.N)
17 | class QuickieTileService : TileService() {
18 |
19 | override fun onBind(intent: Intent?): IBinder? {
20 | requestListeningState(this, ComponentName(this, QuickieTileService::class.java))
21 | return super.onBind(intent)
22 | }
23 |
24 | override fun onStartListening() {
25 | super.onStartListening()
26 | qsTile?.run {
27 | state = Tile.STATE_INACTIVE
28 | updateTile()
29 | }
30 | }
31 |
32 | override fun onClick() {
33 | super.onClick()
34 |
35 | val intent = Intent(this, MainActivity::class.java).apply {
36 | addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
37 | putExtra(MainActivity.OPEN_SCANNER, true)
38 | }
39 |
40 | TileServiceCompat.startActivityAndCollapse(
41 | this,
42 | PendingIntentActivityWrapper(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT, true)
43 | )
44 | }
45 | }
--------------------------------------------------------------------------------
/sample/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
6 |
11 |
14 |
17 |
20 |
23 |
26 |
29 |
32 |
35 |
38 |
41 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable/ic_qs_qrcode.xml:
--------------------------------------------------------------------------------
1 |
2 |
18 |
23 |
26 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable/ic_scan_barcode.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
20 |
21 |
31 |
32 |
39 |
40 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/G00fY2/quickie/5b9cb9538c10617eae8ce2d7416c191e83860f02/sample/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/G00fY2/quickie/5b9cb9538c10617eae8ce2d7416c191e83860f02/sample/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/G00fY2/quickie/5b9cb9538c10617eae8ce2d7416c191e83860f02/sample/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/G00fY2/quickie/5b9cb9538c10617eae8ce2d7416c191e83860f02/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/G00fY2/quickie/5b9cb9538c10617eae8ce2d7416c191e83860f02/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/raw/keep.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3DDC84
4 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Quickie Sample
3 |
4 | QR-Code Scanner
5 | Custom Scanner
6 | OK
7 | Open
8 | Scan barcode
9 | BarcodeFormat
10 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | include(":quickie", ":sample")
2 |
3 | dependencyResolutionManagement {
4 | @Suppress("UnstableApiUsage")
5 | repositories {
6 | google()
7 | mavenCentral()
8 | }
9 | }
10 |
11 | pluginManagement {
12 | repositories {
13 | google()
14 | mavenCentral()
15 | gradlePluginPortal()
16 | }
17 | }
--------------------------------------------------------------------------------