├── .github
├── FUNDING.yml
├── dependbot.yml
└── workflows
│ ├── ci.yml
│ └── github-release.yml
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── SECURITY.md
├── app
├── .gitignore
├── build.gradle.kts
├── libs
│ ├── common.jar
│ ├── exception.jar
│ └── ffmpeg-kit.aar
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── nasahacker
│ │ └── convertit
│ │ ├── AudioConversionTest.kt
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── ic_launcher-playstore.png
│ ├── java
│ │ └── com
│ │ │ └── nasahacker
│ │ │ └── convertit
│ │ │ ├── App.kt
│ │ │ ├── MainActivity.kt
│ │ │ ├── dto
│ │ │ ├── AudioBitrate.kt
│ │ │ ├── AudioCodec.kt
│ │ │ ├── AudioFile.kt
│ │ │ ├── AudioFormat.kt
│ │ │ ├── BottomNavigation.kt
│ │ │ └── ConvertitPreview.kt
│ │ │ ├── service
│ │ │ └── ConvertItService.kt
│ │ │ ├── ui
│ │ │ ├── component
│ │ │ │ ├── AboutAppContent.kt
│ │ │ │ ├── AudioItem.kt
│ │ │ │ ├── BottomNavigationBar.kt
│ │ │ │ ├── CommunityIcon.kt
│ │ │ │ ├── ContactItem.kt
│ │ │ │ ├── DialogConvert.kt
│ │ │ │ ├── DialogDeleteItem.kt
│ │ │ │ ├── DonationCard.kt
│ │ │ │ ├── MainAppBar.kt
│ │ │ │ ├── NoFilesFoundCard.kt
│ │ │ │ ├── RatingDialog.kt
│ │ │ │ └── SectionTitle.kt
│ │ │ ├── navigation
│ │ │ │ └── AppNavHost.kt
│ │ │ ├── screen
│ │ │ │ ├── AboutScreen.kt
│ │ │ │ ├── ConvertsScreen.kt
│ │ │ │ └── HomeScreen.kt
│ │ │ ├── theme
│ │ │ │ ├── Color.kt
│ │ │ │ ├── Theme.kt
│ │ │ │ └── Type.kt
│ │ │ └── viewmodel
│ │ │ │ └── AppViewModel.kt
│ │ │ └── util
│ │ │ ├── AppConfig.kt
│ │ │ └── AppUtil.kt
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ ├── audio_ic.xml
│ │ ├── baseline_info_24.xml
│ │ ├── baseline_stop_24.xml
│ │ ├── discord_ic.xml
│ │ ├── github_ic.xml
│ │ ├── home_filled.xml
│ │ ├── home_outlined.xml
│ │ ├── ic_launcher_background.xml
│ │ ├── play_ic.xml
│ │ ├── share_ic.xml
│ │ ├── storage_filled.xml
│ │ ├── storage_outlined.xml
│ │ └── telegram_ic.xml
│ │ ├── mipmap-anydpi-v26
│ │ └── ic_launcher.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_background.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_monochrome.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_background.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_monochrome.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_background.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_monochrome.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_background.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_monochrome.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_background.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_monochrome.png
│ │ ├── values-bn
│ │ └── strings.xml
│ │ ├── values-night
│ │ └── colors.xml
│ │ ├── values
│ │ ├── colors.xml
│ │ ├── font_certs.xml
│ │ ├── strings.xml
│ │ └── themes.xml
│ │ └── xml
│ │ ├── backup_rules.xml
│ │ ├── data_extraction_rules.xml
│ │ └── file_paths.xml
│ └── test
│ └── java
│ └── com
│ └── nasahacker
│ └── convertit
│ └── ExampleUnitTest.kt
├── build.gradle.kts
├── gradle.properties
├── gradle
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── images
├── image1.png
├── image2.png
└── image3.png
└── settings.gradle.kts
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: CodeWithTamim
2 | patreon: CodeWithTamim
3 |
--------------------------------------------------------------------------------
/.github/dependbot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "gradle" # See documentation for possible values
9 | directory: "/" # Location of package manifests
10 | schedule:
11 | interval: "daily"
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: Build & deploy Android app
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | tag_name:
7 | description: "Tag name for the release (e.g., v1.0.0)"
8 | required: true
9 | release_name:
10 | description: "Release name (optional)"
11 | required: false
12 | default: ""
13 | description:
14 | description: "What's new in this release (Release Notes)"
15 | required: true
16 |
17 | jobs:
18 | deployAndroid:
19 | permissions:
20 | contents: write
21 | name: Build & deploy Android release
22 | runs-on: ubuntu-latest
23 |
24 | steps:
25 | - name: Checkout repository
26 | uses: actions/checkout@v3
27 |
28 | - name: Setup Java
29 | uses: actions/setup-java@v4
30 | with:
31 | distribution: 'temurin'
32 | java-version: '17'
33 |
34 | - name: Setup Android environment
35 | uses: android-actions/setup-android@v3
36 |
37 | - name: Decode Keystore
38 | uses: timheuer/base64-to-file@v1.2.4
39 | id: android_keystore
40 | with:
41 | fileName: "android_keystore.jks"
42 | encodedString: ${{ secrets.KEYSTORE_BASE64 }}
43 |
44 | - name: Build APK and AAB
45 | run: |
46 | chmod 755 gradlew
47 | ./gradlew clean assembleRelease bundleRelease \
48 | -Pandroid.injected.signing.store.file=${{ steps.android_keystore.outputs.filePath }} \
49 | -Pandroid.injected.signing.store.password=${{ secrets.KEYSTORE_PASSWORD }} \
50 | -Pandroid.injected.signing.key.alias=${{ secrets.KEY_ALIAS }} \
51 | -Pandroid.injected.signing.key.password=${{ secrets.KEY_PASSWORD }}
52 |
53 | - name: Create 'whatsnew' Directory and Add Localized Release Notes
54 | run: |
55 | mkdir -p distribution/whatsnew
56 | echo "${{ github.event.inputs.description }}" > distribution/whatsnew/whatsnew-en-US
57 |
58 | - name: Upload APKs to GitHub Release
59 | uses: softprops/action-gh-release@v1
60 | with:
61 | tag_name: ${{ github.event.inputs.tag_name }}
62 | files: |
63 | app/build/outputs/apk/release/app-release.apk
64 | env:
65 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
66 |
67 | - name: Upload AAB to Play Store
68 | uses: r0adkll/upload-google-play@v1.1.3
69 | with:
70 | serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
71 | packageName: com.nasahacker.convertit
72 | releaseFiles: app/build/outputs/bundle/release/app-release.aab
73 | track: production
74 | releaseName: ${{ github.event.inputs.release_name }}
75 | whatsNewDirectory: distribution/whatsnew
76 |
--------------------------------------------------------------------------------
/.github/workflows/github-release.yml:
--------------------------------------------------------------------------------
1 | name: Build & Upload to GitHub Releases
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | tag_name:
7 | description: "Tag name for the release (e.g., v1.0.0)"
8 | required: true
9 | release_name:
10 | description: "Release name (optional)"
11 | required: false
12 | default: ""
13 | description:
14 | description: "What's new in this release (Release Notes)"
15 | required: true
16 | prerelease:
17 | description: "Is this a pre-release? (true/false)"
18 | required: false
19 | default: "false"
20 |
21 | jobs:
22 | buildAndUpload:
23 | permissions:
24 | contents: write
25 | name: Build & Upload Release to GitHub
26 | runs-on: ubuntu-latest
27 |
28 | steps:
29 | - name: Checkout repository
30 | uses: actions/checkout@v3
31 |
32 | - name: Setup Java
33 | uses: actions/setup-java@v4
34 | with:
35 | distribution: 'temurin'
36 | java-version: '17'
37 |
38 | - name: Setup Android environment
39 | uses: android-actions/setup-android@v3
40 |
41 | - name: Decode Keystore
42 | uses: timheuer/base64-to-file@v1.2.4
43 | id: android_keystore
44 | with:
45 | fileName: "android_keystore.jks"
46 | encodedString: ${{ secrets.KEYSTORE_BASE64 }}
47 |
48 | - name: Build APK
49 | run: |
50 | chmod 755 gradlew
51 | ./gradlew clean assembleRelease \
52 | -Pandroid.injected.signing.store.file=${{ steps.android_keystore.outputs.filePath }} \
53 | -Pandroid.injected.signing.store.password=${{ secrets.KEYSTORE_PASSWORD }} \
54 | -Pandroid.injected.signing.key.alias=${{ secrets.KEY_ALIAS }} \
55 | -Pandroid.injected.signing.key.password=${{ secrets.KEY_PASSWORD }}
56 |
57 | - name: Prepare Release Notes
58 | run: echo "${{ github.event.inputs.description }}" > release_notes.txt
59 |
60 | - name: Upload APK to GitHub Release
61 | uses: softprops/action-gh-release@v1
62 | with:
63 | tag_name: ${{ github.event.inputs.tag_name }}
64 | release_name: ${{ github.event.inputs.release_name }}
65 | body_path: release_notes.txt
66 | files: |
67 | app/build/outputs/apk/release/app-release.apk
68 | prerelease: ${{ github.event.inputs.prerelease }}
69 | env:
70 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle/
2 | /build/
3 | app/build/
4 | local.properties
5 | .idea/
6 | /.idea/caches
7 | .idea/libraries
8 | .idea/modules.xml
9 | .idea/workspace.xml
10 | .idea/navEditor.xml
11 | .idea/assetWizardSettings.xml
12 | .kotlinc/
13 | .kotlinc_caches/
14 | .DS_Store
15 | /captures
16 | .externalNativeBuild
17 | .cxx
18 | *.iml
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 |
2 | # Contributing to ConvertIt
3 |
4 | Thank you for contributing to [ConvertIt](https://github.com/CodeWithTamim/Convertit)!
5 |
6 | ## Getting Started
7 |
8 | 1. **Fork and Clone**: Fork the repo, then clone it locally:
9 |
10 |
11 | ```bash
12 |
13 | git clone https://github.com/your-username/Convertit.git
14 | ```
15 | 3. **Branch**: Create a feature or fix branch:
16 |
17 |
18 | ```bash
19 |
20 | git checkout -b feature/your-feature-name
21 | ```
22 |
23 | ## Reporting Issues
24 |
25 | - **Description**: Clearly describe the issue.
26 | - **Steps**: Include steps to reproduce.
27 | - **Environment**: Mention Android version, device, and ConvertIt version.
28 |
29 | ## Code Contributions
30 |
31 | - **Commit Format**:
32 | ```markdown
33 | [Fix] Short description
34 | [Feature] Short description
35 | ```
36 | - **Style**: Follow Kotlin best practices.
37 |
38 | ## Pull Requests
39 |
40 | 1. **Test** your changes.
41 | 2. **Describe** your PR with relevant details.
42 | 3. **Labels**: Apply `[feature]`, `[fix]`, etc., if available.
43 |
44 | Thank you for helping make ConvertIt better!
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for describing the origin of the Work and
142 | reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 🎵 ConvertIt - Audio Converter App
2 | 
3 | 
4 | 
5 | 
6 | [](https://app.fossa.com/projects/git%2Bgithub.com%2FCodeWithTamim%2FConvertit?ref=badge_shield)
7 |
8 | **ConvertIt** is an **ad-free** and **easy-to-use** audio and video-to-audio converter app that helps users convert audio and video files across different formats seamlessly. With **support for popular audio formats** and **customizable bitrates**, ConvertIt delivers high-quality conversions, powered by FFmpeg, all within a clean, user-friendly interface.
9 |
10 | ---
11 |
12 | ## 🚀 Features
13 |
14 | - 🎧 **Audio & Video to Audio Format Conversion**: Effortlessly convert between **FLAC, ALAC, MP3, WAV, AAC, OGG, M4A, AIFF, OPUS, WMA, MKA, SPX**, and more.
15 | - 🎚️ **Bitrate Selection**: Choose from bitrates such as **64k, 96k, 128k, 192k, 256k, 320k, 512k, 768k, and 1024k** for output quality.
16 | - 📱 **Ad-Free**: Enjoy a completely **ad-free** experience.
17 | - 🔧 **FFmpeg Integration**: Built-in FFmpeg ensures **reliable and high-quality** conversions.
18 | - 📐 **Modern Architecture**: Developed using **Kotlin** with **MVVM architecture** for better scalability and maintainability.
19 | - 🧭 **Smooth Navigation**: Implemented using **Android Navigation Component** for easy and smooth in-app navigation.
20 | - 🖥️ **User-Friendly UI**: Designed with simplicity and ease of use in mind, offering a clean and intuitive interface.
21 |
22 | ---
23 |
24 | ## 🖼️ Screenshots Of App
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | ---
33 |
34 | ## 📱 Get It on Play Store
35 |
36 | Download **ConvertIt** directly from the Play Store:
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | ---
45 |
46 | # Certificate Fingerprints for Verification
47 |
48 | To ensure the integrity and authenticity of the app, you can verify the certificate fingerprints for the following platforms:
49 |
50 | ## 1. GitHub
51 |
52 | **MD5 Fingerprint:**
53 |
54 | ```
55 | D2:61:D3:81:CB:02:C3:66:1B:7B:E0:D9:D0:8E:17:BA
56 |
57 | ```
58 | **SHA-1 Fingerprint:**
59 |
60 | ```
61 | E4:7F:C6:11:3A:EB:D5:49:3E:AC:91:88:24:3D:F9:B8:08:F1:FC:9C
62 |
63 | ```
64 | **SHA-256 Fingerprint:**
65 |
66 | ```
67 | 34:52:C6:EF:73:DA:36:31:FA:4E:85:B7:F3:7B:6E:23:F1:64:D9:86:0D:9C:AF:6F:F1:BB:95:DC:89:D3:CF:D4
68 |
69 | ```
70 | ## 2. Google Play Store
71 |
72 | **MD5 Fingerprint:**
73 |
74 | ```
75 | EA:4D:D4:A0:F8:26:11:E7:A7:72:6E:26:1D:A3:60:62
76 |
77 | ```
78 | **SHA-1 Fingerprint:**
79 |
80 | ```
81 | B5:53:A9:A1:8F:AF:4E:E9:3A:58:BA:45:68:1C:A4:A5:87:F4:09:A9
82 | ```
83 | **SHA-256 Fingerprint:**
84 | ```
85 | 30:63:A9:0F:E1:24:58:15:66:50:46:5A:50:41:56:4A:5C:C9:96:6E:B6:8C:29:81:E0:FC:39:B6:A4:62:ED:41
86 | ```
87 |
88 | ### How to Verify the Fingerprints:
89 | 1. Obtain the certificate fingerprint from the app or build package you are installing.
90 | 2. Compare the obtained fingerprints with the ones listed above.
91 | 3. Ensure that the fingerprints match exactly to verify the integrity and authenticity of the app.
92 |
93 | This will ensure that you're installing the original, untampered version of the app.
94 |
95 |
96 | ---
97 |
98 |
99 | Every contribution, big or small, helps keep this project alive and ad-free. Thank you! 💖
100 |
101 | ---
102 |
103 | ## 🛠️ Requirements
104 |
105 | - **Android**: Version **5.0 (Lollipop)** or later
106 | - **Kotlin**: Version **2.1.0** or later
107 | - **Android Studio**: Version **Ladybug** or later
108 |
109 | ---
110 |
111 | ## 🛠️ Installation
112 |
113 | Follow these steps to install and run the app locally:
114 |
115 | 1. Clone the repository:
116 | ```bash
117 | git clone https://github.com/CodeWithTamim/ConvertIt.git
118 | ```
119 |
120 | 2. Open the project in **Android Studio**:
121 | ```bash
122 | open ConvertIt
123 | ```
124 |
125 | 3. Build and run the app on an Android device or emulator.
126 |
127 | ---
128 |
129 | ## 🤝 Contributions
130 |
131 | Contributions are always welcome! Here's how you can contribute:
132 |
133 | 1. **Fork** the repository.
134 | 2. Create a **new branch** with your changes.
135 | 3. **Submit a pull request**.
136 |
137 | Whether it's bug fixes, new features, or improvements, we appreciate your contribution!
138 |
139 | ---
140 | ## Contributors
141 |
142 | 
143 |
144 | ---
145 |
146 | ## ⚖️ License
147 |
148 | This project is licensed under the **Apache License 2.0**. See the [LICENSE](LICENSE) file for more information.
149 |
150 | ---
151 |
152 | Developed with ❤️ by **Tamim**.
153 | ---
154 | ## Star History
155 |
156 | [](https://www.star-history.com/#codewithtamim/convertit&Date)
157 | ---
158 |
159 | [](https://app.fossa.com/projects/git%2Bgithub.com%2FCodeWithTamim%2FConvertit?ref=badge_large)
160 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Supported Versions
4 |
5 | Below are the supported versions of ConvertIt. Security updates are provided only for the current version.
6 |
7 | | Version | Supported |
8 | |-------------------------| ------------------ |
9 | | 1.0.6-beta | :white_check_mark: |
10 | | 1.0.0-beta – 1.0.9-beta | :x: |
11 |
12 | ## Reporting a Vulnerability
13 |
14 | If you discover a security vulnerability in ConvertIt, please report it by following these steps:
15 |
16 | 1. **Email:** Send an email to [security@nasahacker.com](mailto:security@nasahacker.com) with details about the vulnerability.
17 | 2. **Response Time:** Expect an initial response within 48 hours, with regular updates as we work on addressing the issue.
18 | 3. **Vulnerability Assessment:** After reviewing the reported issue, we will communicate whether the vulnerability is accepted, our remediation plan, and estimated patch timelines.
19 |
20 | We appreciate your help in making ConvertIt a safer and more secure app for everyone!
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | /release
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.application)
3 | alias(libs.plugins.kotlin.android)
4 | alias(libs.plugins.kotlin.compose)
5 | }
6 |
7 | android {
8 | val packageName = "com.nasahacker.convertit"
9 | namespace = packageName
10 | compileSdk = 35
11 |
12 | defaultConfig {
13 | applicationId = packageName
14 | minSdk = 21
15 | targetSdk = 35
16 | versionCode = 29
17 | versionName = "1.2.8-lts"
18 | multiDexEnabled = true
19 |
20 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
21 | }
22 |
23 | buildTypes {
24 | release {
25 | isMinifyEnabled = true
26 | isShrinkResources = true
27 | proguardFiles(
28 | getDefaultProguardFile("proguard-android-optimize.txt"),
29 | "proguard-rules.pro",
30 | )
31 | }
32 | }
33 |
34 |
35 | compileOptions {
36 | sourceCompatibility = JavaVersion.VERSION_11
37 | targetCompatibility = JavaVersion.VERSION_11
38 | }
39 | kotlinOptions {
40 | jvmTarget = JavaVersion.VERSION_11.toString()
41 | }
42 | buildFeatures {
43 | compose = true
44 | }
45 |
46 | buildFeatures {
47 | buildConfig = true
48 | }
49 | sourceSets {
50 | getByName("main") {
51 | jniLibs.srcDirs("libs")
52 | }
53 | }
54 | packaging {
55 | jniLibs {
56 | useLegacyPackaging = true
57 | }
58 | }
59 | }
60 |
61 | dependencies {
62 | implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar", "*.jar"))))
63 | implementation(libs.androidx.core.ktx)
64 | implementation(libs.androidx.lifecycle.runtime.ktx)
65 | implementation(libs.androidx.activity.compose)
66 | implementation(platform(libs.androidx.compose.bom))
67 | implementation(libs.androidx.ui)
68 | implementation(libs.androidx.ui.graphics)
69 | implementation(libs.androidx.ui.tooling.preview)
70 | implementation(libs.androidx.material3)
71 | implementation(libs.androidx.runtime.livedata)
72 | testImplementation(libs.junit)
73 | androidTestImplementation(libs.androidx.junit)
74 | androidTestImplementation(libs.androidx.espresso.core)
75 | androidTestImplementation(platform(libs.androidx.compose.bom))
76 | androidTestImplementation(libs.androidx.ui.test.junit4)
77 | debugImplementation(libs.androidx.ui.tooling)
78 | debugImplementation(libs.androidx.ui.test.manifest)
79 | implementation(libs.androidx.ui.text.google.fonts)
80 | implementation(libs.androidx.navigation.compose)
81 | implementation(libs.androidx.material.icons.extended)
82 | }
83 |
--------------------------------------------------------------------------------
/app/libs/common.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheByteArray/Convertit/06377bee0ba08ddb416ff7c308a9e3bab458b23e/app/libs/common.jar
--------------------------------------------------------------------------------
/app/libs/exception.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheByteArray/Convertit/06377bee0ba08ddb416ff7c308a9e3bab458b23e/app/libs/exception.jar
--------------------------------------------------------------------------------
/app/libs/ffmpeg-kit.aar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheByteArray/Convertit/06377bee0ba08ddb416ff7c308a9e3bab458b23e/app/libs/ffmpeg-kit.aar
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
23 | ## Remove all logs on production
24 | #-assumenosideeffects class android.util.Log {
25 | # public static *** d(...);
26 | # public static *** i(...);
27 | # public static *** v(...);
28 | # public static *** w(...);
29 | # public static *** e(...);
30 | #}
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/nasahacker/convertit/AudioConversionTest.kt:
--------------------------------------------------------------------------------
1 | package com.nasahacker.convertit
2 |
3 | import android.content.Context
4 | import androidx.test.core.app.ApplicationProvider
5 | import androidx.test.ext.junit.runners.AndroidJUnit4
6 | import com.nasahacker.convertit.dto.AudioBitrate
7 | import com.nasahacker.convertit.dto.AudioFormat
8 | import com.nasahacker.convertit.util.AppUtil
9 | import org.junit.After
10 | import org.junit.Assert.assertTrue
11 | import org.junit.Before
12 | import org.junit.Test
13 | import org.junit.runner.RunWith
14 | import java.io.File
15 | import java.util.concurrent.CountDownLatch
16 | import java.util.concurrent.TimeUnit
17 |
18 | /**
19 | * @author Tamim Hossain
20 | * @email tamimh.dev@gmail.com
21 | * @license Apache-2.0
22 | *
23 | * ConvertIt is a free and easy-to-use audio converter app.
24 | * It supports popular audio formats like MP3 and M4A.
25 | * With options for high-quality bitrates ranging from 128k to 320k,
26 | * ConvertIt offers a seamless conversion experience tailored to your needs.
27 | */
28 |
29 | @RunWith(AndroidJUnit4::class)
30 | class AudioConversionTest {
31 | private lateinit var context: Context
32 | private lateinit var testFile: File
33 | private lateinit var outputDir: File
34 |
35 | @Before
36 | fun setup() {
37 | context = ApplicationProvider.getApplicationContext()
38 | outputDir = File(context.getExternalFilesDir(null), "test_output")
39 | outputDir.mkdirs()
40 |
41 | testFile = File(outputDir, "sample.flac")
42 | context.assets.open("test_files/sample.flac").use { input ->
43 | testFile.outputStream().use { output ->
44 | input.copyTo(output)
45 | }
46 | }
47 | }
48 |
49 | @After
50 | fun cleanup() {
51 | // Clean up test files
52 | testFile.delete()
53 | outputDir.listFiles()?.forEach { it.delete() }
54 | outputDir.delete()
55 | }
56 |
57 | @Test
58 | fun testConvertToAllFormats() {
59 | val formats = AudioFormat.values()
60 | val bitrates = AudioBitrate.values()
61 |
62 | formats.forEach { format ->
63 | bitrates.forEach { bitrate ->
64 | testConversion(format, bitrate)
65 | }
66 | }
67 | }
68 |
69 | private fun testConversion(format: AudioFormat, bitrate: AudioBitrate) {
70 | val latch = CountDownLatch(1)
71 | var conversionSuccess = false
72 | var errorMessage: String? = null
73 |
74 | AppUtil.convertAudio(
75 | context = context,
76 | playbackSpeed = "1.0",
77 | uris = listOf(android.net.Uri.fromFile(testFile)),
78 | outputFormat = format,
79 | bitrate = bitrate,
80 | onSuccess = { outputPaths ->
81 | // Verify output file exists and has correct extension
82 | val outputFile = File(outputPaths.first())
83 | assertTrue(
84 | "Output file should exist for format ${format.name} and bitrate ${bitrate.name}",
85 | outputFile.exists()
86 | )
87 | assertTrue(
88 | "Output file should have correct extension for format ${format.name}",
89 | outputFile.extension.equals(format.extension.trimStart('.'), ignoreCase = true)
90 | )
91 | conversionSuccess = true
92 | latch.countDown()
93 | },
94 | onFailure = { error ->
95 | errorMessage = error
96 | latch.countDown()
97 | },
98 | onProgress = { /* Progress updates not needed for test */ }
99 | )
100 |
101 | assertTrue(
102 | "Conversion timed out for format ${format.name} and bitrate ${bitrate.name}",
103 | latch.await(30, TimeUnit.SECONDS)
104 | )
105 |
106 | assertTrue(
107 | "Conversion failed for format ${format.name} and bitrate ${bitrate.name}: $errorMessage",
108 | conversionSuccess
109 | )
110 | }
111 | }
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/nasahacker/convertit/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.nasahacker.convertit
2 |
3 | import androidx.test.ext.junit.runners.AndroidJUnit4
4 | import androidx.test.platform.app.InstrumentationRegistry
5 | import org.junit.Assert.*
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | /**
10 | * Instrumented test, which will execute on an Android device.
11 | *
12 | * See [testing documentation](http://d.android.com/tools/testing).
13 | */
14 | @RunWith(AndroidJUnit4::class)
15 | class ExampleInstrumentedTest {
16 | @Test
17 | fun useAppContext() {
18 | // Context of the app under test.
19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
20 | assertEquals("com.nasahacker.convertit", appContext.packageName)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
23 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
40 |
41 |
46 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheByteArray/Convertit/06377bee0ba08ddb416ff7c308a9e3bab458b23e/app/src/main/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/app/src/main/java/com/nasahacker/convertit/App.kt:
--------------------------------------------------------------------------------
1 | package com.nasahacker.convertit
2 |
3 | import android.app.Application
4 | import android.app.NotificationChannel
5 | import android.app.NotificationManager
6 | import android.content.Context
7 | import android.os.Build
8 | import androidx.core.app.NotificationManagerCompat
9 | import com.nasahacker.convertit.util.AppConfig.CHANNEL_ID
10 | import com.nasahacker.convertit.util.AppConfig.CHANNEL_NAME
11 |
12 | /**
13 | * @author Tamim Hossain
14 | * @email tamimh.dev@gmail.com
15 | * @license Apache-2.0
16 | *
17 | * ConvertIt is a free and easy-to-use audio converter app.
18 | * It supports popular audio formats like MP3 and M4A.
19 | * With options for high-quality bitrates ranging from 128k to 320k,
20 | * ConvertIt offers a seamless conversion experience tailored to your needs.
21 | */
22 |
23 | class App : Application() {
24 | companion object {
25 | lateinit var application: App
26 | }
27 |
28 | override fun attachBaseContext(base: Context?) {
29 | super.attachBaseContext(base)
30 | application = this
31 | }
32 |
33 | override fun onCreate() {
34 | super.onCreate()
35 | initChannel()
36 | clearCacheOnStartup()
37 | }
38 |
39 | private fun initChannel() {
40 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
41 | NotificationManagerCompat.from(this).createNotificationChannel(
42 | NotificationChannel(
43 | CHANNEL_ID,
44 | CHANNEL_NAME,
45 | NotificationManager.IMPORTANCE_DEFAULT,
46 | ),
47 | )
48 | }
49 | }
50 |
51 | private fun clearCacheOnStartup() {
52 | try {
53 | cacheDir.listFiles()?.forEach { file ->
54 | if (file.isFile) {
55 | file.delete()
56 | }
57 | }
58 | } catch (e: Exception) {
59 | e.printStackTrace()
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/app/src/main/java/com/nasahacker/convertit/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.nasahacker.convertit
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import androidx.activity.enableEdgeToEdge
7 | import androidx.compose.foundation.layout.padding
8 | import androidx.compose.material3.Scaffold
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.runtime.getValue
11 | import androidx.compose.ui.Modifier
12 | import androidx.compose.ui.tooling.preview.Preview
13 | import androidx.navigation.compose.currentBackStackEntryAsState
14 | import androidx.navigation.compose.rememberNavController
15 | import com.nasahacker.convertit.ui.component.BottomNavigationBar
16 | import com.nasahacker.convertit.ui.component.MainAppBar
17 | import com.nasahacker.convertit.ui.navigation.AppNavHost
18 | import com.nasahacker.convertit.ui.theme.AppTheme
19 | import com.nasahacker.convertit.util.AppUtil
20 |
21 | /**
22 | * @author Tamim Hossain
23 | * @email tamimh.dev@gmail.com
24 | * @license Apache-2.0
25 | *
26 | * ConvertIt is a free and easy-to-use audio converter app.
27 | * It supports popular audio formats like MP3 and M4A.
28 | * With options for high-quality bitrates ranging from 128k to 320k,
29 | * ConvertIt offers a seamless conversion experience tailored to your needs.
30 | */
31 |
32 | class MainActivity : ComponentActivity() {
33 | override fun onCreate(savedInstanceState: Bundle?) {
34 | super.onCreate(savedInstanceState)
35 | enableEdgeToEdge()
36 | setContent {
37 | AppTheme {
38 | val navController = rememberNavController()
39 | val navBackStackEntry by navController.currentBackStackEntryAsState()
40 | val currentRoute = navBackStackEntry?.destination?.route
41 |
42 | Scaffold(
43 | topBar = {
44 | MainAppBar(
45 | onNavigateToAbout = { navController.navigate("about") },
46 | onNavigateBack = { navController.popBackStack() },
47 | isAboutScreen = currentRoute == "about"
48 | )
49 | },
50 | bottomBar = {
51 | if (currentRoute != "about") {
52 | BottomNavigationBar(navController = navController)
53 | }
54 | },
55 | ) { innerPadding ->
56 | AppNavHost(
57 | modifier = Modifier.padding(innerPadding),
58 | controller = navController,
59 | )
60 | }
61 | }
62 | }
63 |
64 | AppUtil.handleNotificationPermission(this)
65 | }
66 | }
67 |
68 |
--------------------------------------------------------------------------------
/app/src/main/java/com/nasahacker/convertit/dto/AudioBitrate.kt:
--------------------------------------------------------------------------------
1 | package com.nasahacker.convertit.dto
2 |
3 | /**
4 | * @author Tamim Hossain
5 | * @email tamimh.dev@gmail.com
6 | * @license Apache-2.0
7 | *
8 | * ConvertIt is a free and easy-to-use audio converter app.
9 | * It supports popular audio formats like MP3 and M4A.
10 | * With options for high-quality bitrates ranging from 128k to 320k,
11 | * ConvertIt offers a seamless conversion experience tailored to your needs.
12 | */
13 |
14 | enum class AudioBitrate(
15 | val bitrate: String,
16 | ) {
17 | BITRATE_64K("64k"),
18 | BITRATE_96K("96k"),
19 | BITRATE_128K("128k"),
20 | BITRATE_192K("192k"),
21 | BITRATE_256K("256k"),
22 | BITRATE_320K("320k"),
23 | BITRATE_512K("512k"),
24 | BITRATE_768K("768k"),
25 | BITRATE_1024K("1024k"),
26 | ;
27 |
28 | companion object {
29 | fun fromBitrate(bitrate: String?): AudioBitrate =
30 | entries.find { it.bitrate.equals(bitrate, ignoreCase = true) }
31 | ?: BITRATE_128K
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app/src/main/java/com/nasahacker/convertit/dto/AudioCodec.kt:
--------------------------------------------------------------------------------
1 | package com.nasahacker.convertit.dto
2 |
3 | /**
4 | * @author Tamim Hossain
5 | * @email tamimh.dev@gmail.com
6 | * @license Apache-2.0
7 | *
8 | * ConvertIt is a free and easy-to-use audio converter app.
9 | * It supports popular audio formats like MP3 and M4A.
10 | * With options for high-quality bitrates ranging from 128k to 320k,
11 | * ConvertIt offers a seamless conversion experience tailored to your needs.
12 | */
13 |
14 | enum class AudioCodec(
15 | val codec: String,
16 | ) {
17 | FLAC("flac"),
18 | MP3("libmp3lame"),
19 | M4A("aac"),
20 | WAV("pcm_s16le"),
21 | AAC("aac"),
22 | OGG("libvorbis"),
23 | OPUS("libopus"),
24 | AIFF("pcm_s16le"),
25 | WMA("wmav2"),
26 | MKA("libvorbis"),
27 | SPX("libspeex"),
28 | ;
29 |
30 | companion object {
31 | fun fromFormat(format: AudioFormat): AudioCodec =
32 | when (format) {
33 | AudioFormat.MP3 -> MP3
34 | AudioFormat.M4A -> M4A
35 | AudioFormat.WAV -> WAV
36 | AudioFormat.AAC -> AAC
37 | AudioFormat.OGG -> OGG
38 | AudioFormat.OPUS -> OPUS
39 | AudioFormat.AIFF -> AIFF
40 | AudioFormat.WMA -> WMA
41 | AudioFormat.MKA -> MKA
42 | AudioFormat.SPX -> SPX
43 | AudioFormat.FLAC -> FLAC
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/app/src/main/java/com/nasahacker/convertit/dto/AudioFile.kt:
--------------------------------------------------------------------------------
1 | package com.nasahacker.convertit.dto
2 |
3 | import java.io.File
4 |
5 | /**
6 | * @author Tamim Hossain
7 | * @email tamimh.dev@gmail.com
8 | * @license Apache-2.0
9 | *
10 | * ConvertIt is a free and easy-to-use audio converter app.
11 | * It supports popular audio formats like MP3 and M4A.
12 | * With options for high-quality bitrates ranging from 128k to 320k,
13 | * ConvertIt offers a seamless conversion experience tailored to your needs.
14 | */
15 |
16 | data class AudioFile(
17 | val name: String,
18 | val size: String,
19 | val file: File,
20 | )
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/nasahacker/convertit/dto/AudioFormat.kt:
--------------------------------------------------------------------------------
1 | package com.nasahacker.convertit.dto
2 |
3 | /**
4 | * @author Tamim Hossain
5 | * @email tamimh.dev@gmail.com
6 | * @license Apache-2.0
7 | *
8 | * ConvertIt is a free and easy-to-use audio converter app.
9 | * It supports popular audio formats like MP3 and M4A.
10 | * With options for high-quality bitrates ranging from 128k to 320k,
11 | * ConvertIt offers a seamless conversion experience tailored to your needs.
12 | */
13 |
14 | enum class AudioFormat(
15 | val extension: String,
16 | ) {
17 | FLAC(".flac"),
18 | MP3(".mp3"),
19 | M4A(".m4a"),
20 | AAC(".aac"),
21 | WAV(".wav"),
22 | OGG(".ogg"),
23 | OPUS(".opus"),
24 | AIFF(".aiff"),
25 | WMA(".wma"),
26 | MKA(".mka"),
27 | SPX(".spx"),
28 | ;
29 |
30 | companion object {
31 | fun fromExtension(extension: String?): AudioFormat =
32 | entries.find { it.extension.equals(extension, ignoreCase = true) }
33 | ?: MP3
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/main/java/com/nasahacker/convertit/dto/BottomNavigation.kt:
--------------------------------------------------------------------------------
1 | package com.nasahacker.convertit.dto
2 |
3 | /**
4 | * @author Tamim Hossain
5 | * @email tamimh.dev@gmail.com
6 | * @license Apache-2.0
7 | *
8 | * ConvertIt is a free and easy-to-use audio converter app.
9 | * It supports popular audio formats like MP3 and M4A.
10 | * With options for high-quality bitrates ranging from 128k to 320k,
11 | * ConvertIt offers a seamless conversion experience tailored to your needs.
12 | */
13 |
14 | sealed class BottomNavigation(
15 | val route: String,
16 | val label: String,
17 | ) {
18 | data object Home : BottomNavigation("home", "Home")
19 |
20 | data object Library : BottomNavigation("library ", "Library ")
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/main/java/com/nasahacker/convertit/dto/ConvertitPreview.kt:
--------------------------------------------------------------------------------
1 | package com.nasahacker.convertit.dto
2 |
3 | import android.content.res.Configuration
4 | import androidx.compose.ui.tooling.preview.Preview
5 |
6 | /**
7 | * Copyright (c) 2025
8 | * Created by: Tamim Hossain (tamim@thebytearray.org)
9 | * Created on: 4/6/25
10 | **/
11 |
12 |
13 | @Preview(
14 | name = "Light Preview",
15 | uiMode = Configuration.UI_MODE_NIGHT_NO,
16 | showSystemUi = true, showBackground = true
17 | )
18 |
19 | annotation class ConvertitLightPreview
20 |
21 | @Preview(
22 | name = "Dark Preview",
23 | uiMode = Configuration.UI_MODE_NIGHT_YES,
24 | showSystemUi = true, showBackground = true
25 | )
26 |
27 | annotation class ConvertitDarkPreview
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/app/src/main/java/com/nasahacker/convertit/service/ConvertItService.kt:
--------------------------------------------------------------------------------
1 | package com.nasahacker.convertit.service
2 |
3 | /**
4 | * @author Tamim Hossain
5 | * @email tamimh.dev@gmail.com
6 | * @license Apache-2.0
7 | *
8 | * ConvertIt is a free and easy-to-use audio converter app.
9 | * It supports popular audio formats like MP3 and M4A.
10 | * With options for high-quality bitrates ranging from 128k to 320k,
11 | * ConvertIt offers a seamless conversion experience tailored to your needs.
12 | */
13 |
14 | import android.Manifest
15 | import android.annotation.SuppressLint
16 | import android.app.Notification
17 | import android.app.PendingIntent
18 | import android.app.Service
19 | import android.content.Intent
20 | import android.content.pm.PackageManager
21 | import android.content.pm.ServiceInfo
22 | import android.net.Uri
23 | import android.os.Build
24 | import android.os.IBinder
25 | import android.util.Log
26 | import androidx.core.app.ActivityCompat
27 | import androidx.core.app.NotificationCompat
28 | import androidx.core.app.NotificationManagerCompat
29 | import androidx.core.app.ServiceCompat
30 | import com.nasahacker.convertit.R
31 | import com.nasahacker.convertit.dto.AudioBitrate
32 | import com.nasahacker.convertit.dto.AudioFormat
33 | import com.nasahacker.convertit.util.AppUtil
34 | import com.nasahacker.convertit.util.AppConfig.ACTION_STOP_SERVICE
35 | import com.nasahacker.convertit.util.AppConfig.AUDIO_FORMAT
36 | import com.nasahacker.convertit.util.AppConfig.AUDIO_PLAYBACK_SPEED
37 | import com.nasahacker.convertit.util.AppConfig.BITRATE
38 | import com.nasahacker.convertit.util.AppConfig.CHANNEL_ID
39 | import com.nasahacker.convertit.util.AppConfig.CONVERT_BROADCAST_ACTION
40 | import com.nasahacker.convertit.util.AppConfig.IS_SUCCESS
41 | import com.nasahacker.convertit.util.AppConfig.URI_LIST
42 | import kotlinx.coroutines.CoroutineScope
43 | import kotlinx.coroutines.Dispatchers
44 | import kotlinx.coroutines.launch
45 |
46 | class ConvertItService : Service() {
47 | companion object {
48 | private const val TAG = "ConvertItService"
49 | var isForegroundServiceStarted = false
50 | }
51 |
52 | private val notificationId = 1
53 |
54 | override fun onBind(intent: Intent?): IBinder? = null
55 |
56 | override fun onCreate() {
57 | super.onCreate()
58 | Log.i(TAG, "Service created - Process ID: ${android.os.Process.myPid()}")
59 | startForegroundServiceWithNotification()
60 | }
61 |
62 | override fun onStartCommand(
63 | intent: Intent?,
64 | flags: Int,
65 | startId: Int,
66 | ): Int {
67 | Log.i(
68 | TAG, "onStartCommand: Received intent with action: ${intent?.action}, startId: $startId"
69 | )
70 |
71 | if (intent?.action == ACTION_STOP_SERVICE) {
72 | Log.i(TAG, "Stopping service as per user request. startId: $startId")
73 | showCompletionNotification(false)
74 | broadcastConversionResult(Intent().apply { action = CONVERT_BROADCAST_ACTION }, false)
75 | stopForegroundService()
76 | return START_NOT_STICKY
77 | }
78 |
79 | val uriList: ArrayList? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
80 | intent?.getParcelableArrayListExtra(URI_LIST, Uri::class.java)
81 | } else {
82 | intent?.getParcelableArrayListExtra(URI_LIST)
83 | }
84 |
85 | val bitrate = AudioBitrate.fromBitrate(intent?.getStringExtra(BITRATE))
86 | val format = AudioFormat.fromExtension(intent?.getStringExtra(AUDIO_FORMAT))
87 | val speed = intent?.getStringExtra(AUDIO_PLAYBACK_SPEED) ?: "1.0"
88 |
89 | Log.d(
90 | TAG, """
91 | Conversion parameters:
92 | - Format: ${format.extension}
93 | - Bitrate: ${bitrate.bitrate}
94 | - Playback Speed: $speed
95 | - Number of files: ${uriList?.size ?: 0}
96 | - Files: ${uriList?.joinToString { it.lastPathSegment ?: "unknown" }}
97 | """.trimIndent()
98 | )
99 |
100 | if (uriList.isNullOrEmpty()) {
101 | Log.e(TAG, "No valid URIs provided. Stopping service. startId: $startId")
102 | showCompletionNotification(false)
103 | broadcastConversionResult(Intent().apply { action = CONVERT_BROADCAST_ACTION }, false)
104 | stopForegroundService()
105 | return START_NOT_STICKY
106 | }
107 |
108 | CoroutineScope(Dispatchers.IO).launch {
109 | try {
110 | Log.i(TAG, "Starting audio conversion for ${uriList.size} files. startId: $startId")
111 | AppUtil.convertAudio(
112 | context = this@ConvertItService,
113 | speed,
114 | uris = uriList,
115 | outputFormat = format,
116 | bitrate = bitrate,
117 | onSuccess = {
118 | Log.i(
119 | TAG,
120 | "Conversion completed successfully for all files. startId: $startId"
121 | )
122 | showCompletionNotification(true)
123 | broadcastConversionResult(Intent().apply {
124 | action = CONVERT_BROADCAST_ACTION
125 | }, true)
126 | stopForegroundService()
127 | },
128 | onFailure = { error ->
129 | Log.e(TAG, "Conversion failed with error: ${error}. startId: $startId")
130 | showCompletionNotification(false)
131 | broadcastConversionResult(Intent().apply {
132 | action = CONVERT_BROADCAST_ACTION
133 | }, false)
134 | stopForegroundService()
135 | },
136 | onProgress = { progress ->
137 | Log.v(TAG, "Conversion progress: $progress%. startId: $startId")
138 | updateNotification(progress)
139 | },
140 | )
141 | } catch (e: Exception) {
142 | Log.e(TAG, "Unexpected error during conversion. startId: $startId", e)
143 | showCompletionNotification(false)
144 | broadcastConversionResult(
145 | Intent().apply { action = CONVERT_BROADCAST_ACTION }, false
146 | )
147 | stopForegroundService()
148 | }
149 | }
150 |
151 | return START_STICKY
152 | }
153 |
154 | private fun broadcastConversionResult(
155 | intent: Intent,
156 | isSuccess: Boolean,
157 | ) {
158 | intent.putExtra(IS_SUCCESS, isSuccess)
159 | sendBroadcast(intent)
160 | Log.d(TAG, "Broadcast conversion result: $isSuccess")
161 | }
162 |
163 | private fun startForegroundServiceWithNotification() {
164 | val notification = createProgressNotification(0, true)
165 | if (!isForegroundServiceStarted) {
166 | ServiceCompat.startForeground(
167 | /* service = */ this,
168 | /* id = */ notificationId,
169 | /* notification = */ notification,
170 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
171 | ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE
172 | } else {
173 | 0
174 | },
175 | )
176 | isForegroundServiceStarted = true
177 | Log.i(TAG, "Started foreground service with notification ID: $notificationId")
178 | } else {
179 | Log.w(TAG, "Foreground service already started. Skipping start.")
180 | }
181 | }
182 |
183 | private fun stopForegroundService() {
184 | if (!isForegroundServiceStarted) {
185 | Log.w(TAG, "Attempted to stop foreground service when it wasn't started")
186 | return
187 | }
188 |
189 | isForegroundServiceStarted = false
190 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
191 | stopForeground(STOP_FOREGROUND_REMOVE)
192 | } else {
193 | stopForeground(true)
194 | }
195 | stopSelf()
196 | Log.i(TAG, "Foreground service stopped and removed")
197 | }
198 |
199 | private fun createProgressNotification(
200 | progress: Int,
201 | isIndeterminate: Boolean,
202 | ): Notification {
203 | val stopIntent = Intent(this, ConvertItService::class.java).apply {
204 | action = ACTION_STOP_SERVICE
205 | }
206 | val stopPendingIntent = PendingIntent.getService(
207 | this,
208 | 0,
209 | stopIntent,
210 | PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
211 | )
212 |
213 | val progressText = if (isIndeterminate) {
214 | getString(R.string.label_converting_audio)
215 | } else {
216 | "Conversion in progress: $progress%"
217 | }
218 |
219 | Log.d(
220 | TAG, "Creating progress notification: $progressText (Indeterminate: $isIndeterminate)"
221 | )
222 |
223 | return NotificationCompat.Builder(this, CHANNEL_ID)
224 | .setContentTitle(getString(R.string.converting_audio_files))
225 | .setContentText(progressText).setSmallIcon(R.mipmap.ic_launcher_foreground)
226 | .setProgress(100, progress, isIndeterminate).setAutoCancel(false).setOngoing(true)
227 | .setDefaults(0).setOnlyAlertOnce(true)
228 | .addAction(R.drawable.baseline_stop_24, "Stop", stopPendingIntent).build()
229 | }
230 |
231 | private fun updateNotification(progress: Int) {
232 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && ActivityCompat.checkSelfPermission(
233 | this, Manifest.permission.POST_NOTIFICATIONS
234 | ) != PackageManager.PERMISSION_GRANTED
235 | ) {
236 | Log.w(
237 | TAG, "Notification permission not granted. Skipping update for progress: $progress%"
238 | )
239 | return
240 | }
241 |
242 | val notification = createProgressNotification(progress, false)
243 | NotificationManagerCompat.from(this).notify(notificationId, notification)
244 | Log.v(TAG, "Updated notification: Progress $progress%")
245 | }
246 |
247 | private fun showCompletionNotification(success: Boolean) {
248 | stopForegroundService()
249 |
250 | val notificationText = if (success) {
251 | getString(R.string.conversion_success)
252 | } else {
253 | getString(R.string.conversion_failed)
254 | }
255 |
256 | Log.i(TAG, "Showing completion notification: $notificationText")
257 |
258 | val notification = NotificationCompat.Builder(this, CHANNEL_ID)
259 | .setContentTitle(getString(R.string.conversion_status)).setContentText(notificationText)
260 | .setSmallIcon(R.mipmap.ic_launcher_foreground).setAutoCancel(true).build()
261 |
262 | if (ActivityCompat.checkSelfPermission(
263 | this, Manifest.permission.POST_NOTIFICATIONS
264 | ) != PackageManager.PERMISSION_GRANTED
265 | ) {
266 | Log.w(
267 | TAG,
268 | "Notification permission not granted. Skipping completion notification for status: $success"
269 | )
270 | return
271 | }
272 |
273 | NotificationManagerCompat.from(this).notify(notificationId, notification)
274 | }
275 | }
276 |
--------------------------------------------------------------------------------
/app/src/main/java/com/nasahacker/convertit/ui/component/AboutAppContent.kt:
--------------------------------------------------------------------------------
1 | package com.nasahacker.convertit.ui.component
2 |
3 | /**
4 | * @author Tamim Hossain
5 | * @email tamimh.dev@gmail.com
6 | * @license Apache-2.0
7 | *
8 | * ConvertIt is a free and easy-to-use audio converter app.
9 | * It supports popular audio formats like MP3 and M4A.
10 | * With options for high-quality bitrates ranging from 128k to 320k,
11 | * ConvertIt offers a seamless conversion experience tailored to your needs.
12 | */
13 |
14 | import android.content.Context
15 | import androidx.compose.foundation.layout.fillMaxWidth
16 | import androidx.compose.foundation.layout.padding
17 | import androidx.compose.material3.Card
18 | import androidx.compose.material3.CardDefaults
19 | import androidx.compose.material3.MaterialTheme
20 | import androidx.compose.material3.Text
21 | import androidx.compose.runtime.Composable
22 | import androidx.compose.ui.Modifier
23 | import androidx.compose.ui.text.style.TextAlign
24 | import androidx.compose.ui.unit.dp
25 | import com.nasahacker.convertit.R
26 |
27 | @Composable
28 | fun AboutAppContent(context: Context) {
29 | Card(
30 | modifier =
31 | Modifier
32 | .fillMaxWidth()
33 | .padding(8.dp),
34 | colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface),
35 | ) {
36 | Text(
37 | text = context.getString(R.string.label_about_app),
38 | style = MaterialTheme.typography.bodyMedium,
39 | modifier = Modifier.padding(8.dp),
40 | textAlign = TextAlign.Center,
41 |
42 | )
43 | }
44 | Text(
45 | text = "Apache 2.0",
46 | style = MaterialTheme.typography.bodyMedium,
47 | modifier = Modifier.padding(top = 4.dp),
48 | )
49 | }
50 |
--------------------------------------------------------------------------------
/app/src/main/java/com/nasahacker/convertit/ui/component/AudioItem.kt:
--------------------------------------------------------------------------------
1 | package com.nasahacker.convertit.ui.component
2 |
3 | import androidx.compose.animation.AnimatedVisibility
4 | import androidx.compose.foundation.ExperimentalFoundationApi
5 | import androidx.compose.foundation.Image
6 | import androidx.compose.foundation.background
7 | import androidx.compose.foundation.clickable
8 | import androidx.compose.foundation.combinedClickable
9 | import androidx.compose.foundation.layout.*
10 | import androidx.compose.foundation.shape.RoundedCornerShape
11 | import androidx.compose.material3.*
12 | import androidx.compose.runtime.Composable
13 | import androidx.compose.ui.Alignment
14 | import androidx.compose.ui.Modifier
15 | import androidx.compose.ui.res.painterResource
16 | import androidx.compose.ui.res.stringResource
17 | import androidx.compose.ui.text.font.FontWeight
18 | import androidx.compose.ui.tooling.preview.Preview
19 | import androidx.compose.ui.unit.dp
20 | import com.nasahacker.convertit.R
21 |
22 | /**
23 | * @author Tamim Hossain
24 | * @email tamimh.dev@gmail.com
25 | * @license Apache-2.0
26 | *
27 | * ConvertIt is a free and easy-to-use audio converter app.
28 | * It supports popular audio formats like MP3 and M4A.
29 | * With options for high-quality bitrates ranging from 128k to 320k,
30 | * ConvertIt offers a seamless conversion experience tailored to your needs.
31 | */
32 |
33 | @OptIn(ExperimentalFoundationApi::class)
34 | @Composable
35 | fun AudioItem(
36 | modifier: Modifier = Modifier,
37 | fileName: String = "Sample Audio File",
38 | fileSize: String = "100KB",
39 | isActionVisible: Boolean = false,
40 | onPlayClick: () -> Unit = {},
41 | onShareClick: () -> Unit = {},
42 | onLongClick: () -> Unit = {},
43 | ) {
44 | Column(
45 | modifier =
46 | modifier
47 | .fillMaxWidth()
48 | .background(MaterialTheme.colorScheme.surface, RoundedCornerShape(8.dp))
49 | .padding(16.dp)
50 | .combinedClickable(onClick = {
51 | }, onLongClick = {
52 | onLongClick()
53 | }),
54 | ) {
55 | Row(
56 | verticalAlignment = Alignment.CenterVertically,
57 | horizontalArrangement = Arrangement.SpaceBetween,
58 | modifier = Modifier.fillMaxWidth(),
59 | ) {
60 | Row(
61 | verticalAlignment = Alignment.CenterVertically,
62 | modifier = Modifier.weight(1f),
63 | ) {
64 | Image(
65 | painter = painterResource(R.drawable.audio_ic),
66 | contentDescription = stringResource(R.string.label_audio_icon),
67 | modifier =
68 | Modifier
69 | .size(50.dp)
70 | .padding(end = 8.dp),
71 | )
72 | Column {
73 | Text(
74 | text = fileName,
75 | style = MaterialTheme.typography.bodyMedium,
76 | fontWeight = FontWeight.Bold,
77 | color = MaterialTheme.colorScheme.onSurface,
78 | )
79 | Spacer(modifier = Modifier.height(4.dp))
80 | Text(
81 | text = fileSize,
82 | style = MaterialTheme.typography.bodySmall,
83 | color = MaterialTheme.colorScheme.onSurfaceVariant,
84 | )
85 | }
86 | }
87 |
88 | AnimatedVisibility(visible = isActionVisible) {
89 | Row(
90 | horizontalArrangement = Arrangement.spacedBy(8.dp),
91 | verticalAlignment = Alignment.CenterVertically,
92 | ) {
93 | Image(
94 | painter = painterResource(R.drawable.play_ic),
95 | contentDescription = stringResource(R.string.label_play_icon),
96 | modifier =
97 | Modifier
98 | .size(40.dp)
99 | .clickable { onPlayClick() },
100 | )
101 | Image(
102 | painter = painterResource(R.drawable.share_ic),
103 | contentDescription = stringResource(R.string.label_share_icon),
104 | modifier =
105 | Modifier
106 | .size(30.dp)
107 | .clickable { onShareClick() },
108 | )
109 | }
110 | }
111 | }
112 | }
113 | }
114 |
115 | @Preview(showBackground = true)
116 | @Composable
117 | fun PreviewAudioItem() {
118 | MaterialTheme {
119 | AudioItem()
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/app/src/main/java/com/nasahacker/convertit/ui/component/BottomNavigationBar.kt:
--------------------------------------------------------------------------------
1 | package com.nasahacker.convertit.ui.component
2 |
3 | import androidx.compose.material3.Icon
4 | import androidx.compose.material3.NavigationBar
5 | import androidx.compose.material3.NavigationBarItem
6 | import androidx.compose.material3.Text
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.res.painterResource
9 | import androidx.navigation.NavController
10 | import androidx.navigation.compose.currentBackStackEntryAsState
11 | import com.nasahacker.convertit.R
12 | import com.nasahacker.convertit.dto.BottomNavigation
13 |
14 | /**
15 | * @author Tamim Hossain
16 | * @email tamimh.dev@gmail.com
17 | * @license Apache-2.0
18 | *
19 | * ConvertIt is a free and easy-to-use audio converter app.
20 | * It supports popular audio formats like MP3 and M4A.
21 | * With options for high-quality bitrates ranging from 128k to 320k,
22 | * ConvertIt offers a seamless conversion experience tailored to your needs.
23 | */
24 |
25 | @Composable
26 | fun BottomNavigationBar(navController: NavController) {
27 | NavigationBar {
28 | val navBackStackEntry = navController.currentBackStackEntryAsState().value
29 | val currentRoute = navBackStackEntry?.destination?.route
30 |
31 | val items =
32 | listOf(
33 | BottomNavigation.Home,
34 | BottomNavigation.Library,
35 | )
36 |
37 | items.forEach { item ->
38 | NavigationBarItem(
39 | icon = {
40 | val iconRes =
41 | if (currentRoute == item.route) {
42 | when (item) {
43 | BottomNavigation.Home -> R.drawable.home_filled
44 | BottomNavigation.Library -> R.drawable.storage_filled
45 | }
46 | } else {
47 | when (item) {
48 | BottomNavigation.Home -> R.drawable.home_outlined
49 | BottomNavigation.Library -> R.drawable.storage_outlined
50 | }
51 | }
52 | Icon(painter = painterResource(id = iconRes), contentDescription = item.label)
53 | },
54 | label = { Text(item.label) },
55 | selected = currentRoute == item.route,
56 | onClick = {
57 | if (currentRoute != item.route) {
58 | navController.navigate(item.route) {
59 | popUpTo(navController.graph.startDestinationId) {
60 | saveState = true
61 | }
62 | launchSingleTop = true
63 | restoreState = true
64 | }
65 | }
66 | },
67 | )
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/app/src/main/java/com/nasahacker/convertit/ui/component/CommunityIcon.kt:
--------------------------------------------------------------------------------
1 | package com.nasahacker.convertit.ui.component
2 |
3 | /**
4 | * @author Tamim Hossain
5 | * @email tamimh.dev@gmail.com
6 | * @license Apache-2.0
7 | *
8 | * ConvertIt is a free and easy-to-use audio converter app.
9 | * It supports popular audio formats like MP3 and M4A.
10 | * With options for high-quality bitrates ranging from 128k to 320k,
11 | * ConvertIt offers a seamless conversion experience tailored to your needs.
12 | */
13 |
14 | import androidx.compose.foundation.layout.size
15 | import androidx.compose.material3.Icon
16 | import androidx.compose.material3.IconButton
17 | import androidx.compose.runtime.Composable
18 | import androidx.compose.ui.Modifier
19 | import androidx.compose.ui.res.painterResource
20 | import androidx.compose.ui.unit.dp
21 |
22 | @Composable
23 | fun CommunityIcon(
24 | iconRes: Int,
25 | contentDescription: String,
26 | onClick: () -> Unit,
27 | ) {
28 | IconButton(onClick = onClick) {
29 | Icon(
30 | painter = painterResource(iconRes),
31 | contentDescription = contentDescription,
32 | modifier = Modifier.size(30.dp),
33 | )
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/main/java/com/nasahacker/convertit/ui/component/ContactItem.kt:
--------------------------------------------------------------------------------
1 | package com.nasahacker.convertit.ui.component
2 |
3 | /**
4 | * @author Tamim Hossain
5 | * @email tamimh.dev@gmail.com
6 | * @license Apache-2.0
7 | *
8 | * ConvertIt is a free and easy-to-use audio converter app.
9 | * It supports popular audio formats like MP3 and M4A.
10 | * With options for high-quality bitrates ranging from 128k to 320k,
11 | * ConvertIt offers a seamless conversion experience tailored to your needs.
12 | */
13 |
14 | import androidx.compose.foundation.clickable
15 | import androidx.compose.foundation.layout.Arrangement
16 | import androidx.compose.foundation.layout.Row
17 | import androidx.compose.foundation.layout.fillMaxWidth
18 | import androidx.compose.foundation.layout.padding
19 | import androidx.compose.material3.MaterialTheme
20 | import androidx.compose.material3.Text
21 | import androidx.compose.runtime.Composable
22 | import androidx.compose.ui.Alignment
23 | import androidx.compose.ui.Modifier
24 | import androidx.compose.ui.unit.dp
25 |
26 | @Composable
27 | fun ContactItem(
28 | name: String,
29 | onClick: () -> Unit,
30 | ) {
31 | Row(
32 | modifier =
33 | Modifier
34 | .fillMaxWidth()
35 | .padding(horizontal = 12.dp, vertical = 4.dp),
36 | verticalAlignment = Alignment.CenterVertically,
37 | horizontalArrangement = Arrangement.Center,
38 | ) {
39 | Text(
40 | text = name,
41 | style = MaterialTheme.typography.bodyMedium,
42 | modifier = Modifier.clickable(onClick = { onClick() }),
43 | )
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/app/src/main/java/com/nasahacker/convertit/ui/component/DialogDeleteItem.kt:
--------------------------------------------------------------------------------
1 | package com.nasahacker.convertit.ui.component
2 |
3 | import androidx.compose.foundation.layout.*
4 | import androidx.compose.foundation.shape.RoundedCornerShape
5 | import androidx.compose.material.icons.Icons
6 | import androidx.compose.material.icons.filled.Delete
7 | import androidx.compose.material.icons.filled.Warning
8 | import androidx.compose.material3.*
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.ui.Alignment
11 | import androidx.compose.ui.Modifier
12 | import androidx.compose.ui.graphics.Color
13 | import androidx.compose.ui.res.stringResource
14 | import androidx.compose.ui.text.font.FontWeight
15 | import androidx.compose.ui.text.style.TextAlign
16 | import androidx.compose.ui.tooling.preview.Preview
17 | import androidx.compose.ui.unit.dp
18 | import com.nasahacker.convertit.R
19 |
20 | /**
21 | * @author Tamim Hossain
22 | * @email tamimh.dev@gmail.com
23 | * @license Apache-2.0
24 | *
25 | * ConvertIt is a free and easy-to-use audio converter app.
26 | * It supports popular audio formats like MP3 and M4A.
27 | * With options for high-quality bitrates ranging from 128k to 320k,
28 | * ConvertIt offers a seamless conversion experience tailored to your needs.
29 | */
30 |
31 | @Composable
32 | fun DialogDeleteItem(
33 | showDialog: Boolean,
34 | onDismissRequest: () -> Unit,
35 | onDeleteConfirm: () -> Unit,
36 | ) {
37 | if (showDialog) {
38 | AlertDialog(
39 | onDismissRequest = onDismissRequest,
40 | icon = {
41 | Icon(
42 | imageVector = Icons.Default.Warning,
43 | contentDescription = null,
44 | tint = MaterialTheme.colorScheme.error,
45 | modifier = Modifier.size(32.dp)
46 | )
47 | },
48 | title = {
49 | Text(
50 | text = stringResource(R.string.label_delete_item),
51 | style = MaterialTheme.typography.titleLarge.copy(
52 | fontWeight = FontWeight.Bold,
53 | color = MaterialTheme.colorScheme.error
54 | ),
55 | textAlign = TextAlign.Center,
56 | modifier = Modifier.fillMaxWidth()
57 | )
58 | },
59 | text = {
60 | Column(
61 | horizontalAlignment = Alignment.CenterHorizontally,
62 | modifier = Modifier.fillMaxWidth()
63 | ) {
64 | Text(
65 | text = stringResource(R.string.label_delete_confirmation),
66 | style = MaterialTheme.typography.bodyLarge.copy(
67 | color = MaterialTheme.colorScheme.onSurfaceVariant
68 | ),
69 | textAlign = TextAlign.Center
70 | )
71 | }
72 | },
73 | confirmButton = {
74 | Button(
75 | onClick = onDeleteConfirm,
76 | shape = RoundedCornerShape(12.dp),
77 | colors = ButtonDefaults.buttonColors(
78 | containerColor = MaterialTheme.colorScheme.error
79 | ),
80 | modifier = Modifier
81 | .fillMaxWidth()
82 | .padding(horizontal = 16.dp, vertical = 4.dp)
83 | ) {
84 | Icon(
85 | imageVector = Icons.Default.Delete,
86 | contentDescription = null,
87 | modifier = Modifier.size(20.dp)
88 | )
89 | Spacer(modifier = Modifier.width(8.dp))
90 | Text(
91 | text = stringResource(R.string.label_delete),
92 | style = MaterialTheme.typography.labelLarge
93 | )
94 | }
95 | },
96 | dismissButton = {
97 | OutlinedButton(
98 | onClick = onDismissRequest,
99 | shape = RoundedCornerShape(12.dp),
100 | modifier = Modifier
101 | .fillMaxWidth()
102 | .padding(horizontal = 16.dp, vertical = 4.dp)
103 | ) {
104 | Text(
105 | text = stringResource(R.string.label_cancel),
106 | style = MaterialTheme.typography.labelLarge
107 | )
108 | }
109 | },
110 | shape = RoundedCornerShape(24.dp),
111 | containerColor = MaterialTheme.colorScheme.surface,
112 | tonalElevation = 8.dp,
113 | modifier = Modifier.padding(vertical = 8.dp)
114 | )
115 | }
116 | }
117 |
118 | @Preview(showBackground = true)
119 | @Composable
120 | fun PreviewDialogDeleteItem() {
121 | MaterialTheme {
122 | DialogDeleteItem(
123 | showDialog = true,
124 | onDismissRequest = {},
125 | onDeleteConfirm = {},
126 | )
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/app/src/main/java/com/nasahacker/convertit/ui/component/DonationCard.kt:
--------------------------------------------------------------------------------
1 | package com.nasahacker.convertit.ui.component
2 |
3 | import android.content.ClipData
4 | import android.content.ClipboardManager
5 | import android.content.Context
6 | import androidx.compose.foundation.ExperimentalFoundationApi
7 | import androidx.compose.foundation.Image
8 | import androidx.compose.foundation.combinedClickable
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.foundation.layout.size
14 | import androidx.compose.material3.Card
15 | import androidx.compose.material3.CardDefaults
16 | import androidx.compose.material3.MaterialTheme
17 | import androidx.compose.material3.Text
18 | import androidx.compose.runtime.Composable
19 | import androidx.compose.ui.Alignment
20 | import androidx.compose.ui.Modifier
21 | import androidx.compose.ui.platform.LocalContext
22 | import androidx.compose.ui.res.painterResource
23 | import androidx.compose.ui.unit.dp
24 |
25 | @OptIn(ExperimentalFoundationApi::class)
26 | @Composable
27 | fun DonationCard(
28 | iconRes: Int,
29 | description: String,
30 | address: String,
31 | ) {
32 | val context = LocalContext.current
33 |
34 | Card(
35 | modifier =
36 | Modifier
37 | .fillMaxWidth()
38 | .padding(8.dp)
39 | .combinedClickable(
40 | onClick = { },
41 | onLongClick = {
42 | val clipboard =
43 | context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
44 | val clip = ClipData.newPlainText(description, address)
45 | clipboard.setPrimaryClip(clip)
46 | },
47 | ),
48 | colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface),
49 | ) {
50 | Row(
51 | modifier =
52 | Modifier
53 | .fillMaxWidth()
54 | .padding(12.dp),
55 | horizontalArrangement = Arrangement.SpaceBetween,
56 | verticalAlignment = Alignment.CenterVertically,
57 | ) {
58 | Image(
59 | painter = painterResource(iconRes),
60 | contentDescription = description,
61 | modifier = Modifier.size(24.dp),
62 | )
63 | Text(
64 | text = address,
65 | style = MaterialTheme.typography.bodyMedium,
66 | modifier = Modifier.padding(start = 8.dp),
67 | )
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/app/src/main/java/com/nasahacker/convertit/ui/component/MainAppBar.kt:
--------------------------------------------------------------------------------
1 | package com.nasahacker.convertit.ui.component
2 |
3 | import androidx.compose.material.icons.Icons
4 | import androidx.compose.material.icons.filled.ArrowBack
5 | import androidx.compose.material.icons.filled.Info
6 | import androidx.compose.material3.ExperimentalMaterial3Api
7 | import androidx.compose.material3.Icon
8 | import androidx.compose.material3.IconButton
9 | import androidx.compose.material3.MaterialTheme
10 | import androidx.compose.material3.Text
11 | import androidx.compose.material3.TopAppBar
12 | import androidx.compose.runtime.Composable
13 | import androidx.compose.ui.res.stringResource
14 | import com.nasahacker.convertit.R
15 |
16 | /**
17 | * @author Tamim Hossain
18 | * @email tamimh.dev@gmail.com
19 | * @license Apache-2.0
20 | *
21 | * ConvertIt is a free and easy-to-use audio converter app.
22 | * It supports popular audio formats like MP3 and M4A.
23 | * With options for high-quality bitrates ranging from 128k to 320k,
24 | * ConvertIt offers a seamless conversion experience tailored to your needs.
25 | */
26 |
27 | @OptIn(ExperimentalMaterial3Api::class)
28 | @Composable
29 | fun MainAppBar(
30 | onNavigateToAbout: () -> Unit,
31 | onNavigateBack: () -> Unit,
32 | isAboutScreen: Boolean = false,
33 | ) {
34 | TopAppBar(
35 | title = { Text(text = if (isAboutScreen) "About" else stringResource(R.string.app_name)) },
36 | navigationIcon = {
37 | if (isAboutScreen) {
38 | IconButton(onClick = onNavigateBack) {
39 | Icon(
40 | imageVector = Icons.Default.ArrowBack,
41 | contentDescription = "Back"
42 | )
43 | }
44 | }
45 | },
46 | actions = {
47 | if (!isAboutScreen) {
48 | IconButton(onClick = onNavigateToAbout) {
49 | Icon(
50 | imageVector = Icons.Default.Info,
51 | contentDescription = stringResource(R.string.label_about)
52 | )
53 | }
54 | }
55 | }
56 | )
57 | }
58 |
--------------------------------------------------------------------------------
/app/src/main/java/com/nasahacker/convertit/ui/component/NoFilesFoundCard.kt:
--------------------------------------------------------------------------------
1 | package com.nasahacker.convertit.ui.component
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.foundation.layout.fillMaxWidth
5 | import androidx.compose.foundation.layout.padding
6 | import androidx.compose.material.icons.Icons
7 | import androidx.compose.material.icons.filled.Audiotrack
8 | import androidx.compose.material3.Icon
9 | import androidx.compose.material3.Text
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.ui.Alignment
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.res.stringResource
14 | import androidx.compose.ui.text.style.TextAlign
15 | import androidx.compose.ui.tooling.preview.Preview
16 | import androidx.compose.ui.unit.dp
17 | import com.nasahacker.convertit.R
18 |
19 | /**
20 | * Copyright (c) 2025
21 | * Created by: Tamim Hossain (tamim@thebytearray.org)
22 | * Created on: 5/6/25
23 | **/
24 |
25 |
26 | @Composable
27 | fun NoFilesFoundCard(modifier: Modifier = Modifier) {
28 |
29 | Column(
30 | modifier = modifier
31 | .fillMaxWidth()
32 | .padding(8.dp)
33 | ) {
34 | Icon(
35 | modifier = Modifier
36 | .align(Alignment.CenterHorizontally)
37 | .padding(8.dp),
38 | imageVector = Icons.Filled.Audiotrack,
39 | contentDescription = null
40 | )
41 |
42 |
43 | Text(
44 | text = stringResource(R.string.label_no_files_found),
45 | textAlign = TextAlign.Center,
46 | modifier = Modifier
47 | .fillMaxWidth()
48 | .padding(8.dp)
49 | )
50 |
51 | }
52 |
53 | }
54 |
55 |
56 | @Preview
57 | @Composable
58 | private fun PreviewNoFilesFoundCard() {
59 | NoFilesFoundCard()
60 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/nasahacker/convertit/ui/component/RatingDialog.kt:
--------------------------------------------------------------------------------
1 | package com.nasahacker.convertit.ui.component
2 |
3 | /**
4 | * @author Tamim Hossain
5 | * @email tamimh.dev@gmail.com
6 | * @license Apache-2.0
7 | *
8 | * ConvertIt is a free and easy-to-use audio converter app.
9 | * It supports popular audio formats like MP3 and M4A.
10 | * With options for high-quality bitrates ranging from 128k to 320k,
11 | * ConvertIt offers a seamless conversion experience tailored to your needs.
12 | */
13 |
14 | import android.content.Context
15 | import android.content.Intent
16 | import androidx.compose.animation.AnimatedVisibility
17 | import androidx.compose.animation.core.animateFloatAsState
18 | import androidx.compose.animation.expandVertically
19 | import androidx.compose.animation.fadeIn
20 | import androidx.compose.animation.fadeOut
21 | import androidx.compose.animation.shrinkVertically
22 | import androidx.compose.foundation.background
23 | import androidx.compose.foundation.layout.*
24 | import androidx.compose.foundation.shape.CircleShape
25 | import androidx.compose.foundation.shape.RoundedCornerShape
26 | import androidx.compose.material.icons.Icons
27 | import androidx.compose.material.icons.filled.Star
28 | import androidx.compose.material.icons.filled.StarBorder
29 | import androidx.compose.material3.*
30 | import androidx.compose.runtime.*
31 | import androidx.compose.ui.Alignment
32 | import androidx.compose.ui.Modifier
33 | import androidx.compose.ui.draw.clip
34 | import androidx.compose.ui.draw.scale
35 | import androidx.compose.ui.platform.LocalContext
36 | import androidx.compose.ui.res.stringResource
37 | import androidx.compose.ui.text.font.FontWeight
38 | import androidx.compose.ui.text.style.TextAlign
39 | import androidx.compose.ui.unit.dp
40 | import androidx.compose.ui.unit.sp
41 | import androidx.compose.ui.window.Dialog
42 | import androidx.core.content.edit
43 | import androidx.core.net.toUri
44 | import com.nasahacker.convertit.App
45 | import com.nasahacker.convertit.R
46 | import com.nasahacker.convertit.util.AppConfig.APP_PREF
47 | import com.nasahacker.convertit.util.AppConfig.PREF_DONT_SHOW_AGAIN
48 |
49 | @Composable
50 | fun RatingDialog(
51 | showReviewDialog: Boolean,
52 | onConfirm: () -> Unit,
53 | onDismiss: () -> Unit,
54 | ) {
55 | val context = LocalContext.current
56 | val sharedPreferences =
57 | remember {
58 | context.getSharedPreferences(APP_PREF, Context.MODE_PRIVATE)
59 | }
60 |
61 | var dontShowAgain by remember {
62 | mutableStateOf(sharedPreferences.getBoolean(PREF_DONT_SHOW_AGAIN, false))
63 | }
64 |
65 | var selectedRating by remember { mutableStateOf(0) }
66 | val appPackageName = App.application.packageName
67 |
68 | if (showReviewDialog && !dontShowAgain) {
69 | Dialog(onDismissRequest = onDismiss) {
70 | Card(
71 | modifier = Modifier
72 | .fillMaxWidth()
73 | .padding(16.dp),
74 | shape = RoundedCornerShape(16.dp),
75 | colors = CardDefaults.cardColors(
76 | containerColor = MaterialTheme.colorScheme.surface
77 | ),
78 | elevation = CardDefaults.cardElevation(
79 | defaultElevation = 8.dp
80 | )
81 | ) {
82 | Column(
83 | modifier = Modifier
84 | .fillMaxWidth()
85 | .padding(24.dp),
86 | horizontalAlignment = Alignment.CenterHorizontally
87 | ) {
88 | // App Icon
89 | Box(
90 | modifier = Modifier
91 | .size(64.dp)
92 | .clip(CircleShape)
93 | .background(MaterialTheme.colorScheme.primaryContainer),
94 | contentAlignment = Alignment.Center
95 | ) {
96 | Icon(
97 | Icons.Filled.Star,
98 | contentDescription = stringResource(R.string.cd_star_icon),
99 | tint = MaterialTheme.colorScheme.primary,
100 | modifier = Modifier.size(32.dp)
101 | )
102 | }
103 |
104 | Spacer(modifier = Modifier.height(16.dp))
105 |
106 | // Title
107 | Text(
108 | text = stringResource(R.string.label_enjoying_app),
109 | style = MaterialTheme.typography.titleLarge.copy(
110 | fontWeight = FontWeight.Bold,
111 | fontSize = 24.sp
112 | ),
113 | color = MaterialTheme.colorScheme.onSurface
114 | )
115 |
116 | Spacer(modifier = Modifier.height(8.dp))
117 |
118 | // Subtitle
119 | Text(
120 | text = stringResource(R.string.label_feedback_help),
121 | style = MaterialTheme.typography.bodyLarge,
122 | color = MaterialTheme.colorScheme.onSurfaceVariant,
123 | textAlign = TextAlign.Center
124 | )
125 |
126 | Spacer(modifier = Modifier.height(24.dp))
127 |
128 | // Star Rating
129 | Row(
130 | modifier = Modifier.fillMaxWidth(),
131 | horizontalArrangement = Arrangement.Center
132 | ) {
133 | repeat(5) { index ->
134 | val scale by animateFloatAsState(
135 | targetValue = if (selectedRating > index) 1.2f else 1f,
136 | label = stringResource(R.string.label_star_scale)
137 | )
138 | IconButton(
139 | onClick = { selectedRating = index + 1 }
140 | ) {
141 | Icon(
142 | imageVector = if (selectedRating > index) Icons.Filled.Star else Icons.Filled.StarBorder,
143 | contentDescription = stringResource(R.string.label_rate_stars, index + 1),
144 | tint = if (selectedRating > index) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.outline,
145 | modifier = Modifier.scale(scale)
146 | )
147 | }
148 | }
149 | }
150 |
151 | Spacer(modifier = Modifier.height(24.dp))
152 |
153 | // Feedback Message
154 | AnimatedVisibility(
155 | visible = selectedRating > 0,
156 | enter = fadeIn() + expandVertically(),
157 | exit = fadeOut() + shrinkVertically()
158 | ) {
159 | Text(
160 | text = when (selectedRating) {
161 | 1 -> stringResource(R.string.label_feedback_1)
162 | 2 -> stringResource(R.string.label_feedback_2)
163 | 3 -> stringResource(R.string.label_feedback_3)
164 | 4 -> stringResource(R.string.label_feedback_4)
165 | 5 -> stringResource(R.string.label_feedback_5)
166 | else -> ""
167 | },
168 | style = MaterialTheme.typography.bodyMedium,
169 | color = MaterialTheme.colorScheme.primary,
170 | textAlign = TextAlign.Center
171 | )
172 | }
173 |
174 | Spacer(modifier = Modifier.height(24.dp))
175 |
176 | // Don't show again checkbox
177 | Row(
178 | modifier = Modifier.fillMaxWidth(),
179 | verticalAlignment = Alignment.CenterVertically
180 | ) {
181 | Checkbox(
182 | checked = dontShowAgain,
183 | onCheckedChange = {
184 | dontShowAgain = it
185 | sharedPreferences.edit { putBoolean(PREF_DONT_SHOW_AGAIN, it) }
186 | },
187 | colors = CheckboxDefaults.colors(
188 | checkedColor = MaterialTheme.colorScheme.primary
189 | )
190 | )
191 | Text(
192 | stringResource(R.string.label_dont_show_again),
193 | style = MaterialTheme.typography.bodyMedium,
194 | color = MaterialTheme.colorScheme.onSurfaceVariant
195 | )
196 | }
197 |
198 | Spacer(modifier = Modifier.height(16.dp))
199 |
200 | // Action Buttons
201 | Row(
202 | modifier = Modifier.fillMaxWidth(),
203 | horizontalArrangement = Arrangement.SpaceBetween
204 | ) {
205 | TextButton(
206 | onClick = {
207 | sharedPreferences.edit { putBoolean(PREF_DONT_SHOW_AGAIN, dontShowAgain) }
208 | onDismiss()
209 | },
210 | modifier = Modifier.weight(1f)
211 | ) {
212 | Text(
213 | text = stringResource(R.string.label_not_now),
214 | style = MaterialTheme.typography.labelLarge,
215 | color = MaterialTheme.colorScheme.secondary
216 | )
217 | }
218 |
219 | Spacer(modifier = Modifier.width(8.dp))
220 |
221 | Button(
222 | onClick = {
223 | val intent = Intent(
224 | Intent.ACTION_VIEW,
225 | context.getString(R.string.label_google_play, appPackageName).toUri()
226 | )
227 | context.startActivity(intent)
228 | sharedPreferences.edit { putBoolean(PREF_DONT_SHOW_AGAIN, dontShowAgain) }
229 | onConfirm()
230 | },
231 | modifier = Modifier.weight(1f),
232 | enabled = selectedRating > 0
233 | ) {
234 | Text(
235 | text = stringResource(R.string.label_rate_now),
236 | style = MaterialTheme.typography.labelLarge
237 | )
238 | }
239 | }
240 | }
241 | }
242 | }
243 | }
244 | }
245 |
--------------------------------------------------------------------------------
/app/src/main/java/com/nasahacker/convertit/ui/component/SectionTitle.kt:
--------------------------------------------------------------------------------
1 | package com.nasahacker.convertit.ui.component
2 |
3 | /**
4 | * @author Tamim Hossain
5 | * @email tamimh.dev@gmail.com
6 | * @license Apache-2.0
7 | *
8 | * ConvertIt is a free and easy-to-use audio converter app.
9 | * It supports popular audio formats like MP3 and M4A.
10 | * With options for high-quality bitrates ranging from 128k to 320k,
11 | * ConvertIt offers a seamless conversion experience tailored to your needs.
12 | */
13 |
14 | import androidx.compose.foundation.layout.padding
15 | import androidx.compose.material3.MaterialTheme
16 | import androidx.compose.material3.Text
17 | import androidx.compose.runtime.Composable
18 | import androidx.compose.ui.Modifier
19 | import androidx.compose.ui.text.style.TextAlign
20 | import androidx.compose.ui.unit.dp
21 |
22 | @Composable
23 | fun SectionTitle(title: String) {
24 | Text(
25 | text = title,
26 | style = MaterialTheme.typography.titleMedium,
27 | modifier = Modifier.padding(top = 12.dp),
28 | textAlign = TextAlign.Center,
29 | )
30 | }
31 |
--------------------------------------------------------------------------------
/app/src/main/java/com/nasahacker/convertit/ui/navigation/AppNavHost.kt:
--------------------------------------------------------------------------------
1 | package com.nasahacker.convertit.ui.navigation
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.ui.Modifier
5 | import androidx.lifecycle.viewmodel.compose.viewModel
6 | import androidx.navigation.NavHostController
7 | import androidx.navigation.compose.NavHost
8 | import androidx.navigation.compose.composable
9 | import com.nasahacker.convertit.dto.BottomNavigation
10 | import com.nasahacker.convertit.ui.screen.ConvertsScreen
11 | import com.nasahacker.convertit.ui.screen.HomeScreen
12 | import com.nasahacker.convertit.ui.screen.AboutScreen
13 | import com.nasahacker.convertit.ui.viewmodel.AppViewModel
14 |
15 | /**
16 | * @author Tamim Hossain
17 | * @email tamimh.dev@gmail.com
18 | * @license Apache-2.0
19 | *
20 | * ConvertIt is a free and easy-to-use audio converter app.
21 | * It supports popular audio formats like MP3 and M4A.
22 | * With options for high-quality bitrates ranging from 128k to 320k,
23 | * ConvertIt offers a seamless conversion experience tailored to your needs.
24 | */
25 |
26 | @Composable
27 | fun AppNavHost(
28 | modifier: Modifier = Modifier,
29 | controller: NavHostController,
30 | ) {
31 | val viewModel: AppViewModel = viewModel()
32 |
33 | NavHost(
34 | modifier = modifier,
35 | navController = controller,
36 | startDestination = BottomNavigation.Home.route,
37 | ) {
38 | composable(BottomNavigation.Home.route) {
39 | HomeScreen(viewModel = viewModel)
40 | }
41 | composable(BottomNavigation.Library.route) {
42 | ConvertsScreen()
43 | }
44 | composable("about") {
45 | AboutScreen()
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/app/src/main/java/com/nasahacker/convertit/ui/screen/AboutScreen.kt:
--------------------------------------------------------------------------------
1 | package com.nasahacker.convertit.ui.screen
2 |
3 | import androidx.compose.foundation.layout.*
4 | import androidx.compose.foundation.rememberScrollState
5 | import androidx.compose.foundation.shape.RoundedCornerShape
6 | import androidx.compose.foundation.verticalScroll
7 | import androidx.compose.material.icons.Icons
8 | import androidx.compose.material.icons.filled.*
9 | import androidx.compose.material3.*
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.ui.Alignment
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.platform.LocalContext
14 | import androidx.compose.ui.res.stringResource
15 | import androidx.compose.ui.text.font.FontWeight
16 | import androidx.compose.ui.text.style.TextAlign
17 | import androidx.compose.ui.tooling.preview.Preview
18 | import androidx.compose.ui.unit.dp
19 | import com.nasahacker.convertit.BuildConfig
20 | import com.nasahacker.convertit.R
21 | import com.nasahacker.convertit.dto.ConvertitDarkPreview
22 | import com.nasahacker.convertit.dto.ConvertitLightPreview
23 | import com.nasahacker.convertit.ui.component.AboutAppContent
24 | import com.nasahacker.convertit.ui.component.CommunityIcon
25 | import com.nasahacker.convertit.ui.component.ContactItem
26 | import com.nasahacker.convertit.util.AppUtil
27 | import com.nasahacker.convertit.util.AppConfig
28 |
29 | /**
30 | * @author Tamim Hossain
31 | * @email tamimh.dev@gmail.com
32 | * @license Apache-2.0
33 | *
34 | * ConvertIt is a free and easy-to-use audio converter app.
35 | * It supports popular audio formats like MP3 and M4A.
36 | * With options for high-quality bitrates ranging from 128k to 320k,
37 | * ConvertIt offers a seamless conversion experience tailored to your needs.
38 | */
39 |
40 | @Composable
41 | fun AboutScreen() {
42 | val context = LocalContext.current
43 |
44 | Surface(
45 | modifier = Modifier.fillMaxSize(),
46 | color = MaterialTheme.colorScheme.background,
47 | ) {
48 | Column(
49 | modifier = Modifier
50 | .fillMaxSize()
51 | .verticalScroll(rememberScrollState())
52 | .padding(horizontal = 16.dp),
53 | horizontalAlignment = Alignment.CenterHorizontally,
54 | ) {
55 | Card(
56 | modifier = Modifier.fillMaxWidth(),
57 | shape = RoundedCornerShape(16.dp),
58 | colors = CardDefaults.cardColors(
59 | containerColor = MaterialTheme.colorScheme.surface
60 | )
61 | ) {
62 | Column(
63 | modifier = Modifier
64 | .fillMaxWidth()
65 | .padding(16.dp),
66 | horizontalAlignment = Alignment.CenterHorizontally
67 | ) {
68 | Text(
69 | text = stringResource(R.string.label_about_title),
70 | style = MaterialTheme.typography.titleLarge.copy(
71 | fontWeight = FontWeight.Bold
72 | ),
73 | color = MaterialTheme.colorScheme.primary,
74 | modifier = Modifier.padding(bottom = 8.dp),
75 | textAlign = TextAlign.Center
76 | )
77 | AboutAppContent(context)
78 | }
79 | }
80 |
81 | Card(
82 | modifier = Modifier.fillMaxWidth(),
83 | shape = RoundedCornerShape(16.dp),
84 | colors = CardDefaults.cardColors(
85 | containerColor = MaterialTheme.colorScheme.surface
86 | )
87 | ) {
88 | Column(
89 | modifier = Modifier
90 | .fillMaxWidth()
91 | .padding(8.dp),
92 | horizontalAlignment = Alignment.CenterHorizontally
93 | ) {
94 | Text(
95 | text = stringResource(R.string.label_contact_us),
96 | style = MaterialTheme.typography.titleLarge.copy(
97 | fontWeight = FontWeight.Bold
98 | ),
99 | color = MaterialTheme.colorScheme.primary,
100 | modifier = Modifier.padding(bottom = 8.dp),
101 | textAlign = TextAlign.Center
102 | )
103 | ContactItem(
104 | name = stringResource(R.string.label_dev),
105 | onClick = { AppUtil.openLink(context, AppConfig.GITHUB_PROFILE) },
106 | )
107 | ContactItem(
108 | name = stringResource(R.string.label_mod),
109 | onClick = { AppUtil.openLink(context, AppConfig.GITHUB_PROFILE_MOD) },
110 | )
111 | }
112 | }
113 |
114 | Card(
115 | modifier = Modifier.fillMaxWidth(),
116 | shape = RoundedCornerShape(8.dp),
117 | colors = CardDefaults.cardColors(
118 | containerColor = MaterialTheme.colorScheme.surface
119 | )
120 | ) {
121 | Column(
122 | modifier = Modifier
123 | .fillMaxWidth()
124 | .padding(8.dp),
125 | horizontalAlignment = Alignment.CenterHorizontally
126 | ) {
127 | Text(
128 | text = stringResource(R.string.label_community),
129 | style = MaterialTheme.typography.titleLarge.copy(
130 | fontWeight = FontWeight.Bold
131 | ),
132 | color = MaterialTheme.colorScheme.primary,
133 | modifier = Modifier.padding(bottom = 16.dp),
134 | textAlign = TextAlign.Center
135 | )
136 | Row(
137 | modifier = Modifier.fillMaxWidth(),
138 | horizontalArrangement = Arrangement.SpaceEvenly
139 | ) {
140 | CommunityIcon(
141 | iconRes = R.drawable.telegram_ic,
142 | contentDescription = stringResource(R.string.label_telegram_icon),
143 | onClick = { AppUtil.openLink(context, AppConfig.TELEGRAM_CHANNEL) },
144 | )
145 | CommunityIcon(
146 | iconRes = R.drawable.discord_ic,
147 | contentDescription = stringResource(R.string.label_dc_icon),
148 | onClick = { AppUtil.openLink(context, AppConfig.DISCORD_CHANNEL) },
149 | )
150 | CommunityIcon(
151 | iconRes = R.drawable.github_ic,
152 | contentDescription = stringResource(R.string.label_github_icon),
153 | onClick = { AppUtil.openLink(context, AppConfig.GITHUB_PROFILE) },
154 | )
155 | }
156 | }
157 | }
158 |
159 | Button(
160 | onClick = { AppUtil.openLink(context, AppConfig.GITHUB_ISSUES_URL) },
161 | modifier = Modifier
162 | .fillMaxWidth()
163 | .padding(vertical = 8.dp),
164 | shape = RoundedCornerShape(12.dp),
165 | colors = ButtonDefaults.buttonColors(
166 | containerColor = MaterialTheme.colorScheme.primary
167 | )
168 | ) {
169 | Icon(
170 | imageVector = Icons.Default.BugReport,
171 | contentDescription = null,
172 | modifier = Modifier.size(20.dp)
173 | )
174 | Spacer(modifier = Modifier.width(8.dp))
175 | Text(
176 | text = stringResource(R.string.label_open_github_issue),
177 | style = MaterialTheme.typography.labelLarge
178 | )
179 | }
180 |
181 | Text(
182 | text = "Version ${BuildConfig.VERSION_NAME}",
183 | style = MaterialTheme.typography.bodyMedium,
184 | color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.padding(top = 8.dp)
185 | )
186 | Text(
187 | text = stringResource(R.string.label_apache_2_0_license),
188 | style = MaterialTheme.typography.bodyMedium,
189 | color = MaterialTheme.colorScheme.onSurfaceVariant,
190 | textAlign = TextAlign.Center,
191 | modifier = Modifier.fillMaxWidth().padding(top = 8.dp)
192 | )
193 | }
194 | }
195 | }
196 |
197 | @ConvertitLightPreview
198 | @ConvertitDarkPreview
199 | @Composable
200 | fun PreviewAboutScreen() {
201 | AboutScreen()
202 | }
203 |
--------------------------------------------------------------------------------
/app/src/main/java/com/nasahacker/convertit/ui/screen/ConvertsScreen.kt:
--------------------------------------------------------------------------------
1 | package com.nasahacker.convertit.ui.screen
2 |
3 | import androidx.compose.foundation.layout.fillMaxSize
4 | import androidx.compose.foundation.lazy.LazyColumn
5 | import androidx.compose.foundation.lazy.items
6 | import androidx.compose.foundation.lazy.rememberLazyListState
7 | import androidx.compose.runtime.*
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.runtime.mutableStateOf
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.platform.LocalContext
12 | import androidx.compose.ui.tooling.preview.Preview
13 | import com.nasahacker.convertit.dto.ConvertitDarkPreview
14 | import com.nasahacker.convertit.dto.ConvertitLightPreview
15 | import com.nasahacker.convertit.ui.component.AudioItem
16 | import com.nasahacker.convertit.ui.component.DialogDeleteItem
17 | import com.nasahacker.convertit.ui.component.NoFilesFoundCard
18 | import com.nasahacker.convertit.util.AppUtil
19 | import java.io.File
20 | import kotlinx.coroutines.launch
21 |
22 | /**
23 | * @author Tamim Hossain
24 | * @email tamimh.dev@gmail.com
25 | * @license Apache-2.0
26 | *
27 | * ConvertIt is a free and easy-to-use audio converter app.
28 | * It supports popular audio formats like MP3 and M4A.
29 | * With options for high-quality bitrates ranging from 128k to 320k,
30 | * ConvertIt offers a seamless conversion experience tailored to your needs.
31 | */
32 |
33 | @Composable
34 | fun ConvertsScreen() {
35 | val context = LocalContext.current
36 | val listState = rememberLazyListState()
37 |
38 | var currentPage by remember { mutableIntStateOf(0) }
39 | val pageSize = 20
40 | var isLoading by remember { mutableStateOf(false) }
41 |
42 |
43 | val initialData = remember {
44 | mutableStateListOf(
45 | *AppUtil.getAudioFilesFromConvertedFolder(context)
46 | .take(pageSize)
47 | .toTypedArray()
48 | )
49 | }
50 |
51 | var showDialog by remember { mutableStateOf(false) }
52 | var fileToDelete by remember { mutableStateOf(null) }
53 |
54 |
55 | LaunchedEffect(listState) {
56 | if (listState.layoutInfo.visibleItemsInfo.lastOrNull()?.index == listState.layoutInfo.totalItemsCount - 1) {
57 | if (!isLoading) {
58 | isLoading = true
59 | currentPage++
60 | val newItems = AppUtil.getAudioFilesFromConvertedFolder(context)
61 | .drop(currentPage * pageSize)
62 | .take(pageSize)
63 | if (newItems.isNotEmpty()) {
64 | initialData.addAll(newItems)
65 | }
66 | isLoading = false
67 | }
68 | }
69 | }
70 |
71 | if (initialData.isNotEmpty()) {
72 | LazyColumn(
73 | modifier = Modifier.fillMaxSize(),
74 | state = listState
75 | ) {
76 | items(initialData) { item ->
77 | AudioItem(
78 | fileName = item.name,
79 | fileSize = item.size,
80 | isActionVisible = true,
81 | onPlayClick = {
82 | AppUtil.openMusicFileInPlayer(context, item.file)
83 | },
84 | onShareClick = {
85 | AppUtil.shareMusicFile(context, item.file)
86 | },
87 | onLongClick = {
88 | showDialog = true
89 | fileToDelete = item.file
90 | },
91 | )
92 | }
93 | }
94 | } else {
95 | NoFilesFoundCard()
96 | }
97 |
98 | if (showDialog) {
99 | DialogDeleteItem(
100 | showDialog = showDialog,
101 | onDismissRequest = { showDialog = false },
102 | onDeleteConfirm = {
103 | fileToDelete?.let {
104 | AppUtil.deleteFile(context, it)
105 | initialData.removeAll { file -> file.file == it }
106 | }
107 | showDialog = false
108 | },
109 | )
110 | }
111 | }
112 |
113 | @ConvertitLightPreview
114 | @ConvertitDarkPreview
115 | @Composable
116 | fun PreviewConvertsScreen(modifier: Modifier = Modifier) {
117 | ConvertsScreen()
118 | }
119 |
--------------------------------------------------------------------------------
/app/src/main/java/com/nasahacker/convertit/ui/screen/HomeScreen.kt:
--------------------------------------------------------------------------------
1 | package com.nasahacker.convertit.ui.screen
2 |
3 | import android.app.Activity
4 | import android.widget.Toast
5 | import androidx.activity.compose.rememberLauncherForActivityResult
6 | import androidx.activity.result.contract.ActivityResultContracts
7 | import androidx.compose.foundation.layout.*
8 | import androidx.compose.foundation.lazy.LazyColumn
9 | import androidx.compose.foundation.lazy.items
10 | import androidx.compose.material3.FloatingActionButton
11 | import androidx.compose.material3.Icon
12 | import androidx.compose.material3.MaterialTheme
13 | import androidx.compose.runtime.*
14 | import androidx.compose.runtime.saveable.rememberSaveable
15 | import androidx.compose.ui.Alignment
16 | import androidx.compose.ui.Modifier
17 | import androidx.compose.ui.res.painterResource
18 | import androidx.compose.ui.res.stringResource
19 | import androidx.compose.ui.tooling.preview.Preview
20 | import androidx.compose.ui.unit.dp
21 | import androidx.lifecycle.viewmodel.compose.viewModel
22 | import com.nasahacker.convertit.R
23 | import com.nasahacker.convertit.service.ConvertItService
24 | import com.nasahacker.convertit.ui.component.AudioItem
25 | import com.nasahacker.convertit.ui.component.DialogConvertAlertDialog
26 | import com.nasahacker.convertit.ui.component.RatingDialog
27 | import com.nasahacker.convertit.ui.viewmodel.AppViewModel
28 | import com.nasahacker.convertit.util.AppUtil
29 | import androidx.compose.runtime.collectAsState
30 | import androidx.compose.ui.platform.LocalContext
31 | import androidx.lifecycle.compose.collectAsStateWithLifecycle
32 | import com.nasahacker.convertit.dto.ConvertitDarkPreview
33 | import com.nasahacker.convertit.dto.ConvertitLightPreview
34 |
35 | /**
36 | * @author Tamim Hossain
37 | * @email tamimh.dev@gmail.com
38 | * @license Apache-2.0
39 | *
40 | * ConvertIt is a free and easy-to-use audio converter app.
41 | * It supports popular audio formats like MP3 and M4A.
42 | * With options for high-quality bitrates ranging from 128k to 320k,
43 | * ConvertIt offers a seamless conversion experience tailored to your needs.
44 | */
45 |
46 | @Composable
47 | fun HomeScreen(
48 | viewModel: AppViewModel = viewModel()
49 | ) {
50 | val context = LocalContext.current
51 | val uriList by viewModel.uriList.collectAsStateWithLifecycle()
52 | val conversionStatus by viewModel.conversionStatus.collectAsStateWithLifecycle()
53 | var showDialog by rememberSaveable { mutableStateOf(false) }
54 | var showReviewDialog by rememberSaveable { mutableStateOf(false) }
55 |
56 | val pickFileLauncher =
57 | rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
58 | if (result.resultCode == Activity.RESULT_OK) {
59 | viewModel.updateUriList(result.data)
60 | showDialog = true
61 | }
62 | }
63 |
64 | LaunchedEffect(conversionStatus) {
65 | conversionStatus?.let { isSuccess ->
66 | if (isSuccess) {
67 | Toast.makeText(
68 | context,
69 | context.getString(R.string.label_conversion_successful),
70 | Toast.LENGTH_SHORT
71 | ).show()
72 | viewModel.clearUriList()
73 | viewModel.resetConversionStatus()
74 | showReviewDialog = true
75 | }
76 | }
77 | }
78 |
79 | RatingDialog(
80 | showReviewDialog = showReviewDialog,
81 | onConfirm = { showReviewDialog = false },
82 | onDismiss = { showReviewDialog = false }
83 | )
84 |
85 | Box(modifier = Modifier.fillMaxSize()) {
86 | LazyColumn(
87 | modifier = Modifier.fillMaxSize(),
88 | contentPadding = PaddingValues(bottom = 80.dp)
89 | ) {
90 | items(uriList) { uri ->
91 | val file = AppUtil.getFileFromUri(context, uri)
92 | AudioItem(
93 | fileName = file?.name.orEmpty(),
94 | fileSize = file?.let { AppUtil.getFileSizeInReadableFormat(context, it) } ?: "Unknown"
95 | )
96 | }
97 | }
98 |
99 | FloatingActionButton(
100 | onClick = {
101 | if (ConvertItService.isForegroundServiceStarted) {
102 | Toast.makeText(
103 | context,
104 | context.getString(R.string.label_warning),
105 | Toast.LENGTH_SHORT
106 | ).show()
107 | } else {
108 | AppUtil.openFilePicker(context, pickFileLauncher)
109 | }
110 | },
111 | containerColor = MaterialTheme.colorScheme.primary,
112 | contentColor = MaterialTheme.colorScheme.onPrimary,
113 | modifier = Modifier
114 | .align(Alignment.BottomEnd)
115 | .padding(26.dp)
116 | ) {
117 | Icon(
118 | painter = painterResource(id = R.drawable.audio_ic),
119 | contentDescription = stringResource(R.string.label_select_files)
120 | )
121 | }
122 | }
123 |
124 | DialogConvertAlertDialog(
125 | showDialog = showDialog,
126 | onDismiss = { showDialog = false },
127 | onCancel = {
128 | viewModel.clearUriList()
129 | showDialog = false
130 | },
131 | uris = uriList
132 | )
133 | }
134 |
135 | @ConvertitLightPreview
136 | @ConvertitDarkPreview
137 | @Composable
138 | fun PreviewMainScreen() {
139 | HomeScreen()
140 | }
141 |
--------------------------------------------------------------------------------
/app/src/main/java/com/nasahacker/convertit/ui/theme/Color.kt:
--------------------------------------------------------------------------------
1 | package com.nasahacker.convertit.ui.theme
2 |
3 | import androidx.compose.ui.graphics.Color
4 |
5 | val primaryLight = Color(0xFF4C662B)
6 | val onPrimaryLight = Color(0xFFFFFFFF)
7 | val primaryContainerLight = Color(0xFFCDEDA3)
8 | val onPrimaryContainerLight = Color(0xFF102000)
9 | val secondaryLight = Color(0xFF586249)
10 | val onSecondaryLight = Color(0xFFFFFFFF)
11 | val secondaryContainerLight = Color(0xFFDCE7C8)
12 | val onSecondaryContainerLight = Color(0xFF151E0B)
13 | val tertiaryLight = Color(0xFF386663)
14 | val onTertiaryLight = Color(0xFFFFFFFF)
15 | val tertiaryContainerLight = Color(0xFFBCECE7)
16 | val onTertiaryContainerLight = Color(0xFF00201E)
17 | val errorLight = Color(0xFFBA1A1A)
18 | val onErrorLight = Color(0xFFFFFFFF)
19 | val errorContainerLight = Color(0xFFFFDAD6)
20 | val onErrorContainerLight = Color(0xFF410002)
21 | val backgroundLight = Color(0xFFF9FAEF)
22 | val onBackgroundLight = Color(0xFF1A1C16)
23 | val surfaceLight = Color(0xFFF9FAEF)
24 | val onSurfaceLight = Color(0xFF1A1C16)
25 | val surfaceVariantLight = Color(0xFFE1E4D5)
26 | val onSurfaceVariantLight = Color(0xFF44483D)
27 | val outlineLight = Color(0xFF75796C)
28 | val outlineVariantLight = Color(0xFFC5C8BA)
29 | val scrimLight = Color(0xFF000000)
30 | val inverseSurfaceLight = Color(0xFF2F312A)
31 | val inverseOnSurfaceLight = Color(0xFFF1F2E6)
32 | val inversePrimaryLight = Color(0xFFB1D18A)
33 | val surfaceDimLight = Color(0xFFDADBD0)
34 | val surfaceBrightLight = Color(0xFFF9FAEF)
35 | val surfaceContainerLowestLight = Color(0xFFFFFFFF)
36 | val surfaceContainerLowLight = Color(0xFFF3F4E9)
37 | val surfaceContainerLight = Color(0xFFEEEFE3)
38 | val surfaceContainerHighLight = Color(0xFFE8E9DE)
39 | val surfaceContainerHighestLight = Color(0xFFE2E3D8)
40 |
41 | val primaryLightMediumContrast = Color(0xFF314A12)
42 | val onPrimaryLightMediumContrast = Color(0xFFFFFFFF)
43 | val primaryContainerLightMediumContrast = Color(0xFF617D3F)
44 | val onPrimaryContainerLightMediumContrast = Color(0xFFFFFFFF)
45 | val secondaryLightMediumContrast = Color(0xFF3C462F)
46 | val onSecondaryLightMediumContrast = Color(0xFFFFFFFF)
47 | val secondaryContainerLightMediumContrast = Color(0xFF6E785E)
48 | val onSecondaryContainerLightMediumContrast = Color(0xFFFFFFFF)
49 | val tertiaryLightMediumContrast = Color(0xFF1A4A47)
50 | val onTertiaryLightMediumContrast = Color(0xFFFFFFFF)
51 | val tertiaryContainerLightMediumContrast = Color(0xFF4F7D79)
52 | val onTertiaryContainerLightMediumContrast = Color(0xFFFFFFFF)
53 | val errorLightMediumContrast = Color(0xFF8C0009)
54 | val onErrorLightMediumContrast = Color(0xFFFFFFFF)
55 | val errorContainerLightMediumContrast = Color(0xFFDA342E)
56 | val onErrorContainerLightMediumContrast = Color(0xFFFFFFFF)
57 | val backgroundLightMediumContrast = Color(0xFFF9FAEF)
58 | val onBackgroundLightMediumContrast = Color(0xFF1A1C16)
59 | val surfaceLightMediumContrast = Color(0xFFF9FAEF)
60 | val onSurfaceLightMediumContrast = Color(0xFF1A1C16)
61 | val surfaceVariantLightMediumContrast = Color(0xFFE1E4D5)
62 | val onSurfaceVariantLightMediumContrast = Color(0xFF404439)
63 | val outlineLightMediumContrast = Color(0xFF5D6155)
64 | val outlineVariantLightMediumContrast = Color(0xFF787C70)
65 | val scrimLightMediumContrast = Color(0xFF000000)
66 | val inverseSurfaceLightMediumContrast = Color(0xFF2F312A)
67 | val inverseOnSurfaceLightMediumContrast = Color(0xFFF1F2E6)
68 | val inversePrimaryLightMediumContrast = Color(0xFFB1D18A)
69 | val surfaceDimLightMediumContrast = Color(0xFFDADBD0)
70 | val surfaceBrightLightMediumContrast = Color(0xFFF9FAEF)
71 | val surfaceContainerLowestLightMediumContrast = Color(0xFFFFFFFF)
72 | val surfaceContainerLowLightMediumContrast = Color(0xFFF3F4E9)
73 | val surfaceContainerLightMediumContrast = Color(0xFFEEEFE3)
74 | val surfaceContainerHighLightMediumContrast = Color(0xFFE8E9DE)
75 | val surfaceContainerHighestLightMediumContrast = Color(0xFFE2E3D8)
76 |
77 | val primaryLightHighContrast = Color(0xFF142700)
78 | val onPrimaryLightHighContrast = Color(0xFFFFFFFF)
79 | val primaryContainerLightHighContrast = Color(0xFF314A12)
80 | val onPrimaryContainerLightHighContrast = Color(0xFFFFFFFF)
81 | val secondaryLightHighContrast = Color(0xFF1C2511)
82 | val onSecondaryLightHighContrast = Color(0xFFFFFFFF)
83 | val secondaryContainerLightHighContrast = Color(0xFF3C462F)
84 | val onSecondaryContainerLightHighContrast = Color(0xFFFFFFFF)
85 | val tertiaryLightHighContrast = Color(0xFF002725)
86 | val onTertiaryLightHighContrast = Color(0xFFFFFFFF)
87 | val tertiaryContainerLightHighContrast = Color(0xFF1A4A47)
88 | val onTertiaryContainerLightHighContrast = Color(0xFFFFFFFF)
89 | val errorLightHighContrast = Color(0xFF4E0002)
90 | val onErrorLightHighContrast = Color(0xFFFFFFFF)
91 | val errorContainerLightHighContrast = Color(0xFF8C0009)
92 | val onErrorContainerLightHighContrast = Color(0xFFFFFFFF)
93 | val backgroundLightHighContrast = Color(0xFFF9FAEF)
94 | val onBackgroundLightHighContrast = Color(0xFF1A1C16)
95 | val surfaceLightHighContrast = Color(0xFFF9FAEF)
96 | val onSurfaceLightHighContrast = Color(0xFF000000)
97 | val surfaceVariantLightHighContrast = Color(0xFFE1E4D5)
98 | val onSurfaceVariantLightHighContrast = Color(0xFF21251C)
99 | val outlineLightHighContrast = Color(0xFF404439)
100 | val outlineVariantLightHighContrast = Color(0xFF404439)
101 | val scrimLightHighContrast = Color(0xFF000000)
102 | val inverseSurfaceLightHighContrast = Color(0xFF2F312A)
103 | val inverseOnSurfaceLightHighContrast = Color(0xFFFFFFFF)
104 | val inversePrimaryLightHighContrast = Color(0xFFD6F7AC)
105 | val surfaceDimLightHighContrast = Color(0xFFDADBD0)
106 | val surfaceBrightLightHighContrast = Color(0xFFF9FAEF)
107 | val surfaceContainerLowestLightHighContrast = Color(0xFFFFFFFF)
108 | val surfaceContainerLowLightHighContrast = Color(0xFFF3F4E9)
109 | val surfaceContainerLightHighContrast = Color(0xFFEEEFE3)
110 | val surfaceContainerHighLightHighContrast = Color(0xFFE8E9DE)
111 | val surfaceContainerHighestLightHighContrast = Color(0xFFE2E3D8)
112 |
113 | val primaryDark = Color(0xFFB1D18A)
114 | val onPrimaryDark = Color(0xFF1F3701)
115 | val primaryContainerDark = Color(0xFF354E16)
116 | val onPrimaryContainerDark = Color(0xFFCDEDA3)
117 | val secondaryDark = Color(0xFFBFCBAD)
118 | val onSecondaryDark = Color(0xFF2A331E)
119 | val secondaryContainerDark = Color(0xFF404A33)
120 | val onSecondaryContainerDark = Color(0xFFDCE7C8)
121 | val tertiaryDark = Color(0xFFA0D0CB)
122 | val onTertiaryDark = Color(0xFF003735)
123 | val tertiaryContainerDark = Color(0xFF1F4E4B)
124 | val onTertiaryContainerDark = Color(0xFFBCECE7)
125 | val errorDark = Color(0xFFFFB4AB)
126 | val onErrorDark = Color(0xFF690005)
127 | val errorContainerDark = Color(0xFF93000A)
128 | val onErrorContainerDark = Color(0xFFFFDAD6)
129 | val backgroundDark = Color(0xFF12140E)
130 | val onBackgroundDark = Color(0xFFE2E3D8)
131 | val surfaceDark = Color(0xFF12140E)
132 | val onSurfaceDark = Color(0xFFE2E3D8)
133 | val surfaceVariantDark = Color(0xFF44483D)
134 | val onSurfaceVariantDark = Color(0xFFC5C8BA)
135 | val outlineDark = Color(0xFF8F9285)
136 | val outlineVariantDark = Color(0xFF44483D)
137 | val scrimDark = Color(0xFF000000)
138 | val inverseSurfaceDark = Color(0xFFE2E3D8)
139 | val inverseOnSurfaceDark = Color(0xFF2F312A)
140 | val inversePrimaryDark = Color(0xFF4C662B)
141 | val surfaceDimDark = Color(0xFF12140E)
142 | val surfaceBrightDark = Color(0xFF383A32)
143 | val surfaceContainerLowestDark = Color(0xFF0C0F09)
144 | val surfaceContainerLowDark = Color(0xFF1A1C16)
145 | val surfaceContainerDark = Color(0xFF1E201A)
146 | val surfaceContainerHighDark = Color(0xFF282B24)
147 | val surfaceContainerHighestDark = Color(0xFF33362E)
148 |
149 | val primaryDarkMediumContrast = Color(0xFFB5D58E)
150 | val onPrimaryDarkMediumContrast = Color(0xFF0C1A00)
151 | val primaryContainerDarkMediumContrast = Color(0xFF7D9A59)
152 | val onPrimaryContainerDarkMediumContrast = Color(0xFF000000)
153 | val secondaryDarkMediumContrast = Color(0xFFC4CFB1)
154 | val onSecondaryDarkMediumContrast = Color(0xFF101907)
155 | val secondaryContainerDarkMediumContrast = Color(0xFF8A9579)
156 | val onSecondaryContainerDarkMediumContrast = Color(0xFF000000)
157 | val tertiaryDarkMediumContrast = Color(0xFFA4D4D0)
158 | val onTertiaryDarkMediumContrast = Color(0xFF001A19)
159 | val tertiaryContainerDarkMediumContrast = Color(0xFF6B9995)
160 | val onTertiaryContainerDarkMediumContrast = Color(0xFF000000)
161 | val errorDarkMediumContrast = Color(0xFFFFBAB1)
162 | val onErrorDarkMediumContrast = Color(0xFF370001)
163 | val errorContainerDarkMediumContrast = Color(0xFFFF5449)
164 | val onErrorContainerDarkMediumContrast = Color(0xFF000000)
165 | val backgroundDarkMediumContrast = Color(0xFF12140E)
166 | val onBackgroundDarkMediumContrast = Color(0xFFE2E3D8)
167 | val surfaceDarkMediumContrast = Color(0xFF12140E)
168 | val onSurfaceDarkMediumContrast = Color(0xFFFBFCF0)
169 | val surfaceVariantDarkMediumContrast = Color(0xFF44483D)
170 | val onSurfaceVariantDarkMediumContrast = Color(0xFFC9CCBE)
171 | val outlineDarkMediumContrast = Color(0xFFA1A497)
172 | val outlineVariantDarkMediumContrast = Color(0xFF818578)
173 | val scrimDarkMediumContrast = Color(0xFF000000)
174 | val inverseSurfaceDarkMediumContrast = Color(0xFFE2E3D8)
175 | val inverseOnSurfaceDarkMediumContrast = Color(0xFF282B24)
176 | val inversePrimaryDarkMediumContrast = Color(0xFF364F17)
177 | val surfaceDimDarkMediumContrast = Color(0xFF12140E)
178 | val surfaceBrightDarkMediumContrast = Color(0xFF383A32)
179 | val surfaceContainerLowestDarkMediumContrast = Color(0xFF0C0F09)
180 | val surfaceContainerLowDarkMediumContrast = Color(0xFF1A1C16)
181 | val surfaceContainerDarkMediumContrast = Color(0xFF1E201A)
182 | val surfaceContainerHighDarkMediumContrast = Color(0xFF282B24)
183 | val surfaceContainerHighestDarkMediumContrast = Color(0xFF33362E)
184 |
185 | val primaryDarkHighContrast = Color(0xFFF4FFDF)
186 | val onPrimaryDarkHighContrast = Color(0xFF000000)
187 | val primaryContainerDarkHighContrast = Color(0xFFB5D58E)
188 | val onPrimaryContainerDarkHighContrast = Color(0xFF000000)
189 | val secondaryDarkHighContrast = Color(0xFFF4FFDF)
190 | val onSecondaryDarkHighContrast = Color(0xFF000000)
191 | val secondaryContainerDarkHighContrast = Color(0xFFC4CFB1)
192 | val onSecondaryContainerDarkHighContrast = Color(0xFF000000)
193 | val tertiaryDarkHighContrast = Color(0xFFEAFFFC)
194 | val onTertiaryDarkHighContrast = Color(0xFF000000)
195 | val tertiaryContainerDarkHighContrast = Color(0xFFA4D4D0)
196 | val onTertiaryContainerDarkHighContrast = Color(0xFF000000)
197 | val errorDarkHighContrast = Color(0xFFFFF9F9)
198 | val onErrorDarkHighContrast = Color(0xFF000000)
199 | val errorContainerDarkHighContrast = Color(0xFFFFBAB1)
200 | val onErrorContainerDarkHighContrast = Color(0xFF000000)
201 | val backgroundDarkHighContrast = Color(0xFF12140E)
202 | val onBackgroundDarkHighContrast = Color(0xFFE2E3D8)
203 | val surfaceDarkHighContrast = Color(0xFF12140E)
204 | val onSurfaceDarkHighContrast = Color(0xFFFFFFFF)
205 | val surfaceVariantDarkHighContrast = Color(0xFF44483D)
206 | val onSurfaceVariantDarkHighContrast = Color(0xFFF9FCED)
207 | val outlineDarkHighContrast = Color(0xFFC9CCBE)
208 | val outlineVariantDarkHighContrast = Color(0xFFC9CCBE)
209 | val scrimDarkHighContrast = Color(0xFF000000)
210 | val inverseSurfaceDarkHighContrast = Color(0xFFE2E3D8)
211 | val inverseOnSurfaceDarkHighContrast = Color(0xFF000000)
212 | val inversePrimaryDarkHighContrast = Color(0xFF1A3000)
213 | val surfaceDimDarkHighContrast = Color(0xFF12140E)
214 | val surfaceBrightDarkHighContrast = Color(0xFF383A32)
215 | val surfaceContainerLowestDarkHighContrast = Color(0xFF0C0F09)
216 | val surfaceContainerLowDarkHighContrast = Color(0xFF1A1C16)
217 | val surfaceContainerDarkHighContrast = Color(0xFF1E201A)
218 | val surfaceContainerHighDarkHighContrast = Color(0xFF282B24)
219 | val surfaceContainerHighestDarkHighContrast = Color(0xFF33362E)
220 |
--------------------------------------------------------------------------------
/app/src/main/java/com/nasahacker/convertit/ui/theme/Theme.kt:
--------------------------------------------------------------------------------
1 | package com.nasahacker.convertit.ui.theme
2 | import android.os.Build
3 | import androidx.compose.foundation.isSystemInDarkTheme
4 | import androidx.compose.material3.MaterialTheme
5 | import androidx.compose.material3.darkColorScheme
6 | import androidx.compose.material3.dynamicDarkColorScheme
7 | import androidx.compose.material3.dynamicLightColorScheme
8 | import androidx.compose.material3.lightColorScheme
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.runtime.Immutable
11 | import androidx.compose.ui.graphics.Color
12 | import androidx.compose.ui.platform.LocalContext
13 |
14 | private val lightScheme =
15 | lightColorScheme(
16 | primary = primaryLight,
17 | onPrimary = onPrimaryLight,
18 | primaryContainer = primaryContainerLight,
19 | onPrimaryContainer = onPrimaryContainerLight,
20 | secondary = secondaryLight,
21 | onSecondary = onSecondaryLight,
22 | secondaryContainer = secondaryContainerLight,
23 | onSecondaryContainer = onSecondaryContainerLight,
24 | tertiary = tertiaryLight,
25 | onTertiary = onTertiaryLight,
26 | tertiaryContainer = tertiaryContainerLight,
27 | onTertiaryContainer = onTertiaryContainerLight,
28 | error = errorLight,
29 | onError = onErrorLight,
30 | errorContainer = errorContainerLight,
31 | onErrorContainer = onErrorContainerLight,
32 | background = backgroundLight,
33 | onBackground = onBackgroundLight,
34 | surface = surfaceLight,
35 | onSurface = onSurfaceLight,
36 | surfaceVariant = surfaceVariantLight,
37 | onSurfaceVariant = onSurfaceVariantLight,
38 | outline = outlineLight,
39 | outlineVariant = outlineVariantLight,
40 | scrim = scrimLight,
41 | inverseSurface = inverseSurfaceLight,
42 | inverseOnSurface = inverseOnSurfaceLight,
43 | inversePrimary = inversePrimaryLight,
44 | surfaceDim = surfaceDimLight,
45 | surfaceBright = surfaceBrightLight,
46 | surfaceContainerLowest = surfaceContainerLowestLight,
47 | surfaceContainerLow = surfaceContainerLowLight,
48 | surfaceContainer = surfaceContainerLight,
49 | surfaceContainerHigh = surfaceContainerHighLight,
50 | surfaceContainerHighest = surfaceContainerHighestLight,
51 | )
52 |
53 | private val darkScheme =
54 | darkColorScheme(
55 | primary = primaryDark,
56 | onPrimary = onPrimaryDark,
57 | primaryContainer = primaryContainerDark,
58 | onPrimaryContainer = onPrimaryContainerDark,
59 | secondary = secondaryDark,
60 | onSecondary = onSecondaryDark,
61 | secondaryContainer = secondaryContainerDark,
62 | onSecondaryContainer = onSecondaryContainerDark,
63 | tertiary = tertiaryDark,
64 | onTertiary = onTertiaryDark,
65 | tertiaryContainer = tertiaryContainerDark,
66 | onTertiaryContainer = onTertiaryContainerDark,
67 | error = errorDark,
68 | onError = onErrorDark,
69 | errorContainer = errorContainerDark,
70 | onErrorContainer = onErrorContainerDark,
71 | background = backgroundDark,
72 | onBackground = onBackgroundDark,
73 | surface = surfaceDark,
74 | onSurface = onSurfaceDark,
75 | surfaceVariant = surfaceVariantDark,
76 | onSurfaceVariant = onSurfaceVariantDark,
77 | outline = outlineDark,
78 | outlineVariant = outlineVariantDark,
79 | scrim = scrimDark,
80 | inverseSurface = inverseSurfaceDark,
81 | inverseOnSurface = inverseOnSurfaceDark,
82 | inversePrimary = inversePrimaryDark,
83 | surfaceDim = surfaceDimDark,
84 | surfaceBright = surfaceBrightDark,
85 | surfaceContainerLowest = surfaceContainerLowestDark,
86 | surfaceContainerLow = surfaceContainerLowDark,
87 | surfaceContainer = surfaceContainerDark,
88 | surfaceContainerHigh = surfaceContainerHighDark,
89 | surfaceContainerHighest = surfaceContainerHighestDark,
90 | )
91 |
92 | private val mediumContrastLightColorScheme =
93 | lightColorScheme(
94 | primary = primaryLightMediumContrast,
95 | onPrimary = onPrimaryLightMediumContrast,
96 | primaryContainer = primaryContainerLightMediumContrast,
97 | onPrimaryContainer = onPrimaryContainerLightMediumContrast,
98 | secondary = secondaryLightMediumContrast,
99 | onSecondary = onSecondaryLightMediumContrast,
100 | secondaryContainer = secondaryContainerLightMediumContrast,
101 | onSecondaryContainer = onSecondaryContainerLightMediumContrast,
102 | tertiary = tertiaryLightMediumContrast,
103 | onTertiary = onTertiaryLightMediumContrast,
104 | tertiaryContainer = tertiaryContainerLightMediumContrast,
105 | onTertiaryContainer = onTertiaryContainerLightMediumContrast,
106 | error = errorLightMediumContrast,
107 | onError = onErrorLightMediumContrast,
108 | errorContainer = errorContainerLightMediumContrast,
109 | onErrorContainer = onErrorContainerLightMediumContrast,
110 | background = backgroundLightMediumContrast,
111 | onBackground = onBackgroundLightMediumContrast,
112 | surface = surfaceLightMediumContrast,
113 | onSurface = onSurfaceLightMediumContrast,
114 | surfaceVariant = surfaceVariantLightMediumContrast,
115 | onSurfaceVariant = onSurfaceVariantLightMediumContrast,
116 | outline = outlineLightMediumContrast,
117 | outlineVariant = outlineVariantLightMediumContrast,
118 | scrim = scrimLightMediumContrast,
119 | inverseSurface = inverseSurfaceLightMediumContrast,
120 | inverseOnSurface = inverseOnSurfaceLightMediumContrast,
121 | inversePrimary = inversePrimaryLightMediumContrast,
122 | surfaceDim = surfaceDimLightMediumContrast,
123 | surfaceBright = surfaceBrightLightMediumContrast,
124 | surfaceContainerLowest = surfaceContainerLowestLightMediumContrast,
125 | surfaceContainerLow = surfaceContainerLowLightMediumContrast,
126 | surfaceContainer = surfaceContainerLightMediumContrast,
127 | surfaceContainerHigh = surfaceContainerHighLightMediumContrast,
128 | surfaceContainerHighest = surfaceContainerHighestLightMediumContrast,
129 | )
130 |
131 | private val highContrastLightColorScheme =
132 | lightColorScheme(
133 | primary = primaryLightHighContrast,
134 | onPrimary = onPrimaryLightHighContrast,
135 | primaryContainer = primaryContainerLightHighContrast,
136 | onPrimaryContainer = onPrimaryContainerLightHighContrast,
137 | secondary = secondaryLightHighContrast,
138 | onSecondary = onSecondaryLightHighContrast,
139 | secondaryContainer = secondaryContainerLightHighContrast,
140 | onSecondaryContainer = onSecondaryContainerLightHighContrast,
141 | tertiary = tertiaryLightHighContrast,
142 | onTertiary = onTertiaryLightHighContrast,
143 | tertiaryContainer = tertiaryContainerLightHighContrast,
144 | onTertiaryContainer = onTertiaryContainerLightHighContrast,
145 | error = errorLightHighContrast,
146 | onError = onErrorLightHighContrast,
147 | errorContainer = errorContainerLightHighContrast,
148 | onErrorContainer = onErrorContainerLightHighContrast,
149 | background = backgroundLightHighContrast,
150 | onBackground = onBackgroundLightHighContrast,
151 | surface = surfaceLightHighContrast,
152 | onSurface = onSurfaceLightHighContrast,
153 | surfaceVariant = surfaceVariantLightHighContrast,
154 | onSurfaceVariant = onSurfaceVariantLightHighContrast,
155 | outline = outlineLightHighContrast,
156 | outlineVariant = outlineVariantLightHighContrast,
157 | scrim = scrimLightHighContrast,
158 | inverseSurface = inverseSurfaceLightHighContrast,
159 | inverseOnSurface = inverseOnSurfaceLightHighContrast,
160 | inversePrimary = inversePrimaryLightHighContrast,
161 | surfaceDim = surfaceDimLightHighContrast,
162 | surfaceBright = surfaceBrightLightHighContrast,
163 | surfaceContainerLowest = surfaceContainerLowestLightHighContrast,
164 | surfaceContainerLow = surfaceContainerLowLightHighContrast,
165 | surfaceContainer = surfaceContainerLightHighContrast,
166 | surfaceContainerHigh = surfaceContainerHighLightHighContrast,
167 | surfaceContainerHighest = surfaceContainerHighestLightHighContrast,
168 | )
169 |
170 | private val mediumContrastDarkColorScheme =
171 | darkColorScheme(
172 | primary = primaryDarkMediumContrast,
173 | onPrimary = onPrimaryDarkMediumContrast,
174 | primaryContainer = primaryContainerDarkMediumContrast,
175 | onPrimaryContainer = onPrimaryContainerDarkMediumContrast,
176 | secondary = secondaryDarkMediumContrast,
177 | onSecondary = onSecondaryDarkMediumContrast,
178 | secondaryContainer = secondaryContainerDarkMediumContrast,
179 | onSecondaryContainer = onSecondaryContainerDarkMediumContrast,
180 | tertiary = tertiaryDarkMediumContrast,
181 | onTertiary = onTertiaryDarkMediumContrast,
182 | tertiaryContainer = tertiaryContainerDarkMediumContrast,
183 | onTertiaryContainer = onTertiaryContainerDarkMediumContrast,
184 | error = errorDarkMediumContrast,
185 | onError = onErrorDarkMediumContrast,
186 | errorContainer = errorContainerDarkMediumContrast,
187 | onErrorContainer = onErrorContainerDarkMediumContrast,
188 | background = backgroundDarkMediumContrast,
189 | onBackground = onBackgroundDarkMediumContrast,
190 | surface = surfaceDarkMediumContrast,
191 | onSurface = onSurfaceDarkMediumContrast,
192 | surfaceVariant = surfaceVariantDarkMediumContrast,
193 | onSurfaceVariant = onSurfaceVariantDarkMediumContrast,
194 | outline = outlineDarkMediumContrast,
195 | outlineVariant = outlineVariantDarkMediumContrast,
196 | scrim = scrimDarkMediumContrast,
197 | inverseSurface = inverseSurfaceDarkMediumContrast,
198 | inverseOnSurface = inverseOnSurfaceDarkMediumContrast,
199 | inversePrimary = inversePrimaryDarkMediumContrast,
200 | surfaceDim = surfaceDimDarkMediumContrast,
201 | surfaceBright = surfaceBrightDarkMediumContrast,
202 | surfaceContainerLowest = surfaceContainerLowestDarkMediumContrast,
203 | surfaceContainerLow = surfaceContainerLowDarkMediumContrast,
204 | surfaceContainer = surfaceContainerDarkMediumContrast,
205 | surfaceContainerHigh = surfaceContainerHighDarkMediumContrast,
206 | surfaceContainerHighest = surfaceContainerHighestDarkMediumContrast,
207 | )
208 |
209 | private val highContrastDarkColorScheme =
210 | darkColorScheme(
211 | primary = primaryDarkHighContrast,
212 | onPrimary = onPrimaryDarkHighContrast,
213 | primaryContainer = primaryContainerDarkHighContrast,
214 | onPrimaryContainer = onPrimaryContainerDarkHighContrast,
215 | secondary = secondaryDarkHighContrast,
216 | onSecondary = onSecondaryDarkHighContrast,
217 | secondaryContainer = secondaryContainerDarkHighContrast,
218 | onSecondaryContainer = onSecondaryContainerDarkHighContrast,
219 | tertiary = tertiaryDarkHighContrast,
220 | onTertiary = onTertiaryDarkHighContrast,
221 | tertiaryContainer = tertiaryContainerDarkHighContrast,
222 | onTertiaryContainer = onTertiaryContainerDarkHighContrast,
223 | error = errorDarkHighContrast,
224 | onError = onErrorDarkHighContrast,
225 | errorContainer = errorContainerDarkHighContrast,
226 | onErrorContainer = onErrorContainerDarkHighContrast,
227 | background = backgroundDarkHighContrast,
228 | onBackground = onBackgroundDarkHighContrast,
229 | surface = surfaceDarkHighContrast,
230 | onSurface = onSurfaceDarkHighContrast,
231 | surfaceVariant = surfaceVariantDarkHighContrast,
232 | onSurfaceVariant = onSurfaceVariantDarkHighContrast,
233 | outline = outlineDarkHighContrast,
234 | outlineVariant = outlineVariantDarkHighContrast,
235 | scrim = scrimDarkHighContrast,
236 | inverseSurface = inverseSurfaceDarkHighContrast,
237 | inverseOnSurface = inverseOnSurfaceDarkHighContrast,
238 | inversePrimary = inversePrimaryDarkHighContrast,
239 | surfaceDim = surfaceDimDarkHighContrast,
240 | surfaceBright = surfaceBrightDarkHighContrast,
241 | surfaceContainerLowest = surfaceContainerLowestDarkHighContrast,
242 | surfaceContainerLow = surfaceContainerLowDarkHighContrast,
243 | surfaceContainer = surfaceContainerDarkHighContrast,
244 | surfaceContainerHigh = surfaceContainerHighDarkHighContrast,
245 | surfaceContainerHighest = surfaceContainerHighestDarkHighContrast,
246 | )
247 |
248 | @Immutable
249 | data class ColorFamily(
250 | val color: Color,
251 | val onColor: Color,
252 | val colorContainer: Color,
253 | val onColorContainer: Color,
254 | )
255 |
256 | val unspecified_scheme =
257 | ColorFamily(
258 | Color.Unspecified,
259 | Color.Unspecified,
260 | Color.Unspecified,
261 | Color.Unspecified,
262 | )
263 |
264 | @Composable
265 | fun AppTheme(
266 | darkTheme: Boolean = isSystemInDarkTheme(),
267 | // Dynamic color is available on Android 12+
268 | dynamicColor: Boolean = true,
269 | content:
270 | @Composable()
271 | () -> Unit,
272 | ) {
273 | val colorScheme =
274 | when {
275 | dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
276 | val context = LocalContext.current
277 | if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
278 | }
279 |
280 | darkTheme -> darkScheme
281 | else -> lightScheme
282 | }
283 |
284 | MaterialTheme(
285 | colorScheme = colorScheme,
286 | typography = AppTypography,
287 | content = content,
288 | )
289 | }
290 |
--------------------------------------------------------------------------------
/app/src/main/java/com/nasahacker/convertit/ui/theme/Type.kt:
--------------------------------------------------------------------------------
1 | package com.nasahacker.convertit.ui.theme
2 |
3 | import androidx.compose.material3.Typography
4 | import androidx.compose.ui.text.font.FontFamily
5 | import androidx.compose.ui.text.googlefonts.Font
6 | import androidx.compose.ui.text.googlefonts.GoogleFont
7 | import com.nasahacker.convertit.R
8 |
9 | val provider =
10 | GoogleFont.Provider(
11 | providerAuthority = "com.google.android.gms.fonts",
12 | providerPackage = "com.google.android.gms",
13 | certificates = R.array.com_google_android_gms_fonts_certs,
14 | )
15 |
16 | val bodyFontFamily =
17 | FontFamily(
18 | Font(
19 | googleFont = GoogleFont("Roboto"),
20 | fontProvider = provider,
21 | ),
22 | )
23 |
24 | val displayFontFamily =
25 | FontFamily(
26 | Font(
27 | googleFont = GoogleFont("Roboto"),
28 | fontProvider = provider,
29 | ),
30 | )
31 |
32 | // Default Material 3 typography values
33 | val baseline = Typography()
34 |
35 | val AppTypography =
36 | Typography(
37 | displayLarge = baseline.displayLarge.copy(fontFamily = displayFontFamily),
38 | displayMedium = baseline.displayMedium.copy(fontFamily = displayFontFamily),
39 | displaySmall = baseline.displaySmall.copy(fontFamily = displayFontFamily),
40 | headlineLarge = baseline.headlineLarge.copy(fontFamily = displayFontFamily),
41 | headlineMedium = baseline.headlineMedium.copy(fontFamily = displayFontFamily),
42 | headlineSmall = baseline.headlineSmall.copy(fontFamily = displayFontFamily),
43 | titleLarge = baseline.titleLarge.copy(fontFamily = displayFontFamily),
44 | titleMedium = baseline.titleMedium.copy(fontFamily = displayFontFamily),
45 | titleSmall = baseline.titleSmall.copy(fontFamily = displayFontFamily),
46 | bodyLarge = baseline.bodyLarge.copy(fontFamily = bodyFontFamily),
47 | bodyMedium = baseline.bodyMedium.copy(fontFamily = bodyFontFamily),
48 | bodySmall = baseline.bodySmall.copy(fontFamily = bodyFontFamily),
49 | labelLarge = baseline.labelLarge.copy(fontFamily = bodyFontFamily),
50 | labelMedium = baseline.labelMedium.copy(fontFamily = bodyFontFamily),
51 | labelSmall = baseline.labelSmall.copy(fontFamily = bodyFontFamily),
52 | )
53 |
--------------------------------------------------------------------------------
/app/src/main/java/com/nasahacker/convertit/ui/viewmodel/AppViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.nasahacker.convertit.ui.viewmodel
2 |
3 | import android.content.BroadcastReceiver
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.content.IntentFilter
7 | import android.net.Uri
8 | import androidx.core.content.ContextCompat
9 | import androidx.lifecycle.ViewModel
10 | import androidx.lifecycle.viewModelScope
11 | import com.nasahacker.convertit.App
12 | import com.nasahacker.convertit.util.AppUtil
13 | import com.nasahacker.convertit.util.AppConfig
14 | import com.nasahacker.convertit.util.AppConfig.IS_SUCCESS
15 | import kotlinx.coroutines.flow.MutableStateFlow
16 | import kotlinx.coroutines.flow.StateFlow
17 | import kotlinx.coroutines.launch
18 |
19 | /**
20 | * @author Tamim Hossain
21 | * @email tamimh.dev@gmail.com
22 | * @license Apache-2.0
23 | *
24 | * ConvertIt is a free and easy-to-use audio converter app.
25 | * It supports popular audio formats like MP3 and M4A.
26 | * With options for high-quality bitrates ranging from 128k to 320k,
27 | * ConvertIt offers a seamless conversion experience tailored to your needs.
28 | */
29 |
30 | class AppViewModel : ViewModel() {
31 | private val _uriList = MutableStateFlow>(ArrayList())
32 | val uriList: StateFlow> = _uriList
33 |
34 | private val _conversionStatus = MutableStateFlow(null)
35 | val conversionStatus: StateFlow = _conversionStatus
36 |
37 | private val conversionStatusReceiver =
38 | object : BroadcastReceiver() {
39 | override fun onReceive(
40 | context: Context?,
41 | intent: Intent?,
42 | ) {
43 | val isSuccess = intent?.getBooleanExtra(IS_SUCCESS, false) == true
44 | viewModelScope.launch {
45 | _conversionStatus.value = isSuccess
46 | if (isSuccess) {
47 | clearUriList()
48 | }
49 | }
50 | }
51 | }
52 |
53 | fun resetConversionStatus() {
54 | viewModelScope.launch {
55 | _conversionStatus.value = null
56 | }
57 | }
58 |
59 | init {
60 | startListeningForBroadcasts()
61 | }
62 |
63 | /**
64 | * Updates the URI list with new URIs from the given intent.
65 | */
66 | fun updateUriList(intent: Intent?) {
67 | viewModelScope.launch {
68 | intent?.let {
69 | val uris = AppUtil.getUriListFromIntent(it)
70 | if (uris.isNotEmpty()) {
71 | val updatedList = ArrayList(_uriList.value).apply { addAll(uris) }
72 | _uriList.value = updatedList
73 | }
74 | }
75 | }
76 | }
77 |
78 | /**
79 | * Registers the BroadcastReceiver to listen for conversion status updates.
80 | */
81 | private fun startListeningForBroadcasts() {
82 | val intentFilter = IntentFilter(AppConfig.CONVERT_BROADCAST_ACTION)
83 | ContextCompat.registerReceiver(
84 | App.application,
85 | conversionStatusReceiver,
86 | intentFilter,
87 | AppUtil.receiverFlags(),
88 | )
89 | }
90 |
91 | /**
92 | * Clears the URI list.
93 | */
94 | fun clearUriList() {
95 | viewModelScope.launch {
96 | _uriList.value = ArrayList()
97 | }
98 | }
99 |
100 | /**
101 | * Unregisters the BroadcastReceiver when the ViewModel is cleared.
102 | */
103 | override fun onCleared() {
104 | super.onCleared()
105 | App.application.unregisterReceiver(conversionStatusReceiver)
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/app/src/main/java/com/nasahacker/convertit/util/AppConfig.kt:
--------------------------------------------------------------------------------
1 | package com.nasahacker.convertit.util
2 |
3 | /**
4 | * @author Tamim Hossain
5 | * @email tamimh.dev@gmail.com
6 | * @license Apache-2.0
7 | *
8 | * ConvertIt is a free and easy-to-use audio converter app.
9 | * It supports popular audio formats like MP3 and M4A.
10 | * With options for high-quality bitrates ranging from 128k to 320k,
11 | * ConvertIt offers a seamless conversion experience tailored to your needs.
12 | */
13 |
14 | object AppConfig {
15 | const val GITHUB_ISSUES_URL = "https://github.com/TheByteArray/ConvertIt/issues"
16 | const val STORAGE_PERMISSION_CODE = 101
17 |
18 | val BITRATE_ARRAY =
19 | listOf(
20 | "64k",
21 | "96k",
22 | "128k",
23 | "192k",
24 | "256k",
25 | "320k",
26 | "512k",
27 | "768k",
28 | "1024k",
29 | )
30 | val FORMAT_ARRAY =
31 | listOf(
32 | ".mp3",
33 | ".m4a",
34 | ".aac",
35 | ".ogg",
36 | ".opus",
37 | ".wma",
38 | ".mka",
39 | ".spx",
40 | )
41 |
42 | val FORMAT_BITRATE_MAP = mapOf(
43 | ".mp3" to listOf("64k", "96k", "128k", "192k", "256k", "320k"),
44 | ".aac" to listOf("64k", "96k", "128k", "192k", "256k", "320k"),
45 | ".m4a" to listOf("64k", "96k", "128k", "192k", "256k", "320k", "512k"),
46 | ".ogg" to listOf("64k", "96k", "128k", "192k", "256k", "320k", "512k"),
47 | ".opus" to listOf("64k", "96k", "128k", "192k", "256k", "320k"),
48 | ".wma" to listOf("64k", "96k", "128k", "192k", "256k", "320k"),
49 | ".mka" to BITRATE_ARRAY,
50 | ".spx" to listOf("64k", "96k", "128k", "192k"),
51 | )
52 |
53 | const val URI_LIST = "uri_list"
54 | const val BITRATE = "bitrate"
55 | const val AUDIO_FORMAT = "audio_format"
56 | const val AUDIO_PLAYBACK_SPEED = "audio_playback_speed"
57 | const val CONVERT_BROADCAST_ACTION = "com.nasahacker.convertit.ACTION_CONVERSION_COMPLETE"
58 | const val ACTION_STOP_SERVICE = "com.nasahacker.convertit.ACTION_STOP_SERVICE"
59 | const val IS_SUCCESS = "isSuccess"
60 | const val CHANNEL_ID = "CONVERT_IT_CHANNEL_ID"
61 | const val CHANNEL_NAME = "ConvertIt Notifications"
62 | const val FOLDER_DIR = "ConvertIt"
63 | const val DISCORD_CHANNEL = "https://discord.com/invite/2WCsnpw4et"
64 | const val GITHUB_PROFILE = "https://github.com/codewithtamim"
65 | const val GITHUB_PROFILE_MOD = "https://github.com/moontahid"
66 | const val TELEGRAM_CHANNEL = "https://t.me/thebytearray"
67 | const val APP_PREF = "app_prefs"
68 | const val PREF_DONT_SHOW_AGAIN = "pref_dont_show_again"
69 | }
70 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/audio_ic.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/baseline_info_24.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/baseline_stop_24.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/discord_ic.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/github_ic.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/home_filled.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/home_outlined.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
10 |
12 |
14 |
16 |
18 |
20 |
22 |
24 |
26 |
28 |
30 |
32 |
34 |
36 |
38 |
40 |
42 |
44 |
46 |
48 |
50 |
52 |
54 |
56 |
58 |
60 |
62 |
64 |
66 |
68 |
70 |
72 |
74 |
75 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/play_ic.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/share_ic.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/storage_filled.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/storage_outlined.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/telegram_ic.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheByteArray/Convertit/06377bee0ba08ddb416ff7c308a9e3bab458b23e/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheByteArray/Convertit/06377bee0ba08ddb416ff7c308a9e3bab458b23e/app/src/main/res/mipmap-hdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheByteArray/Convertit/06377bee0ba08ddb416ff7c308a9e3bab458b23e/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheByteArray/Convertit/06377bee0ba08ddb416ff7c308a9e3bab458b23e/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheByteArray/Convertit/06377bee0ba08ddb416ff7c308a9e3bab458b23e/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheByteArray/Convertit/06377bee0ba08ddb416ff7c308a9e3bab458b23e/app/src/main/res/mipmap-mdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheByteArray/Convertit/06377bee0ba08ddb416ff7c308a9e3bab458b23e/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheByteArray/Convertit/06377bee0ba08ddb416ff7c308a9e3bab458b23e/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheByteArray/Convertit/06377bee0ba08ddb416ff7c308a9e3bab458b23e/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheByteArray/Convertit/06377bee0ba08ddb416ff7c308a9e3bab458b23e/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheByteArray/Convertit/06377bee0ba08ddb416ff7c308a9e3bab458b23e/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheByteArray/Convertit/06377bee0ba08ddb416ff7c308a9e3bab458b23e/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheByteArray/Convertit/06377bee0ba08ddb416ff7c308a9e3bab458b23e/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheByteArray/Convertit/06377bee0ba08ddb416ff7c308a9e3bab458b23e/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheByteArray/Convertit/06377bee0ba08ddb416ff7c308a9e3bab458b23e/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheByteArray/Convertit/06377bee0ba08ddb416ff7c308a9e3bab458b23e/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheByteArray/Convertit/06377bee0ba08ddb416ff7c308a9e3bab458b23e/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheByteArray/Convertit/06377bee0ba08ddb416ff7c308a9e3bab458b23e/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheByteArray/Convertit/06377bee0ba08ddb416ff7c308a9e3bab458b23e/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheByteArray/Convertit/06377bee0ba08ddb416ff7c308a9e3bab458b23e/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/app/src/main/res/values-bn/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Convertit
4 | %1$.1f %2$s
5 | ফাইল কনভার্ট হচ্ছে
6 | অ্যাপ সম্পর্কে
7 | ConvertIt একটি বিজ্ঞাপন-মুক্ত অ্যান্ড্রয়েড অ্যাপ, যা FLAC, ALAC, MP3, WAV, AAC, OGG, M4A, AIFF, OPUS, WMA, MKA এবং SPX এর মতো বিভিন্ন ফরম্যাটে অডিও ও ভিডিও ফাইল কনভার্ট করে। এটি Kotlin, Compose এবং FFmpeg দিয়ে তৈরি, এবং এতে রয়েছে উচ্চ মানের কনভার্সনের জন্য ফ্লেক্সিবল Bitrate অপশন। ভিডিও থেকে অডিও কনভার্সনও সাপোর্ট করে। আপনার কন্ট্রিবিউশন স্বাগত।
8 | যোগাযোগ
9 | ব্যাকগ্রাউন্ডে প্রসেস হচ্ছে
10 | স্টোরেজ পারমিশন ইতিমধ্যে দেওয়া হয়েছে
11 | এই ফাইলের কনভার্সনে ব্যর্থ: %1$s (এরর কোড: %2$s)
12 | Metadata আপডেট করতে ব্যর্থ (এরর কোড: %1$d)
13 | এই ফাইলটি খোলার জন্য কোনো অ্যাপ পাওয়া যায়নি
14 | ফাইল খুঁজে পাওয়া যায়নি
15 | ফাইল শেয়ার করুন
16 | পুনরায় চালু করুন
17 | Quick Actions
18 | একটি অ্যাকশন নির্বাচন করুন
19 | ডিলিট করুন
20 | Metadata দেখুন
21 | ভার্সন: %1$s
22 | বাতিল করুন
23 | সফলভাবে কনভার্ট হয়েছে
24 | কনভার্সনে ব্যর্থ
25 | Bitrate: %1$s ও Format: %2$s দিয়ে কনভার্সন শুরু হয়েছে। অনুগ্রহ করে অপেক্ষা করুন।
26 | ফাইল সফলভাবে ডিলিট হয়েছে
27 | অপারেশন চলাকালে একটি সমস্যা হয়েছে
28 | অ্যাপ সম্পর্কে
29 | Discord
30 | GitHub
31 | Telegram
32 | অবদানকারী
33 | Apache 2.0 লাইসেন্স
34 | বিটরেট
35 | ফরম্যাট
36 | কনভার্ট করুন
37 | ফাইল সার্চ করুন
38 | অডিও সিলেক্ট করুন
39 | শিরোনাম: %1$s
40 | শিল্পী: %1$s
41 | অ্যালবাম: %1$s
42 | Genre: %1$s
43 | ট্র্যাক: %1$s
44 | বছর: %1$s
45 | অজানা
46 | কোনো Metadata পাওয়া যায়নি
47 | কনভার্সন সফল হয়েছে
48 | কনভার্সন ব্যর্থ হয়েছে
49 | কনভার্সনের অবস্থা
50 | সোর্স থেকে ফাইল কপি করতে ব্যর্থ
51 | আইটেম ডিলিট করুন
52 | আপনি কি নিশ্চিতভাবে এই আইটেমটি ডিলিট করতে চান? এটি আর ফিরিয়ে আনা যাবে না।
53 | পেছনে যান
54 | শেয়ার আইকন
55 | প্লে আইকন
56 | অডিও আইকন
57 | Format সিলেক্ট করুন
58 | Bitrate সিলেক্ট করুন
59 | আমাদের সাথে যোগাযোগ করুন
60 | তামিম হোসেন (ডেভেলপার)
61 | মুনতাহিদ (মডারেটর)
62 | কমিউনিটি
63 | Telegram আইকন
64 | Discord আইকন
65 | GitHub - Tamim Hossain
66 | সমস্যা রিপোর্ট করুন
67 | GitHub Issue ওপেন করুন
68 | কনভার্সন সফলভাবে সম্পন্ন হয়েছে
69 | দয়া করে বর্তমান কনভার্সন শেষ না হওয়া পর্যন্ত অপেক্ষা করুন
70 | কনভার্সন সেটিংস
71 | প্লেব্যাক স্পিড
72 | স্টার আইকন
73 | আপনি যদি আমাদের অ্যাপ পছন্দ করেন, Google Play-এ একটি রেটিং দিন
74 | https://play.google.com/store/apps/details?id=%1$s
75 | রেট দিন
76 | ফাইল নির্বাচন করুন
77 | ফোল্ডারে ওপেন করুন
78 | এই ইনপুট ফরম্যাটটি সাপোর্ট করে না
79 | Invalid কনভার্সন: %1$s থেকে %2$s
80 | অজানা
81 | 0 B
82 | কনভার্সন চলছে: %1$d%%
83 | থামান
84 | বর্তমান: %.2fx
85 | ConvertIt ভালো লাগছে?
86 | আপনার মতামত আমাদের উন্নত করতে সাহায্য করে!
87 | স্টার স্কেল
88 | %1$d স্টার দিন
89 | দুঃখিত আপনি সন্তুষ্ট নন। আমাদের উন্নত করতে সাহায্য করুন!
90 | আমরা আরও ভালো করার জন্য কাজ করছি!
91 | আপনার মতামতের জন্য ধন্যবাদ!
92 | খুশি হয়েছি আপনি পছন্দ করছেন!
93 | আপনি অসাধারণ! ভালোবাসার জন্য ধন্যবাদ!
94 | আবার দেখাবেন না
95 | এখন নয়
96 | এখনই রেট দিন
97 | পেছনে
98 | অ্যাপ সম্পর্কে
99 | স্যাম্পল অডিও ফাইল
100 | 100KB
101 | কোনো কনভার্টেড ফাইল পাওয়া যায়নি। \nএকটু কনভার্ট করে দেখুন! 🙂
102 |
103 |
--------------------------------------------------------------------------------
/app/src/main/res/values-night/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFFFFFFF
4 | #FFBB86FC
5 | #FF6200EE
6 | #FF3700B3
7 | #FF03DAC5
8 | #FF018786
9 | #FF000000
10 | #FFFFFFFF
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FF000000
4 | #FFBB86FC
5 | #FF6200EE
6 | #FF3700B3
7 | #FF03DAC5
8 | #FF018786
9 | #FF000000
10 | #FFFFFFFF
11 |
--------------------------------------------------------------------------------
/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 |
33 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Convertit
4 | %1$.1f %2$s
5 | Converting Files
6 | About
7 | ConvertIt is an ad-free Android app for converting audio and video files to various formats like FLAC, ALAC, MP3, WAV, AAC, OGG, M4A, AIFF, OPUS, WMA, MKA, and SPX. Built with Kotlin, Compose, and FFmpeg, it offers flexible bitrate options for high-quality conversions. Video-to-audio conversion is also supported. Contributions are welcome!
8 | Contact
9 | Processing in Background
10 | Storage permissions have been granted
11 | Conversion failed for file: %1$s (Error code: %2$s)
12 | Metadata update failed (Error code: %1$d)
13 | No application found to open this file
14 | File not found
15 | Share File
16 | Restart
17 | Quick Actions
18 | Select an action
19 | Delete
20 | View Metadata
21 | Version: %1$s
22 | Cancel
23 | Conversion completed successfully
24 | Conversion failed
25 | Starting conversion with %1$s bitrate and %2$s format. Please wait.
26 | File has been deleted successfully
27 | An error occurred during the operation
28 | About
29 | Discord
30 | GitHub
31 | Telegram
32 | Contributors
33 | Apache 2.0 License
34 | Bitrate
35 | Format
36 | Convert
37 | Search Files
38 | Select Audio
39 | Title: %1$s
40 | Artist: %1$s
41 | Album: %1$s
42 | Genre: %1$s
43 | Track: %1$s
44 | Year: %1$s
45 | Unknown
46 | No metadata available
47 | Conversion Successful
48 | Conversion Failed
49 | Conversion Status
50 | Failed to copy file from source
51 | Delete Item
52 | Are you sure you want to delete this item? This action cannot be undone.
53 | Back
54 | Share Icon
55 | Play Icon
56 | Audio Icon
57 | Select Format
58 | Select Bitrate
59 | Contact Us
60 | Tamim Hossain (Developer)
61 | Moontahid (Moderator)
62 | Community
63 | Telegram Icon
64 | Discord Icon
65 | GitHub - Tamim Hossain
66 | Report an Issue
67 | Open GitHub Issue
68 | Conversion completed successfully
69 | Please wait for the current conversion to complete
70 | Conversion Settings
71 | Playback Speed
72 | Star Icon
73 | If you enjoy using our application, please take a moment to rate us on Google Play
74 | https://play.google.com/store/apps/details?id=%1$s
75 | Rate Us
76 | Select Files
77 | Open in Folder
78 | Unsupported input format
79 | Invalid conversion: %1$s to %2$s
80 | Unknown
81 | 0 B
82 | Conversion in progress: %1$d%%
83 | Stop
84 | Current: %.2fx
85 | Enjoying ConvertIt?
86 | Your feedback helps us improve!
87 | Star Scale
88 | Rate %1$d stars
89 | We\'re sorry to hear that. Help us improve!
90 | We\'re working to make it better!
91 | Thanks for your feedback!
92 | We\'re glad you like it!
93 | You\'re awesome! Thanks for the love!
94 | Don\'t show again
95 | Not Now
96 | Rate Now
97 | Back
98 | About
99 | Sample Audio File
100 | 100KB
101 | No converted files found. \nTry converting some! 🙂
102 |
103 |
104 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/file_paths.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/src/test/java/com/nasahacker/convertit/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.nasahacker.convertit
2 |
3 | import org.junit.Assert.*
4 | import org.junit.Test
5 |
6 | /**
7 | * Example local unit test, which will execute on the development machine (host).
8 | *
9 | * See [testing documentation](http://d.android.com/tools/testing).
10 | */
11 | class ExampleUnitTest {
12 | @Test
13 | fun addition_isCorrect() {
14 | assertEquals(4, 2 + 2)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | plugins {
3 | alias(libs.plugins.android.application) apply false
4 | alias(libs.plugins.kotlin.android) apply false
5 | alias(libs.plugins.kotlin.compose) apply false
6 | }
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. For more details, visit
12 | # https://developer.android.com/r/tools/gradle-multi-project-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
24 | android.enableJetifier=true
25 |
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | agp = "8.10.1"
3 | kotlin = "2.1.20"
4 | coreKtx = "1.16.0"
5 | junit = "4.13.2"
6 | junitVersion = "1.2.1"
7 | espressoCore = "3.6.1"
8 | lifecycleRuntimeKtx = "2.9.0"
9 | activityCompose = "1.10.1"
10 | composeBom = "2025.05.01"
11 | materialIconsExtended = "1.7.8"
12 | navigationCompose = "2.9.0"
13 | uiTextGoogleFonts = "1.8.2"
14 | runtimeLivedata = "1.8.2"
15 | [libraries]
16 | androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
17 | androidx-material-icons-extended = { module = "androidx.compose.material:material-icons-extended", version.ref = "materialIconsExtended" }
18 | androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
19 | androidx-ui-text-google-fonts = { module = "androidx.compose.ui:ui-text-google-fonts", version.ref = "uiTextGoogleFonts" }
20 | junit = { group = "junit", name = "junit", version.ref = "junit" }
21 | androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
22 | androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
23 | androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
24 | androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
25 | androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
26 | androidx-ui = { group = "androidx.compose.ui", name = "ui" }
27 | androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
28 | androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
29 | androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
30 | androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
31 | androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
32 | androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
33 | androidx-runtime-livedata = { group = "androidx.compose.runtime", name = "runtime-livedata", version.ref = "runtimeLivedata" }
34 | [plugins]
35 | android-application = { id = "com.android.application", version.ref = "agp" }
36 | kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
37 | kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
38 |
39 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheByteArray/Convertit/06377bee0ba08ddb416ff7c308a9e3bab458b23e/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Nov 29 20:26:28 BDT 2024
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-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 |
--------------------------------------------------------------------------------
/images/image1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheByteArray/Convertit/06377bee0ba08ddb416ff7c308a9e3bab458b23e/images/image1.png
--------------------------------------------------------------------------------
/images/image2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheByteArray/Convertit/06377bee0ba08ddb416ff7c308a9e3bab458b23e/images/image2.png
--------------------------------------------------------------------------------
/images/image3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheByteArray/Convertit/06377bee0ba08ddb416ff7c308a9e3bab458b23e/images/image3.png
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google {
4 | content {
5 | includeGroupByRegex("com\\.android.*")
6 | includeGroupByRegex("com\\.google.*")
7 | includeGroupByRegex("androidx.*")
8 | }
9 | }
10 | mavenCentral()
11 | gradlePluginPortal()
12 | }
13 | }
14 | dependencyResolutionManagement {
15 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
16 | repositories {
17 | google()
18 | mavenCentral()
19 | }
20 | }
21 |
22 | rootProject.name = "ConvertIt"
23 | include(":app")
24 |
--------------------------------------------------------------------------------