├── .gitignore
├── CONTRIBUTORS.md
├── FEATURES.md
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle.kts
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── icon.png
│ ├── java
│ └── com
│ │ └── corphish
│ │ └── quicktools
│ │ ├── TextToolsApplication.kt
│ │ ├── activities
│ │ ├── EvalActivity.kt
│ │ ├── FindAndReplaceActivity.kt
│ │ ├── MainActivity.kt
│ │ ├── NoUIActivity.kt
│ │ ├── OnBoardingActivity.kt
│ │ ├── OptionsActivity.kt
│ │ ├── RouterActivity.kt
│ │ ├── SaveTextActivity.kt
│ │ ├── SettingsActivity.kt
│ │ ├── SimulationActivity.kt
│ │ ├── TextCountActivity.kt
│ │ ├── TransformActivity.kt
│ │ ├── TryOutActivity.kt
│ │ └── WUPActivity.kt
│ │ ├── data
│ │ ├── Constants.kt
│ │ └── Result.kt
│ │ ├── extensions
│ │ └── StringExtensions.kt
│ │ ├── features
│ │ └── Feature.kt
│ │ ├── modules
│ │ └── AppModule.kt
│ │ ├── repository
│ │ ├── ContextMenuOptionsRepository.kt
│ │ ├── ContextMenuOptionsRepositoryImpl.kt
│ │ ├── SettingsRepository.kt
│ │ ├── TextRepository.kt
│ │ └── TextRepositoryImpl.kt
│ │ ├── text
│ │ ├── TextReplacementAction.kt
│ │ ├── TextReplacementManager.kt
│ │ └── TextTransformer.kt
│ │ ├── ui
│ │ ├── common
│ │ │ ├── AppBars.kt
│ │ │ ├── Buttons.kt
│ │ │ └── ListDialog.kt
│ │ └── theme
│ │ │ ├── Color.kt
│ │ │ ├── Theme.kt
│ │ │ └── Type.kt
│ │ ├── utils
│ │ └── ClipboardHelper.kt
│ │ └── viewmodels
│ │ ├── EvalViewModel.kt
│ │ ├── MainViewModel.kt
│ │ ├── OnBoardingViewModel.kt
│ │ ├── OptionsViewModel.kt
│ │ ├── SaveTextViewModel.kt
│ │ ├── SettingsViewModel.kt
│ │ ├── TextCountViewModel.kt
│ │ ├── TextReplacementViewModel.kt
│ │ ├── TextTransformViewModel.kt
│ │ └── WUPViewModel.kt
│ └── res
│ ├── drawable
│ ├── ic_copy.xml
│ ├── ic_done.xml
│ ├── ic_done_all.xml
│ ├── ic_find_and_replace.xml
│ ├── ic_launcher_foreground.xml
│ ├── ic_next.xml
│ ├── ic_numbers.xml
│ ├── ic_open_in_new.xml
│ ├── ic_previous.xml
│ ├── ic_redo.xml
│ ├── ic_reset.xml
│ ├── ic_save.xml
│ ├── ic_text_count.xml
│ ├── ic_text_transform.xml
│ ├── ic_undo.xml
│ └── ic_whatsapp.xml
│ ├── mipmap-anydpi
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.webp
│ └── ic_launcher_round.webp
│ ├── mipmap-mdpi
│ ├── ic_launcher.webp
│ └── ic_launcher_round.webp
│ ├── mipmap-xhdpi
│ ├── ic_launcher.webp
│ └── ic_launcher_round.webp
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.webp
│ └── ic_launcher_round.webp
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.webp
│ └── ic_launcher_round.webp
│ ├── resources.properties
│ ├── values-es
│ └── strings.xml
│ ├── values-ko
│ └── strings.xml
│ ├── values-pt-rBR
│ └── strings.xml
│ ├── values
│ ├── colors.xml
│ ├── font_certs.xml
│ ├── ic_launcher_background.xml
│ ├── strings.xml
│ └── themes.xml
│ └── xml
│ ├── backup_rules.xml
│ └── data_extraction_rules.xml
├── assets
├── eval.gif
├── find_replace.gif
├── save.gif
├── single_options.png
├── text_count.gif
├── text_transform.gif
├── wup_1.gif
└── wup_2.gif
├── build.gradle.kts
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── metadata
└── en-US
│ ├── full_description.txt
│ ├── images
│ ├── icon.png
│ └── phoneScreenshots
│ │ ├── 1.png
│ │ ├── 2.png
│ │ ├── 3.png
│ │ ├── 4.png
│ │ ├── 5.png
│ │ ├── 6.png
│ │ ├── 7.png
│ │ └── 8.png
│ ├── short_description.txt
│ └── title.txt
└── settings.gradle.kts
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | .idea/
5 | .DS_Store
6 | /build
7 | /captures
8 | .externalNativeBuild
9 | .cxx
10 | local.properties
11 | *.jks
12 | *.apk
13 | app/release/
14 | app/singleOption/
15 | app/multipleOptions/
--------------------------------------------------------------------------------
/CONTRIBUTORS.md:
--------------------------------------------------------------------------------
1 | # Contributors
2 | Shout out to the people who have contributed to this project. You can find the repo contributors [here](https://github.com/corphish/TextTools/graphs/contributors).
3 |
4 | ### Idea contributors
5 | People who have suggested cool ideas that have been added to the app are mentioned below.
6 |
7 | - Raffael (raffael7777@proton.me)
8 | - [boognish-rising](https://github.com/boognish-rising)
9 | - [brimwats](https://github.com/brimwats)
10 | - [AjayRahul1](https://github.com/AjayRahul1)
11 | - [Solovertical](https://github.com/Solovertical)
12 | - [sming](https://github.com/sming)
13 | - [synalice](https://github.com/synalice)
14 |
15 | ### Translators
16 | People who have suggested translations for various languages that have been added in the app are mentioned below. Big thanks to them and if you want to contribute translations, you can do it [here](https://crowdin.com/project/text-tools).
17 |
18 | - [elimirien](https://crowdin.com/profile/elimirien)
19 | - [Pvnk](https://crowdin.com/profile/pvnk)
20 | - [Marcio Andrade](https://crowdin.com/profile/marciozomb13)
--------------------------------------------------------------------------------
/FEATURES.md:
--------------------------------------------------------------------------------
1 | # Features
2 | All features shown here were recorded in the `Multiple Option`. UI will slightly vary depending on the variant, but all the functionality and its UX will remain the same.
3 |
4 | ### Text unsaved numbers in WhatsApp
5 | Often we find ourselves needing to text/send file/share location to someone for temporary purposes (if you ask me, I face this a lot in situations where I go to a printing centre where they ask me to share the file that I want to print over WhatsApp, so I have to save their contact, refresh my WhatsApp contact list and then send them). Having a context menu option to directly open a WhatsApp chat from their phone number would save a lot of time and keep your phonebook clean, right?
6 |
7 | 
8 | 
9 |
10 | ### Evaluate mathematical expressions inline
11 | We may often find ourselves to mathematically calculate something while texting someone (probably while making plans). Having an option to perform math calculations in line without needing to open calculator could save time.
12 |
13 | 
14 |
15 | ### Transform text
16 | Transform the selected text inline. Supported transformations include changing the case, wrapping it with text and many more.
17 |
18 | 
19 |
20 | ### Text count
21 | Get detailed analysis about character count, word count, letter count and more for the selected text.
22 |
23 | 
24 |
25 | ### Save text
26 | Lets you save the selected text into a file so that it can be accessed later.
27 |
28 | 
29 |
30 | ### Find & Replace
31 | Quickly find certain text and replace them with text of your choice within the selected text.
32 |
33 | 
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Text Tools
2 |
3 | 
4 | []()
5 |
6 | Collection of useful text related tools that can be accessed from the context menu that appears on text selection. The feature list will keep on growing. Let me know if you have any ideas.
7 |
8 | [
](https://f-droid.org/packages/com.corphish.quicktools)
11 |
12 | ### Current Features
13 | - Text unsaved numbers in WhatsApp
14 | - Evaluate mathematical expressions inline
15 | - Transform text
16 | - Text count
17 | - Save text to a file
18 | - Find & Replace in text
19 |
20 | For detailed description of each feature and their demo, please check the [features page](FEATURES.md).
21 |
22 | ### App usage
23 | The above features are provided as context menu options on selected text. Text Tools also lets the user choose how he/she wants the context menu to appear. In other words, users have total control on what appears in the context menu.
24 |
25 | - Users can choose between "Multiple options" and "Single option".
26 | - "Multiple options" will present all the options at once in the context menu.
27 | - "Single option" will present a single option (as "Text Tools") which will then reveal all the options.
28 | - If "Single option" is selected, only relevant options (depending on whether the source text is editable or non-editable) will be shown.
29 | - In all cases, users can choose which options they would want to see in the context. If a certain feature is not useful to an user, he/she can deselect it in the app and that option will not appear in the context menu (for "Multiple options") or in the dialog that presents the supported options (in case of "Single option").
30 |
31 | ### Development methods
32 | - Developed in Jetpack compose using the latest libraries.
33 | - Use Material design for UI, with color scheme derived using Material You.
34 | - Follows MVVM architecture using ViewModels with StateFlows wherever possible.
35 | - Uses Dependency Injection provided by the Hilt library.
36 |
37 | ### Installation warning
38 | While installing the app (you have to side load), Google Play may block the installation saying it has never seen the app before. While it is true (from Google Play POV), you can proceed with installing the app. If you have doubts about the app being malicious, you are free to verify the same from the source code (it would not be open-source if the app was malicious in the first place, right?).
39 |
40 | ### Issues
41 | Issues may be observed as in the contextual menu options may not be available inside certain apps, or the functionalities may not work as expected. In such cases, kindly raise issue in this Github repo.
42 |
43 | ### Translations
44 | If you would like to contribute to this project by translating the various text shown in the app, you may do so by heading over to the [Crowdin Project](https://crowdin.com/project/text-tools).
45 |
46 | ### Support
47 | If you like my work and would like to support me, you can help to [buy me un gelato](https://www.paypal.com/paypalme/corphish).
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.android.application")
3 | id("org.jetbrains.kotlin.android")
4 | id("kotlin-kapt")
5 | id("com.google.dagger.hilt.android")
6 | id("org.jetbrains.kotlin.plugin.compose")
7 | }
8 |
9 | val variantMultipleOptions = "multipleOptions"
10 | val variantSingleOption = "singleOption"
11 |
12 | android {
13 | namespace = "com.corphish.quicktools"
14 | compileSdk = 35
15 |
16 | defaultConfig {
17 | applicationId = "com.corphish.quicktools"
18 | minSdk = 30
19 | targetSdk = 35
20 | versionCode = 22
21 | versionName = "2.1.0"
22 |
23 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
24 | vectorDrawables {
25 | useSupportLibrary = true
26 | }
27 | }
28 |
29 | buildTypes {
30 | release {
31 | isMinifyEnabled = true
32 | proguardFiles(
33 | getDefaultProguardFile("proguard-android-optimize.txt"),
34 | "proguard-rules.pro"
35 | )
36 | }
37 | }
38 |
39 | compileOptions {
40 | sourceCompatibility = JavaVersion.VERSION_17
41 | targetCompatibility = JavaVersion.VERSION_17
42 | }
43 | kotlinOptions {
44 | jvmTarget = "17"
45 | }
46 | kotlin {
47 | jvmToolchain(17)
48 | }
49 | buildFeatures {
50 | compose = true
51 | buildConfig = true
52 | }
53 | composeOptions {
54 | kotlinCompilerExtensionVersion = "1.4.3"
55 | }
56 | packaging {
57 | resources {
58 | excludes += "/META-INF/{AL2.0,LGPL2.1}"
59 | }
60 | }
61 |
62 | dependenciesInfo {
63 | // Disables dependency metadata when building APKs.
64 | includeInApk = false
65 | // Disables dependency metadata when building Android App Bundles.
66 | includeInBundle = false
67 | }
68 |
69 | androidResources {
70 | generateLocaleConfig = true
71 | }
72 | }
73 |
74 | dependencies {
75 | // Core
76 | implementation("androidx.core:core-ktx:1.16.0")
77 | implementation("androidx.appcompat:appcompat:1.7.0")
78 | implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.9.0")
79 | implementation("androidx.preference:preference-ktx:1.2.1")
80 |
81 | // Material 3 expressive
82 | implementation("androidx.compose.material3:material3-android:1.4.0-alpha15")
83 | implementation("androidx.compose.material:material-icons-extended:1.7.8")
84 |
85 | // Compose
86 | implementation(platform("androidx.compose:compose-bom:2025.05.01"))
87 | implementation("androidx.activity:activity-compose:1.10.1")
88 | implementation("androidx.compose.ui:ui")
89 | implementation("androidx.compose.ui:ui-graphics")
90 | implementation("androidx.compose.ui:ui-tooling-preview")
91 | implementation("androidx.compose.ui:ui-text-google-fonts")
92 | implementation("androidx.lifecycle:lifecycle-viewmodel-compose")
93 | implementation ("androidx.constraintlayout:constraintlayout-compose:1.1.1")
94 |
95 | // To evaluate mathematical expressions
96 | implementation("net.objecthunter:exp4j:0.4.8")
97 |
98 | // Dagger
99 | implementation("com.google.dagger:hilt-android:2.51.1")
100 | implementation("androidx.compose.material3:material3")
101 | kapt("com.google.dagger:hilt-compiler:2.51.1")
102 |
103 |
104 | // Testing
105 | androidTestImplementation(platform("androidx.compose:compose-bom:2025.05.01"))
106 | androidTestImplementation("androidx.compose.ui:ui-test-junit4")
107 |
108 | // Compose debug
109 | debugImplementation("androidx.compose.ui:ui-tooling")
110 | debugImplementation("androidx.compose.ui:ui-test-manifest")
111 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
18 |
23 |
28 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
64 |
69 |
75 |
76 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
107 |
108 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
139 |
140 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
171 |
172 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
203 |
204 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
234 |
235 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
264 |
265 |
266 |
--------------------------------------------------------------------------------
/app/src/main/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/corphish/TextTools/743de19a692652e3f6d050e8d978180be0e66fc6/app/src/main/icon.png
--------------------------------------------------------------------------------
/app/src/main/java/com/corphish/quicktools/TextToolsApplication.kt:
--------------------------------------------------------------------------------
1 | package com.corphish.quicktools
2 |
3 | import android.app.Application
4 | import dagger.hilt.android.HiltAndroidApp
5 |
6 | @HiltAndroidApp
7 | class TextToolsApplication: Application()
--------------------------------------------------------------------------------
/app/src/main/java/com/corphish/quicktools/activities/EvalActivity.kt:
--------------------------------------------------------------------------------
1 | package com.corphish.quicktools.activities
2 |
3 | import android.content.ClipData
4 | import android.content.ClipboardManager
5 | import android.content.Intent
6 | import android.widget.Toast
7 | import androidx.activity.compose.setContent
8 | import androidx.activity.viewModels
9 | import androidx.compose.foundation.layout.Arrangement
10 | import androidx.compose.foundation.layout.Row
11 | import androidx.compose.foundation.layout.fillMaxWidth
12 | import androidx.compose.foundation.layout.padding
13 | import androidx.compose.material3.Checkbox
14 | import androidx.compose.material3.Text
15 | import androidx.compose.runtime.Composable
16 | import androidx.compose.runtime.mutableStateOf
17 | import androidx.compose.runtime.remember
18 | import androidx.compose.ui.Alignment
19 | import androidx.compose.ui.Modifier
20 | import androidx.compose.ui.res.stringResource
21 | import androidx.compose.ui.unit.dp
22 | import androidx.lifecycle.lifecycleScope
23 | import com.corphish.quicktools.R
24 | import com.corphish.quicktools.data.Constants
25 | import com.corphish.quicktools.data.Result
26 | import com.corphish.quicktools.ui.common.ListDialog
27 | import com.corphish.quicktools.ui.theme.QuickToolsTheme
28 | import com.corphish.quicktools.ui.theme.TypographyV2
29 | import com.corphish.quicktools.utils.ClipboardHelper
30 | import com.corphish.quicktools.viewmodels.EvalViewModel
31 | import com.corphish.quicktools.viewmodels.EvalViewModel.Companion.EVAL_RESULT_APPEND
32 | import com.corphish.quicktools.viewmodels.EvalViewModel.Companion.EVAL_RESULT_COPY_TO_CLIPBOARD
33 | import com.corphish.quicktools.viewmodels.EvalViewModel.Companion.EVAL_RESULT_MODE_ASK_NEXT_TIME
34 | import com.corphish.quicktools.viewmodels.EvalViewModel.Companion.EVAL_RESULT_REPLACE
35 | import com.corphish.quicktools.viewmodels.EvaluateResult
36 | import dagger.hilt.android.AndroidEntryPoint
37 | import kotlinx.coroutines.launch
38 |
39 | @AndroidEntryPoint
40 | class EvalActivity : NoUIActivity() {
41 | // Result generator
42 | private val _replaceResultGenerator: (String, String) -> String = { _, result -> result }
43 | private val _appendResultGenerator: (String, String) -> String =
44 | { input, result -> "$input = $result" }
45 |
46 | private val evalViewModel: EvalViewModel by viewModels()
47 |
48 | override fun handleIntent(intent: Intent): Boolean {
49 | if (intent.hasExtra(Intent.EXTRA_PROCESS_TEXT)) {
50 | val readonly = intent.getBooleanExtra(Intent.EXTRA_PROCESS_TEXT_READONLY, false)
51 | val forceCopy = intent.getBooleanExtra(Constants.INTENT_FORCE_COPY, false)
52 |
53 | if (readonly) {
54 | // We are only interested in editable text
55 | Toast.makeText(this, R.string.editable_error, Toast.LENGTH_LONG).show()
56 | return true
57 | }
58 |
59 | val text = intent.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT).toString()
60 |
61 | lifecycleScope.launch {
62 | evalViewModel.shouldForceCopy(forceCopy)
63 | }
64 |
65 | lifecycleScope.launch {
66 | evalViewModel.evalMode.collect { mode ->
67 | when (mode) {
68 | EVAL_RESULT_MODE_ASK_NEXT_TIME -> {
69 | setContent {
70 | UserSelectionDialog(
71 | onSelected = { selectedMode, rememberChoice ->
72 | evalViewModel.denoteModeSelectionByUser(selectedMode)
73 | evalViewModel.denoteUserRememberChoice(rememberChoice)
74 | evalViewModel.evaluate(text)
75 | },
76 | onDismiss = { finish() }
77 | )
78 | }
79 | }
80 |
81 | else -> {
82 | evalViewModel.evaluate(text)
83 | }
84 | }
85 | }
86 | }
87 |
88 | lifecycleScope.launch {
89 | evalViewModel.evalResult.collect { result: Result ->
90 | when (result) {
91 | is Result.Success -> {
92 | val str = result.value.resultString
93 | when (val mode = result.value.finalMode) {
94 | EVAL_RESULT_REPLACE, EVAL_RESULT_APPEND -> {
95 | handleEvaluationIntent(text, str, mode)
96 | }
97 |
98 | EVAL_RESULT_COPY_TO_CLIPBOARD -> {
99 | ClipboardHelper.copyToClipboard(this@EvalActivity, str)
100 | }
101 | }
102 |
103 | finish()
104 | }
105 |
106 | Result.Error -> {
107 | Toast.makeText(
108 | this@EvalActivity,
109 | R.string.invalid_expression,
110 | Toast.LENGTH_LONG
111 | ).show()
112 |
113 | finish()
114 | }
115 |
116 | Result.Initial -> {
117 | // Do nothing
118 | }
119 | }
120 | }
121 | }
122 | }
123 |
124 | return false
125 | }
126 |
127 | private fun handleEvaluationIntent(text: String, result: String, mode: Int) {
128 | val resultGenerator: (String, String) -> String =
129 | if (mode == EVAL_RESULT_APPEND) _appendResultGenerator else _replaceResultGenerator
130 |
131 | val resultIntent = Intent()
132 | resultIntent.putExtra(Intent.EXTRA_PROCESS_TEXT, resultGenerator(text, result))
133 | setResult(RESULT_OK, resultIntent)
134 | }
135 | }
136 |
137 | @Composable
138 | fun UserSelectionDialog(
139 | onSelected: (Int, Boolean) -> Unit,
140 | onDismiss: () -> Unit
141 | ) {
142 | val rememberUserChoiceEnabled = remember {
143 | mutableStateOf(false)
144 | }
145 |
146 | QuickToolsTheme {
147 | ListDialog(
148 | title = stringResource(id = R.string.eval_title_small),
149 | message = stringResource(id = R.string.eval_result_desc),
150 | list = listOf(
151 | R.string.eval_mode_result,
152 | R.string.eval_mode_append,
153 | R.string.eval_mode_copy
154 | ),
155 | supportBack = false,
156 | dismissible = true,
157 | stringSelector = {
158 | stringResource(id = it)
159 | },
160 | additionalContent = {
161 | Row(
162 | modifier = Modifier
163 | .padding(top = 8.dp)
164 | .fillMaxWidth(),
165 | verticalAlignment = Alignment.CenterVertically,
166 | horizontalArrangement = Arrangement.Center
167 | ) {
168 |
169 | Checkbox(
170 | checked = rememberUserChoiceEnabled.value,
171 | onCheckedChange = { rememberUserChoiceEnabled.value = it },
172 | enabled = true,
173 | )
174 |
175 | Text(
176 | text = stringResource(id = R.string.remember_this_choice),
177 | style = TypographyV2.bodyMedium
178 | )
179 | }
180 | },
181 | iconSelector = { R.drawable.ic_numbers },
182 | onItemSelected = {
183 | when (it) {
184 | 0 -> {
185 | onSelected(EVAL_RESULT_REPLACE, rememberUserChoiceEnabled.value)
186 | }
187 |
188 | 1 -> {
189 | onSelected(EVAL_RESULT_APPEND, rememberUserChoiceEnabled.value)
190 | }
191 |
192 | 2 -> {
193 | onSelected(EVAL_RESULT_COPY_TO_CLIPBOARD, rememberUserChoiceEnabled.value)
194 | }
195 | }
196 | }) { onDismiss() }
197 | }
198 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/corphish/quicktools/activities/NoUIActivity.kt:
--------------------------------------------------------------------------------
1 | package com.corphish.quicktools.activities
2 |
3 | import android.content.Intent
4 | import android.os.Bundle
5 | import androidx.appcompat.app.AppCompatActivity
6 |
7 | /**
8 | * Base activity class for other feature classes.
9 | */
10 | abstract class NoUIActivity: AppCompatActivity() {
11 |
12 | /**
13 | * Main intent handling logic goes here that must be overridden by other feature classes.
14 | * @return Boolean value indicating whether the activity should be finished after handling the
15 | * intent or not.
16 | */
17 | abstract fun handleIntent(intent: Intent): Boolean
18 |
19 | override fun onCreate(savedInstanceState: Bundle?) {
20 | super.onCreate(savedInstanceState)
21 | handleIntentWrapper(intent)
22 | }
23 |
24 | override fun onNewIntent(intent: Intent) {
25 | super.onNewIntent(intent)
26 | handleIntentWrapper(intent)
27 | }
28 |
29 | private fun handleIntentWrapper(intent: Intent) {
30 | if (handleIntent(intent))
31 | finish()
32 | }
33 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/corphish/quicktools/activities/OnBoardingActivity.kt:
--------------------------------------------------------------------------------
1 | package com.corphish.quicktools.activities
2 |
3 | import android.content.Intent
4 | import android.os.Bundle
5 | import androidx.activity.ComponentActivity
6 | import androidx.activity.compose.setContent
7 | import androidx.activity.enableEdgeToEdge
8 | import androidx.activity.viewModels
9 | import androidx.annotation.StringRes
10 | import androidx.compose.foundation.background
11 | import androidx.compose.foundation.layout.Arrangement
12 | import androidx.compose.foundation.layout.Box
13 | import androidx.compose.foundation.layout.Column
14 | import androidx.compose.foundation.layout.PaddingValues
15 | import androidx.compose.foundation.layout.calculateEndPadding
16 | import androidx.compose.foundation.layout.calculateStartPadding
17 | import androidx.compose.foundation.layout.fillMaxSize
18 | import androidx.compose.foundation.layout.fillMaxWidth
19 | import androidx.compose.foundation.layout.padding
20 | import androidx.compose.foundation.layout.size
21 | import androidx.compose.foundation.shape.CircleShape
22 | import androidx.compose.material.icons.Icons
23 | import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
24 | import androidx.compose.material.icons.filled.Done
25 | import androidx.compose.material.icons.filled.Settings
26 | import androidx.compose.material3.Card
27 | import androidx.compose.material3.CardColors
28 | import androidx.compose.material3.Icon
29 | import androidx.compose.material3.IconButton
30 | import androidx.compose.material3.MaterialTheme
31 | import androidx.compose.material3.Scaffold
32 | import androidx.compose.material3.Text
33 | import androidx.compose.runtime.Composable
34 | import androidx.compose.runtime.getValue
35 | import androidx.compose.runtime.mutableIntStateOf
36 | import androidx.compose.runtime.mutableStateOf
37 | import androidx.compose.runtime.remember
38 | import androidx.compose.runtime.setValue
39 | import androidx.compose.ui.Alignment
40 | import androidx.compose.ui.Modifier
41 | import androidx.compose.ui.draw.clip
42 | import androidx.compose.ui.res.painterResource
43 | import androidx.compose.ui.res.stringResource
44 | import androidx.compose.ui.text.font.FontWeight
45 | import androidx.compose.ui.text.style.TextAlign
46 | import androidx.compose.ui.tooling.preview.Preview
47 | import androidx.compose.ui.unit.LayoutDirection
48 | import androidx.compose.ui.unit.dp
49 | import com.corphish.quicktools.R
50 | import com.corphish.quicktools.repository.AppMode
51 | import com.corphish.quicktools.ui.theme.BrandFontFamily
52 | import com.corphish.quicktools.ui.theme.QuickToolsTheme
53 | import com.corphish.quicktools.viewmodels.OnBoardingViewModel
54 | import dagger.hilt.android.AndroidEntryPoint
55 |
56 | @AndroidEntryPoint
57 | class OnBoardingActivity : ComponentActivity() {
58 | private val viewModel: OnBoardingViewModel by viewModels()
59 |
60 | private fun switchToMain() {
61 | startActivity(Intent(this, MainActivity::class.java))
62 | }
63 |
64 | override fun onCreate(savedInstanceState: Bundle?) {
65 | super.onCreate(savedInstanceState)
66 | enableEdgeToEdge()
67 | setContent {
68 | QuickToolsTheme {
69 | Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
70 | OnBoarding(
71 | paddingValues = innerPadding,
72 | onAppModeSelected = {
73 | viewModel.setAppMode(it)
74 | },
75 | onFinish = {
76 | viewModel.setOnBoardingDone(true)
77 | switchToMain()
78 | finish()
79 | }
80 | )
81 | }
82 | }
83 | }
84 | }
85 | }
86 |
87 | @Composable
88 | fun OnBoarding(
89 | paddingValues: PaddingValues = PaddingValues(),
90 | onAppModeSelected: (AppMode) -> Unit = {},
91 | onFinish: () -> Unit = {},
92 | ) {
93 | var page by remember { mutableIntStateOf(0) }
94 |
95 | when (page) {
96 | 0 -> InitialPage(
97 | paddingValues = paddingValues
98 | ) { page = 1 }
99 | 1 -> ModeSelectionScreen(
100 | paddingValues = paddingValues,
101 | onModeSelected = { onAppModeSelected(it) },
102 | onFinish = { onFinish() }
103 | )
104 | }
105 | }
106 |
107 | @Composable
108 | fun InitialPage(
109 | paddingValues: PaddingValues = PaddingValues(),
110 | onNextPressed: () -> Unit = {}
111 | ) {
112 | Column(
113 | modifier = Modifier.fillMaxSize(),
114 | horizontalAlignment = Alignment.CenterHorizontally,
115 | verticalArrangement = Arrangement.Center
116 | ) {
117 | Icon(
118 | painter = painterResource(R.drawable.ic_launcher_foreground),
119 | contentDescription = "",
120 | tint = MaterialTheme.colorScheme.primary,
121 | modifier = Modifier.size(256.dp)
122 | )
123 |
124 | Text(
125 | text = stringResource(R.string.app_name),
126 | style = MaterialTheme.typography.headlineMedium,
127 | fontFamily = BrandFontFamily,
128 | fontWeight = FontWeight.Bold,
129 | color = MaterialTheme.colorScheme.primary,
130 | )
131 |
132 | Text(
133 | text = stringResource(R.string.app_desc_short),
134 | style = MaterialTheme.typography.bodyMedium,
135 | modifier = Modifier.padding(horizontal = 16.dp),
136 | textAlign = TextAlign.Center
137 | )
138 |
139 | IconButton(
140 | onClick = { onNextPressed() },
141 | modifier = Modifier
142 | .padding(vertical = 32.dp)
143 | .clip(CircleShape)
144 | .size(64.dp)
145 | .background(MaterialTheme.colorScheme.primary)
146 | ) {
147 | Icon(
148 | Icons.AutoMirrored.Filled.KeyboardArrowRight,
149 | contentDescription = "",
150 | tint = MaterialTheme.colorScheme.onPrimary
151 | )
152 | }
153 | }
154 | }
155 |
156 | @Composable
157 | fun ModeSelectionScreen(
158 | paddingValues: PaddingValues = PaddingValues(),
159 | onModeSelected: (AppMode) -> Unit = {},
160 | onFinish: () -> Unit = {},
161 | ) {
162 | var selectedMode by remember { mutableStateOf(AppMode.SINGLE) }
163 | Column(
164 | modifier = Modifier
165 | .padding(
166 | start = paddingValues.calculateStartPadding(LayoutDirection.Ltr).plus(16.dp),
167 | end = paddingValues.calculateEndPadding(LayoutDirection.Ltr).plus(16.dp),
168 | top = paddingValues.calculateTopPadding().plus(16.dp),
169 | bottom = paddingValues.calculateBottomPadding().plus(16.dp)
170 | )
171 | .fillMaxSize()
172 | ) {
173 | Icon(
174 | Icons.Filled.Settings,
175 | contentDescription = "",
176 | tint = MaterialTheme.colorScheme.primary,
177 | modifier = Modifier.size(64.dp)
178 | )
179 |
180 | Text(
181 | text = stringResource(R.string.mode_select_title),
182 | style = MaterialTheme.typography.headlineMedium,
183 | fontFamily = BrandFontFamily,
184 | fontWeight = FontWeight.Bold,
185 | color = MaterialTheme.colorScheme.primary,
186 | )
187 |
188 | Text(
189 | text = stringResource(R.string.mode_select_desc),
190 | style = MaterialTheme.typography.bodyMedium,
191 | modifier = Modifier.padding(bottom = 32.dp)
192 | )
193 |
194 | ModeCard(
195 | title = R.string.mode_single_title,
196 | desc = R.string.mode_single_desc,
197 | isSelected = selectedMode == AppMode.SINGLE,
198 | onClick = {
199 | selectedMode = AppMode.SINGLE
200 | onModeSelected(AppMode.SINGLE)
201 | }
202 | )
203 |
204 | ModeCard(
205 | title = R.string.mode_multi_title,
206 | desc = R.string.mode_multi_desc,
207 | isSelected = selectedMode == AppMode.MULTI,
208 | onClick = {
209 | selectedMode = AppMode.MULTI
210 | onModeSelected(AppMode.MULTI)
211 | }
212 | )
213 |
214 | Box(modifier = Modifier.weight(1f))
215 |
216 | IconButton(
217 | onClick = { onFinish() },
218 | modifier = Modifier
219 | .padding(vertical = 16.dp)
220 | .clip(CircleShape)
221 | .size(64.dp)
222 | .background(MaterialTheme.colorScheme.primary)
223 | .align(Alignment.CenterHorizontally)
224 | ) {
225 | Icon(
226 | Icons.Default.Done,
227 | contentDescription = "",
228 | tint = MaterialTheme.colorScheme.onPrimary
229 | )
230 | }
231 | }
232 | }
233 |
234 | @Composable
235 | fun ModeCard(
236 | @StringRes title: Int,
237 | @StringRes desc: Int,
238 | isSelected: Boolean = false,
239 | onClick: () -> Unit = {},
240 | ) {
241 | Card(
242 | onClick = { onClick() },
243 | modifier = Modifier
244 | .fillMaxWidth()
245 | .padding(top = 4.dp),
246 | colors = CardColors(
247 | containerColor = if (isSelected) MaterialTheme.colorScheme.primaryContainer else MaterialTheme.colorScheme.surfaceContainer,
248 | contentColor = if (isSelected) MaterialTheme.colorScheme.onPrimaryContainer else MaterialTheme.colorScheme.onSurface,
249 | disabledContainerColor = MaterialTheme.colorScheme.surface,
250 | disabledContentColor = MaterialTheme.colorScheme.onSurface
251 | )
252 | ) {
253 | Column(modifier = Modifier.padding(16.dp)) {
254 | Text(
255 | stringResource(title),
256 | fontWeight = FontWeight.Bold,
257 | fontFamily = BrandFontFamily
258 | )
259 | Text(
260 | stringResource(desc),
261 | style = MaterialTheme.typography.bodySmall,
262 | )
263 | }
264 | }
265 | }
266 |
267 | @Preview(showBackground = true)
268 | @Composable
269 | fun OnBoardingPreview() {
270 | QuickToolsTheme {
271 | OnBoarding()
272 | }
273 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/corphish/quicktools/activities/OptionsActivity.kt:
--------------------------------------------------------------------------------
1 | package com.corphish.quicktools.activities
2 |
3 | import android.app.Activity
4 | import android.content.Intent
5 | import androidx.activity.compose.setContent
6 | import androidx.activity.result.contract.ActivityResultContracts
7 | import androidx.activity.viewModels
8 | import androidx.annotation.DrawableRes
9 | import androidx.annotation.StringRes
10 | import androidx.compose.runtime.collectAsState
11 | import androidx.compose.runtime.getValue
12 | import androidx.compose.runtime.mutableStateOf
13 | import androidx.compose.runtime.remember
14 | import androidx.compose.ui.res.stringResource
15 | import com.corphish.quicktools.R
16 | import com.corphish.quicktools.data.Constants
17 | import com.corphish.quicktools.extensions.truncate
18 | import com.corphish.quicktools.repository.FeatureIds
19 | import com.corphish.quicktools.ui.common.ListDialog
20 | import com.corphish.quicktools.ui.theme.QuickToolsTheme
21 | import com.corphish.quicktools.viewmodels.OptionsViewModel
22 | import dagger.hilt.android.AndroidEntryPoint
23 |
24 | @AndroidEntryPoint
25 | class OptionsActivity : NoUIActivity() {
26 | private val viewModel: OptionsViewModel by viewModels()
27 |
28 | private val _options = listOf(
29 | Option(FeatureIds.WHATSAPP, R.string.whatsapp, R.drawable.ic_whatsapp, WUPActivity::class.java, false),
30 | Option(FeatureIds.EVAL, R.string.eval_title_small, R.drawable.ic_numbers, EvalActivity::class.java, true),
31 | Option(FeatureIds.TRANSFORM, R.string.transform_long, R.drawable.ic_text_transform, TransformActivity::class.java, true),
32 | Option(FeatureIds.TEXT_COUNT, R.string.text_count, R.drawable.ic_text_count, TextCountActivity::class.java, false),
33 | Option(FeatureIds.SAVE_TEXT, R.string.save_text_title, R.drawable.ic_save, SaveTextActivity::class.java, false),
34 | Option(FeatureIds.FIND_AND_REPLACE, R.string.title_activity_find_and_replace, R.drawable.ic_find_and_replace, FindAndReplaceActivity::class.java, true)
35 | )
36 |
37 | private val router = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
38 | if (it.resultCode == RESULT_OK) {
39 | setResult(RESULT_OK, it.data)
40 | }
41 |
42 | finish()
43 | }
44 |
45 | override fun handleIntent(intent: Intent): Boolean {
46 | if (intent.hasExtra(Intent.EXTRA_PROCESS_TEXT)) {
47 | val text = intent.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT).toString()
48 | val readonly = intent.getBooleanExtra(Intent.EXTRA_PROCESS_TEXT_READONLY, false)
49 | val forceCopy = intent.getBooleanExtra(Constants.INTENT_FORCE_COPY, false)
50 | setContent {
51 | QuickToolsTheme {
52 | val enabledFeatures by viewModel.enabledFeatures.collectAsState()
53 | val optionDialog = remember {
54 | mutableStateOf(true)
55 | }
56 |
57 | if (optionDialog.value) {
58 | val list = _options.filter { enabledFeatures.contains(it.id) && (!readonly || !it.requiresEditable) }
59 | ListDialog(
60 | title = stringResource(id = R.string.app_name),
61 | message = stringResource(id = R.string.app_single_op, text.truncate()),
62 | list = list,
63 | stringSelector = { stringResource(id = it.optionResourceId) },
64 | iconSelector = { it.icon },
65 | onItemSelected = {
66 | val routeIntent = Intent(this, list[it].handlingClass)
67 | routeIntent.putExtra(Intent.EXTRA_PROCESS_TEXT, text)
68 | routeIntent.putExtra(Intent.EXTRA_PROCESS_TEXT_READONLY, readonly)
69 | routeIntent.putExtra(Constants.INTENT_FORCE_COPY, forceCopy)
70 | router.launch(routeIntent)
71 | optionDialog.value = false
72 | }) {
73 | optionDialog.value = false
74 | finish()
75 | }
76 | }
77 | }
78 | }
79 | return false
80 | }
81 |
82 | return true
83 | }
84 |
85 | /**
86 | * Denotes an option.
87 | */
88 | data class Option(
89 | /**
90 | * Id of the option.
91 | */
92 | val id: FeatureIds,
93 |
94 | /**
95 | * String resource id of the option.
96 | */
97 | @StringRes val optionResourceId: Int,
98 |
99 | /**
100 | * Icon of this option
101 | */
102 | @DrawableRes val icon: Int,
103 |
104 | /**
105 | * Activity that handles the option function.
106 | */
107 | val handlingClass: Class,
108 |
109 | /**
110 | * Boolean indicating whether this option requires editable
111 | * text input.
112 | */
113 | val requiresEditable: Boolean
114 | )
115 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/corphish/quicktools/activities/RouterActivity.kt:
--------------------------------------------------------------------------------
1 | package com.corphish.quicktools.activities
2 |
3 | import android.content.Intent
4 | import android.os.Bundle
5 | import androidx.activity.viewModels
6 | import androidx.appcompat.app.AppCompatActivity
7 | import androidx.lifecycle.lifecycleScope
8 | import com.corphish.quicktools.viewmodels.OnBoardingViewModel
9 | import dagger.hilt.android.AndroidEntryPoint
10 | import kotlinx.coroutines.launch
11 |
12 | @AndroidEntryPoint
13 | class RouterActivity : AppCompatActivity() {
14 | private val viewModel: OnBoardingViewModel by viewModels()
15 |
16 | override fun onCreate(savedInstanceState: Bundle?) {
17 | super.onCreate(savedInstanceState)
18 |
19 | lifecycleScope.launch {
20 | viewModel.onBoardingDone.collect { onBoardingDone ->
21 | val intent = if (onBoardingDone) {
22 | Intent(this@RouterActivity, MainActivity::class.java)
23 | } else {
24 | Intent(this@RouterActivity, OnBoardingActivity::class.java)
25 | }
26 |
27 | startActivity(intent)
28 | finish()
29 | }
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/corphish/quicktools/activities/SaveTextActivity.kt:
--------------------------------------------------------------------------------
1 | package com.corphish.quicktools.activities
2 |
3 | import android.app.Activity
4 | import android.content.Intent
5 | import android.os.Build
6 | import android.widget.Toast
7 | import androidx.activity.compose.setContent
8 | import androidx.activity.result.contract.ActivityResultContracts
9 | import androidx.activity.viewModels
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.ui.res.stringResource
12 | import androidx.lifecycle.lifecycleScope
13 | import com.corphish.quicktools.R
14 | import com.corphish.quicktools.data.Result
15 | import com.corphish.quicktools.ui.common.ListDialog
16 | import com.corphish.quicktools.ui.theme.QuickToolsTheme
17 | import com.corphish.quicktools.viewmodels.SaveTextViewModel
18 | import dagger.hilt.android.AndroidEntryPoint
19 | import kotlinx.coroutines.launch
20 |
21 | @AndroidEntryPoint
22 | class SaveTextActivity : NoUIActivity() {
23 |
24 | private val viewmodel: SaveTextViewModel by viewModels()
25 |
26 | override fun handleIntent(intent: Intent): Boolean {
27 | val text = intent.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT).toString()
28 |
29 | setContent {
30 | QuickToolsTheme {
31 | SaveTextSelectionDialog(
32 | onProcessNoteSelected = { processNote(text) },
33 | onProcessFileSelected = { processTextFile(text) },
34 | onDismiss = { finish() }
35 | )
36 | }
37 | }
38 |
39 | lifecycleScope.launch {
40 | viewmodel.saveTextStatus.collect {
41 | when (it) {
42 | is Result.Initial -> {
43 | // Do nothing
44 | }
45 | is Result.Error -> {
46 | Toast.makeText(this@SaveTextActivity, R.string.save_text_error, Toast.LENGTH_LONG).show()
47 | finish()
48 | }
49 | is Result.Success -> {
50 | Toast.makeText(this@SaveTextActivity, R.string.save_text_success, Toast.LENGTH_LONG).show()
51 | finish()
52 | }
53 | }
54 | }
55 | }
56 |
57 | // Do not finish or else the dialog will go away
58 | return false
59 | }
60 |
61 | private fun processNote(text: String) {
62 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
63 | try {
64 | val intent = Intent(Intent.ACTION_CREATE_NOTE)
65 | intent.putExtra(Intent.EXTRA_TEXT, text)
66 | startActivity(intent)
67 | } catch (e: Exception) {
68 | Toast.makeText(this, R.string.no_note_taking_apps, Toast.LENGTH_LONG).show()
69 | }
70 | } else {
71 | // Ideally the option should not be visible
72 | Toast.makeText(this, R.string.note_taking_not_supported, Toast.LENGTH_LONG).show()
73 | }
74 |
75 | finish()
76 | }
77 |
78 | // Looks like it is not possible to transmit the selected text via the intent
79 | // and receive it in the result. So we save it as instance variable.
80 | // P.S - Passing via intent.putExtra() and then retrieving in intent.getExtra() does not work.
81 | private var mSelectedText = ""
82 | private val mResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
83 | if (result.resultCode == Activity.RESULT_OK) {
84 | // There are no request codes
85 | val data: Intent? = result.data
86 | if (data != null) {
87 | viewmodel.saveText(data.data, mSelectedText)
88 | mSelectedText = ""
89 | }
90 | }
91 | }
92 |
93 | private fun processTextFile(text: String) {
94 | val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
95 | intent.setType("text/plain")
96 | mSelectedText = text
97 | mResultLauncher.launch(intent)
98 | }
99 | }
100 |
101 | @Composable
102 | fun SaveTextSelectionDialog(
103 | onProcessNoteSelected: () -> Unit,
104 | onProcessFileSelected: () -> Unit,
105 | onDismiss: () -> Unit,
106 | ) {
107 | // A surface container using the 'background' color from the theme
108 | ListDialog(
109 | title = stringResource(id = R.string.save_text_title),
110 | message = stringResource(id = R.string.save_text_message),
111 | list = listOf(
112 | R.string.save_option_txt,
113 | // R.string.save_option_note, Note taking supported in keep only in A14 but not working in keep
114 | ),
115 | onItemSelected = {
116 | when (it) {
117 | 0 -> {
118 | // Text
119 | onProcessFileSelected()
120 | }
121 |
122 | 1 -> {
123 | // Note
124 | onProcessNoteSelected()
125 | }
126 | }
127 | },
128 | stringSelector = { stringResource(id = it) },
129 | iconSelector = { R.drawable.ic_save }
130 | ) {
131 | onDismiss()
132 | }
133 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/corphish/quicktools/activities/SimulationActivity.kt:
--------------------------------------------------------------------------------
1 | package com.corphish.quicktools.activities
2 |
3 | import android.content.Intent
4 | import android.os.Bundle
5 | import androidx.activity.ComponentActivity
6 | import androidx.activity.compose.setContent
7 | import androidx.activity.enableEdgeToEdge
8 | import androidx.compose.foundation.background
9 | import androidx.compose.foundation.layout.calculateEndPadding
10 | import androidx.compose.foundation.layout.calculateStartPadding
11 | import androidx.compose.foundation.layout.fillMaxSize
12 | import androidx.compose.foundation.layout.padding
13 | import androidx.compose.foundation.layout.size
14 | import androidx.compose.foundation.shape.CircleShape
15 | import androidx.compose.material.icons.Icons
16 | import androidx.compose.material.icons.automirrored.filled.ArrowRight
17 | import androidx.compose.material3.ExperimentalMaterial3Api
18 | import androidx.compose.material3.Icon
19 | import androidx.compose.material3.IconButton
20 | import androidx.compose.material3.MaterialTheme
21 | import androidx.compose.material3.OutlinedTextField
22 | import androidx.compose.material3.Scaffold
23 | import androidx.compose.material3.Text
24 | import androidx.compose.runtime.Composable
25 | import androidx.compose.runtime.getValue
26 | import androidx.compose.runtime.mutableStateOf
27 | import androidx.compose.runtime.remember
28 | import androidx.compose.runtime.setValue
29 | import androidx.compose.ui.Modifier
30 | import androidx.compose.ui.draw.clip
31 | import androidx.compose.ui.res.stringResource
32 | import androidx.compose.ui.text.style.TextAlign
33 | import androidx.compose.ui.tooling.preview.Preview
34 | import androidx.compose.ui.unit.LayoutDirection
35 | import androidx.compose.ui.unit.dp
36 | import androidx.constraintlayout.compose.ConstraintLayout
37 | import androidx.constraintlayout.compose.Dimension
38 | import com.corphish.quicktools.R
39 | import com.corphish.quicktools.data.Constants
40 | import com.corphish.quicktools.ui.common.CustomTopAppBar
41 | import com.corphish.quicktools.ui.theme.QuickToolsTheme
42 |
43 | class SimulationActivity : ComponentActivity() {
44 | @OptIn(ExperimentalMaterial3Api::class)
45 | override fun onCreate(savedInstanceState: Bundle?) {
46 | super.onCreate(savedInstanceState)
47 | enableEdgeToEdge()
48 | setContent {
49 | QuickToolsTheme {
50 | Scaffold(
51 | modifier = Modifier.fillMaxSize(),
52 | topBar = {
53 | CustomTopAppBar(
54 | id = R.string.simulate,
55 | onNavigationClick = { finish() })
56 | }
57 | ) { innerPadding ->
58 | Simulation(
59 | modifier = Modifier.padding(innerPadding),
60 | onSimulate = {
61 | val intent = Intent(Intent.ACTION_PROCESS_TEXT).apply {
62 | type = "text/plain"
63 | setClass(this@SimulationActivity, OptionsActivity::class.java)
64 | putExtra(Intent.EXTRA_PROCESS_TEXT, it)
65 | putExtra(Intent.EXTRA_PROCESS_TEXT_READONLY, false)
66 | putExtra(Constants.INTENT_FORCE_COPY, true)
67 | }
68 |
69 | startActivity(intent)
70 | }
71 | )
72 | }
73 | }
74 | }
75 | }
76 | }
77 |
78 | @Composable
79 | fun Simulation(modifier: Modifier = Modifier, onSimulate: (String) -> Unit = {}) {
80 | var inputText by remember { mutableStateOf("") }
81 | ConstraintLayout(
82 | modifier = modifier.fillMaxSize()
83 | ) {
84 | val (userInput, infoText, launchSimulation) = createRefs()
85 |
86 | OutlinedTextField(
87 | value = inputText,
88 | onValueChange = {
89 | inputText = it
90 | },
91 | modifier = Modifier.constrainAs(userInput) {
92 | top.linkTo(parent.top, margin = 16.dp)
93 | bottom.linkTo(infoText.top, margin = 8.dp)
94 | start.linkTo(parent.start, margin = 8.dp)
95 | end.linkTo(parent.end, margin = 8.dp)
96 | height = Dimension.fillToConstraints
97 | width = Dimension.fillToConstraints
98 | },
99 | label = { Text(text = stringResource(id = R.string.input)) },
100 | )
101 |
102 | Text(
103 | stringResource(id = R.string.simulate_details),
104 | style = MaterialTheme.typography.bodySmall,
105 | textAlign = TextAlign.Center,
106 | modifier = Modifier.constrainAs(infoText) {
107 | top.linkTo(userInput.bottom, margin = 8.dp)
108 | bottom.linkTo(launchSimulation.top, margin = 8.dp)
109 | start.linkTo(parent.start, margin = 8.dp)
110 | end.linkTo(parent.end, margin = 8.dp)
111 | width = Dimension.fillToConstraints
112 | }
113 | )
114 |
115 | IconButton(
116 | onClick = { onSimulate(inputText) },
117 | modifier = Modifier
118 | .constrainAs(launchSimulation) {
119 | top.linkTo(infoText.bottom, margin = 8.dp)
120 | start.linkTo(parent.start)
121 | end.linkTo(parent.end)
122 | bottom.linkTo(parent.bottom, margin = 16.dp)
123 | width = Dimension.wrapContent
124 | height = Dimension.wrapContent
125 | }
126 | .clip(CircleShape)
127 | .size(64.dp)
128 | .background(MaterialTheme.colorScheme.primary)
129 | ) {
130 | Icon(
131 | Icons.AutoMirrored.Filled.ArrowRight,
132 | contentDescription = null,
133 | tint = MaterialTheme.colorScheme.onPrimary,
134 | modifier = Modifier.size(48.dp)
135 | )
136 | }
137 | }
138 | }
139 |
140 | @Preview(showBackground = true)
141 | @Composable
142 | fun GreetingPreview3() {
143 | QuickToolsTheme {
144 | Simulation()
145 | }
146 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/corphish/quicktools/activities/TryOutActivity.kt:
--------------------------------------------------------------------------------
1 | package com.corphish.quicktools.activities
2 |
3 | import android.app.Activity
4 | import android.os.Bundle
5 | import androidx.activity.ComponentActivity
6 | import androidx.activity.compose.setContent
7 | import androidx.compose.foundation.layout.Arrangement
8 | import androidx.compose.foundation.layout.Box
9 | import androidx.compose.foundation.layout.Column
10 | import androidx.compose.foundation.layout.Row
11 | import androidx.compose.foundation.layout.fillMaxSize
12 | import androidx.compose.foundation.layout.fillMaxWidth
13 | import androidx.compose.foundation.layout.padding
14 | import androidx.compose.material.icons.Icons
15 | import androidx.compose.material.icons.filled.CheckCircle
16 | import androidx.compose.material.icons.filled.Warning
17 | import androidx.compose.material3.AlertDialog
18 | import androidx.compose.material3.Button
19 | import androidx.compose.material3.FilledTonalButton
20 | import androidx.compose.material3.Icon
21 | import androidx.compose.material3.MaterialTheme
22 | import androidx.compose.material3.Surface
23 | import androidx.compose.material3.Text
24 | import androidx.compose.material3.TextButton
25 | import androidx.compose.material3.TextField
26 | import androidx.compose.runtime.Composable
27 | import androidx.compose.runtime.getValue
28 | import androidx.compose.runtime.mutableStateOf
29 | import androidx.compose.runtime.remember
30 | import androidx.compose.runtime.setValue
31 | import androidx.compose.ui.Alignment
32 | import androidx.compose.ui.Modifier
33 | import androidx.compose.ui.graphics.vector.ImageVector
34 | import androidx.compose.ui.platform.LocalContext
35 | import androidx.compose.ui.platform.LocalUriHandler
36 | import androidx.compose.ui.res.stringResource
37 | import androidx.compose.ui.tooling.preview.Preview
38 | import androidx.compose.ui.unit.dp
39 | import com.corphish.quicktools.R
40 | import com.corphish.quicktools.data.Constants
41 | import com.corphish.quicktools.ui.theme.QuickToolsTheme
42 | import com.corphish.quicktools.ui.theme.Typography
43 |
44 | /**
45 | * Try outs are disabled as of now because it looks like there is a known issue with
46 | * Jetpack Compose textfields which does not show context menu.
47 | */
48 | class TryOutActivity : ComponentActivity() {
49 | override fun onCreate(savedInstanceState: Bundle?) {
50 | super.onCreate(savedInstanceState)
51 | val mode = intent.getStringExtra(TRY_OUT_FLOW) ?: TRY_OUT_FLOW_WUP
52 | setContent {
53 | QuickToolsTheme {
54 | // A surface container using the 'background' color from the theme
55 | Surface(
56 | modifier = Modifier.fillMaxSize(),
57 | color = MaterialTheme.colorScheme.background
58 | ) {
59 | TryOut(mode)
60 | }
61 | }
62 | }
63 | }
64 |
65 | companion object {
66 | // Flows
67 | const val TRY_OUT_FLOW_WUP = "wup"
68 | const val TRY_OUT_FLOW_EVAL = "eval"
69 | const val TRY_OUT_FLOW = "flow"
70 | }
71 | }
72 |
73 | @Composable
74 | fun TryOut(flow: String) {
75 | val openSuccessDialog = remember { mutableStateOf(false) }
76 | val openErrorDialog = remember { mutableStateOf(false) }
77 | var text by remember { mutableStateOf("") }
78 | val activity = (LocalContext.current as? Activity)
79 | val uriHandler = LocalUriHandler.current
80 |
81 | Column(
82 | modifier = Modifier.padding(16.dp)
83 | ) {
84 | Text(
85 | text = stringResource(id = R.string.app_name),
86 | style = Typography.headlineLarge,
87 | color = MaterialTheme.colorScheme.primary,
88 | modifier = Modifier.padding(bottom = 16.dp)
89 | )
90 | Text(
91 | text = stringResource(
92 | id = when (flow) {
93 | TryOutActivity.TRY_OUT_FLOW_WUP -> R.string.try_wup
94 | TryOutActivity.TRY_OUT_FLOW_EVAL -> R.string.try_eval
95 | else -> R.string.trial_error
96 | }
97 | ),
98 | style = Typography.bodyMedium
99 | )
100 | Box(modifier = Modifier.fillMaxSize()) {
101 | Column(
102 | modifier = Modifier.align(Alignment.BottomCenter)
103 | ) {
104 | TextField(
105 | modifier = Modifier.fillMaxWidth(),
106 | value = text,
107 | onValueChange = { text = it },
108 | label = { Text("Enter text here") }
109 | )
110 |
111 | Row(
112 | modifier = Modifier
113 | .padding(top = 16.dp)
114 | .fillMaxWidth(),
115 | horizontalArrangement = Arrangement.SpaceBetween
116 | ) {
117 | FilledTonalButton(
118 | onClick = { openErrorDialog.value = true },
119 | modifier = Modifier
120 | .fillMaxWidth()
121 | .weight(1f)
122 | .padding(end = 4.dp)
123 | ) { Text(text = stringResource(id = R.string.trial_error)) }
124 | Button(
125 | onClick = { openSuccessDialog.value = true },
126 | modifier = Modifier
127 | .fillMaxWidth()
128 | .weight(1f)
129 | .padding(start = 4.dp)
130 | ) { Text(text = stringResource(id = R.string.trial_success)) }
131 | }
132 | }
133 | }
134 | }
135 |
136 | when {
137 | openSuccessDialog.value -> {
138 | MessageDialog(
139 | hasDismissButton = false,
140 | onDismissRequest = { /* Not needed */ },
141 | onConfirmation = {
142 | openSuccessDialog.value = false
143 | activity?.finish()
144 | },
145 | dialogTitle = stringResource(id = R.string.trial_success),
146 | dialogText = stringResource(id = R.string.trial_success_msg),
147 | icon = Icons.Default.CheckCircle
148 | )
149 | }
150 |
151 | openErrorDialog.value -> {
152 | MessageDialog(
153 | hasDismissButton = true,
154 | onDismissRequest = {
155 | openErrorDialog.value = false
156 | activity?.finish()
157 | },
158 | onConfirmation = {
159 | openErrorDialog.value = false
160 | activity?.finish()
161 | uriHandler.openUri(Constants.ISSUES_PAGE_LINK)
162 | },
163 | dialogTitle = stringResource(id = R.string.trial_error),
164 | dialogText = stringResource(id = R.string.trial_error_msg),
165 | icon = Icons.Default.Warning
166 | )
167 | }
168 | }
169 | }
170 |
171 | @Composable
172 | fun MessageDialog(
173 | hasDismissButton: Boolean,
174 | onDismissRequest: () -> Unit,
175 | onConfirmation: () -> Unit,
176 | dialogTitle: String,
177 | dialogText: String,
178 | icon: ImageVector,
179 | ) {
180 | AlertDialog(
181 | icon = {
182 | Icon(icon, contentDescription = "Example Icon")
183 | },
184 | title = {
185 | Text(text = dialogTitle)
186 | },
187 | text = {
188 | Text(text = dialogText)
189 | },
190 | onDismissRequest = {
191 | onDismissRequest()
192 | },
193 | confirmButton = {
194 | TextButton(
195 | onClick = {
196 | onConfirmation()
197 | }
198 | ) {
199 | Text(stringResource(id = android.R.string.ok))
200 | }
201 | },
202 | dismissButton = {
203 | if (hasDismissButton) {
204 | TextButton(
205 | onClick = {
206 | onDismissRequest()
207 | }
208 | ) {
209 | Text(stringResource(id = android.R.string.cancel))
210 | }
211 | }
212 | }
213 | )
214 | }
215 |
216 | @Preview(showBackground = true)
217 | @Composable
218 | fun GreetingPreview2() {
219 | QuickToolsTheme {
220 | TryOut(TryOutActivity.TRY_OUT_FLOW_WUP)
221 | }
222 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/corphish/quicktools/activities/WUPActivity.kt:
--------------------------------------------------------------------------------
1 | package com.corphish.quicktools.activities
2 |
3 | import android.content.Intent
4 | import android.net.Uri
5 | import android.widget.Toast
6 | import androidx.activity.viewModels
7 | import androidx.lifecycle.lifecycleScope
8 | import com.corphish.quicktools.R
9 | import com.corphish.quicktools.data.Constants
10 | import com.corphish.quicktools.data.Result
11 | import com.corphish.quicktools.viewmodels.WUPViewModel
12 | import dagger.hilt.android.AndroidEntryPoint
13 | import kotlinx.coroutines.launch
14 |
15 | /**
16 | * WUP (WhatsApp Unknown Phone number) activity handles messaging to
17 | * unknown phone numbers in whatsapp without saving them as a contact by
18 | * opening it in wa.me/.
19 | */
20 | @AndroidEntryPoint
21 | class WUPActivity : NoUIActivity() {
22 | private val wupViewModel: WUPViewModel by viewModels()
23 |
24 | override fun handleIntent(intent: Intent): Boolean {
25 | if (intent.hasExtra(Intent.EXTRA_PROCESS_TEXT)) {
26 | val text = intent.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT)?.toString() ?: ""
27 |
28 | lifecycleScope.launch {
29 | wupViewModel.processedPhoneNumber.collect {
30 | when (it) {
31 | is Result.Success -> {
32 | openInWeb(phoneNumber = it.value)
33 | }
34 |
35 | is Result.Error -> {
36 | Toast.makeText(this@WUPActivity, R.string.invalid_phone_number, Toast.LENGTH_LONG).show()
37 | }
38 |
39 | is Result.Initial -> {
40 | // Do nothing
41 | }
42 | }
43 | }
44 | }
45 |
46 | wupViewModel.determinePhoneNumber(text)
47 | }
48 |
49 | return true
50 | }
51 |
52 | private fun openInWeb(phoneNumber: String) {
53 | val url = "${Constants.WHATSAPP_API_LINK}$phoneNumber"
54 | val browserIntent = Intent(Intent.ACTION_VIEW)
55 | browserIntent.data = Uri.parse(url)
56 | startActivity(browserIntent)
57 | }
58 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/corphish/quicktools/data/Constants.kt:
--------------------------------------------------------------------------------
1 | package com.corphish.quicktools.data
2 |
3 | /**
4 | * Constants.
5 | */
6 | object Constants {
7 | // Phone number regular expression
8 | const val PHONE_NUMBER_REGEX = "^\\s*(?:\\+?(\\d{1,3}))?[-. (]*(\\d{3})[-. )]*(\\d{3})[-. ]*(\\d{4})(?: *x(\\d+))?\\s*\$"
9 | val COUNTRY_CODE_REGEX = "^\\+\\d{1,3}\$".toRegex()
10 |
11 | // Remove these special characters from the phone number
12 | val PHONE_NUMBER_SPECIAL_CHARACTERS = listOf(" ", "-", "(", ")")
13 |
14 | // URLs
15 | const val SOURCE_LINK = "https://github.com/corphish/TextTools/"
16 | const val CONTRIBUTORS_LINK = "${SOURCE_LINK}blob/main/CONTRIBUTORS.md"
17 | const val RELEASES_PAGE_LINK = "${SOURCE_LINK}releases"
18 | const val ISSUES_PAGE_LINK = "${SOURCE_LINK}issues"
19 | const val DONATE_LINK = "https://www.paypal.com/paypalme/corphish"
20 | const val WHATSAPP_API_LINK = "https://wa.me/"
21 |
22 | // Intent to force copy instead of applying
23 | const val INTENT_FORCE_COPY = "force_copy"
24 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/corphish/quicktools/data/Result.kt:
--------------------------------------------------------------------------------
1 | package com.corphish.quicktools.data
2 |
3 | sealed class Result {
4 | // Called when we have a successful call of operation with a value
5 | class Success(val value: T): Result()
6 |
7 | // Called when there is an error while performing an operation
8 | data object Error : Result()
9 |
10 | // Initial value
11 | data object Initial: Result()
12 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/corphish/quicktools/extensions/StringExtensions.kt:
--------------------------------------------------------------------------------
1 | package com.corphish.quicktools.extensions
2 |
3 | /**
4 | * Truncates text till a certain length
5 | */
6 | fun String.truncate(len: Int = 16): String {
7 | val suffix = "..."
8 | return if (this.length <= len) {
9 | this
10 | } else {
11 | this.substring(0, len - suffix.length) + suffix
12 | }
13 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/corphish/quicktools/features/Feature.kt:
--------------------------------------------------------------------------------
1 | package com.corphish.quicktools.features
2 |
3 | import androidx.annotation.DrawableRes
4 | import androidx.annotation.StringRes
5 | import com.corphish.quicktools.R
6 | import com.corphish.quicktools.activities.TryOutActivity
7 | import com.corphish.quicktools.repository.FeatureIds
8 |
9 | /**
10 | * Data class that defines a feature.
11 | */
12 | data class Feature(
13 | val id: FeatureIds,
14 | @DrawableRes val icon: Int,
15 | @StringRes val featureTitle: Int,
16 | @StringRes val featureDesc: Int,
17 | @StringRes val contextMenuText: Int,
18 | val flow: String,
19 | ) {
20 | companion object {
21 | val LIST = listOf(
22 | Feature(
23 | id = FeatureIds.WHATSAPP,
24 | icon = R.drawable.ic_whatsapp,
25 | featureTitle = R.string.wup_title,
26 | featureDesc = R.string.wup_desc,
27 | contextMenuText = R.string.context_menu_whatsapp,
28 | flow = TryOutActivity.TRY_OUT_FLOW_WUP,
29 | ),
30 | Feature(
31 | id = FeatureIds.EVAL,
32 | icon = R.drawable.ic_numbers,
33 | featureTitle = R.string.eval_title,
34 | featureDesc = R.string.eval_desc,
35 | contextMenuText = R.string.context_menu_eval,
36 | flow = TryOutActivity.TRY_OUT_FLOW_EVAL,
37 | ),
38 | Feature(
39 | id = FeatureIds.TRANSFORM,
40 | icon = R.drawable.ic_text_transform,
41 | featureTitle = R.string.transform_long,
42 | featureDesc = R.string.transform_desc,
43 | contextMenuText = R.string.context_menu_transform,
44 | flow = TryOutActivity.TRY_OUT_FLOW
45 | ),
46 | Feature(
47 | id = FeatureIds.TEXT_COUNT,
48 | icon = R.drawable.ic_text_count,
49 | featureTitle = R.string.text_count,
50 | featureDesc = R.string.text_count_desc,
51 | contextMenuText = R.string.context_menu_text_count,
52 | flow = TryOutActivity.TRY_OUT_FLOW
53 | ),
54 | Feature(
55 | id = FeatureIds.SAVE_TEXT,
56 | icon = R.drawable.ic_save,
57 | featureTitle = R.string.save_text_title,
58 | featureDesc = R.string.save_text_desc,
59 | contextMenuText = R.string.context_menu_save,
60 | flow = TryOutActivity.TRY_OUT_FLOW
61 | ),
62 | Feature(
63 | id = FeatureIds.FIND_AND_REPLACE,
64 | icon = R.drawable.ic_find_and_replace,
65 | featureTitle = R.string.title_activity_find_and_replace,
66 | featureDesc = R.string.find_and_replace_desc,
67 | contextMenuText = R.string.context_menu_find_and_replace,
68 | flow = TryOutActivity.TRY_OUT_FLOW
69 | )
70 | )
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/app/src/main/java/com/corphish/quicktools/modules/AppModule.kt:
--------------------------------------------------------------------------------
1 | package com.corphish.quicktools.modules
2 |
3 | import android.content.Context
4 | import com.corphish.quicktools.repository.ContextMenuOptionsRepository
5 | import com.corphish.quicktools.repository.ContextMenuOptionsRepositoryImpl
6 | import com.corphish.quicktools.repository.SettingsRepository
7 | import com.corphish.quicktools.repository.TextRepository
8 | import com.corphish.quicktools.repository.TextRepositoryImpl
9 | import dagger.Module
10 | import dagger.Provides
11 | import dagger.hilt.InstallIn
12 | import dagger.hilt.android.qualifiers.ApplicationContext
13 | import dagger.hilt.components.SingletonComponent
14 | import javax.inject.Singleton
15 |
16 | @Module
17 | @InstallIn(SingletonComponent::class)
18 | object AppModule {
19 | @Provides
20 | @Singleton
21 | fun provideSettingsRepository(@ApplicationContext context: Context) =
22 | SettingsRepository(context)
23 |
24 | @Provides
25 | @Singleton
26 | fun provideTextRepository(@ApplicationContext context: Context): TextRepository =
27 | TextRepositoryImpl(context)
28 |
29 | @Provides
30 | @Singleton
31 | fun provideContextMenuOptionsRepository(@ApplicationContext context: Context): ContextMenuOptionsRepository =
32 | ContextMenuOptionsRepositoryImpl(context)
33 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/corphish/quicktools/repository/ContextMenuOptionsRepository.kt:
--------------------------------------------------------------------------------
1 | package com.corphish.quicktools.repository
2 |
3 | /**
4 | * Repository for the context menu options.
5 | */
6 | interface ContextMenuOptionsRepository {
7 |
8 | /**
9 | * Gets the current app mode.
10 | */
11 | fun getCurrentAppMode(): AppMode
12 |
13 | /**
14 | * Sets the current app mode.
15 | */
16 | fun setCurrentAppMode(mode: AppMode)
17 |
18 | /**
19 | * Returns the currently enabled feature set.
20 | */
21 | fun getCurrentlyEnabledFeatures(): List
22 |
23 | /**
24 | * Enables or disables a feature.
25 | */
26 | fun enableOrDisableFeature(feature: FeatureIds, enabled: Boolean)
27 | }
28 |
29 | enum class AppMode {
30 | SINGLE,
31 | MULTI
32 | }
33 |
34 | enum class FeatureIds {
35 | EVAL,
36 | WHATSAPP,
37 | TRANSFORM,
38 | TEXT_COUNT,
39 | SAVE_TEXT,
40 | FIND_AND_REPLACE
41 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/corphish/quicktools/repository/ContextMenuOptionsRepositoryImpl.kt:
--------------------------------------------------------------------------------
1 | package com.corphish.quicktools.repository
2 |
3 | import android.content.ComponentName
4 | import android.content.Context
5 | import android.content.pm.PackageManager
6 | import androidx.core.content.edit
7 | import androidx.preference.PreferenceManager
8 |
9 | /**
10 | * Implementation of the [ContextMenuOptionsRepository].
11 | * This class is also responsible for enabling/disabling actual components.
12 | */
13 | class ContextMenuOptionsRepositoryImpl(private val context: Context) :
14 | ContextMenuOptionsRepository {
15 | private val _modeKey = "context_menu_mode"
16 | private val _featuresKeySuffix = "context_menu_features_"
17 | private val _sharedPreferenceManager = PreferenceManager.getDefaultSharedPreferences(context)
18 | private val _packageManager = context.packageManager
19 |
20 | // Manifest name mappings
21 | private val _optionsActivityName = context.packageName + ".activities.OptionsActivity"
22 | private val _multiFeatureAliasMapping = mapOf(
23 | FeatureIds.EVAL to context.packageName + ".activities.EvalActivityAlias",
24 | FeatureIds.WHATSAPP to context.packageName + ".activities.WUPActivityAlias",
25 | FeatureIds.TRANSFORM to context.packageName + ".activities.TransformActivityAlias",
26 | FeatureIds.TEXT_COUNT to context.packageName + ".activities.TextCountActivityAlias",
27 | FeatureIds.FIND_AND_REPLACE to context.packageName + ".activities.FindAndReplaceActivityAlias",
28 | FeatureIds.SAVE_TEXT to context.packageName + ".activities.SaveTextActivityAlias"
29 | )
30 |
31 | override fun getCurrentAppMode(): AppMode {
32 | val mode = _sharedPreferenceManager.getString(_modeKey, AppMode.SINGLE.name)
33 | ?: return AppMode.SINGLE
34 |
35 | return AppMode.valueOf(mode)
36 | }
37 |
38 | override fun setCurrentAppMode(mode: AppMode) {
39 | _sharedPreferenceManager.edit {
40 | putString(_modeKey, mode.name)
41 | }
42 |
43 | switchModeTo(mode)
44 | }
45 |
46 | private fun switchModeTo(mode: AppMode) {
47 | when (mode) {
48 | AppMode.SINGLE -> {
49 | // Enable options activity
50 | val optionsActivityComp = ComponentName(context.packageName, _optionsActivityName)
51 | _packageManager.setComponentEnabledSetting(
52 | optionsActivityComp,
53 | PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
54 | PackageManager.DONT_KILL_APP
55 | )
56 |
57 | // Disable all multi feature aliases
58 | _multiFeatureAliasMapping.forEach {
59 | _packageManager.setComponentEnabledSetting(
60 | ComponentName(context.packageName, it.value),
61 | PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
62 | PackageManager.DONT_KILL_APP
63 | )
64 | }
65 | }
66 |
67 | AppMode.MULTI -> {
68 | // Disable options activity
69 | val optionsActivityComp = ComponentName(context.packageName, _optionsActivityName)
70 | _packageManager.setComponentEnabledSetting(
71 | optionsActivityComp,
72 | PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
73 | PackageManager.DONT_KILL_APP
74 | )
75 |
76 | // Enable all the multi feature aliases
77 | val currentFeatures = getCurrentlyEnabledFeatures()
78 | _multiFeatureAliasMapping.forEach {
79 | _packageManager.setComponentEnabledSetting(
80 | ComponentName(context.packageName, it.value),
81 | if (currentFeatures.contains(it.key)) PackageManager.COMPONENT_ENABLED_STATE_ENABLED else PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
82 | PackageManager.DONT_KILL_APP
83 | )
84 | }
85 | }
86 | }
87 | }
88 |
89 | override fun getCurrentlyEnabledFeatures(): List {
90 | val enabledFeatures = mutableListOf()
91 | _multiFeatureAliasMapping.forEach { (featureIds, _) ->
92 | if (_sharedPreferenceManager.getBoolean(_featuresKeySuffix + featureIds.name, true)) {
93 | enabledFeatures.add(featureIds)
94 | }
95 | }
96 |
97 | return enabledFeatures
98 | }
99 |
100 | override fun enableOrDisableFeature(feature: FeatureIds, enabled: Boolean) {
101 | _sharedPreferenceManager.edit {
102 | putBoolean(_featuresKeySuffix + feature.name, enabled)
103 | }
104 |
105 | // Enable or disable the component if we are in multi mode
106 | // For single no action required
107 | val mode = getCurrentAppMode()
108 | if (mode == AppMode.MULTI) {
109 | val featureAlias = _multiFeatureAliasMapping[feature] ?: return
110 | val component = ComponentName(context.packageName, featureAlias)
111 | _packageManager.setComponentEnabledSetting(
112 | component,
113 | if (enabled) PackageManager.COMPONENT_ENABLED_STATE_ENABLED else PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
114 | PackageManager.DONT_KILL_APP
115 | )
116 | }
117 | }
118 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/corphish/quicktools/repository/SettingsRepository.kt:
--------------------------------------------------------------------------------
1 | package com.corphish.quicktools.repository
2 |
3 | import android.content.Context
4 | import androidx.core.content.edit
5 | import androidx.preference.PreferenceManager
6 | import com.corphish.quicktools.viewmodels.EvalViewModel.Companion.EVAL_RESULT_MODE_ASK_NEXT_TIME
7 |
8 | class SettingsRepository(
9 | context: Context
10 | ) {
11 | private val _prependCCEnabledKey = "prepend_country_code_enabled"
12 | private val _prependCCKey = "prepend_country_code"
13 | private val _decimalPoints = "decimal_points"
14 | private val _evaluateResultMode = "eval_result_mode"
15 | private val _onboardingDone = "onboarding_done"
16 |
17 | private val _sharedPreferenceManager = PreferenceManager.getDefaultSharedPreferences(context)
18 |
19 | fun getPrependCountryCodeEnabled() =
20 | _sharedPreferenceManager.getBoolean(_prependCCEnabledKey, false)
21 |
22 | fun setPrependCountryCodeEnabled(enabled: Boolean) {
23 | _sharedPreferenceManager.edit {
24 | putBoolean(_prependCCEnabledKey, enabled)
25 | }
26 | }
27 |
28 | fun getPrependCountryCode() =
29 | _sharedPreferenceManager.getString(_prependCCKey, "")
30 |
31 | fun setPrependCountryCode(code: String) {
32 | _sharedPreferenceManager.edit {
33 | putString(_prependCCKey, code)
34 | }
35 | }
36 |
37 | fun getDecimalPoints() =
38 | _sharedPreferenceManager.getInt(_decimalPoints, 2)
39 |
40 | fun setDecimalPoints(points: Int) {
41 | _sharedPreferenceManager.edit {
42 | putInt(_decimalPoints, points)
43 | }
44 | }
45 |
46 | fun getEvaluateResultMode() =
47 | _sharedPreferenceManager.getInt(_evaluateResultMode, EVAL_RESULT_MODE_ASK_NEXT_TIME)
48 |
49 | fun setEvaluateResultMode(mode: Int) {
50 | _sharedPreferenceManager.edit {
51 | putInt(_evaluateResultMode, mode)
52 | }
53 | }
54 |
55 | fun getOnboardingDone() =
56 | _sharedPreferenceManager.getBoolean(_onboardingDone, false)
57 |
58 | fun setOnboardingDone(done: Boolean) {
59 | _sharedPreferenceManager.edit {
60 | putBoolean(_onboardingDone, done)
61 | }
62 | }
63 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/corphish/quicktools/repository/TextRepository.kt:
--------------------------------------------------------------------------------
1 | package com.corphish.quicktools.repository
2 |
3 | import android.net.Uri
4 |
5 | /**
6 | * Common definition for text repository capabilities for file writes.
7 | */
8 | interface TextRepository {
9 | /**
10 | * Writes the given text to somewhere.
11 | * @return Boolean indicating success state.
12 | */
13 | fun writeText(uri: Uri?, text: String): Boolean
14 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/corphish/quicktools/repository/TextRepositoryImpl.kt:
--------------------------------------------------------------------------------
1 | package com.corphish.quicktools.repository
2 |
3 | import android.content.Context
4 | import android.net.Uri
5 | import java.io.BufferedWriter
6 | import java.io.IOException
7 | import java.io.OutputStream
8 | import java.io.OutputStreamWriter
9 |
10 | /**
11 | * Uri based text repository for file writes
12 | */
13 | class TextRepositoryImpl(val context: Context): TextRepository {
14 | override fun writeText(uri: Uri?, text: String): Boolean {
15 | if (uri == null) {
16 | return false
17 | }
18 |
19 | val outputStream: OutputStream?
20 | try {
21 | outputStream = context.contentResolver.openOutputStream(uri)
22 | val bw = BufferedWriter(OutputStreamWriter(outputStream))
23 | bw.write(text)
24 | bw.flush()
25 | bw.close()
26 | } catch (e: IOException) {
27 | e.printStackTrace()
28 | return false
29 | }
30 |
31 | return true
32 | }
33 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/corphish/quicktools/text/TextReplacementAction.kt:
--------------------------------------------------------------------------------
1 | package com.corphish.quicktools.text
2 |
3 | data class TextReplacementAction(
4 | // Old text that was replaced
5 | val oldText: String,
6 |
7 | // New text that the old text was replaced with
8 | val newText: String,
9 | )
10 |
--------------------------------------------------------------------------------
/app/src/main/java/com/corphish/quicktools/text/TextReplacementManager.kt:
--------------------------------------------------------------------------------
1 | package com.corphish.quicktools.text
2 |
3 | import android.util.Log
4 | import androidx.compose.ui.text.TextRange
5 |
6 | /**
7 | * Class that handles text replacement to some initial text with proper tracking of actions.
8 | */
9 | class TextReplacementManager(private val inputString: String) {
10 | // List of actions performed
11 | private val mActionList = mutableListOf()
12 |
13 | // Current state of action
14 | private var mActionPointer = 0
15 |
16 | private var mCurrentText = inputString
17 |
18 | private val _debug = false
19 |
20 | init {
21 | dumpState("Init")
22 | }
23 |
24 | /**
25 | * Resets the text to initial state.
26 | */
27 | fun reset(): String {
28 | mCurrentText = inputString
29 | mActionList.clear()
30 | mActionPointer = 0
31 |
32 | return mCurrentText
33 | }
34 |
35 | /**
36 | * Replaces one occurrence of the text defined by the range with the newText.
37 | */
38 | fun replaceOne(range: TextRange, newText: String): String {
39 | val res = mCurrentText.substring(0, range.start) + newText + mCurrentText.substring(range.end)
40 |
41 | addAction(TextReplacementAction(mCurrentText, res))
42 |
43 | mCurrentText = res
44 | return res
45 | }
46 |
47 | /**
48 | * Replaces all occurrence of the text defined by the range with the newText.
49 | */
50 | fun replaceAll(oldText: String, newText: String, ignoreCase: Boolean): String {
51 | val res = mCurrentText.replace(oldText, newText, ignoreCase)
52 |
53 | addAction(TextReplacementAction(mCurrentText, res))
54 |
55 | mCurrentText = res
56 | return res
57 | }
58 |
59 | /**
60 | * Checks whether it is possible to perform undo on current state.
61 | */
62 | fun canUndo() =
63 | mActionList.isNotEmpty() && mActionPointer > 0
64 |
65 | /**
66 | * Checks whether it is possible to perform redo on current state.
67 | */
68 | fun canRedo() =
69 | mActionList.isNotEmpty() && mActionPointer < mActionList.size
70 |
71 | private fun addAction(action: TextReplacementAction) {
72 | if (mActionList.isEmpty() || mActionPointer == mActionList.size) {
73 | mActionList += action
74 | mActionPointer += 1
75 | } else {
76 | // Currently we are in a state where an undo operation is done.
77 | // In such case, we add the action at the current pointer index
78 | // and remove the next action elements.
79 | mActionList.add(mActionPointer, action)
80 | mActionList.subList(mActionPointer + 1, mActionList.size).clear()
81 | mActionPointer += 1
82 | }
83 |
84 | dumpState("addAction")
85 | }
86 |
87 | /**
88 | * Performs the undo operation.
89 | */
90 | fun undo(): String {
91 | if (mActionPointer == 0) {
92 | return mCurrentText
93 | }
94 |
95 | // We undo the last operation
96 | val action = mActionList[mActionPointer - 1]
97 |
98 | mCurrentText = action.oldText
99 | mActionPointer -= 1
100 |
101 | dumpState("Undo")
102 |
103 | return mCurrentText
104 | }
105 |
106 | /**
107 | * Performs the redo operation.
108 | */
109 | fun redo(): String {
110 | if (mActionPointer == mActionList.size) {
111 | return mCurrentText
112 | }
113 |
114 | // We redo the current operation
115 | val action = mActionList[mActionPointer]
116 |
117 | mCurrentText = action.newText
118 | mActionPointer += 1
119 |
120 | dumpState("Redo")
121 |
122 | return mCurrentText
123 | }
124 |
125 | fun updateText(newText: String) {
126 | addAction(TextReplacementAction(mCurrentText, newText))
127 | mCurrentText = newText
128 | }
129 |
130 | private fun dumpState(caller: String) {
131 | if (_debug) {
132 | Log.d("TextReplacementManager", "[$caller] actions=$mActionList, ptr=$mActionPointer")
133 | }
134 | }
135 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/corphish/quicktools/ui/common/AppBars.kt:
--------------------------------------------------------------------------------
1 | @file:OptIn(ExperimentalMaterial3Api::class)
2 |
3 | package com.corphish.quicktools.ui.common
4 |
5 | import androidx.annotation.StringRes
6 | import androidx.compose.foundation.layout.size
7 | import androidx.compose.material.icons.Icons
8 | import androidx.compose.material.icons.automirrored.outlined.KeyboardArrowLeft
9 | import androidx.compose.material3.ExperimentalMaterial3Api
10 | import androidx.compose.material3.Icon
11 | import androidx.compose.material3.IconButton
12 | import androidx.compose.material3.MaterialTheme
13 | import androidx.compose.material3.MediumTopAppBar
14 | import androidx.compose.material3.Text
15 | import androidx.compose.material3.TopAppBarDefaults
16 | import androidx.compose.material3.TopAppBarScrollBehavior
17 | import androidx.compose.runtime.Composable
18 | import androidx.compose.ui.Modifier
19 | import androidx.compose.ui.res.stringResource
20 | import androidx.compose.ui.unit.dp
21 | import com.corphish.quicktools.ui.theme.BrandFontFamily
22 | import com.corphish.quicktools.ui.theme.TypographyV2
23 |
24 | @Composable
25 | fun CustomTopAppBar(
26 | @StringRes id: Int,
27 | onNavigationClick: () -> Unit = {},
28 | scrollBehavior: TopAppBarScrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(),
29 | ) {
30 | MediumTopAppBar(
31 | title = {
32 | Text(
33 | text = stringResource(id = id),
34 | style = TypographyV2.headlineMedium,
35 | color = MaterialTheme.colorScheme.primary,
36 | fontFamily = BrandFontFamily,
37 | )
38 | },
39 | navigationIcon = {
40 | IconButton(onClick = { onNavigationClick() }) {
41 | Icon(
42 | imageVector = Icons.AutoMirrored.Outlined.KeyboardArrowLeft,
43 | contentDescription = "",
44 | modifier = Modifier.size(32.dp),
45 | tint = MaterialTheme.colorScheme.primary
46 | )
47 | }
48 | },
49 | scrollBehavior = scrollBehavior
50 | )
51 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/corphish/quicktools/ui/common/Buttons.kt:
--------------------------------------------------------------------------------
1 | package com.corphish.quicktools.ui.common
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.foundation.layout.padding
6 | import androidx.compose.foundation.layout.size
7 | import androidx.compose.foundation.shape.CircleShape
8 | import androidx.compose.material3.Icon
9 | import androidx.compose.material3.IconButton
10 | import androidx.compose.material3.IconButtonDefaults
11 | import androidx.compose.material3.MaterialTheme
12 | import androidx.compose.material3.Text
13 | import androidx.compose.runtime.Composable
14 | import androidx.compose.ui.Alignment
15 | import androidx.compose.ui.Modifier
16 | import androidx.compose.ui.draw.clip
17 | import androidx.compose.ui.graphics.Color
18 | import androidx.compose.ui.graphics.painter.Painter
19 | import androidx.compose.ui.graphics.vector.ImageVector
20 | import androidx.compose.ui.text.font.FontWeight
21 | import androidx.compose.ui.unit.dp
22 | import com.corphish.quicktools.ui.theme.BrandFontFamily
23 |
24 | @Composable
25 | fun CircularButtonWithText(
26 | modifier: Modifier = Modifier,
27 | imageVector: ImageVector? = null,
28 | painterResource: Painter? = null,
29 | enabled: Boolean = true,
30 | text: String = "",
31 | onClick: () -> Unit = {}
32 | ) {
33 | Column(
34 | modifier = modifier.clickable {
35 | if (enabled) {
36 | onClick()
37 | }
38 | },
39 | horizontalAlignment = Alignment.CenterHorizontally
40 | ) {
41 | IconButton(
42 | onClick = { onClick() },
43 | enabled = enabled,
44 | colors = IconButtonDefaults.iconButtonColors(
45 | containerColor = MaterialTheme.colorScheme.primary,
46 | contentColor = MaterialTheme.colorScheme.onPrimary,
47 | disabledContainerColor = Color.Gray
48 | ),
49 | modifier = Modifier
50 | .clip(CircleShape)
51 | .size(48.dp)
52 | ) {
53 | if (imageVector != null) {
54 | Icon(
55 | imageVector = imageVector,
56 | contentDescription = "",
57 | modifier = Modifier.size(24.dp)
58 | )
59 | } else if (painterResource != null) {
60 | Icon(
61 | painter = painterResource,
62 | contentDescription = "",
63 | modifier = Modifier.size(24.dp)
64 | )
65 | }
66 | }
67 |
68 | Text(
69 | text = text,
70 | color = if (enabled) MaterialTheme.colorScheme.onBackground else Color.Gray,
71 | fontFamily = BrandFontFamily,
72 | fontWeight = FontWeight.W600,
73 | modifier = Modifier.padding(top = 4.dp)
74 | )
75 | }
76 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/corphish/quicktools/ui/common/ListDialog.kt:
--------------------------------------------------------------------------------
1 | package com.corphish.quicktools.ui.common
2 |
3 | import androidx.annotation.DrawableRes
4 | import androidx.compose.foundation.Image
5 | import androidx.compose.foundation.background
6 | import androidx.compose.foundation.clickable
7 | import androidx.compose.foundation.layout.Box
8 | import androidx.compose.foundation.layout.Column
9 | import androidx.compose.foundation.layout.ColumnScope
10 | import androidx.compose.foundation.layout.Row
11 | import androidx.compose.foundation.layout.fillMaxWidth
12 | import androidx.compose.foundation.layout.padding
13 | import androidx.compose.foundation.layout.size
14 | import androidx.compose.foundation.shape.CircleShape
15 | import androidx.compose.foundation.shape.RoundedCornerShape
16 | import androidx.compose.material.icons.Icons
17 | import androidx.compose.material.icons.automirrored.outlined.KeyboardArrowLeft
18 | import androidx.compose.material3.Card
19 | import androidx.compose.material3.CardDefaults
20 | import androidx.compose.material3.Icon
21 | import androidx.compose.material3.IconButton
22 | import androidx.compose.material3.MaterialTheme
23 | import androidx.compose.material3.Text
24 | import androidx.compose.runtime.Composable
25 | import androidx.compose.ui.Alignment
26 | import androidx.compose.ui.Modifier
27 | import androidx.compose.ui.draw.clip
28 | import androidx.compose.ui.graphics.ColorFilter
29 | import androidx.compose.ui.res.painterResource
30 | import androidx.compose.ui.text.font.FontWeight
31 | import androidx.compose.ui.unit.dp
32 | import androidx.compose.ui.window.Dialog
33 | import androidx.compose.ui.window.DialogProperties
34 | import com.corphish.quicktools.ui.theme.BrandFontFamily
35 | import com.corphish.quicktools.ui.theme.TypographyV2
36 |
37 | /**
38 | * A list of selectable options shown inside a dialog.
39 | */
40 | @Composable
41 | fun ListDialog(
42 | title: String,
43 | message: String,
44 | list: List,
45 | supportBack: Boolean = false,
46 | onItemSelected: (Int) -> Unit,
47 | stringSelector: @Composable (T) -> String,
48 | iconSelector: @Composable (T) -> Int,
49 | onBackPressed: () -> Unit = {},
50 | additionalContent: @Composable ColumnScope.() -> Unit = {},
51 | dismissible: Boolean = true,
52 | onDismissRequest: () -> Unit
53 | ) {
54 | Dialog(
55 | onDismissRequest = { onDismissRequest() },
56 | properties = DialogProperties(
57 | dismissOnBackPress = dismissible,
58 | dismissOnClickOutside = dismissible
59 | )
60 | ) {
61 | Card(
62 | modifier = Modifier
63 | .fillMaxWidth()
64 | .padding(16.dp),
65 | shape = RoundedCornerShape(16.dp),
66 | elevation = CardDefaults.cardElevation(defaultElevation = 8.dp)
67 | ) {
68 | Column(
69 | modifier = Modifier
70 | .padding(16.dp),
71 | ) {
72 | Row(
73 | verticalAlignment = Alignment.CenterVertically,
74 | modifier = Modifier.padding(bottom = 4.dp)
75 | ) {
76 | if (supportBack) {
77 | IconButton(
78 | onClick = { onBackPressed() },
79 | modifier = Modifier
80 | .clip(CircleShape)
81 | .size(48.dp)
82 | .background(MaterialTheme.colorScheme.primary)
83 | ) {
84 | Icon(
85 | imageVector = Icons.AutoMirrored.Outlined.KeyboardArrowLeft,
86 | contentDescription = "",
87 | modifier = Modifier.size(32.dp),
88 | tint = MaterialTheme.colorScheme.onPrimary
89 | )
90 | }
91 | }
92 |
93 | Text(
94 | text = title,
95 | style = TypographyV2.headlineMedium,
96 | fontFamily = BrandFontFamily,
97 | color = MaterialTheme.colorScheme.primary,
98 | modifier = Modifier.padding(start = if (supportBack) 16.dp else 0.dp)
99 | )
100 | }
101 |
102 | Text(
103 | text = message,
104 | style = TypographyV2.bodyMedium,
105 | modifier = Modifier.padding(bottom = 16.dp)
106 | )
107 |
108 | for ((index, item) in list.withIndex()) {
109 | ListDialogItem(text = stringSelector(item), icon = iconSelector(item)) {
110 | onItemSelected(index)
111 | }
112 | }
113 |
114 | additionalContent()
115 | }
116 | }
117 | }
118 | }
119 |
120 | @Composable
121 | fun ListDialogItem(text: String, @DrawableRes icon: Int, onClick: () -> Unit) {
122 | Box(
123 | modifier = Modifier
124 | .fillMaxWidth()
125 | .clickable { onClick() }
126 | ) {
127 | Row(
128 | modifier = Modifier.padding(all = 8.dp),
129 | verticalAlignment = Alignment.CenterVertically,
130 | ) {
131 | Box(
132 | modifier = Modifier
133 | .size(36.dp)
134 | .clip(CircleShape)
135 | .background(MaterialTheme.colorScheme.primary),
136 | contentAlignment = Alignment.Center
137 | ) {
138 | Image(
139 | painterResource(id = icon),
140 | contentDescription = "",
141 | modifier = Modifier.size(24.dp),
142 | colorFilter = ColorFilter.tint(color = MaterialTheme.colorScheme.onPrimary)
143 | )
144 | }
145 |
146 | Text(
147 | text = text,
148 | style = TypographyV2.labelMedium,
149 | fontFamily = BrandFontFamily,
150 | fontWeight = FontWeight.W400,
151 | modifier = Modifier.padding(start = 16.dp)
152 | )
153 | }
154 | }
155 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/corphish/quicktools/ui/theme/Color.kt:
--------------------------------------------------------------------------------
1 | package com.corphish.quicktools.ui.theme
2 |
3 | import androidx.compose.ui.graphics.Color
4 |
5 | val BlueForeground = Color(0xFF0099FF)
6 | val BlueBackground = Color(0xFFCCEBFF)
--------------------------------------------------------------------------------
/app/src/main/java/com/corphish/quicktools/ui/theme/Theme.kt:
--------------------------------------------------------------------------------
1 | package com.corphish.quicktools.ui.theme
2 |
3 | import android.app.Activity
4 | import android.os.Build
5 | import androidx.compose.foundation.isSystemInDarkTheme
6 | import androidx.compose.material3.MaterialTheme
7 | import androidx.compose.material3.darkColorScheme
8 | import androidx.compose.material3.dynamicDarkColorScheme
9 | import androidx.compose.material3.dynamicLightColorScheme
10 | import androidx.compose.material3.lightColorScheme
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.runtime.SideEffect
13 | import androidx.compose.ui.graphics.toArgb
14 | import androidx.compose.ui.platform.LocalContext
15 | import androidx.compose.ui.platform.LocalView
16 | import androidx.core.view.WindowCompat
17 |
18 | private val DarkColorScheme = darkColorScheme(
19 | primary = BlueForeground,
20 | secondary = BlueBackground,
21 | tertiary = BlueForeground
22 | )
23 |
24 | private val LightColorScheme = lightColorScheme(
25 | primary = BlueForeground,
26 | secondary = BlueBackground,
27 | tertiary = BlueForeground
28 |
29 | /* Other default colors to override
30 | background = Color(0xFFFFFBFE),
31 | surface = Color(0xFFFFFBFE),
32 | onPrimary = Color.White,
33 | onSecondary = Color.White,
34 | onTertiary = Color.White,
35 | onBackground = Color(0xFF1C1B1F),
36 | onSurface = Color(0xFF1C1B1F),
37 | */
38 | )
39 |
40 | @Composable
41 | fun QuickToolsTheme(
42 | darkTheme: Boolean = isSystemInDarkTheme(),
43 | // Dynamic color is available on Android 12+
44 | dynamicColor: Boolean = true,
45 | content: @Composable () -> Unit
46 | ) {
47 | val colorScheme = when {
48 | dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
49 | val context = LocalContext.current
50 | if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
51 | }
52 |
53 | darkTheme -> DarkColorScheme
54 | else -> LightColorScheme
55 | }
56 | val view = LocalView.current
57 |
58 | if (!view.isInEditMode) {
59 | SideEffect {
60 | val window = (view.context as Activity).window
61 |
62 | // API 35 like status bar looks wierd in API 34 and below
63 | // So we continue to use old status bar in older APIs
64 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) {
65 | window.statusBarColor = colorScheme.primary.toArgb()
66 | WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
67 | } else {
68 | WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars =
69 | !darkTheme
70 | }
71 | }
72 | }
73 |
74 | MaterialTheme(
75 | colorScheme = colorScheme,
76 | typography = Typography,
77 | content = content
78 | )
79 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/corphish/quicktools/ui/theme/Type.kt:
--------------------------------------------------------------------------------
1 | package com.corphish.quicktools.ui.theme
2 |
3 | import androidx.compose.material3.Typography
4 | import androidx.compose.ui.text.TextStyle
5 | import androidx.compose.ui.text.font.FontFamily
6 | import androidx.compose.ui.text.font.FontWeight
7 | import androidx.compose.ui.text.googlefonts.Font
8 | import androidx.compose.ui.text.googlefonts.GoogleFont
9 | import androidx.compose.ui.unit.sp
10 | import com.corphish.quicktools.R
11 |
12 | val provider = GoogleFont.Provider(
13 | providerAuthority = "com.google.android.gms.fonts",
14 | providerPackage = "com.google.android.gms",
15 | certificates = R.array.com_google_android_gms_fonts_certs
16 | )
17 |
18 | val fontName = GoogleFont("Montserrat")
19 |
20 | val BrandFontFamily = FontFamily(
21 | Font(googleFont = fontName, fontProvider = provider, weight = FontWeight.W200),
22 | Font(googleFont = fontName, fontProvider = provider, weight = FontWeight.W300),
23 | Font(googleFont = fontName, fontProvider = provider),
24 | Font(googleFont = fontName, fontProvider = provider, weight = FontWeight.W500),
25 | Font(googleFont = fontName, fontProvider = provider, weight = FontWeight.W600),
26 | )
27 |
28 | // Set of Material typography styles to start with
29 | val Typography = Typography(
30 | bodyLarge = TextStyle(
31 | fontFamily = FontFamily.Default,
32 | fontWeight = FontWeight.Normal,
33 | fontSize = 16.sp,
34 | lineHeight = 24.sp,
35 | letterSpacing = 0.5.sp
36 | ),
37 | titleLarge = TextStyle(
38 | fontFamily = FontFamily.Default,
39 | fontWeight = FontWeight.Normal,
40 | fontSize = 22.sp,
41 | lineHeight = 28.sp,
42 | letterSpacing = 0.sp
43 | ),
44 | labelSmall = TextStyle(
45 | fontFamily = FontFamily.Default,
46 | fontWeight = FontWeight.Medium,
47 | fontSize = 11.sp,
48 | lineHeight = 16.sp,
49 | letterSpacing = 0.5.sp
50 | )
51 | )
52 |
53 | val TypographyV2 = Typography(
54 | headlineLarge = TextStyle(
55 | fontFamily = BrandFontFamily,
56 | fontWeight = FontWeight.W500,
57 | fontSize = 48.sp
58 | ),
59 | headlineMedium = TextStyle(
60 | fontFamily = BrandFontFamily,
61 | fontWeight = FontWeight.Bold,
62 | fontSize = 32.sp
63 | ),
64 | headlineSmall = TextStyle(
65 | fontFamily = BrandFontFamily,
66 | fontWeight = FontWeight.W500,
67 | fontSize = 28.sp
68 | ),
69 | labelLarge = TextStyle(
70 | fontFamily = BrandFontFamily,
71 | fontWeight = FontWeight.W500,
72 | fontSize = 24.sp
73 | ),
74 | labelMedium = TextStyle(
75 | fontFamily = BrandFontFamily,
76 | fontWeight = FontWeight.W500,
77 | fontSize = 16.sp
78 | ),
79 | labelSmall = TextStyle(
80 | fontFamily = BrandFontFamily,
81 | fontWeight = FontWeight.W600,
82 | fontSize = 12.sp
83 | ),
84 | bodyLarge = TextStyle(
85 | fontSize = 16.sp,
86 | letterSpacing = 0.5.sp
87 | ),
88 | bodyMedium = TextStyle(
89 | fontSize = 14.sp,
90 | letterSpacing = 0.5.sp
91 | ),
92 | bodySmall = TextStyle(
93 | fontSize = 12.sp,
94 | letterSpacing = 0.5.sp
95 | ),
96 | displayLarge = TextStyle(
97 | fontFamily = BrandFontFamily,
98 | fontSize = 16.sp,
99 | letterSpacing = 0.5.sp
100 | ),
101 | displayMedium = TextStyle(
102 | fontFamily = BrandFontFamily,
103 | fontSize = 14.sp,
104 | letterSpacing = 0.5.sp
105 | ),
106 | displaySmall = TextStyle(
107 | fontFamily = BrandFontFamily,
108 | fontSize = 12.sp,
109 | letterSpacing = 0.5.sp
110 | )
111 | )
--------------------------------------------------------------------------------
/app/src/main/java/com/corphish/quicktools/utils/ClipboardHelper.kt:
--------------------------------------------------------------------------------
1 | package com.corphish.quicktools.utils
2 |
3 | import android.content.ClipData
4 | import android.content.ClipboardManager
5 | import android.content.Context
6 | import android.widget.Toast
7 | import androidx.appcompat.app.AppCompatActivity.CLIPBOARD_SERVICE
8 | import com.corphish.quicktools.R
9 |
10 | /**
11 | * Helper to copy to clipboard
12 | */
13 | object ClipboardHelper {
14 | fun copyToClipboard(context: Context, text: String) {
15 | val clipboard = context.getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
16 | val clip = ClipData.newPlainText("text_tools_result", text)
17 | clipboard.setPrimaryClip(clip)
18 | Toast.makeText(context, R.string.copied_to_clipboard, Toast.LENGTH_LONG).show()
19 | }
20 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/corphish/quicktools/viewmodels/EvalViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.corphish.quicktools.viewmodels
2 |
3 | import androidx.lifecycle.ViewModel
4 | import androidx.lifecycle.viewModelScope
5 | import com.corphish.quicktools.data.Result
6 | import com.corphish.quicktools.repository.SettingsRepository
7 | import dagger.hilt.android.lifecycle.HiltViewModel
8 | import kotlinx.coroutines.flow.MutableStateFlow
9 | import kotlinx.coroutines.flow.StateFlow
10 | import kotlinx.coroutines.launch
11 | import net.objecthunter.exp4j.ExpressionBuilder
12 | import java.text.DecimalFormat
13 | import javax.inject.Inject
14 | import kotlin.math.ceil
15 | import kotlin.math.floor
16 |
17 | @HiltViewModel
18 | class EvalViewModel @Inject constructor(private val settingsRepository: SettingsRepository) :
19 | ViewModel() {
20 | private val _evalMode = MutableStateFlow(settingsRepository.getEvaluateResultMode())
21 | val evalMode: StateFlow = _evalMode
22 |
23 | // To be populated when user finally selects a mode, if at all they select
24 | private var _userSelectedMode = _evalMode.value
25 |
26 | fun denoteModeSelectionByUser(selectedMode: Int) {
27 | this._userSelectedMode = selectedMode
28 | }
29 |
30 | fun denoteUserRememberChoice(choice: Boolean) {
31 | if (choice) {
32 | settingsRepository.setEvaluateResultMode(_userSelectedMode)
33 | }
34 | }
35 |
36 | suspend fun shouldForceCopy(choice: Boolean) {
37 | if (choice) {
38 | _userSelectedMode = EVAL_RESULT_COPY_TO_CLIPBOARD
39 | _evalMode.emit(EVAL_RESULT_COPY_TO_CLIPBOARD)
40 | }
41 | }
42 |
43 | private val _evalResult = MutableStateFlow>(Result.Initial)
44 | val evalResult: StateFlow> = _evalResult
45 |
46 | fun evaluate(text: String) {
47 | viewModelScope.launch {
48 | val decimalPoints = settingsRepository.getDecimalPoints()
49 | try {
50 | val expression = ExpressionBuilder(text).build()
51 | val result = expression.evaluate()
52 |
53 | _evalResult.value = Result.Success(
54 | EvaluateResult(
55 | resultString = if (ceil(result) == floor(result)) {
56 | result.toInt().toString()
57 | } else {
58 | val decimalFormat = DecimalFormat("0.${"#".repeat(decimalPoints)}")
59 | decimalFormat.format(result)
60 | },
61 | finalMode = _userSelectedMode
62 | )
63 | )
64 | } catch (e: Exception) {
65 | _evalResult.value = Result.Error
66 | }
67 | }
68 | }
69 |
70 | companion object {
71 | // Eval result mode choices will be shown to user next time
72 | const val EVAL_RESULT_MODE_ASK_NEXT_TIME = 0
73 |
74 | // Eval result will be replaced by the selected text
75 | const val EVAL_RESULT_REPLACE = 1
76 |
77 | // Eval result will be appended after the selected text with = sign
78 | const val EVAL_RESULT_APPEND = 2
79 |
80 | // Eval result will be copied to the clipboard
81 | const val EVAL_RESULT_COPY_TO_CLIPBOARD = 3
82 | }
83 | }
84 |
85 | data class EvaluateResult(
86 | val resultString: String,
87 | val finalMode: Int,
88 | )
--------------------------------------------------------------------------------
/app/src/main/java/com/corphish/quicktools/viewmodels/MainViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.corphish.quicktools.viewmodels
2 |
3 | import androidx.lifecycle.ViewModel
4 | import androidx.lifecycle.viewModelScope
5 | import com.corphish.quicktools.repository.AppMode
6 | import com.corphish.quicktools.repository.ContextMenuOptionsRepository
7 | import com.corphish.quicktools.repository.FeatureIds
8 | import dagger.hilt.android.lifecycle.HiltViewModel
9 | import kotlinx.coroutines.flow.MutableStateFlow
10 | import kotlinx.coroutines.flow.asStateFlow
11 | import kotlinx.coroutines.launch
12 | import javax.inject.Inject
13 |
14 | @HiltViewModel
15 | class MainViewModel @Inject constructor(private val contextMenuOptionsRepository: ContextMenuOptionsRepository) :
16 | ViewModel() {
17 | private val _enabledFeatures =
18 | MutableStateFlow(listOf())
19 | val enabledFeatures = _enabledFeatures.asStateFlow()
20 |
21 | private val _appMode = MutableStateFlow(AppMode.SINGLE)
22 | val appMode = _appMode.asStateFlow()
23 |
24 | fun init() {
25 | _enabledFeatures.value = contextMenuOptionsRepository.getCurrentlyEnabledFeatures()
26 | _appMode.value = contextMenuOptionsRepository.getCurrentAppMode()
27 | }
28 |
29 | fun enableOrDisableFeature(feature: FeatureIds, enabled: Boolean) {
30 | viewModelScope.launch {
31 | contextMenuOptionsRepository.enableOrDisableFeature(feature, enabled)
32 | _enabledFeatures.value = contextMenuOptionsRepository.getCurrentlyEnabledFeatures()
33 | }
34 | }
35 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/corphish/quicktools/viewmodels/OnBoardingViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.corphish.quicktools.viewmodels
2 |
3 | import androidx.lifecycle.ViewModel
4 | import androidx.lifecycle.viewModelScope
5 | import com.corphish.quicktools.repository.AppMode
6 | import com.corphish.quicktools.repository.ContextMenuOptionsRepository
7 | import com.corphish.quicktools.repository.SettingsRepository
8 | import dagger.hilt.android.lifecycle.HiltViewModel
9 | import kotlinx.coroutines.flow.MutableStateFlow
10 | import kotlinx.coroutines.flow.asStateFlow
11 | import kotlinx.coroutines.launch
12 | import javax.inject.Inject
13 |
14 | @HiltViewModel
15 | class OnBoardingViewModel @Inject constructor(
16 | private val settingsRepository: SettingsRepository,
17 | private val contextOptionsRepository: ContextMenuOptionsRepository,
18 | ) : ViewModel() {
19 | private val _onBoardingDone = MutableStateFlow(settingsRepository.getOnboardingDone())
20 | val onBoardingDone = _onBoardingDone.asStateFlow()
21 |
22 | fun setOnBoardingDone(done: Boolean) {
23 | viewModelScope.launch {
24 | settingsRepository.setOnboardingDone(done)
25 | _onBoardingDone.value = done
26 | }
27 | }
28 |
29 | fun setAppMode(mode: AppMode) {
30 | viewModelScope.launch {
31 | contextOptionsRepository.setCurrentAppMode(mode)
32 | }
33 | }
34 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/corphish/quicktools/viewmodels/OptionsViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.corphish.quicktools.viewmodels
2 |
3 | import androidx.lifecycle.ViewModel
4 | import com.corphish.quicktools.repository.ContextMenuOptionsRepository
5 | import dagger.hilt.android.lifecycle.HiltViewModel
6 | import kotlinx.coroutines.flow.MutableStateFlow
7 | import kotlinx.coroutines.flow.asStateFlow
8 | import javax.inject.Inject
9 |
10 | @HiltViewModel
11 | class OptionsViewModel @Inject constructor(contextMenuOptionsRepository: ContextMenuOptionsRepository) :
12 | ViewModel() {
13 | private val _enabledFeatures = MutableStateFlow(contextMenuOptionsRepository.getCurrentlyEnabledFeatures())
14 | val enabledFeatures = _enabledFeatures.asStateFlow()
15 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/corphish/quicktools/viewmodels/SaveTextViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.corphish.quicktools.viewmodels
2 |
3 | import android.net.Uri
4 | import androidx.lifecycle.ViewModel
5 | import androidx.lifecycle.viewModelScope
6 | import com.corphish.quicktools.data.Result
7 | import com.corphish.quicktools.repository.TextRepository
8 | import dagger.hilt.android.lifecycle.HiltViewModel
9 | import kotlinx.coroutines.flow.MutableStateFlow
10 | import kotlinx.coroutines.flow.StateFlow
11 | import kotlinx.coroutines.launch
12 | import javax.inject.Inject
13 |
14 | @HiltViewModel
15 | class SaveTextViewModel @Inject constructor(private val textRepository: TextRepository) :
16 | ViewModel() {
17 | private val _saveTextStatus = MutableStateFlow>(Result.Initial)
18 | val saveTextStatus: StateFlow> = _saveTextStatus
19 |
20 | fun saveText(uri: Uri?, text: String) {
21 | viewModelScope.launch {
22 | val res = textRepository.writeText(uri, text)
23 | _saveTextStatus.value = if (res) Result.Success(true) else Result.Error
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/corphish/quicktools/viewmodels/SettingsViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.corphish.quicktools.viewmodels
2 |
3 | import androidx.lifecycle.ViewModel
4 | import androidx.lifecycle.viewModelScope
5 | import com.corphish.quicktools.BuildConfig
6 | import com.corphish.quicktools.data.Constants
7 | import com.corphish.quicktools.repository.AppMode
8 | import com.corphish.quicktools.repository.ContextMenuOptionsRepository
9 | import com.corphish.quicktools.repository.SettingsRepository
10 | import dagger.hilt.android.lifecycle.HiltViewModel
11 | import kotlinx.coroutines.flow.MutableStateFlow
12 | import kotlinx.coroutines.flow.StateFlow
13 | import kotlinx.coroutines.launch
14 | import javax.inject.Inject
15 |
16 | @HiltViewModel
17 | class SettingsViewModel @Inject constructor(
18 | private val settingsRepository: SettingsRepository,
19 | private val contextMenuOptionsRepository: ContextMenuOptionsRepository,
20 | ): ViewModel() {
21 | private val _prependCountryCodeEnabled = MutableStateFlow(settingsRepository.getPrependCountryCodeEnabled())
22 | private val _prependCountryCode = MutableStateFlow(settingsRepository.getPrependCountryCode())
23 | private val _prependCountryCodeIsValid = MutableStateFlow(Constants.COUNTRY_CODE_REGEX.matches(_prependCountryCode.value ?: ""))
24 | private val _decimalPoints = MutableStateFlow(settingsRepository.getDecimalPoints())
25 | private val _evalResultMode = MutableStateFlow(settingsRepository.getEvaluateResultMode())
26 | private val _appVersionName = MutableStateFlow(BuildConfig.VERSION_NAME)
27 | private val _appVersionCode = MutableStateFlow(BuildConfig.VERSION_CODE)
28 | private val _appMode = MutableStateFlow(contextMenuOptionsRepository.getCurrentAppMode())
29 |
30 | val prependCountryCodeEnabled: StateFlow = _prependCountryCodeEnabled
31 | val prependCountryCode: StateFlow = _prependCountryCode
32 | val prependCountryCodeIsValid: StateFlow = _prependCountryCodeIsValid
33 | val decimalPoints: StateFlow = _decimalPoints
34 | val evalResultMode: StateFlow = _evalResultMode
35 | val appVersionName: StateFlow = _appVersionName
36 | val appVersionCode: StateFlow = _appVersionCode
37 | val appMode: StateFlow = _appMode
38 |
39 | fun updatePrependCountryCodeEnabled(enabled: Boolean) {
40 | viewModelScope.launch {
41 | settingsRepository.setPrependCountryCodeEnabled(enabled)
42 | _prependCountryCodeEnabled.value = enabled
43 | }
44 | }
45 |
46 | fun updatePrependCountryCode(code: String) {
47 | viewModelScope.launch {
48 | val match = Constants.COUNTRY_CODE_REGEX.matches(code)
49 | _prependCountryCode.value = code
50 |
51 | if (match) {
52 | settingsRepository.setPrependCountryCode(code)
53 | _prependCountryCodeIsValid.value = true
54 | } else {
55 | _prependCountryCodeIsValid.value = false
56 | }
57 | }
58 | }
59 |
60 | /**
61 | * Should be called when activity is exiting to finalise the country code prepend
62 | * enabled setting.
63 | * Ideally, we turn off country code prepend enabled if the country code is invalid.
64 | */
65 | fun invalidateCountryCodePrependSetting() {
66 | viewModelScope.launch {
67 | if (!_prependCountryCodeIsValid.value) {
68 | updatePrependCountryCodeEnabled(false)
69 | settingsRepository.setPrependCountryCode("")
70 | }
71 | }
72 | }
73 |
74 | fun updateDecimalPoints(count: Int) {
75 | viewModelScope.launch {
76 | settingsRepository.setDecimalPoints(count)
77 | _decimalPoints.value = count
78 | }
79 | }
80 |
81 | fun updateEvaluateResultMode(mode: Int) {
82 | viewModelScope.launch {
83 | settingsRepository.setEvaluateResultMode(mode)
84 | _evalResultMode.value = mode
85 | }
86 | }
87 |
88 | fun updateAppMode(mode: AppMode) {
89 | viewModelScope.launch {
90 | contextMenuOptionsRepository.setCurrentAppMode(mode)
91 | _appMode.value = mode
92 | }
93 | }
94 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/corphish/quicktools/viewmodels/TextCountViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.corphish.quicktools.viewmodels
2 |
3 | import androidx.lifecycle.ViewModel
4 | import androidx.lifecycle.viewModelScope
5 | import kotlinx.coroutines.flow.MutableStateFlow
6 | import kotlinx.coroutines.flow.asStateFlow
7 | import kotlinx.coroutines.launch
8 |
9 | class TextCountViewModel : ViewModel() {
10 | private val _text = MutableStateFlow("")
11 | val text = _text.asStateFlow()
12 |
13 | private val _characterCount = MutableStateFlow(0)
14 | val characterCount = _characterCount.asStateFlow()
15 |
16 | private val _letterCount = MutableStateFlow(0)
17 | val letterCount = _letterCount.asStateFlow()
18 |
19 | private val _digitCount = MutableStateFlow(0)
20 | val digitCount = _digitCount.asStateFlow()
21 |
22 | private val _wordCount = MutableStateFlow(0)
23 | val wordCount = _wordCount.asStateFlow()
24 |
25 | private val _spaceCount = MutableStateFlow(0)
26 | val spaceCount = _spaceCount.asStateFlow()
27 |
28 | private val _symbolCount = MutableStateFlow(0)
29 | val symbolCount = _symbolCount.asStateFlow()
30 |
31 | private val _wordFrequency = MutableStateFlow(listOf>())
32 | val wordFrequency = _wordFrequency.asStateFlow()
33 |
34 | fun setTextAndProcess(text: String) {
35 | viewModelScope.launch {
36 | _text.value = text
37 |
38 | val freq = mutableMapOf()
39 | val list = mutableListOf>()
40 |
41 | _characterCount.value = 0
42 | _letterCount.value = 0
43 | _digitCount.value = 0
44 | _wordCount.value = 0
45 | _spaceCount.value = 0
46 | _symbolCount.value = 0
47 | _wordFrequency.value = mutableListOf()
48 |
49 | _characterCount.value = text.length
50 | for (c in text.toCharArray()) {
51 | if (c == ' ') {
52 | _spaceCount.value += 1
53 | } else if (Character.isLetter(c)) {
54 | _letterCount.value += 1
55 | } else if (Character.isDigit(c)) {
56 | _digitCount.value += 1
57 | } else {
58 | _symbolCount.value += 1
59 | }
60 | }
61 |
62 | for (w in text.split(" ")) {
63 | if (w.isEmpty()) {
64 | continue
65 | }
66 |
67 | _wordCount.value += 1
68 | freq[w] = (freq[w] ?: 0) + 1
69 | }
70 |
71 | for (e in freq.entries) {
72 | list += e.key to e.value
73 | }
74 |
75 | list.sortBy { -it.second }
76 | _wordFrequency.value = list.toList()
77 | }
78 | }
79 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/corphish/quicktools/viewmodels/TextReplacementViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.corphish.quicktools.viewmodels
2 |
3 | import androidx.compose.ui.text.TextRange
4 | import androidx.lifecycle.ViewModel
5 | import androidx.lifecycle.viewModelScope
6 | import com.corphish.quicktools.text.TextReplacementManager
7 | import kotlinx.coroutines.flow.MutableStateFlow
8 | import kotlinx.coroutines.flow.asStateFlow
9 | import kotlinx.coroutines.launch
10 |
11 | class TextReplacementViewModel : ViewModel() {
12 | private lateinit var _textReplacementManager: TextReplacementManager
13 |
14 | // Selected text can be edited further by the user
15 | private val _mainText = MutableStateFlow("")
16 | val mainText = _mainText.asStateFlow()
17 |
18 | // Find and replace text states
19 | private val _findText = MutableStateFlow("")
20 | private val _replaceText = MutableStateFlow("")
21 | private val _ignoreCase = MutableStateFlow(false)
22 | val findText = _findText.asStateFlow()
23 | val replaceText = _replaceText.asStateFlow()
24 | val ignoreCase = _ignoreCase.asStateFlow()
25 |
26 | // Counter states
27 | private val _counterIndex = MutableStateFlow(0)
28 | private val _counterTotal = MutableStateFlow(0)
29 | val counterIndex = _counterIndex.asStateFlow()
30 | val counterTotal = _counterTotal.asStateFlow()
31 |
32 | // Find range state
33 | private val _findRanges = MutableStateFlow(listOf())
34 | val findRanges = _findRanges.asStateFlow()
35 |
36 | // Undo and redo states
37 | private val _undoState = MutableStateFlow(false)
38 | private val _redoState = MutableStateFlow(false)
39 | val undoState = _undoState.asStateFlow()
40 | val redoState = _redoState.asStateFlow()
41 |
42 | fun initializeWith(originalString: String) {
43 | _textReplacementManager = TextReplacementManager(originalString)
44 | _mainText.value = originalString
45 | }
46 |
47 | private fun invokeFind() {
48 | if (_findText.value.isNotEmpty()) {
49 | _findRanges.value = findText(_mainText.value, _findText.value, _ignoreCase.value)
50 | _counterIndex.value = 0
51 | _counterTotal.value = _findRanges.value.size
52 | } else {
53 | _findRanges.value = listOf()
54 | _counterIndex.value = 0
55 | _counterTotal.value = 0
56 | }
57 |
58 | _undoState.value = _textReplacementManager.canUndo()
59 | _redoState.value = _textReplacementManager.canRedo()
60 | }
61 |
62 | fun undo() {
63 | viewModelScope.launch {
64 | _mainText.value = _textReplacementManager.undo()
65 | invokeFind()
66 | }
67 | }
68 |
69 | fun redo() {
70 | viewModelScope.launch {
71 | _mainText.value = _textReplacementManager.redo()
72 | invokeFind()
73 | }
74 | }
75 |
76 | fun reset() {
77 | viewModelScope.launch {
78 | _mainText.value = _textReplacementManager.reset()
79 | _findRanges.value = listOf()
80 | _counterIndex.value = 0
81 | _counterTotal.value = 0
82 | _undoState.value = _textReplacementManager.canUndo()
83 | _redoState.value = _textReplacementManager.canRedo()
84 | _findText.value = ""
85 | _replaceText.value = ""
86 | }
87 | }
88 |
89 | fun replaceFirst() {
90 | viewModelScope.launch {
91 | _mainText.value = _textReplacementManager.replaceOne(
92 | _findRanges.value[_counterIndex.value],
93 | _replaceText.value
94 | )
95 | invokeFind()
96 | }
97 | }
98 |
99 | fun replaceAll() {
100 | viewModelScope.launch {
101 | _mainText.value = _textReplacementManager.replaceAll(
102 | _findText.value,
103 | _replaceText.value,
104 | _ignoreCase.value
105 | )
106 | invokeFind()
107 | }
108 | }
109 |
110 | fun setFindText(text: String) {
111 | viewModelScope.launch {
112 | _findText.value = text
113 | invokeFind()
114 | }
115 | }
116 |
117 | fun setReplaceText(text: String) {
118 | viewModelScope.launch {
119 | _replaceText.value = text
120 | }
121 | }
122 |
123 | fun setIgnoreCase(state: Boolean) {
124 | viewModelScope.launch {
125 | _ignoreCase.value = state
126 | invokeFind()
127 | }
128 | }
129 |
130 | fun updateMainText(text: String) {
131 | viewModelScope.launch {
132 | _mainText.value = text
133 | _textReplacementManager.updateText(text)
134 | _undoState.value = _textReplacementManager.canUndo()
135 | _redoState.value = _textReplacementManager.canRedo()
136 | }
137 | }
138 |
139 | fun decrementCounter() {
140 | viewModelScope.launch {
141 | _counterIndex.value = (_counterIndex.value - 1) % _counterTotal.value
142 | if (_counterIndex.value < 0) {
143 | _counterIndex.value = _counterTotal.value - 1
144 | }
145 | }
146 | }
147 |
148 | fun incrementCounter() {
149 | viewModelScope.launch {
150 | _counterIndex.value = (_counterIndex.value + 1) % _counterTotal.value
151 | }
152 | }
153 |
154 | private fun findText(
155 | mainInput: String,
156 | findText: String,
157 | ignoreCase: Boolean
158 | ): List {
159 | val result = mutableListOf()
160 | var index = -findText.length
161 | do {
162 | index = mainInput.indexOf(
163 | findText,
164 | startIndex = index + findText.length,
165 | ignoreCase = ignoreCase
166 | )
167 | if (index != -1) {
168 | result += TextRange(start = index, end = index + findText.length)
169 | }
170 | } while (index >= 0)
171 |
172 | return result
173 | }
174 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/corphish/quicktools/viewmodels/WUPViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.corphish.quicktools.viewmodels
2 |
3 | import androidx.lifecycle.ViewModel
4 | import androidx.lifecycle.viewModelScope
5 | import com.corphish.quicktools.data.Constants
6 | import com.corphish.quicktools.data.Result
7 | import com.corphish.quicktools.repository.SettingsRepository
8 | import dagger.hilt.android.lifecycle.HiltViewModel
9 | import kotlinx.coroutines.flow.MutableStateFlow
10 | import kotlinx.coroutines.flow.StateFlow
11 | import kotlinx.coroutines.launch
12 | import javax.inject.Inject
13 |
14 | @HiltViewModel
15 | class WUPViewModel @Inject constructor(private val settingsRepository: SettingsRepository): ViewModel() {
16 | // Nullable type to denote when phone number is invalid
17 | private val _processedPhoneNumber = MutableStateFlow>(Result.Initial)
18 | val processedPhoneNumber: StateFlow> = _processedPhoneNumber
19 |
20 | fun determinePhoneNumber(data: String?) {
21 | viewModelScope.launch {
22 | if (data == null) {
23 | _processedPhoneNumber.value = Result.Error
24 | } else {
25 | val modified = specialCharactersRemovedFrom(data)
26 | val regex = Regex(Constants.PHONE_NUMBER_REGEX)
27 | if (regex.matches(modified)) {
28 | _processedPhoneNumber.value = Result.Success(countryCodedNumber(modified))
29 | } else {
30 | _processedPhoneNumber.value = Result.Error
31 | }
32 | }
33 | }
34 | }
35 |
36 | private fun countryCodedNumber(phoneNumber: String): String {
37 | return if (settingsRepository.getPrependCountryCodeEnabled()) {
38 | val code = settingsRepository.getPrependCountryCode()
39 | if (code == null) {
40 | phoneNumber
41 | } else if (phoneNumber.startsWith(code)) {
42 | // If the number already starts with country code, no need to append.
43 | phoneNumber
44 | } else if (phoneNumber.startsWith("+")) {
45 | // If the number starts with some country code that is not the user specified
46 | // country code, it must be considered.
47 | phoneNumber
48 | } else {
49 | "$code$phoneNumber"
50 | }
51 | } else {
52 | phoneNumber
53 | }
54 | }
55 |
56 | private fun specialCharactersRemovedFrom(phoneNumber: String) : String {
57 | var res = phoneNumber
58 | for (char in Constants.PHONE_NUMBER_SPECIAL_CHARACTERS) {
59 | res = res.replace(char, "")
60 | }
61 |
62 | return res
63 | }
64 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_copy.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_done.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_done_all.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_find_and_replace.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
11 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_next.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_numbers.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_open_in_new.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_previous.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_redo.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_reset.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_save.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_text_count.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_text_transform.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_undo.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_whatsapp.xml:
--------------------------------------------------------------------------------
1 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/corphish/TextTools/743de19a692652e3f6d050e8d978180be0e66fc6/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/corphish/TextTools/743de19a692652e3f6d050e8d978180be0e66fc6/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/corphish/TextTools/743de19a692652e3f6d050e8d978180be0e66fc6/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/corphish/TextTools/743de19a692652e3f6d050e8d978180be0e66fc6/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/corphish/TextTools/743de19a692652e3f6d050e8d978180be0e66fc6/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/corphish/TextTools/743de19a692652e3f6d050e8d978180be0e66fc6/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/corphish/TextTools/743de19a692652e3f6d050e8d978180be0e66fc6/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/corphish/TextTools/743de19a692652e3f6d050e8d978180be0e66fc6/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/corphish/TextTools/743de19a692652e3f6d050e8d978180be0e66fc6/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/corphish/TextTools/743de19a692652e3f6d050e8d978180be0e66fc6/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/resources.properties:
--------------------------------------------------------------------------------
1 | unqualifiedResLocale=en-US
--------------------------------------------------------------------------------
/app/src/main/res/values-es/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Herramientas de texto
4 | Colección de funciones útiles relacionadas con texto a las que se puede acceder desde muchas aplicaciones dentro del sistema Android seleccionando un texto dentro de ellas. Las opciones admitidas aparecen en el menú contextual que aparece después de seleccionar un texto. Es posible que tengas que revelarlos presionando el ícono de 3 puntos en el menú contextual. La lista de funciones compatibles seguirá creciendo.
5 | Seleccione la función que le gustaría realizar. Texto seleccionado - %1$s
6 | Caracteristicas
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/values-ko/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 텍스트 툴즈
4 | 다양한 앱들에서 사용할 수 있는, 텍스트를 선택하면 나오는 안드로이드의 컨텍스트 메뉴 기능을 확장해 선택한 텍스트와 관련해 유용한 기능들을 사용할 수 있도록 합니다. 컨텍스트 메뉴의 3개의 점 버튼을 누르는 것을 통해 접근할 수 있으며, 더 다양한 기능을 추가할 예정입니다.
5 | 선택하신 %1$s 에 대해 실행하고자 하는 기능을 선택하세요.
6 | 기능
7 | 컨텍스트 메뉴 옵션:
8 | 왓츠앱 관련
9 | 왓츠앱
10 | 선택한 연락처를 저장하지 않고 바로 왓츠앱으로 전송
11 | 선택한 연락처로 저장 없이 메시지를 전송합니다. 일회성으로 정보를 보내는 데 유용합니다.
12 | 수식 계산
13 | 선택한 수식의 결과 계산
14 | 계산하기
15 | 선택한 수식을 계산해 결과로 대체합니다. 편집 가능한 텍스트 영역에서만 동작합니다.
16 | 계산 가능한 올바른 수식 표현이 아닙니다.
17 | 기능 시험해보기
18 | - 텍스트 필드에 임의의 전화번호를 입력해보세요.\n- 길게 누르고 블록 영역을 조절해, 입력한 전화번호를 선택하세요.\n- [T\u00B2] 의 왓츠앱을 선택하세요. 보이지 않는 경우, 3개의 점을 눌러 해당 버튼이 나타나게 해야 합니다.\n- 기기에 왓츠앱이 설치되어 있고, 전화번호의 사용자가 왓츠앱을 사용중인 경우 입력한 번호 상대방과의 대화창이 열립니다.
19 | - 텍스트 필드에 (1+2^3+4*5)의 괄호 안과 같은 간단한 수식을 입력해보세요.\n- 길게 누르고 블록 영역을 조절해, 입력한 수식을 선택하세요.\n- [T\u00B2] 의 계산하기를 선택하세요. 보이지 않는 경우, 3개의 점을 눌러 해당 버튼이 나타나게 해야 합니다.\n- 인식 가능한 형태로 입력한 수식의 결과를 확인 가능합니다.
20 | 예상치 못한 에러가 발생했습니다.
21 | 잘 따라오셨습니다! 이제 어느 앱에서도 텍스트를 선택할 수 있다면 같은 작업을 하실 수 있습니다.
22 | 제대로 동작하지 않는 경우, GitHub의 Issue에 해당 문제를 등록해 다음 버전에서 문제를 해결하도록 할 수 있습니다. 지금 진행하시겠습니까?
23 | 작동했습니다.
24 | 작동에 문제가 발생했습니다.
25 | 앱 정보
26 | 버전: %1$s (%2$d)
27 | 오픈 소스
28 | 이 앱은 오픈 소스 프로젝트입니다. 활발한 개발 참여나 문제 보고가 다음 버전에 큰 도움이 됩니다!
29 | 확인하기
30 | 유효하지 않은 형식의 전화번호입니다.
31 | 설정
32 | 전화번호 앞에 자동으로 국가코드 추가하기
33 | 선택한 전화번호에 국가코드가 없는 경우, 이 옵션을 활성화할 경우 입력한 국가코드를 자동으로 앞에 추가합니다.
34 | 메시징
35 | 자동추가 국가코드값
36 | 소수점 자리수
37 | 수식 계산 결과로 정수가 아닌 값이 나올 경우, 표시할 소수점의 자리수입니다. 결과가 정수면 표시하지 않습니다.
38 | 앱 버전목록
39 | 변환하기
40 | 변환하기
41 | 선택한 문자열을 어떤 형식으로 변환할지 선택해주세요.
42 | 문자열 변환하기
43 | 선택한 문자열의 대소문자 변환, 다른 문자로 둘러싸기 등의 다양한 기능을 제공합니다.
44 | 문자열 둘러싸기
45 | 대소문자 변경
46 | 선택한 문자열을 감쌀 기호를 선택하세요.
47 | \'\' (작은 따옴표)
48 | \"\" (큰따옴표)
49 | () (소괄호)
50 | {} (중괄호)
51 | [] (대괄호)
52 | 사용자 지정 기호 또는 문자열
53 | 문자열을 감싸는데 단순 문자뿐만이 아니라 숫자 및 다른 문자열도 사용 가능합니다. 커스텀으로 입력하신 문자열 중 앞쪽 절반이 선택한 문자열의 앞을, 뒤쪽 절반이 뒤를 감싸게 됩니다.
54 | 커스텀 문자열을 절반으로 나눠 선택한 문자열의 앞뒤를 감싸는 구조이기 때문에, 전체 커스텀 문자열은 짝수개의 문자들이어야 합니다.
55 | 선택한 문자열을 어떤 대소문자 형식으로 변환할지 선택해주세요. (한글은 대소문자 개념이 없습니다)
56 | 대문자
57 | 소문자
58 | 제목 (첫 글자만 대문자)
59 | 제목 (전체 대문자)
60 | 대소문자 무작위
61 | 도움을 주신 분들
62 | 미리보기 활성화
63 | 결과: %1$s
64 | 이 기능은 편집 가능한 텍스트 영역에서만 동작합니다.
65 | 프리뷰는 전체 중 일부만을 보여주며, 변환은 선택하신 모든 영역에 전체 적용됩니다.
66 |
67 |
--------------------------------------------------------------------------------
/app/src/main/res/values-pt-rBR/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Ferramentas de Texto
4 | Coleção de funções úteis relacionadas a texto que podem ser acessadas em vários aplicativos dentro do sistema Android selecionando um texto dentro dele. As opções suportadas aparecem no menu de contexto que aparece depois de selecionar um texto. Você pode ter que revelá-los pressionando o ícone de 3 pontos no menu de contexto. A lista de funções suportadas continuará crescendo.
5 | Coleção de funções úteis relacionadas com texto que podem ser acessadas em vários aplicativos dentro do sistema Android selecionando um texto dentro no campo de texto
6 | Selecione a função que você gostaria de executar. Texto selecionado - %1$s
7 | Funções
8 | Opção do menu contexto:
9 | [T\u00B2] WhatsApp
10 | WhatsApp
11 | Conversar com números não salvos no WhatsApp
12 | Escreva mensagens facilmente para qualquer número não salvo no WhatsApp. Útil se você quer mandar mensagens apenas brevemente.
13 | [T\u00B2] Avaliar
14 | Calcular expressões matemáticas
15 | Avaliar
16 | Avalie expressões matemáticas no ato e substitua a expressão por seu resultado. Funciona apenas no texto selecionado que possa ser editado.
17 | Expressão matemática inválida
18 | Experimentar
19 | - No campo de texto abaixo, digite qualquer número de telefone.\n- Toque e mantenha pressionado o número de telefone que acabou de digitar.\n- Toque na opção [T\u00B2] WhatsApp. Se a opção não estiver visível, toque nos três pontos para revelá-la.\n- Se o número de telefone estiver disponível no WhatsApp (e se o WhatsApp estiver instalado), ele deverá abrir o bate-papo no WhatsApp.
20 | - No campo de texto abaixo, digite qualquer expressão matemática (por exemplo, 1 + 2 + 3).\n- Toque e mantenha pressionada a expressão que acabou de digitar.\n- Toque na opção [T\u00B2] Avaliar. Se a opção não estiver visível, toque nos 3 pontos para revelá-la.\n- Se a expressão matemática digitada for válida, você verá o resultado.
21 | Ocorreu um erro inesperado
22 | Ótimo! Você pode fazer o mesmo dentro de qualquer aplicativo, contanto que você possa selecionar um texto.
23 | Se não funcionou, você pode levantar uma questão no repositório do Github. Gostaria de fazer isso agora?
24 | Funcionou
25 | Não funcionou
26 | Informação do app
27 | Versão - %1$s (%2$d)
28 | Código Aberto
29 | Este app tem código aberto! Sinta-se à vontade para contribuir, levantar problemas e conferir.
30 | Dar uma olhada
31 | Número de telefone inválido
32 | Configurações
33 | Adicionar código do país automaticamente
34 | Se o número de telefone selecionado não tiver um código de país, o código de país predefinido será adicionado automaticamente se habilitado.
35 | Mensagens
36 | Código de país predefinido
37 | Casas decimais
38 | Número de dígitos mostrados após o ponto decimal como resultado de uma função avaliada. Se o resultado for um inteiro (número inteiro sem número decimal), o ponto decimal não será mostrado.
39 | Página de compilações
40 | [T\u00B2] Transformar
41 | Transformar
42 | Selecione como você deseja transformar o texto
43 | Transformar texto
44 | Fornece várias maneiras de transformar o texto selecionado como mudar maiúsculas/minúsculas, cercá-lo com outro texto e muito mais.
45 | Envolver texto
46 | Alterar caixa
47 | Selecione o caractere com o qual você gostaria de encapsular o texto
48 | \'\' (Vírgula invertida)
49 | \"\" (Vírgula invertida dupla)
50 | () (Parênteses)
51 | {} (Chaves)
52 | [] (Colchetes)
53 | Invólucro personalizado
54 | O invólucro de texto personalizado deve conter um número par de letras. A primeira metade irá ao começo e a segunda metade irá ao final do texto selecionado.
55 | Um invólucro de texto personalizado deve conter um número par de letras
56 | Escolha como você deseja transformar o texto
57 | MAIÚSCULAS
58 | minúsculas
59 | Maiúscula (apenas primeira palavra)
60 | Maiúsculas (Todas As Palavras)
61 | CaPiTaLiZaÇãO AlEaTóRiA
62 | Colaboradores
63 | Ativar prévia
64 | Resultado - %1$s
65 | Esta opção funciona apenas em entradas de texto editáveis
66 | O resultado pré-visualizado está abreviado. A transformação será aplicada a toda a seleção.
67 | Doar
68 | Você gosta deste app? Se quiser, você pode fazer uma pequena doação. Isso ajuda a manter-me motivado e a trabalhar neste projeto para torná-lo ainda melhor.
69 | [T\u00B2] Salvar
70 | Salvar texto
71 | Salvar o texto selecionado para que você possa acessá-lo mais tarde
72 | Permite que você salve o texto selecionado (para algum arquivo) para que você possa acessá-lo mais tarde.
73 | Como arquivo .txt
74 | Como uma nota
75 | Nenhum aplicativo de anotações encontrado
76 | Tomada de notas não suportada
77 | O texto selecionado foi salvo com sucesso
78 | Falha ao salvar o texto
79 | Localizar e Substituir
80 | [T\u00B2] Localizar e Substituir
81 | Substituir
82 | Substituir Todos
83 | Localizar Próxima (%1$d/%2$d)
84 | Substituir
85 | Localizar
86 | Ignorar maiúsculas/minúsculas
87 | Desfazer
88 | Salvar
89 | Aplicar
90 | Redefinir
91 | Refazer
92 | Encontre rapidamente um determinado texto e os substitua pelo texto da sua escolha no texto selecionado.
93 | Selecione o que você gostaria de fazer com o resultado
94 | Substituir o texto selecionado pelo resultado
95 | Acrescentar resultado ao texto selecionado
96 | Copiar resultado para área de transferência
97 | Lembrar desta escolha
98 | Não tenho certeza, pergunte-me da próxima vez
99 | Manipulação de resultados
100 | Copiado para a área de transferência
101 | Ordenar linhas
102 | Contar texto
103 | Repetir texto
104 | Remover texto
105 | Adicionar prefixo/sufixo
106 | Número de linhas
107 | Inverter texto
108 | Pré-visualizar
109 | Entrar com texto
110 | Nenhum
111 | Remover primeira
112 | Remover última
113 | Remover tudo
114 | Prefixo
115 | Sufixo
116 | Decorar texto
117 | Negrito (serif)
118 | Itálico (serif)
119 | Negrito/Itálico (serif)
120 | Negrito (sans)
121 | Itálico (sans)
122 | Negrito/Itálico (sans)
123 | Riscado (curto)
124 | Riscado (longo)
125 | Palavras inversas
126 | Linhas inversas
127 | Remover espaços em branco
128 | Remover quebras de linha
129 | Remover linhas em branco
130 | Remover palavras duplicadas
131 | Remover palavras duplicadas (maiúsculas e minúsculas)
132 | Remover linhas duplicadas
133 | Remover linhas duplicadas (maiúsculas e minúsculas)
134 | Cursiva
135 | Contagem de Texto
136 | Caracteres
137 | Letras
138 | Palavras
139 | Dígitos
140 | Espaços
141 | Outros símbolos
142 | Frequência de palavras
143 | Estatísticas
144 | [T\u00B2] Contagem de Texto
145 | Obtenha uma análise detalhada sobre a contagem de caracteres, contagem de palavras, contagem de letras e muito mais para o texto selecionado.
146 | Tutorial
147 | Opções do menu de contexto
148 | Escolha como você deseja que as opções do menu de contexto apareçam quando você selecionar o texto. Ele também pode ser alterado mais tarde nas configurações.
149 | Opção única
150 | Apenas uma opção de menu de contexto aparecerá que quando clicado revelará todas as ações suportadas
151 | Múltiplas opções
152 | Todas as ações suportadas pelo aplicativo serão mostradas no menu de contexto
153 | Também é possível escolher quais opções devem estar disponíveis no menu de contexto.
154 | Editar
155 | Feito
156 | App
157 | Código de país inválido
158 |
159 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/app/src/main/res/values/font_certs.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 | - @array/com_google_android_gms_fonts_certs_dev
20 | - @array/com_google_android_gms_fonts_certs_prod
21 |
22 |
23 | -
24 | MIIEqDCCA5CgAwIBAgIJANWFuGx90071MA0GCSqGSIb3DQEBBAUAMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAeFw0wODA0MTUyMzM2NTZaFw0zNTA5MDEyMzM2NTZaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBANbOLggKv+IxTdGNs8/TGFy0PTP6DHThvbbR24kT9ixcOd9W+EaBPWW+wPPKQmsHxajtWjmQwWfna8mZuSeJS48LIgAZlKkpFeVyxW0qMBujb8X8ETrWy550NaFtI6t9+u7hZeTfHwqNvacKhp1RbE6dBRGWynwMVX8XW8N1+UjFaq6GCJukT4qmpN2afb8sCjUigq0GuMwYXrFVee74bQgLHWGJwPmvmLHC69EH6kWr22ijx4OKXlSIx2xT1AsSHee70w5iDBiK4aph27yH3TxkXy9V89TDdexAcKk/cVHYNnDBapcavl7y0RiQ4biu8ymM8Ga/nmzhRKya6G0cGw8CAQOjgfwwgfkwHQYDVR0OBBYEFI0cxb6VTEM8YYY6FbBMvAPyT+CyMIHJBgNVHSMEgcEwgb6AFI0cxb6VTEM8YYY6FbBMvAPyT+CyoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJANWFuGx90071MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADggEBABnTDPEF+3iSP0wNfdIjIz1AlnrPzgAIHVvXxunW7SBrDhEglQZBbKJEk5kT0mtKoOD1JMrSu1xuTKEBahWRbqHsXclaXjoBADb0kkjVEJu/Lh5hgYZnOjvlba8Ld7HCKePCVePoTJBdI4fvugnL8TsgK05aIskyY0hKI9L8KfqfGTl1lzOv2KoWD0KWwtAWPoGChZxmQ+nBli+gwYMzM1vAkP+aayLe0a1EQimlOalO762r0GXO0ks+UeXde2Z4e+8S/pf7pITEI/tP+MxJTALw9QUWEv9lKTk+jkbqxbsh8nfBUapfKqYn0eidpwq2AzVp3juYl7//fKnaPhJD9gs=
25 |
26 |
27 |
28 | -
29 | MIIEQzCCAyugAwIBAgIJAMLgh0ZkSjCNMA0GCSqGSIb3DQEBBAUAMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDAeFw0wODA4MjEyMzEzMzRaFw0zNjAxMDcyMzEzMzRaMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBAKtWLgDYO6IIrgqWbxJOKdoR8qtW0I9Y4sypEwPpt1TTcvZApxsdyxMJZ2JORland2qSGT2y5b+3JKkedxiLDmpHpDsz2WCbdxgxRczfey5YZnTJ4VZbH0xqWVW/8lGmPav5xVwnIiJS6HXk+BVKZF+JcWjAsb/GEuq/eFdpuzSqeYTcfi6idkyugwfYwXFU1+5fZKUaRKYCwkkFQVfcAs1fXA5V+++FGfvjJ/CxURaSxaBvGdGDhfXE28LWuT9ozCl5xw4Yq5OGazvV24mZVSoOO0yZ31j7kYvtwYK6NeADwbSxDdJEqO4k//0zOHKrUiGYXtqw/A0LFFtqoZKFjnkCAQOjgdkwgdYwHQYDVR0OBBYEFMd9jMIhF1Ylmn/Tgt9r45jk14alMIGmBgNVHSMEgZ4wgZuAFMd9jMIhF1Ylmn/Tgt9r45jk14aloXikdjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLR29vZ2xlIEluYy4xEDAOBgNVBAsTB0FuZHJvaWQxEDAOBgNVBAMTB0FuZHJvaWSCCQDC4IdGZEowjTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBAUAA4IBAQBt0lLO74UwLDYKqs6Tm8/yzKkEu116FmH4rkaymUIE0P9KaMftGlMexFlaYjzmB2OxZyl6euNXEsQH8gjwyxCUKRJNexBiGcCEyj6z+a1fuHHvkiaai+KL8W1EyNmgjmyy8AW7P+LLlkR+ho5zEHatRbM/YAnqGcFh5iZBqpknHf1SKMXFh4dd239FJ1jWYfbMDMy3NS5CTMQ2XFI1MvcyUTdZPErjQfTbQe3aDQsQcafEQPD+nqActifKZ0Np0IS9L9kR/wbNvyz6ENwPiTrjV2KRkEjH78ZMcUQXg0L3BYHJ3lc69Vs5Ddf9uUGGMYldX3WfMBEmh/9iFBDAaTCK
30 |
31 |
32 |
--------------------------------------------------------------------------------
/app/src/main/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #99D6FF
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Text Tools
3 | Collection of useful text related functions which can be accessed within many apps inside the Android System by selecting a text within it. The supported options appear in the context menu that appears after selecting a text. You may have to reveal them by pressing the 3 dots icon in the context menu. The list of supported functions will keep on growing.
4 | Collection of useful text related functions which can be accessed within many apps inside the Android System by selecting a text within it
5 | Select the function that you would like to perform. Selected text - %1$s
6 |
7 | Features
8 | Context menu option:
9 |
10 | [T\u00B2] WhatsApp
11 | WhatsApp
12 | Text unsaved numbers in WhatsApp
13 | Easily text any unsaved number in WhatsApp. Useful if you want to text people for temporary purposes.
14 |
15 | [T\u00B2] Evaluate
16 | Evaluate mathematical expressions
17 | Evaluate
18 | Evaluate mathematical expressions inline and replace the expression with its result. Works only on text selected that can be edited.
19 | Invalid mathematical expression
20 | Try It Out
21 |
22 | - In the below text field, type any phone number.\n- Tap and hold on the phone number you just typed.\n- Tap on the [T\u00B2] WhatsApp option. If the option is not visible, tap on the 3 dots to reveal it.\n- If the phone number is available on WhatsApp (and if WhatsApp is installed) it should open the chat in WhatsApp.
23 | - In the below text field, type any mathematical expression (for example, 1 + 2 + 3).\n- Tap and hold on the expression you just typed.\n- Tap on the [T\u00B2] Evaluate option. If the option is not visible, tap on the 3 dots to reveal it.\n- If the mathematical expression typed was valid, you should see its result.
24 | Unexpected error occurred
25 | Great! You can do the same inside any app as long as you can select a text.
26 | If it did not work, you can raise an issue in the Github repo. Would you like to do that now?
27 | It worked
28 | It did not work
29 |
30 | App Info
31 | Version - %1$s (%2$d)
32 |
33 | Open Source
34 | This app is open source! Feel free to contribute, raise issue and check it out.
35 | Check it out
36 | Invalid phone number
37 | Settings
38 | Automatically add country code
39 | If the selected phone number does not have any country code, the preset country code will be automatically added if enabled.
40 | Messaging
41 | Preset country code
42 | Decimal points
43 | Number of digits shown after the decimal point as a result of an evaluate function. If the result is an integer (whole number with no decimal), the decimal point will not be shown.
44 | Releases page
45 |
46 | [T\u00B2] Transform
47 | Transform
48 | Select how you want to transform the text
49 | Transform text
50 | Provides various ways to transform the selected text like changing its case, wrapping it with other text and more.
51 | Wrap text
52 | Change case
53 |
54 | Select the character with which you would like to wrap the text
55 | \'\' (Single inverted commas)
56 | \"\" (Double inverted commas)
57 | () (First brackets)
58 | {} (Curly braces)
59 | [] (Square brackets)
60 | Custom wrapper text
61 | The custom wrapper text should contain even number of letters. The first half of the custom wrapper text will be added to beginning of the selected text and the remaining will be added to the end of the selected text.
62 | Custom wrapper text must contain even number of letters
63 |
64 | Select how you want to change the case
65 | UPPER CASE
66 | lower case
67 | Title case (first word only)
68 | Title Case (All Words)
69 | rAnDOm cASe
70 | Contributors
71 |
72 | Enable preview
73 | Result - %1$s
74 |
75 | This option works only on editable text input
76 | Previewed result is truncated. Transformation will be applied to the entire selection.
77 | Donate
78 | Do you like this app? If so, you can make a small donation. This helps to keep me motivated and work on this project to make it even better.
79 | [T\u00B2] Save
80 | Save text
81 | Save the selected text so that you can access it later
82 | Lets you save the selected text (to some file) so that you can access it later.
83 | As .txt file
84 | As a note
85 | No note taking apps found
86 | Note taking is not supported
87 | Successfully saved the selected text
88 | Failed to save text
89 |
90 | Find & Replace
91 | [T\u00B2] Find & Replace
92 | Replace
93 | Replace All
94 | Find Next (%1$d/%2$d)
95 | Replace
96 | Find
97 | Ignore case
98 | Undo
99 | Save
100 | Apply
101 | Reset
102 | Redo
103 | Quickly find certain text and replace them with text of your choice within the selected text.
104 | Select what you would like to do with the result
105 | Replace selected text with result
106 | Append result to the selected text
107 | Copy result to the clipboard
108 | Remember this choice
109 | Not sure, ask me next time
110 | Result handling
111 | Copied to clipboard
112 | Sort lines
113 | Count text
114 | Repeat text
115 | Remove text
116 | Add prefix/suffix
117 | Number lines
118 | Reverse text
119 | Preview
120 | Input text
121 | None
122 | Remove first
123 | Remove last
124 | Remove all
125 | Prefix
126 | Suffix
127 | Decorate text
128 | Bold (serif)
129 | Italic (serif)
130 | Bold/Italic (serif)
131 | Bold (sans)
132 | Italic (sans)
133 | Bold/Italic (sans)
134 | Strikethrough (short)
135 | Strikethrough (long)
136 | Reverse words
137 | Reverse lines
138 | Remove whitespaces
139 | Remove line breaks
140 | Remove empty lines
141 | Remove duplicate words
142 | Remove duplicate words (case sensitive)
143 | Remove duplicate lines
144 | Remove duplicate lines (case sensitive)
145 | Cursive
146 | Text Count
147 | Characters
148 | Letters
149 | Words
150 | Digits
151 | Spaces
152 | Other symbols
153 | Word frequency
154 | Statistics
155 | [T\u00B2] Text Count
156 | Get detailed analysis about character count, word count, letter count and more for the selected text.
157 | Onboarding
158 | Context menu options
159 | Choose how you would like the context menu options to appear when you select text. It can also be changed later in the settings.
160 | Single option
161 | Only one context menu option will appear which when clicked will reveal all the supported actions
162 | Multiple options
163 | All the actions supported by the app will be shown in the context menu
164 | It is also possible to choose which options should be available in the context menu.
165 | Edit
166 | Done
167 | App
168 | Invalid country code
169 | Line break
170 | Replace whitespace
171 | After certain characters
172 | After certain words
173 | Copy to clipboard
174 | SimulationActivity
175 | Simulate
176 | If the Text Tools options are not seen for the selected text in an other app, that text can be copied and pasted here for simulation of text tools functions.
177 | Enter or paste the text here and then click the button below to simulate various functions of Text Tools. Any text produced by a function of Text Tools (like Text Transform) will not be reflected here but rather be copied to the clipboard.
178 | Max characters per line
179 | Squeeze
180 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/assets/eval.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/corphish/TextTools/743de19a692652e3f6d050e8d978180be0e66fc6/assets/eval.gif
--------------------------------------------------------------------------------
/assets/find_replace.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/corphish/TextTools/743de19a692652e3f6d050e8d978180be0e66fc6/assets/find_replace.gif
--------------------------------------------------------------------------------
/assets/save.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/corphish/TextTools/743de19a692652e3f6d050e8d978180be0e66fc6/assets/save.gif
--------------------------------------------------------------------------------
/assets/single_options.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/corphish/TextTools/743de19a692652e3f6d050e8d978180be0e66fc6/assets/single_options.png
--------------------------------------------------------------------------------
/assets/text_count.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/corphish/TextTools/743de19a692652e3f6d050e8d978180be0e66fc6/assets/text_count.gif
--------------------------------------------------------------------------------
/assets/text_transform.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/corphish/TextTools/743de19a692652e3f6d050e8d978180be0e66fc6/assets/text_transform.gif
--------------------------------------------------------------------------------
/assets/wup_1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/corphish/TextTools/743de19a692652e3f6d050e8d978180be0e66fc6/assets/wup_1.gif
--------------------------------------------------------------------------------
/assets/wup_2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/corphish/TextTools/743de19a692652e3f6d050e8d978180be0e66fc6/assets/wup_2.gif
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | plugins {
3 | id("com.android.application") version "8.8.2" apply false
4 | id("org.jetbrains.kotlin.android") version "1.9.24" apply false
5 | id("com.google.dagger.hilt.android") version "2.51.1" apply false
6 | id("org.jetbrains.kotlin.plugin.compose") version "2.0.0" apply false
7 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/corphish/TextTools/743de19a692652e3f6d050e8d978180be0e66fc6/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sat Aug 19 10:53:41 IST 2023
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/metadata/en-US/full_description.txt:
--------------------------------------------------------------------------------
1 | Collection of useful text related tools that can be accessed from the context menu that appears on text selection. The feature list will keep on growing. Let me know if you have any ideas.
2 |
3 | Current Features
4 | 1. Text unsaved numbers in WhatsApp - Often we find ourselves needing to text/send file/share location to someone for temporary purposes (if you ask me, I face this a lot in situations where I go to a printing centre where they ask me to share the file that I want to print over WhatsApp, so I have to save their contact, refresh my WhatsApp contact list and then send them). Having a context menu option to directly open a WhatsApp chat from their phone number would save a lot of time and keep your phonebook clean, right?
5 | 2. Evaluate mathematical expressions inline - We may often find ourselves to mathematically calculate something while texting someone (probably while making plans). Having an option to perform math calculations in line without needing to open calculator could save time.
6 | 3. Transform text - Transform the selected text inline. Supported transformations include changing the case, wrapping it with text etc.
7 | 4. Text count - Get detailed analysis about character count, word count, letter count and more for the selected text.
8 | 5. Save text to a file - Lets you save the selected text into a file so that it can be accessed later.
9 | 6. Find & Replace - Quickly find certain text and replace them with text of your choice within the selected text.
10 |
11 | Where are screenshots?
12 | The app adds capabilities to text selection wherever supported in the Android System, there is nothing much to do inside the app, hence very less screenshots.
13 |
14 | Installation Warning
15 | While installing the app (you have to sideload), Google Play may block the installation saying it has never seen the app before. While it is true (from Google Play POV), kindly proceed with installing the app. If you have doubts about the app being malicious, you are free to verify the same from the source code (it would not be open-source if the app was malicious in the first place, right?).
--------------------------------------------------------------------------------
/metadata/en-US/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/corphish/TextTools/743de19a692652e3f6d050e8d978180be0e66fc6/metadata/en-US/images/icon.png
--------------------------------------------------------------------------------
/metadata/en-US/images/phoneScreenshots/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/corphish/TextTools/743de19a692652e3f6d050e8d978180be0e66fc6/metadata/en-US/images/phoneScreenshots/1.png
--------------------------------------------------------------------------------
/metadata/en-US/images/phoneScreenshots/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/corphish/TextTools/743de19a692652e3f6d050e8d978180be0e66fc6/metadata/en-US/images/phoneScreenshots/2.png
--------------------------------------------------------------------------------
/metadata/en-US/images/phoneScreenshots/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/corphish/TextTools/743de19a692652e3f6d050e8d978180be0e66fc6/metadata/en-US/images/phoneScreenshots/3.png
--------------------------------------------------------------------------------
/metadata/en-US/images/phoneScreenshots/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/corphish/TextTools/743de19a692652e3f6d050e8d978180be0e66fc6/metadata/en-US/images/phoneScreenshots/4.png
--------------------------------------------------------------------------------
/metadata/en-US/images/phoneScreenshots/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/corphish/TextTools/743de19a692652e3f6d050e8d978180be0e66fc6/metadata/en-US/images/phoneScreenshots/5.png
--------------------------------------------------------------------------------
/metadata/en-US/images/phoneScreenshots/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/corphish/TextTools/743de19a692652e3f6d050e8d978180be0e66fc6/metadata/en-US/images/phoneScreenshots/6.png
--------------------------------------------------------------------------------
/metadata/en-US/images/phoneScreenshots/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/corphish/TextTools/743de19a692652e3f6d050e8d978180be0e66fc6/metadata/en-US/images/phoneScreenshots/7.png
--------------------------------------------------------------------------------
/metadata/en-US/images/phoneScreenshots/8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/corphish/TextTools/743de19a692652e3f6d050e8d978180be0e66fc6/metadata/en-US/images/phoneScreenshots/8.png
--------------------------------------------------------------------------------
/metadata/en-US/short_description.txt:
--------------------------------------------------------------------------------
1 | Useful text related functions that can be done on the selected text
--------------------------------------------------------------------------------
/metadata/en-US/title.txt:
--------------------------------------------------------------------------------
1 | Text Tools
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | @file:Suppress("UnstableApiUsage")
2 |
3 | pluginManagement {
4 | repositories {
5 | google()
6 | mavenCentral()
7 | gradlePluginPortal()
8 | }
9 | }
10 | dependencyResolutionManagement {
11 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
12 | repositories {
13 | google()
14 | mavenCentral()
15 | }
16 | }
17 |
18 | rootProject.name = "Quick Tools"
19 | include(":app")
20 |
--------------------------------------------------------------------------------