├── .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 | ![Kotlin](https://img.shields.io/badge/Kotlin-7F52FF?style=for-the-badge&logo=kotlin&logoColor=white) 3 | ![Compose](https://img.shields.io/badge/Jetpack_Compose-343434?style=for-the-badge&logo=jetpack-compose) 4 | ![Material Design 3](https://img.shields.io/badge/Material%203-4285F4?style=for-the-badge&logo=material-design&logoColor=white) 5 | ![Downloads](https://img.shields.io/github/downloads/TheByteArray/ConvertIt/total?style=for-the-badge&logo=download) 6 | [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FCodeWithTamim%2FConvertit.svg?type=shield)](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 | Screenshot 1 28 | Screenshot 2 29 | Screenshot 3 30 |

31 | 32 | --- 33 | 34 | ## 📱 Get It on Play Store 35 | 36 | Download **ConvertIt** directly from the Play Store: 37 | 38 |

39 | 40 | Get it on Google Play 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 | ![Contributors](https://contributors-img.web.app/image?repo=CodeWithTamim/ConvertIt) 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 | [![Star History Chart](https://api.star-history.com/svg?repos=codewithtamim/convertit&type=Date)](https://www.star-history.com/#codewithtamim/convertit&Date) 157 | --- 158 | 159 | [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FCodeWithTamim%2FConvertit.svg?type=large)](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 |